@nagoorkani/session-capture-vue-replay 0.1.0 → 0.1.2
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/dist/index.js +20 -12
- package/dist/upload.d.ts +19 -0
- package/dist/upload.js +37 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { isSampled, ReplayGate, safe } from '@nagoorkani/session-capture-core';
|
|
|
18
18
|
import { currentConfig, keepSessionAlive, onError, onSession, } from '@nagoorkani/session-capture-vue';
|
|
19
19
|
import { record } from 'rrweb';
|
|
20
20
|
import { Recorder } from "./recorder.js";
|
|
21
|
-
import { uploadSegment } from "./upload.js";
|
|
21
|
+
import { splitSegment, uploadSegment } from "./upload.js";
|
|
22
22
|
/** Set by `installReplay`; read by the flusher, which outlives one call. */
|
|
23
23
|
const uploadOptions = {
|
|
24
24
|
endpoint: '',
|
|
@@ -182,17 +182,25 @@ async function flushNow(unloading = false) {
|
|
|
182
182
|
keepSessionAlive();
|
|
183
183
|
sending = true;
|
|
184
184
|
try {
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
185
|
+
const chunks = splitSegment(segment);
|
|
186
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
187
|
+
const chunk = chunks[i];
|
|
188
|
+
const result = await uploadSegment({
|
|
189
|
+
endpoint: uploadOptions.endpoint,
|
|
190
|
+
apiKey: uploadOptions.apiKey,
|
|
191
|
+
sessionId,
|
|
192
|
+
keepalive: unloading,
|
|
193
|
+
}, chunk);
|
|
194
|
+
uploadOptions.onUpload?.(result, chunk.length);
|
|
195
|
+
// Kept for the next tick, unloading or not: a tab switch fires the same
|
|
196
|
+
// event as a close, and a tab that comes back should still have its
|
|
197
|
+
// full snapshot. A tab that really closed takes the memory with it.
|
|
198
|
+
// Kept in order, because rrweb's stream is only meaningful in order.
|
|
199
|
+
if (result.kind === 'failed') {
|
|
200
|
+
retrying = chunks.slice(i).flat();
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
196
204
|
}
|
|
197
205
|
finally {
|
|
198
206
|
sending = false;
|
package/dist/upload.d.ts
CHANGED
|
@@ -20,6 +20,25 @@ export interface UploadOptions {
|
|
|
20
20
|
/** Ask the browser to finish this one after the page is gone. */
|
|
21
21
|
keepalive?: boolean;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Browsers refuse a `keepalive` request whose body would take the page's
|
|
25
|
+
* in-flight keepalive bytes over 64 KB — the fetch rejects at once. The first
|
|
26
|
+
* segment carries the full DOM snapshot and is almost always larger, so it
|
|
27
|
+
* went out as keepalive on a tab switch and was lost, leaving every later
|
|
28
|
+
* segment as mutations with nothing to apply them to. Above this, send it
|
|
29
|
+
* as an ordinary request: a hidden tab usually still runs, and that is the
|
|
30
|
+
* common case.
|
|
31
|
+
*/
|
|
32
|
+
export declare const KEEPALIVE_MAX_BYTES: number;
|
|
33
|
+
/**
|
|
34
|
+
* Ingestion's per-upload limits: `MAX_FRAMES` in `event-schema`'s replay
|
|
35
|
+
* container, and half its 8 MB body limit. A busy page emits thousands of
|
|
36
|
+
* rrweb events in thirty seconds, and one oversized upload is rejected whole.
|
|
37
|
+
*/
|
|
38
|
+
export declare const MAX_UPLOAD_EVENTS = 2000;
|
|
39
|
+
export declare const MAX_UPLOAD_BYTES: number;
|
|
40
|
+
/** Split a segment into uploads ingestion will accept, in order. */
|
|
41
|
+
export declare function splitSegment(segment: readonly Segment[]): Segment[][];
|
|
23
42
|
export type UploadResult = {
|
|
24
43
|
kind: 'sent';
|
|
25
44
|
events: number;
|
package/dist/upload.js
CHANGED
|
@@ -12,6 +12,42 @@
|
|
|
12
12
|
* shows a recording worth the quota.
|
|
13
13
|
*/
|
|
14
14
|
import { encodeContainer, MAGIC_DOM } from '@nagoorkani/session-capture-core';
|
|
15
|
+
/**
|
|
16
|
+
* Browsers refuse a `keepalive` request whose body would take the page's
|
|
17
|
+
* in-flight keepalive bytes over 64 KB — the fetch rejects at once. The first
|
|
18
|
+
* segment carries the full DOM snapshot and is almost always larger, so it
|
|
19
|
+
* went out as keepalive on a tab switch and was lost, leaving every later
|
|
20
|
+
* segment as mutations with nothing to apply them to. Above this, send it
|
|
21
|
+
* as an ordinary request: a hidden tab usually still runs, and that is the
|
|
22
|
+
* common case.
|
|
23
|
+
*/
|
|
24
|
+
export const KEEPALIVE_MAX_BYTES = 60 * 1024;
|
|
25
|
+
/**
|
|
26
|
+
* Ingestion's per-upload limits: `MAX_FRAMES` in `event-schema`'s replay
|
|
27
|
+
* container, and half its 8 MB body limit. A busy page emits thousands of
|
|
28
|
+
* rrweb events in thirty seconds, and one oversized upload is rejected whole.
|
|
29
|
+
*/
|
|
30
|
+
export const MAX_UPLOAD_EVENTS = 2000;
|
|
31
|
+
export const MAX_UPLOAD_BYTES = 4 * 1024 * 1024;
|
|
32
|
+
/** Split a segment into uploads ingestion will accept, in order. */
|
|
33
|
+
export function splitSegment(segment) {
|
|
34
|
+
const chunks = [];
|
|
35
|
+
let chunk = [];
|
|
36
|
+
let bytes = 0;
|
|
37
|
+
for (const event of segment) {
|
|
38
|
+
if (chunk.length > 0 &&
|
|
39
|
+
(chunk.length >= MAX_UPLOAD_EVENTS || bytes + event.payload.byteLength > MAX_UPLOAD_BYTES)) {
|
|
40
|
+
chunks.push(chunk);
|
|
41
|
+
chunk = [];
|
|
42
|
+
bytes = 0;
|
|
43
|
+
}
|
|
44
|
+
chunk.push(event);
|
|
45
|
+
bytes += event.payload.byteLength;
|
|
46
|
+
}
|
|
47
|
+
if (chunk.length > 0)
|
|
48
|
+
chunks.push(chunk);
|
|
49
|
+
return chunks;
|
|
50
|
+
}
|
|
15
51
|
export async function uploadSegment(options, segment) {
|
|
16
52
|
if (segment.length === 0)
|
|
17
53
|
return { kind: 'skipped', reason: 'no events' };
|
|
@@ -35,7 +71,7 @@ export async function uploadSegment(options, segment) {
|
|
|
35
71
|
authorization: `Bearer ${options.apiKey}`,
|
|
36
72
|
},
|
|
37
73
|
body: body,
|
|
38
|
-
keepalive: options.keepalive === true,
|
|
74
|
+
keepalive: options.keepalive === true && body.byteLength <= KEEPALIVE_MAX_BYTES,
|
|
39
75
|
});
|
|
40
76
|
return response.ok
|
|
41
77
|
? { kind: 'sent', events: segment.length }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nagoorkani/session-capture-vue-replay",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Optional DOM session replay for @nagoorkani/session-capture-vue",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@nagoorkani/session-capture-core": "0.1.
|
|
22
|
+
"@nagoorkani/session-capture-core": "^0.1.4"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"rrweb": "^2.0.0",
|
|
26
|
-
"@nagoorkani/session-capture-vue": "0.1.
|
|
26
|
+
"@nagoorkani/session-capture-vue": "^0.1.2"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.0.0",
|