@arvoretech/bug-jar 0.4.0 → 0.7.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
@@ -34,7 +34,7 @@ opens it in the real app, and the encrypted package is uploaded to S3. See
34
34
  <script src="https://unpkg.com/@arvoretech/bug-jar"></script>
35
35
  <script>
36
36
  BugJar.init({
37
- debugSessionEndpoint: 'https://api.arvore.com.br/debug-sessions',
37
+ debugSessionEndpoint: 'https://livros.arvore.com.br/debug-sessions',
38
38
  })
39
39
  </script>
40
40
  ```
@@ -67,7 +67,7 @@ init({
67
67
  onCapture: (report) => console.log(report),
68
68
 
69
69
  // Debug session
70
- debugSessionEndpoint: 'https://api.arvore.com.br/debug-sessions',
70
+ debugSessionEndpoint: 'https://livros.arvore.com.br/debug-sessions',
71
71
  // debugToken is read automatically from the ?debug=<token> query param
72
72
  })
73
73
  ```
@@ -101,7 +101,7 @@ private S3 bucket via a presigned URL. No AWS credentials ever reach the browser
101
101
  <script src="https://unpkg.com/@arvoretech/bug-jar"></script>
102
102
  <script>
103
103
  BugJar.init({
104
- debugSessionEndpoint: 'https://api.arvore.com.br/debug-sessions',
104
+ debugSessionEndpoint: 'https://livros.arvore.com.br/debug-sessions',
105
105
  // debugToken is read automatically from the ?debug=<token> query param
106
106
  })
107
107
  </script>
@@ -110,18 +110,59 @@ private S3 bucket via a presigned URL. No AWS credentials ever reach the browser
110
110
  Flow:
111
111
 
112
112
  1. Staff (admin) calls `POST /debug-sessions` with the target app URL and gets back
113
- a `debugLink` (`https://app.../?debug=<token>`), the presigned PUT URL, the
114
- server public key, and an expiry.
115
- 2. The user opens the link. Bug Jar detects `?debug=<token>`, fetches the session
116
- from `debugSessionEndpoint`, starts screen recording, and shows a top banner.
117
- 3. The user clicks **Finalizar e enviar**. Bug Jar builds the ZIP, encrypts it
118
- (libsodium sealed box + secretbox hybrid), and uploads the encrypted blob to S3.
119
- 4. Staff downloads the `.bin` via `GET /debug-sessions/:token/download` and
120
- decrypts it offline with the private key.
121
-
122
- The package leaves the browser already unreadable; only the holder of the X25519
123
- private key can open it. `libsodium-wrappers` is loaded dynamically, so it only
124
- ships when debug mode is actually used.
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://livros.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
+ ## Speed test
137
+
138
+ At the start of each page, Bug Jar runs a fast.com-style network speed test
139
+ against the Árvore backend and stores the result in that page's `meta.json`:
140
+
141
+ - **latency** — minimum and jitter from repeated `GET /speedtest/ping`
142
+ - **download (Mbps)** — 8 MB pulled across 4 parallel streams
143
+ (`GET /speedtest/download?bytes=N`)
144
+ - **upload (Mbps)** — 4 MB sent to `POST /speedtest/upload` (server discards it)
145
+
146
+ It measures the real end-to-end throughput between the user and our
147
+ infrastructure (which is exactly what we want when debugging "the app is slow").
148
+ The test runs once per page and is best-effort — failures leave the fields null
149
+ without blocking the session.
150
+
151
+ ## Encryption
152
+
153
+ Each stream (data and video, per page) is encrypted with libsodium
154
+ `crypto_secretstream_xchacha20poly1305`: a random symmetric key drives the stream,
155
+ and that key is sealed with `crypto_box_seal` (X25519) to the server public key.
156
+ The first chunk of every stream is `[magic][version][sealedKeyLen][sealedKey][header]`;
157
+ subsequent chunks are the encrypted stream messages, the last carrying `TAG_FINAL`.
158
+ Chunks leave the browser already unreadable; only the holder of the X25519 private
159
+ key can open them. `libsodium-wrappers` is loaded dynamically, so it only ships
160
+ when debug mode is actually used.
161
+
162
+ > Cross-app continuity works because all apps live under `*.arvore.com.br` and
163
+ > share the session cookie. Screen recording restarts per app (the browser does
164
+ > not allow `getDisplayMedia` to persist across full page loads / origins), so the
165
+ > video is reconstructed per page rather than as one continuous file.
125
166
 
