@arvoretech/bug-jar 0.3.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # 🪲 Bug Jar
2
2
 
3
- Capture everything for non-technical people to help debug. One script tag, zero config.
3
+ Capture everything for non-technical people to help debug. One debug link, zero config.
4
4
 
5
- Bug Jar is a lightweight JavaScript library that silently records all browser activity (network requests, console logs, errors, user actions, performance metrics, and more) and lets anyone — even non-technical users — generate a complete debug report with a single click.
5
+ Bug Jar is a lightweight JavaScript library that silently records all browser activity (network requests, console logs, errors, user actions, performance metrics, and more) and lets you capture a complete, encrypted debug session from a one-time debug link.
6
6
 
7
7
  ## Why?
8
8
 
9
- Because asking someone to "open DevTools and check the Network tab" doesn't scale. Bug Jar gives your support team, QA, and stakeholders a one-click way to capture everything developers need to reproduce and fix bugs.
9
+ Because asking someone to "open DevTools and check the Network tab" doesn't scale. Bug Jar gives your support team and QA a link they can send to anyone to capture everything developers need to reproduce and fix bugs.
10
10
 
11
11
  ## What it captures
12
12
 
@@ -26,30 +26,19 @@ Because asking someone to "open DevTools and check the Network tab" doesn't scal
26
26
 
27
27
  ## Quick Start
28
28
 
