@brunyee-studio/onus-sdk 0.1.0 → 1.0.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 +108 -6
- package/dist/{chunk-SLWBPQ6C.js → chunk-YPQXIZ4X.js} +134 -19
- package/dist/index.cjs +364 -43
- package/dist/index.d.ts +16 -9
- package/dist/index.js +7 -3
- package/dist/{replay-D7ejwI0s.d.ts → replay-BElhLiw8.d.ts} +52 -14
- package/dist/replay.d.ts +1 -1
- package/dist/replay.js +221 -23
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DSN parsing for @onus
|
|
2
|
+
* DSN parsing for @brunyee-studio/onus-sdk (ONUS-68).
|
|
3
3
|
*
|
|
4
4
|
* Mirrors `src/lib/ingest/dsn.ts` semantics (the server side of the same
|
|
5
5
|
* protocol): `{PROTOCOL}://{PUBLIC_KEY}@{HOST}/{NUMERIC_PROJECT_ID}` with no
|
|
@@ -26,7 +26,34 @@ declare function parseDsn(raw: string | undefined | null): ParsedDsn | null;
|
|
|
26
26
|
declare function buildSentryAuthHeader(dsn: ParsedDsn, clientVersion: string): string;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Replay privacy resolution (capture-safety spec): rrweb's own defaults only
|
|
30
|
+
* mask password inputs, so the SDK forces safer defaults BEFORE serialization
|
|
31
|
+
* and lets integrators opt down deliberately through `ReplayPrivacyOptions`.
|
|
32
|
+
* Keys in PRIVACY_CONTROLLED_KEYS are stripped from a bare `recordOptions`
|
|
33
|
+
* passthrough so it can never defeat masking, un-reviewed pixel/iframe
|
|
34
|
+
* capture, replace the SDK's `emit` callback, or pre-pack events through
|
|
35
|
+
* rrweb `packFn`/`plugins` (which run BEFORE emit and could bypass the SDK's
|
|
36
|
+
* Meta href sanitization).
|
|
37
|
+
*/
|
|
38
|
+
interface ReplayPrivacyOptions {
|
|
39
|
+
/** Mask every input value (default true). Passwords are always masked. */
|
|
40
|
+
maskAllInputs?: boolean;
|
|
41
|
+
/** Selector whose text nodes are masked (default '*' — all text). */
|
|
42
|
+
maskTextSelector?: string;
|
|
43
|
+
/** Per-input-type mask map; `password: true` is forced. */
|
|
44
|
+
maskInputOptions?: Record<string, unknown>;
|
|
45
|
+
/** Custom masking transform for text nodes. */
|
|
46
|
+
maskTextFn?: (text: string) => string;
|
|
47
|
+
/** ADDITIVE extra block selectors appended to the built-in block list
|
|
48
|
+
* ('[data-onus-private]', 'input[type="hidden"]', 'input[type="radio"]',
|
|
49
|
+
* 'input[type="checkbox"]'); invalid extras are ignored. */
|
|
50
|
+
blockSelector?: string;
|
|
51
|
+
/** Elements to skip without blocking their subtree. */
|
|
52
|
+
ignoreSelector?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Sentry-compatible envelope building for @brunyee-studio/onus-sdk (ONUS-68).
|
|
30
57
|
*
|
|
31
58
|
* Wire contract (`src/app/api/[projectId]/envelope/route.ts`):
|
|
32
59
|
* - first line: JSON envelope header (event_id, sent_at, sdk)
|
|
@@ -73,13 +100,16 @@ interface ReplayOptions {
|
|
|
73
100
|
environment?: string;
|
|
74
101
|
release?: string;
|
|
75
102
|
/**
|
|
76
|
-
* @onus
|
|
103
|
+
* @brunyee-studio/onus-analytics session id (or getter) attached to each replay_event so
|
|
77
104
|
* the sessions surface can link playback (ONUS-155). A getter is evaluated
|
|
78
105
|
* per segment flush so rotated session ids stay fresh.
|
|
79
106
|
*/
|
|
80
107
|
sessionId?: string | (() => string | null | undefined);
|
|
81
|
-
/** rrweb record
|
|
108
|
+
/** Non-privacy rrweb record tuning (sampling caps, etc.); privacy-controlled
|
|
109
|
+
* keys (masking, emit, packFn/plugins, canvas/iframe) are stripped. */
|
|
82
110
|
recordOptions?: Record<string, unknown>;
|
|
111
|
+
/** Deliberate privacy opt-downs; safe defaults apply when omitted. */
|
|
112
|
+
privacy?: ReplayPrivacyOptions;
|
|
83
113
|
/**
|
|
84
114
|
* How long buffered rrweb events wait before flushing as one segment
|
|
85
115
|
* envelope (ms, default 5000 — Sentry's segment cadence). Batching keeps a
|
|
@@ -91,6 +121,8 @@ interface ReplayOptions {
|
|
|
91
121
|
/** Test seam: recorder + fetch injection (defaults to lazy rrweb import). */
|
|
92
122
|
recorderFactory?: () => Promise<Recorder | null>;
|
|
93
123
|
fetchImpl?: typeof fetch;
|
|
124
|
+
/** Internal guard: reject an auto-start invalidated during an async boundary. */
|
|
125
|
+
shouldProceed?: () => boolean;
|
|
94
126
|
}
|
|
95
127
|
/** Minimal structural subset of rrweb's record function used here. */
|
|
96
128
|
interface Recorder {
|
|
@@ -101,26 +133,32 @@ interface Recorder {
|
|
|
101
133
|
*/
|
|
102
134
|
wire?: (opts: unknown) => void;
|
|
103
135
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
136
|
+
/** rrweb Meta events (type 4) carry the page href; strip query, fragment, and
|
|
137
|
+
* credentials before recording. */
|
|
138
|
+
declare function stripUrlQueryAndFragment(raw: string): string;
|
|
139
|
+
/** One recorded rrweb event as delivered by the recorder's emit callback. */
|
|
140
|
+
type RecordedEvent = Record<string, unknown>;
|
|
141
|
+
/** Minimize recorded URL metadata before buffering (Meta href only). */
|
|
142
|
+
declare function sanitizeRecordedEvent(event: unknown): RecordedEvent;
|
|
112
143
|
/**
|
|
113
144
|
* Start session replay. No-op (returns null) when replay is already running,
|
|
114
145
|
* the DSN is invalid, the recorder factory fails, or rrweb cannot be loaded.
|
|
115
146
|
*/
|
|
116
147
|
declare function startSessionReplay(options?: ReplayOptions): Promise<string | null>;
|
|
148
|
+
/** Internal SDK auto-start seam; direct host calls use startSessionReplay instead. */
|
|
149
|
+
declare function startAutoSessionReplay(options: ReplayOptions, generation: number): Promise<string | null>;
|
|
117
150
|
/** Stop replay. No-op when not running. Flushes any buffered tail. */
|
|
118
151
|
declare function stopSessionReplay(): void;
|
|
152
|
+
/** Hard revoke on denial: also abort all retained stopped-session tails. */
|
|
153
|
+
declare function revokeActiveReplay(): void;
|
|
119
154
|
/** Currently active replay id (null when off). */
|
|
120
155
|
declare function activeReplayId(): string | null;
|
|
121
156
|
/** Test seam. */
|
|
122
157
|
declare function resetReplayForTest(): void;
|
|
123
158
|
/** Build + send one segment envelope (replay_event + replay_recording pair). */
|
|
124
|
-
declare function emitSegment(dsn: NonNullable<ReturnType<typeof parseDsn>>, options: ReplayOptions, state:
|
|
159
|
+
declare function emitSegment(dsn: NonNullable<ReturnType<typeof parseDsn>>, options: ReplayOptions, state: {
|
|
160
|
+
replayId: string;
|
|
161
|
+
abort: AbortController | null;
|
|
162
|
+
}, event: unknown, segmentId: number, timestamp: string): Promise<boolean>;
|
|
125
163
|
|
|
126
|
-
export { type EnvelopeItem as E, MAX_ENVELOPE_BYTES as M, type ParsedDsn as P, type
|
|
164
|
+
export { type EnvelopeItem as E, MAX_ENVELOPE_BYTES as M, type ParsedDsn as P, type ReplayPrivacyOptions as R, type Recorder as a, REPLAY_ID_RE as b, buildEnvelope as c, buildSentryAuthHeader as d, eventId as e, type RecordedEvent as f, type ReplayOptions as g, activeReplayId as h, emitSegment as i, revokeActiveReplay as j, startAutoSessionReplay as k, startSessionReplay as l, stopSessionReplay as m, stripUrlQueryAndFragment as n, parseDsn as p, resetReplayForTest as r, sanitizeRecordedEvent as s };
|
package/dist/replay.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { b as REPLAY_ID_RE, f as RecordedEvent, a as Recorder, g as ReplayOptions, h as activeReplayId, i as emitSegment, p as parseDsn, r as resetReplayForTest, j as revokeActiveReplay, s as sanitizeRecordedEvent, k as startAutoSessionReplay, l as startSessionReplay, m as stopSessionReplay, n as stripUrlQueryAndFragment } from './replay-BElhLiw8.js';
|
package/dist/replay.js
CHANGED
|
@@ -3,30 +3,168 @@ import {
|
|
|
3
3
|
SDK_NAME,
|
|
4
4
|
buildEnvelope,
|
|
5
5
|
eventId,
|
|
6
|
+
getActiveReplayId,
|
|
7
|
+
isReplayCaptureAllowed,
|
|
6
8
|
parseDsn,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
registerReplayStop,
|
|
10
|
+
registerStaleAutoReplayRelease,
|
|
11
|
+
resetCaptureGateForTest,
|
|
12
|
+
sendEnvelope,
|
|
13
|
+
setActiveReplayId
|
|
14
|
+
} from "./chunk-YPQXIZ4X.js";
|
|
15
|
+
|
|
16
|
+
// src/replay-privacy.ts
|
|
17
|
+
var PRIVACY_CONTROLLED_KEYS = /* @__PURE__ */ new Set([
|
|
18
|
+
"emit",
|
|
19
|
+
"maskAllInputs",
|
|
20
|
+
"maskTextSelector",
|
|
21
|
+
"maskTextClass",
|
|
22
|
+
"maskInputOptions",
|
|
23
|
+
"maskInputFn",
|
|
24
|
+
"maskTextFn",
|
|
25
|
+
"blockSelector",
|
|
26
|
+
"blockClass",
|
|
27
|
+
"ignoreClass",
|
|
28
|
+
"ignoreSelector",
|
|
29
|
+
// rrweb runs packers/plugins BEFORE the emit callback, so an allowed
|
|
30
|
+
// passthrough could serialize events that never pass through the SDK's
|
|
31
|
+
// Meta href sanitization. They are SDK-controlled, never passthrough.
|
|
32
|
+
"packFn",
|
|
33
|
+
"plugins",
|
|
34
|
+
"recordCanvas",
|
|
35
|
+
"recordCrossOriginIframes",
|
|
36
|
+
"inlineImages"
|
|
37
|
+
]);
|
|
38
|
+
var PRIVACY_DEFAULTS = {
|
|
39
|
+
maskAllInputs: true,
|
|
40
|
+
maskTextSelector: "*",
|
|
41
|
+
blockSelector: '[data-onus-private], input[type="hidden"], input[type="radio"], input[type="checkbox"]'
|
|
42
|
+
};
|
|
43
|
+
function isValidCssSelector(raw) {
|
|
44
|
+
if (typeof document === "undefined") return false;
|
|
45
|
+
try {
|
|
46
|
+
document.querySelector(raw);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function validSelectorExtra(raw) {
|
|
53
|
+
if (typeof raw !== "string" || raw.trim().length === 0) return "";
|
|
54
|
+
return isValidCssSelector(raw) ? raw : "";
|
|
55
|
+
}
|
|
56
|
+
function maskTextSelectorOrFallback(raw) {
|
|
57
|
+
if (typeof raw !== "string" || raw.trim().length === 0) return PRIVACY_DEFAULTS.maskTextSelector;
|
|
58
|
+
return isValidCssSelector(raw) ? raw : PRIVACY_DEFAULTS.maskTextSelector;
|
|
59
|
+
}
|
|
60
|
+
function blockSelectorWithExtra(extra) {
|
|
61
|
+
const extra_ = validSelectorExtra(extra);
|
|
62
|
+
return extra_ ? `${PRIVACY_DEFAULTS.blockSelector}, ${extra_}` : PRIVACY_DEFAULTS.blockSelector;
|
|
63
|
+
}
|
|
64
|
+
function resolveRecordPrivacy(recordOptions, privacy) {
|
|
65
|
+
const passthrough = {};
|
|
66
|
+
for (const [key, value] of Object.entries(recordOptions ?? {})) {
|
|
67
|
+
if (!PRIVACY_CONTROLLED_KEYS.has(key)) passthrough[key] = value;
|
|
68
|
+
}
|
|
69
|
+
const effective = {
|
|
70
|
+
...passthrough,
|
|
71
|
+
maskAllInputs: privacy?.maskAllInputs ?? PRIVACY_DEFAULTS.maskAllInputs,
|
|
72
|
+
maskTextSelector: maskTextSelectorOrFallback(privacy?.maskTextSelector),
|
|
73
|
+
blockSelector: blockSelectorWithExtra(privacy?.blockSelector),
|
|
74
|
+
recordCanvas: false,
|
|
75
|
+
recordCrossOriginIframes: false,
|
|
76
|
+
inlineImages: false,
|
|
77
|
+
// Password masking is non-negotiable, regardless of opt-downs above.
|
|
78
|
+
maskInputOptions: {
|
|
79
|
+
...privacy?.maskInputOptions ?? {},
|
|
80
|
+
password: true
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
if (privacy?.maskTextFn) effective["maskTextFn"] = privacy.maskTextFn;
|
|
84
|
+
if (privacy?.ignoreSelector) effective["ignoreSelector"] = privacy.ignoreSelector;
|
|
85
|
+
return effective;
|
|
86
|
+
}
|
|
9
87
|
|
|
10
88
|
// src/replay.ts
|
|
89
|
+
function stripUrlQueryAndFragment(raw) {
|
|
90
|
+
try {
|
|
91
|
+
const url = new URL(raw);
|
|
92
|
+
url.username = "";
|
|
93
|
+
url.password = "";
|
|
94
|
+
url.search = "";
|
|
95
|
+
url.hash = "";
|
|
96
|
+
return url.toString();
|
|
97
|
+
} catch {
|
|
98
|
+
const noHash = raw.split("#")[0] ?? raw;
|
|
99
|
+
return noHash.split("?")[0] ?? noHash;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function sanitizeRecordedEvent(event) {
|
|
103
|
+
const candidate = event;
|
|
104
|
+
if (!candidate || typeof candidate !== "object" || candidate.type !== 4 || !candidate.data || typeof candidate.data !== "object") {
|
|
105
|
+
return event;
|
|
106
|
+
}
|
|
107
|
+
const data = { ...candidate.data };
|
|
108
|
+
if (typeof data["href"] === "string") data["href"] = stripUrlQueryAndFragment(data["href"]);
|
|
109
|
+
return { ...candidate, data };
|
|
110
|
+
}
|
|
11
111
|
var active = null;
|
|
12
|
-
|
|
112
|
+
var pendingTails = /* @__PURE__ */ new Set();
|
|
113
|
+
var MAX_PENDING_TAILS = 8;
|
|
114
|
+
function retainPendingTail(state) {
|
|
115
|
+
pendingTails.add(state);
|
|
116
|
+
if (pendingTails.size <= MAX_PENDING_TAILS) return;
|
|
117
|
+
const oldest = pendingTails.values().next().value;
|
|
118
|
+
if (!oldest) return;
|
|
119
|
+
pendingTails.delete(oldest);
|
|
120
|
+
oldest.abort?.abort();
|
|
121
|
+
oldest.buffer.length = 0;
|
|
122
|
+
}
|
|
123
|
+
function clearFailedClaim(state) {
|
|
124
|
+
if (active === state) active = null;
|
|
125
|
+
if (getActiveReplayId() === state.replayId) setActiveReplayId(null);
|
|
126
|
+
}
|
|
127
|
+
function releaseStaleAutoReplay(generation) {
|
|
128
|
+
if (active?.origin.kind === "auto" && active.origin.generation !== generation) {
|
|
129
|
+
dropActiveReplay();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function startSessionReplay(options = {}) {
|
|
133
|
+
return startRecording(options, { kind: "manual" });
|
|
134
|
+
}
|
|
135
|
+
function startAutoSessionReplay(options, generation) {
|
|
136
|
+
return startRecording(options, { kind: "auto", generation });
|
|
137
|
+
}
|
|
138
|
+
async function startRecording(options, origin) {
|
|
139
|
+
if (!isReplayCaptureAllowed() || options.shouldProceed?.() === false) return null;
|
|
140
|
+
registerStaleAutoReplayRelease(releaseStaleAutoReplay);
|
|
13
141
|
if (active) return active.replayId;
|
|
14
142
|
const dsn = parseDsn(options.dsn);
|
|
15
143
|
if (!dsn) return null;
|
|
16
144
|
const replayId = eventId().replace(/-/g, "");
|
|
17
|
-
const state = {
|
|
145
|
+
const state = {
|
|
146
|
+
replayId,
|
|
147
|
+
origin,
|
|
148
|
+
stop: null,
|
|
149
|
+
rawStop: null,
|
|
150
|
+
abort: new AbortController(),
|
|
151
|
+
pendingSends: 0,
|
|
152
|
+
buffer: [],
|
|
153
|
+
timer: null
|
|
154
|
+
};
|
|
18
155
|
active = state;
|
|
156
|
+
registerReplayStop(revokeActiveReplay);
|
|
19
157
|
let record = null;
|
|
20
158
|
if (options.recorderFactory) {
|
|
21
159
|
let recorder;
|
|
22
160
|
try {
|
|
23
161
|
recorder = await options.recorderFactory();
|
|
24
162
|
} catch {
|
|
25
|
-
|
|
163
|
+
clearFailedClaim(state);
|
|
26
164
|
return null;
|
|
27
165
|
}
|
|
28
166
|
if (!recorder) {
|
|
29
|
-
|
|
167
|
+
clearFailedClaim(state);
|
|
30
168
|
return null;
|
|
31
169
|
}
|
|
32
170
|
record = (_opts) => {
|
|
@@ -38,15 +176,19 @@ async function startSessionReplay(options = {}) {
|
|
|
38
176
|
const mod = await import("rrweb");
|
|
39
177
|
const fn = mod.record;
|
|
40
178
|
if (typeof fn !== "function") {
|
|
41
|
-
|
|
179
|
+
clearFailedClaim(state);
|
|
42
180
|
return null;
|
|
43
181
|
}
|
|
44
182
|
record = fn;
|
|
45
183
|
} catch {
|
|
46
|
-
|
|
184
|
+
clearFailedClaim(state);
|
|
47
185
|
return null;
|
|
48
186
|
}
|
|
49
187
|
}
|
|
188
|
+
if (active !== state || !isReplayCaptureAllowed() || options.shouldProceed?.() === false) {
|
|
189
|
+
clearFailedClaim(state);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
50
192
|
const segmentIntervalMs = options.segmentIntervalMs ?? 5e3;
|
|
51
193
|
const maxEventsPerSegment = options.maxEventsPerSegment ?? 100;
|
|
52
194
|
const narrowedDsn = dsn;
|
|
@@ -55,21 +197,36 @@ async function startSessionReplay(options = {}) {
|
|
|
55
197
|
clearTimeout(state.timer);
|
|
56
198
|
state.timer = null;
|
|
57
199
|
}
|
|
200
|
+
if (state.abort?.signal.aborted || options.shouldProceed?.() === false) {
|
|
201
|
+
state.buffer.length = 0;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
58
204
|
if (state.buffer.length === 0) return;
|
|
59
205
|
const events = state.buffer;
|
|
60
206
|
state.buffer = [];
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
207
|
+
state.pendingSends++;
|
|
208
|
+
try {
|
|
209
|
+
await emitSegment(
|
|
210
|
+
narrowedDsn,
|
|
211
|
+
options,
|
|
212
|
+
state,
|
|
213
|
+
events,
|
|
214
|
+
++segmentCounter,
|
|
215
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
216
|
+
);
|
|
217
|
+
} finally {
|
|
218
|
+
state.pendingSends--;
|
|
219
|
+
if (state.pendingSends === 0 && active !== state) pendingTails.delete(state);
|
|
220
|
+
}
|
|
69
221
|
}
|
|
222
|
+
const effectiveRecordOptions = resolveRecordPrivacy(options.recordOptions, options.privacy);
|
|
70
223
|
const stop = record({
|
|
224
|
+
...effectiveRecordOptions,
|
|
71
225
|
emit: (event) => {
|
|
72
|
-
state.
|
|
226
|
+
if (active !== state || !isReplayCaptureAllowed() || options.shouldProceed?.() === false) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
state.buffer.push(sanitizeRecordedEvent(event));
|
|
73
230
|
if (state.buffer.length >= maxEventsPerSegment) {
|
|
74
231
|
void flushBuffer();
|
|
75
232
|
return;
|
|
@@ -80,9 +237,16 @@ async function startSessionReplay(options = {}) {
|
|
|
80
237
|
void flushBuffer();
|
|
81
238
|
}, segmentIntervalMs);
|
|
82
239
|
}
|
|
83
|
-
}
|
|
84
|
-
...options.recordOptions
|
|
240
|
+
}
|
|
85
241
|
});
|
|
242
|
+
if (active !== state || !isReplayCaptureAllowed() || options.shouldProceed?.() === false) {
|
|
243
|
+
stop?.();
|
|
244
|
+
state.abort?.abort();
|
|
245
|
+
state.buffer.length = 0;
|
|
246
|
+
clearFailedClaim(state);
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
state.rawStop = typeof stop === "function" ? stop : null;
|
|
86
250
|
state.stop = typeof stop === "function" ? () => {
|
|
87
251
|
stop();
|
|
88
252
|
void flushBuffer();
|
|
@@ -103,19 +267,48 @@ function stopSessionReplay() {
|
|
|
103
267
|
const stopping = active;
|
|
104
268
|
active = null;
|
|
105
269
|
if (!stopping) return;
|
|
270
|
+
retainPendingTail(stopping);
|
|
106
271
|
stopping.stop?.();
|
|
107
272
|
if (stopping.timer !== null) {
|
|
108
273
|
clearTimeout(stopping.timer);
|
|
109
274
|
stopping.timer = null;
|
|
110
275
|
}
|
|
111
276
|
stopping.buffer.length = 0;
|
|
277
|
+
stopping.rawStop = null;
|
|
278
|
+
if (stopping.pendingSends === 0) pendingTails.delete(stopping);
|
|
279
|
+
setActiveReplayId(null);
|
|
280
|
+
}
|
|
281
|
+
function dropActiveReplay() {
|
|
282
|
+
const stopping = active;
|
|
283
|
+
active = null;
|
|
284
|
+
if (!stopping) return;
|
|
285
|
+
try {
|
|
286
|
+
stopping.rawStop?.();
|
|
287
|
+
} catch {
|
|
288
|
+
}
|
|
289
|
+
if (stopping.timer !== null) {
|
|
290
|
+
clearTimeout(stopping.timer);
|
|
291
|
+
stopping.timer = null;
|
|
292
|
+
}
|
|
293
|
+
stopping.abort?.abort();
|
|
294
|
+
stopping.buffer.length = 0;
|
|
295
|
+
setActiveReplayId(null);
|
|
296
|
+
}
|
|
297
|
+
function revokeActiveReplay() {
|
|
298
|
+
dropActiveReplay();
|
|
299
|
+
for (const tail of pendingTails) {
|
|
300
|
+
tail.abort?.abort();
|
|
301
|
+
tail.buffer.length = 0;
|
|
302
|
+
}
|
|
303
|
+
pendingTails.clear();
|
|
304
|
+
setActiveReplayId(null);
|
|
112
305
|
}
|
|
113
306
|
function activeReplayId() {
|
|
114
307
|
return active?.replayId ?? null;
|
|
115
308
|
}
|
|
116
309
|
function resetReplayForTest() {
|
|
117
|
-
|
|
118
|
-
|
|
310
|
+
revokeActiveReplay();
|
|
311
|
+
resetCaptureGateForTest();
|
|
119
312
|
segmentCounter = 0;
|
|
120
313
|
}
|
|
121
314
|
function resolveSessionId(options) {
|
|
@@ -159,7 +352,8 @@ ${JSON.stringify(event ?? [])}`;
|
|
|
159
352
|
url: dsn.envelopeUrl,
|
|
160
353
|
authHeader: `Sentry sentry_key=${dsn.key}, sentry_version=7, sentry_client=onus.javascript/0.1.0`,
|
|
161
354
|
body: serialized,
|
|
162
|
-
fetchImpl: options.fetchImpl
|
|
355
|
+
fetchImpl: options.fetchImpl,
|
|
356
|
+
signal: state.abort?.signal
|
|
163
357
|
});
|
|
164
358
|
return result.ok;
|
|
165
359
|
}
|
|
@@ -169,6 +363,10 @@ export {
|
|
|
169
363
|
emitSegment,
|
|
170
364
|
parseDsn,
|
|
171
365
|
resetReplayForTest,
|
|
366
|
+
revokeActiveReplay,
|
|
367
|
+
sanitizeRecordedEvent,
|
|
368
|
+
startAutoSessionReplay,
|
|
172
369
|
startSessionReplay,
|
|
173
|
-
stopSessionReplay
|
|
370
|
+
stopSessionReplay,
|
|
371
|
+
stripUrlQueryAndFragment
|
|
174
372
|
};
|
package/package.json
CHANGED