126
167
 
127
168
  ## Privacy & Security
package/dist/index.d.mts CHANGED
@@ -17,6 +17,7 @@ interface BugJarConfig {
17
17
  onCapture?: (report: BugReport) => void;
18
18
  debugToken?: string;
19
19
  debugSessionEndpoint?: string;
20
+ ignoreUrls?: string[];
20
21
  }
21
22
  interface NetworkEntry {
22
23
  id: string;
@@ -136,8 +137,6 @@ declare function exportAsZip(report: BugReport, videoBlob: Blob | null): Promise
136
137
  interface DebugSessionPayload {
137
138
  token: string;
138
139
  publicKey: string;
139
- presignedPut: string;
140
- reportId: string;
141
140
  expiresAt: number;
142
141
  }
143
142
 
@@ -153,8 +152,14 @@ declare class BugJar {
153
152
  private screenshot;
154
153
  private featureFlags;
155
154
  private screenRecorder;
156
- private debugBanner;
155
+ private sessionBorder;
157
156
  private debugSession;
157
+ private dataUploader;
158
+ private videoUploader;
159
+ private pageId;
160
+ private dataInterval;
161
+ private lastFlushTs;
162
+ private finalized;
158
163
  private started;
159
164
  constructor(config?: Partial<BugJarConfig>);
160
165
  start(): void;
@@ -165,7 +170,11 @@ declare class BugJar {
165
170
  captureAndExport(userDescription?: string): Promise<BugReport>;
166
171
  getReport(): Promise<BugReport>;
167
172
  private startDebugSession;
168
- private finishDebugSession;
173
+ private startDataStreaming;
174
+ private registerFlushHandlers;
175
+ private finalizePage;
176
+ private runSpeedtest;
177
+ endDebugSession(): void;
169
178
  }
170
179
  declare function init(config?: Partial<BugJarConfig>): BugJar;
171
180
 
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@ interface BugJarConfig {
17
17
  onCapture?: (report: BugReport) => void;
18
18
  debugToken?: string;
19
19
  debugSessionEndpoint?: string;
20
+ ignoreUrls?: string[];
20
21
  }
21
22
  interface NetworkEntry {
22
23
  id: string;
@@ -136,8 +137,6 @@ declare function exportAsZip(report: BugReport, videoBlob: Blob | null): Promise
136
137
  interface DebugSessionPayload {
137
138
  token: string;
138
139
  publicKey: string;
139
- presignedPut: string;
140
- reportId: string;
141
140
  expiresAt: number;
142
141
  }
143
142
 
@@ -153,8 +152,14 @@ declare class BugJar {
153
152
  private screenshot;
154
153
  private featureFlags;
155
154
  private screenRecorder;
156
- private debugBanner;
155
+ private sessionBorder;
157
156
  private debugSession;
157
+ private dataUploader;
158
+ private videoUploader;
159
+ private pageId;
160
+ private dataInterval;
161
+ private lastFlushTs;
162
+ private finalized;
158
163
  private started;
159
164
  constructor(config?: Partial<BugJarConfig>);
160
165
  start(): void;
@@ -165,7 +170,11 @@ declare class BugJar {
165
170
  captureAndExport(userDescription?: string): Promise<BugReport>;
166
171
  getReport(): Promise<BugReport>;
167
172
  private startDebugSession;
168
- private finishDebugSession;
173
+ private startDataStreaming;
174
+ private registerFlushHandlers;
175
+ private finalizePage;
176
+ private runSpeedtest;
177
+ endDebugSession(): void;
169
178
  }
170
179
  declare function init(config?: Partial<BugJarConfig>): BugJar;
171
180