29
- ### Script tag (zero config)
29
+ Bug Jar runs as a **debug session**: staff generates a one-time link, the user
30
+ opens it in the real app, and the encrypted package is uploaded to S3. See
31
+ [Debug Session Mode](#debug-session-mode-encrypted-upload-to-s3) below.
30
32
 
31
33
  ```html
32
34
  <script src="https://unpkg.com/@arvoretech/bug-jar"></script>
33
35
  <script>
34
- BugJar.init();
36
+ BugJar.init({
37
+ debugSessionEndpoint: 'https://api.arvore.com.br/debug-sessions',
38
+ })
35
39
  </script>
36
40
  ```
37
41
 
38
- ### NPM
39
-
40
- ```bash
41
- npm install @arvoretech/bug-jar
42
- ```
43
-
44
- ```typescript
45
- import { init } from '@arvoretech/bug-jar'
46
-
47
- init({
48
- endpoint: 'https://your-api.com/bug-reports',
49
- uiPosition: 'bottom-right',
50
- })
51
- ```
52
-
53
42
  ## Configuration
54
43
 
55
44
  ```typescript
@@ -74,16 +63,12 @@ init({
74
63
  // Privacy — fields containing these strings are redacted
75
64
  sensitiveFields: ['password', 'token', 'secret', 'authorization', 'cookie', 'session', 'credit_card', 'cvv', 'ssn', 'cpf'],
76
65
 
77
- // Where to send reports (if not set, downloads as JSON file)
78
- endpoint: 'https://your-api.com/bug-reports',
79
-
80
66
  // Callback when report is generated
81
67
  onCapture: (report) => console.log(report),
82
68
 
83
- // UI widget
84
- ui: true,
85
- uiPosition: 'bottom-right', // bottom-right | bottom-left | top-right | top-left
86
- uiLabel: 'Reportar Bug',
69
+ // Debug session
70
+ debugSessionEndpoint: 'https://api.arvore.com.br/debug-sessions',
71
+ // debugToken is read automatically from the ?debug=<token> query param
87
72
  })
88
73
  ```
89
74
 
@@ -92,7 +77,7 @@ init({
92
77
  ```typescript
93
78
  import { BugJar } from '@arvoretech/bug-jar'
94
79
 
95
- const jar = new BugJar({ ui: false })
80
+ const jar = new BugJar()
96
81
  jar.start()
97
82
 
98
83
  // Later, capture a report programmatically
@@ -105,12 +90,72 @@ await fetch('/api/bugs', {
105
90
  })
106
91
  ```
107
92
 
93
+ ## Debug Session Mode (encrypted upload to S3)
94
+
95
+ For non-technical users, staff can generate a one-time debug link. The user opens
96
+ the link in the real app, reproduces the bug, and Bug Jar records the whole flow,
97
+ encrypts the package end-to-end in the browser, and uploads it straight to a
98
+ private S3 bucket via a presigned URL. No AWS credentials ever reach the browser.
99
+
100
+ ```html
101
+ <script src="https://unpkg.com/@arvoretech/bug-jar"></script>
102
+ <script>
103
+ BugJar.init({
104
+ debugSessionEndpoint: 'https://api.arvore.com.br/debug-sessions',
105
+ // debugToken is read automatically from the ?debug=<token> query param
106
+ })
107
+ </script>
108
+ ```
109
+
110
+ Flow:
111
+
112
+ 1. Staff (admin) calls `POST /debug-sessions` with the target app URL and gets back
113
+ a `debugLink` (`https://app.../?debug=<token>`), the server public key, and an expiry.
114
+ 2. The user opens the link. Bug Jar detects `?debug=<token>`, persists the token in
115
+ a `.arvore.com.br` cookie, fetches the session, starts screen recording, and
116
+ shows an animated **session border** (no button, no banner).
117
+ 3. The user just uses the app. The session survives navigation across apps
118
+ (app-v2 → legacy → reader) by re-hydrating the token from the cookie on each
119
+ page. Each page streams **encrypted chunks** to S3 in real time:
120
+ - **data** (network/console/errors/actions/storage) every 5s as JSONL
121
+ - **video** chunks straight from `MediaRecorder` as they are produced
122
+ Chunks are uploaded to `debug/<token>/<pageId>/<stream>/<seq>.bin` via per-chunk
123
+ presigned PUT URLs (`POST /debug-sessions/:token/chunk-url`). Page metadata goes
124
+ to `debug/<token>/<pageId>/meta.json` (`POST /debug-sessions/:token/meta`).
125
+ 4. Staff reconstructs the session offline:
126
+ ```bash
127
+ BUG_JAR_API=https://api.arvore.com.br/debug-sessions \
128
+ BUG_JAR_API_TOKEN=<admin-jwt> \
129
+ node scripts/decrypt-session.cjs <token> ./out
130
+ ```
131
+ This reads the X25519 private key from Secrets Manager (`bug-jar/keypair`),
132
+ downloads all chunks via `GET /debug-sessions/:token/download`, decrypts each
133
+ per-page stream, and writes `<pageId>/data.jsonl`, `<pageId>/video.webm`,
134
+ `<pageId>/meta.json`.
135
+
136
+ ## Encryption
137
+
138
+ Each stream (data and video, per page) is encrypted with libsodium
139
+ `crypto_secretstream_xchacha20poly1305`: a random symmetric key drives the stream,
140
+ and that key is sealed with `crypto_box_seal` (X25519) to the server public key.
141
+ The first chunk of every stream is `[magic][version][sealedKeyLen][sealedKey][header]`;
142
+ subsequent chunks are the encrypted stream messages, the last carrying `TAG_FINAL`.
143
+ Chunks leave the browser already unreadable; only the holder of the X25519 private
144
+ key can open them. `libsodium-wrappers` is loaded dynamically, so it only ships
145
+ when debug mode is actually used.
146
+
147
+ > Cross-app continuity works because all apps live under `*.arvore.com.br` and
148
+ > share the session cookie. Screen recording restarts per app (the browser does
149
+ > not allow `getDisplayMedia` to persist across full page loads / origins), so the
150
+ > video is reconstructed per page rather than as one continuous file.
151
+
152
+
108
153
  ## Privacy & Security
109
154
 
110
155
  - Sensitive fields are automatically redacted (passwords, tokens, secrets, etc.)
111
156
  - Password inputs are never captured
112
157
  - You control what gets captured via configuration
113
- - No data leaves the browser unless you configure an endpoint
158
+ - No data leaves the browser unless a debug session is active
114
159
  - All processing happens client-side
115
160
 
116
161
  ## Browser Support
package/dist/index.d.mts CHANGED
@@ -15,11 +15,8 @@ interface BugJarConfig {
15
15
  sensitiveFields: string[];
16
16
  endpoint?: string;
17
17
  onCapture?: (report: BugReport) => void;
18
- ui: boolean;
19
- uiPosition: "bottom-right" | "bottom-left" | "top-right" | "top-left";
20
- uiLabel: string;
21
- hideOnMobile?: boolean;
22
- uiCompactOnMobile?: boolean;
18
+ debugToken?: string;
19
+ debugSessionEndpoint?: string;
23
20
  }
24
21
  interface NetworkEntry {
25
22
  id: string;
@@ -136,6 +133,12 @@ declare function generateSummary(report: BugReport): string;
136
133
 
137
134
  declare function exportAsZip(report: BugReport, videoBlob: Blob | null): Promise<Blob>;
138
135
 
136
+ interface DebugSessionPayload {
137
+ token: string;
138
+ publicKey: string;
139
+ expiresAt: number;
140
+ }
141
+
139
142
  declare class BugJar {
140
143
  private config;
141
144
  private network;
@@ -148,7 +151,13 @@ declare class BugJar {
148
151
  private screenshot;
149
152
  private featureFlags;
150
153
  private screenRecorder;
151
- private ui;
154
+ private sessionBorder;
155
+ private debugSession;
156
+ private dataUploader;
157
+ private videoUploader;
158
+ private pageId;
159
+ private dataInterval;
160
+ private finalized;
152
161
  private started;
153
162
  constructor(config?: Partial<BugJarConfig>);
154
163
  start(): void;
@@ -158,7 +167,12 @@ declare class BugJar {
158
167
  capture(userDescription?: string): Promise<BugReport>;
159
168
  captureAndExport(userDescription?: string): Promise<BugReport>;
160
169
  getReport(): Promise<BugReport>;
170
+ private startDebugSession;
171
+ private startDataStreaming;
172
+ private registerFlushHandlers;
173
+ private finalizePage;
174
+ endDebugSession(): void;
161
175
  }
162
176
  declare function init(config?: Partial<BugJarConfig>): BugJar;
163
177
 
164
- export { BugJar, type BugJarConfig, type BugReport, BugJar as default, exportAsZip, generateSummary, init };
178
+ export { BugJar, type BugJarConfig, type BugReport, type DebugSessionPayload, BugJar as default, exportAsZip, generateSummary, init };
package/dist/index.d.ts CHANGED
@@ -15,11 +15,8 @@ interface BugJarConfig {
15
15
  sensitiveFields: string[];
16
16
  endpoint?: string;
17
17
  onCapture?: (report: BugReport) => void;
18
- ui: boolean;
19
- uiPosition: "bottom-right" | "bottom-left" | "top-right" | "top-left";
20
- uiLabel: string;
21
- hideOnMobile?: boolean;
22
- uiCompactOnMobile?: boolean;
18
+ debugToken?: string;
19
+ debugSessionEndpoint?: string;
23
20
  }
24
21
  interface NetworkEntry {
25
22
  id: string;
@@ -136,6 +133,12 @@ declare function generateSummary(report: BugReport): string;
136
133
 
137
134
  declare function exportAsZip(report: BugReport, videoBlob: Blob | null): Promise<Blob>;
138
135
 
136
+ interface DebugSessionPayload {
137
+ token: string;
138
+ publicKey: string;
139
+ expiresAt: number;
140
+ }
141
+
139
142
  declare class BugJar {
140
143
  private config;
141
144
  private network;
@@ -148,7 +151,13 @@ declare class BugJar {
148
151
  private screenshot;
149
152
  private featureFlags;
150
153
  private screenRecorder;
151
- private ui;
154
+ private sessionBorder;
155
+ private debugSession;
156
+ private dataUploader;
157
+ private videoUploader;
158
+ private pageId;
159
+ private dataInterval;
160
+ private finalized;
152
161
  private started;
153
162
  constructor(config?: Partial<BugJarConfig>);
154
163
  start(): void;
@@ -158,7 +167,12 @@ declare class BugJar {
158
167
  capture(userDescription?: string): Promise<BugReport>;
159
168
  captureAndExport(userDescription?: string): Promise<BugReport>;
160
169
  getReport(): Promise<BugReport>;
170
+ private startDebugSession;
171
+ private startDataStreaming;
172
+ private registerFlushHandlers;
173
+ private finalizePage;
174
+ endDebugSession(): void;
161
175
  }
162
176
  declare function init(config?: Partial<BugJarConfig>): BugJar;
163
177
 
164
- export { BugJar, type BugJarConfig, type BugReport, BugJar as default, exportAsZip, generateSummary, init };
178
+ export { BugJar, type BugJarConfig, type BugReport, type DebugSessionPayload, BugJar as default, exportAsZip, generateSummary, init };