Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ BugStr is a privacy-focused crash reporting library for Nostr applications. It u
|----------|-----------|--------|
| Android/Kotlin | `android/` | Production |
| TypeScript | `typescript/` | Production |
| Flutter/Dart | `dart/` | Planned |
| Flutter/Dart | `dart/` | Library |
| Rust | `rust/` | CLI + Library |
| Go | `go/` | Library |
| Python | `python/` | Library |
| React Native | `react-native/` | Library |

### Key NIPs

Expand All @@ -30,6 +34,9 @@ Ensure docstring coverage for any code added or modified:
- **Kotlin**: Use KDoc format (`/** ... */`)
- **Dart**: Use dartdoc format (`/// ...`)
- **TypeScript**: Use JSDoc format (`/** ... */`)
- **Rust**: Use rustdoc format (`/// ...` or `//!`)
- **Go**: Use godoc format (comment before declaration)
- **Python**: Use docstrings (`"""..."""`)

All public classes, methods, and non-trivial functions must have documentation explaining:
- Purpose and behavior
Expand Down Expand Up @@ -139,19 +146,30 @@ NIP-17 messages should be tested against multiple clients:
bugstr/
├── android/ # Android/Kotlin implementation
│ ├── src/main/java/com/bugstr/
│ │ ├── BugstrCrashHandler.kt
│ │ ├── BugstrCrashReportCache.kt
│ │ ├── BugstrReportAssembler.kt
│ │ ├── BugstrAnrWatcher.kt
│ │ └── ui/BugstrCrashPrompt.kt
│ ├── bugstr-nostr-crypto/ # NIP-17/44/59 (Kotlin)
│ ├── CHANGELOG.md
│ └── README.md
├── typescript/ # TypeScript implementation
│ ├── src/
│ ├── CHANGELOG.md
│ └── README.md
├── dart/ # Flutter/Dart (planned)
├── dart/ # Flutter/Dart implementation
│ ├── lib/src/
│ ├── CHANGELOG.md
│ └── README.md
├── rust/ # Rust CLI + library
│ ├── src/
│ ├── CHANGELOG.md
│ └── README.md
├── go/ # Go library
│ ├── bugstr.go
│ └── README.md
├── python/ # Python library
│ ├── bugstr/
│ └── README.md
├── react-native/ # React Native library
│ ├── src/
│ └── README.md
├── test-vectors/ # Shared NIP-17 compliance tests
│ └── nip17-gift-wrap.json
├── AGENTS.md # This file (shared guidelines)
Expand Down
72 changes: 66 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ Bugstr delivers crash reports via [NIP-17](https://github.com/nostr-protocol/nip

## Platforms

| Platform | Status | Directory |
|----------|--------|-----------|
| Android/Kotlin | Production | [`android/`](android/) |
| TypeScript | Production | [`typescript/`](typescript/) |
| Flutter/Dart | Skeleton | [`dart/`](dart/) |
| Rust | CLI + Library | [`rust/`](rust/) |
| Platform | Status | Directory | Tested |
|----------|--------|-----------|--------|
| Android/Kotlin | Production | [`android/`](android/) | ✅ [Zapstore](https://github.com/zapstore/zapstore/pull/272) |
| TypeScript | Production | [`typescript/`](typescript/) | 🐹 Guinea pigs needed |
| Flutter/Dart | Library | [`dart/`](dart/) | 🐹 Guinea pigs needed |
| Rust | CLI + Library | [`rust/`](rust/) | 🐹 Guinea pigs needed |
| Go | Library | [`go/`](go/) | 🐹 Guinea pigs needed |
| Python | Library | [`python/`](python/) | 🐹 Guinea pigs needed |
| React Native | Library | [`react-native/`](react-native/) | 🐹 Guinea pigs needed |

## How It Works

Expand All @@ -27,6 +30,63 @@ Crash → Cache locally → App restart → Show consent dialog → User approve
4. **NIP-17 DM** - Encrypted, gift-wrapped message sent to developer
5. **Auto-expiration** - Report deleted from relays after 30 days

## Default Relays

All SDKs use the same default relay list, chosen for reliability:

| Relay | Max Event Size | Max WebSocket | Notes |
|-------|----------------|---------------|-------|
| `wss://relay.damus.io` | 64 KB | 128 KB | strfry defaults |
| `wss://relay.primal.net` | 64 KB | 128 KB | strfry defaults |
| `wss://nos.lol` | 128 KB | 128 KB | Fallback relay |

**Note:** Most relays use strfry defaults (64 KB event size, 128 KB websocket payload). The practical limit for crash reports is ~60 KB to allow for gift-wrap envelope overhead.

You can override these defaults via the `relays` configuration option in each SDK.

## Size Limits & Compression

Crash reports are subject to relay message size limits (see [NIP-11](https://nips.nostr.com/11) `max_message_length`).

| Relay Limit | Compatibility |
|-------------|---------------|
| 64 KB | ~99% of relays |
| 128 KB | ~90% of relays |
| 512 KB+ | Major relays only |

**Practical limit:** Keep compressed payloads under **60 KB** for universal delivery (allows ~500 bytes for gift-wrap envelope overhead).

| Payload Size | Behavior |
|--------------|----------|
| < 1 KB | Sent as plain JSON |
| ≥ 1 KB | Compressed with gzip, base64-encoded |

### Compression Format

Large payloads are wrapped in a versioned envelope:

```json
{
"v": 1,
"compression": "gzip",
"payload": "<base64-encoded-gzip-data>"
}
```

Stack traces are automatically truncated to fit within limits (default: 200 KB before compression). The receiver CLI/WebUI automatically detects and decompresses payloads.

### Compression Efficiency

Gzip typically achieves **70-90% reduction** on stack traces due to their repetitive text patterns:

| Original Size | Compressed | Reduction |
|---------------|------------|-----------|
| 10 KB | ~1-2 KB | ~80-90% |
| 50 KB | ~5-10 KB | ~80-90% |
| 200 KB | ~20-40 KB | ~80-85% |

With gzip compression (70-90% reduction), most crash reports fit well within the 64 KB strfry default limit. For maximum compatibility, keep compressed payloads under 60 KB.

## NIP Compliance

All implementations follow the same Nostr standards:
Expand Down
20 changes: 15 additions & 5 deletions dart/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Initial package skeleton with API structure
- `UnsignedNostrEvent` with `computeId()` for NIP-01 compliance
- Placeholder classes for crash handling and NIP-17 crypto
- Full Bugstr client with `init()` and `captureException()` API
- NIP-17 gift-wrapped crash reports using `ndk` package
- `BugstrConfig` with default relays (damus, primal, nos.lol)
- `CrashPayload` with auto-redaction for sensitive data (nsec, cashu tokens, etc.)
- Auto-install of Flutter error handlers on initialization
- `beforeSend` hook to modify/filter payloads before sending
- `confirmSend` hook for user consent dialogs
- Gzip compression for payloads over 1 KB
- Random timestamps (±2 days) for timing analysis protection
- Platform and device info in crash reports

### Removed
- Placeholder skeleton files (bugstr_crash_handler.dart, etc.)

### Notes
- Implementation pending - contributions welcome
- See README.md for planned features
- Uses `ndk` package for NIP-44 encryption and NIP-59 gift wrap
- Tested relay compatibility: damus (1MB), primal (1MB), nos.lol (128KB)
127 changes: 103 additions & 24 deletions dart/README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,127 @@
# Bugstr for Flutter/Dart

Privacy-focused crash reporting for Flutter/Dart apps using NIP-17 gift-wrapped DMs.
Zero-infrastructure crash reporting — no server to run, no SaaS to pay for.

> **Status: Skeleton** - This package provides the API structure but is not yet implemented. Contributions welcome!
## Installation

## Planned Features

- `BugstrCrashHandler` - Captures uncaught Flutter/Dart exceptions
- `BugstrCrashReportCache` - Local file-based crash storage with rotation
- `BugstrReportAssembler` - Formats crash reports with metadata
- `Nip17PayloadBuilder` - NIP-17/44/59 gift wrap building
```yaml
dependencies:
bugstr: ^0.1.0
```

## Planned Usage
## Quick Start

```dart
import 'package:bugstr/bugstr.dart';

void main() {
BugstrCrashHandler.install(
cache: BugstrCrashReportCache(maxReports: 3),
assembler: BugstrReportAssembler(
appName: 'My App',
appVersion: '1.0.0',
),
Bugstr.init(
developerPubkey: 'npub1...',
environment: 'production',
release: '1.0.0',
);

runApp(MyApp());
}
```

## NIP Compliance
## Manual Capture

```dart
try {
riskyOperation();
} catch (e, stack) {
Bugstr.captureException(e, stack);
}
```

## Configuration Options

```dart
Bugstr.init(
// Required: Your npub or hex pubkey
developerPubkey: 'npub1...',

// Optional: Custom relays (defaults to damus, primal, nos.lol)
relays: ['wss://relay.damus.io', 'wss://relay.primal.net'],

// Optional: Environment tag
environment: 'production',

// Optional: Release version
release: '1.0.0',

// Optional: Custom redaction patterns
redactPatterns: [
RegExp(r'api_key=[^&]+'),
],

The implementation will follow:
- **NIP-17** - Private Direct Messages (kind 14 rumors)
- **NIP-44** - Versioned Encryption (v2)
- **NIP-59** - Gift Wrap (rumor → seal → gift wrap)
- **NIP-40** - Expiration Timestamp
// Optional: Modify payload before sending
beforeSend: (payload) {
// Return null to drop, or modify and return
return payload;
},

**Important**: Rumors must include `id` (computed) and `sig: ""` (empty string) per spec.
// Optional: Confirm with user before sending
confirmSend: (message, stackPreview) async {
return await showConfirmDialog(message);
},
);
```

## Default Relays

| Relay | Max Event Size | Notes |
|-------|----------------|-------|
| `wss://relay.damus.io` | 64 KB | strfry defaults |
| `wss://relay.primal.net` | 64 KB | strfry defaults |
| `wss://nos.lol` | 128 KB | Fallback |

## Compression

Payloads over 1 KB are automatically gzip compressed:

```json
{
"v": 1,
"compression": "gzip",
"payload": "<base64-encoded-gzip>"
}
```

## Contributing
## Privacy Features

See the [monorepo AGENTS.md](../AGENTS.md) for contributor guidelines.
- **NIP-17 Gift Wrap**: Crash reports are encrypted with NIP-44 and wrapped per NIP-59
- **Random Timestamps**: Created_at randomized within ±2 days to prevent timing analysis
- **Ephemeral Keys**: Each gift wrap uses a fresh random key
- **Auto-expiration**: Reports expire after 30 days (NIP-40)
- **Redaction**: Sensitive patterns (tokens, keys, invoices) are auto-redacted

## How It Works

1. **Crash occurs** → Flutter/Dart error handler captures it
2. **Payload built** → Stack trace redacted and truncated
3. **User consent** → Optional confirmation dialog
4. **Gift wrapped** → Encrypted with NIP-44, wrapped per NIP-59
5. **Published** → Sent to relays as kind 1059 event

## NIP Compliance

- **NIP-01**: Event structure and ID computation
- **NIP-17**: Private Direct Messages (kind 14 rumors)
- **NIP-44**: Versioned Encryption (v2)
- **NIP-59**: Gift Wrap (rumor → seal → gift wrap)
- **NIP-40**: Expiration Timestamp

## Other Platforms

- [Android/Kotlin](../android/)
- [TypeScript](../typescript/)
- [Rust](../rust/)
- [Go](../go/)
- [Python](../python/)
- [React Native](../react-native/)

## License

[MIT](../LICENSE)
23 changes: 19 additions & 4 deletions dart/lib/bugstr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,25 @@
///
/// Delivers crash reports via NIP-17 gift-wrapped encrypted DMs
/// with user consent and auto-expiration.
///
/// ## Quick Start
///
/// ```dart
/// import 'package:bugstr/bugstr.dart';
///
/// void main() {
/// Bugstr.init(
/// developerPubkey: 'npub1...',
/// environment: 'production',
/// release: '1.0.0',
/// );
///
/// runApp(MyApp());
/// }
/// ```
library bugstr;

export 'src/bugstr_crash_handler.dart';
export 'src/bugstr_crash_report_cache.dart';
export 'src/bugstr_report_assembler.dart';
export 'src/nip17_payload_builder.dart';
export 'src/config.dart';
export 'src/payload.dart';
export 'src/bugstr_client.dart';
export 'src/compression.dart';
Loading