@brunyee-studio/onus-sdk 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,588 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/dsn.ts
34
+ function parseDsn(raw) {
35
+ if (!raw) return null;
36
+ let url;
37
+ try {
38
+ url = new URL(raw);
39
+ } catch {
40
+ return null;
41
+ }
42
+ if (url.protocol !== "https:" && url.protocol !== "http:") return null;
43
+ const key = url.username.split(":")[0] ?? "";
44
+ if (!key) return null;
45
+ const segments = url.pathname.split("/").filter((s) => s !== "");
46
+ if (segments.length !== 1) return null;
47
+ const projectId = segments[0] ?? "";
48
+ if (!/^\d+$/.test(projectId)) return null;
49
+ return {
50
+ key,
51
+ host: url.host,
52
+ projectId,
53
+ envelopeUrl: `${url.protocol}//${url.host}/api/${projectId}/envelope/`,
54
+ origin: url.origin
55
+ };
56
+ }
57
+ function buildSentryAuthHeader(dsn, clientVersion) {
58
+ return `Sentry sentry_key=${dsn.key}, sentry_version=7, sentry_client=onus.javascript/${clientVersion}`;
59
+ }
60
+ var init_dsn = __esm({
61
+ "src/dsn.ts"() {
62
+ "use strict";
63
+ }
64
+ });
65
+
66
+ // src/envelope.ts
67
+ function eventId() {
68
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
69
+ return crypto.randomUUID();
70
+ }
71
+ const bytes = new Uint8Array(16);
72
+ crypto.getRandomValues(bytes);
73
+ bytes[6] = bytes[6] & 15 | 64;
74
+ bytes[8] = bytes[8] & 63 | 128;
75
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
76
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
77
+ }
78
+ function itemHeader(type, length) {
79
+ const header = { type, length };
80
+ return JSON.stringify(header);
81
+ }
82
+ function buildEnvelope(header, items) {
83
+ const parts = [JSON.stringify(header)];
84
+ for (const item of items) {
85
+ parts.push(itemHeader(item.type, item.type === "replay_recording" ? item.length : void 0));
86
+ if (item.type === "replay_recording") {
87
+ parts.push(item.payload);
88
+ } else {
89
+ parts.push(JSON.stringify(item.payload));
90
+ }
91
+ }
92
+ const serialized = parts.join("\n");
93
+ if (new TextEncoder().encode(serialized).byteLength > MAX_ENVELOPE_BYTES) {
94
+ return null;
95
+ }
96
+ return serialized;
97
+ }
98
+ function withReplayContext(event, replayId) {
99
+ if (!replayId || !REPLAY_ID_RE.test(replayId)) return event;
100
+ const contexts = {
101
+ ...event["contexts"],
102
+ replay: { replay_id: replayId }
103
+ };
104
+ return { ...event, contexts };
105
+ }
106
+ var MAX_ENVELOPE_BYTES, REPLAY_ID_RE;
107
+ var init_envelope = __esm({
108
+ "src/envelope.ts"() {
109
+ "use strict";
110
+ MAX_ENVELOPE_BYTES = 1024 * 1024;
111
+ REPLAY_ID_RE = /^[a-zA-Z0-9_-]{1,64}$/;
112
+ }
113
+ });
114
+
115
+ // src/transport.ts
116
+ async function gzipText(text) {
117
+ const CS = globalThis.CompressionStream;
118
+ if (!CS || typeof Response === "undefined") return null;
119
+ try {
120
+ const body = new Response(text).body;
121
+ if (!body) return null;
122
+ const buf = await new Response(body.pipeThrough(new CS("gzip"))).arrayBuffer();
123
+ return new Uint8Array(buf);
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+ function delay(ms) {
129
+ return new Promise((resolve) => setTimeout(resolve, ms));
130
+ }
131
+ async function sendEnvelope(opts) {
132
+ const fetchImpl = opts.fetchImpl ?? fetch;
133
+ const maxRetries = opts.maxRetries ?? 1;
134
+ const backoffMs = opts.backoffMs ?? 500;
135
+ const sleep = opts.delayImpl ?? delay;
136
+ const gz = await gzipText(opts.body);
137
+ const headers = {
138
+ "Content-Type": "application/x-sentry-envelope",
139
+ "X-Sentry-Auth": opts.authHeader,
140
+ ...gz ? { "Content-Encoding": "gzip" } : {}
141
+ };
142
+ for (let attempt = 0; ; attempt++) {
143
+ let res;
144
+ try {
145
+ res = await fetchImpl(opts.url, {
146
+ method: "POST",
147
+ headers,
148
+ body: gz ?? opts.body,
149
+ // Analytics-style telemetry: never block page unload on the response.
150
+ keepalive: true
151
+ });
152
+ } catch (e) {
153
+ return { ok: false, error: e instanceof Error ? e.message : "network error" };
154
+ }
155
+ if (res.ok) return { ok: true, status: res.status };
156
+ if (res.status === 429 && attempt < maxRetries) {
157
+ const retryAfter = Number(res.headers.get("retry-after") ?? "");
158
+ const wait = Math.min(
159
+ Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1e3 : backoffMs * (attempt + 1),
160
+ 1e4
161
+ );
162
+ await sleep(wait);
163
+ continue;
164
+ }
165
+ if (res.status === 429) {
166
+ const retryAfter = Number(res.headers.get("retry-after") ?? "");
167
+ return {
168
+ ok: false,
169
+ status: 429,
170
+ retryAfterSeconds: Number.isFinite(retryAfter) ? retryAfter : void 0,
171
+ error: "rate limited"
172
+ };
173
+ }
174
+ return { ok: false, status: res.status, error: `ingest responded ${res.status}` };
175
+ }
176
+ }
177
+ var init_transport = __esm({
178
+ "src/transport.ts"() {
179
+ "use strict";
180
+ }
181
+ });
182
+
183
+ // src/replay.ts
184
+ var replay_exports = {};
185
+ __export(replay_exports, {
186
+ REPLAY_ID_RE: () => REPLAY_ID_RE,
187
+ activeReplayId: () => activeReplayId,
188
+ emitSegment: () => emitSegment,
189
+ parseDsn: () => parseDsn,
190
+ resetReplayForTest: () => resetReplayForTest,
191
+ startSessionReplay: () => startSessionReplay,
192
+ stopSessionReplay: () => stopSessionReplay
193
+ });
194
+ async function startSessionReplay(options = {}) {
195
+ if (active) return active.replayId;
196
+ const dsn = parseDsn(options.dsn);
197
+ if (!dsn) return null;
198
+ const replayId = eventId().replace(/-/g, "");
199
+ const state2 = { replayId, stop: null, buffer: [], timer: null };
200
+ active = state2;
201
+ let record = null;
202
+ if (options.recorderFactory) {
203
+ let recorder;
204
+ try {
205
+ recorder = await options.recorderFactory();
206
+ } catch {
207
+ active = null;
208
+ return null;
209
+ }
210
+ if (!recorder) {
211
+ active = null;
212
+ return null;
213
+ }
214
+ record = (_opts) => {
215
+ recorder.wire?.(_opts);
216
+ return () => recorder.stop();
217
+ };
218
+ } else {
219
+ try {
220
+ const mod = await import("rrweb");
221
+ const fn = mod.record;
222
+ if (typeof fn !== "function") {
223
+ active = null;
224
+ return null;
225
+ }
226
+ record = fn;
227
+ } catch {
228
+ active = null;
229
+ return null;
230
+ }
231
+ }
232
+ const segmentIntervalMs = options.segmentIntervalMs ?? 5e3;
233
+ const maxEventsPerSegment = options.maxEventsPerSegment ?? 100;
234
+ const narrowedDsn = dsn;
235
+ async function flushBuffer() {
236
+ if (state2.timer !== null) {
237
+ clearTimeout(state2.timer);
238
+ state2.timer = null;
239
+ }
240
+ if (state2.buffer.length === 0) return;
241
+ const events = state2.buffer;
242
+ state2.buffer = [];
243
+ await emitSegment(
244
+ narrowedDsn,
245
+ options,
246
+ state2,
247
+ events,
248
+ ++segmentCounter,
249
+ (/* @__PURE__ */ new Date()).toISOString()
250
+ );
251
+ }
252
+ const stop = record({
253
+ emit: (event) => {
254
+ state2.buffer.push(event);
255
+ if (state2.buffer.length >= maxEventsPerSegment) {
256
+ void flushBuffer();
257
+ return;
258
+ }
259
+ if (state2.timer === null) {
260
+ state2.timer = setTimeout(() => {
261
+ state2.timer = null;
262
+ void flushBuffer();
263
+ }, segmentIntervalMs);
264
+ }
265
+ },
266
+ ...options.recordOptions
267
+ });
268
+ state2.stop = typeof stop === "function" ? () => {
269
+ stop();
270
+ void flushBuffer();
271
+ } : null;
272
+ if (typeof window !== "undefined") {
273
+ window.addEventListener(
274
+ "pagehide",
275
+ () => {
276
+ void flushBuffer();
277
+ },
278
+ { once: true }
279
+ );
280
+ }
281
+ return replayId;
282
+ }
283
+ function stopSessionReplay() {
284
+ const stopping = active;
285
+ active = null;
286
+ if (!stopping) return;
287
+ stopping.stop?.();
288
+ if (stopping.timer !== null) {
289
+ clearTimeout(stopping.timer);
290
+ stopping.timer = null;
291
+ }
292
+ stopping.buffer.length = 0;
293
+ }
294
+ function activeReplayId() {
295
+ return active?.replayId ?? null;
296
+ }
297
+ function resetReplayForTest() {
298
+ active?.stop?.();
299
+ active = null;
300
+ segmentCounter = 0;
301
+ }
302
+ function resolveSessionId(options) {
303
+ const raw = typeof options.sessionId === "function" ? options.sessionId() : options.sessionId;
304
+ return typeof raw === "string" && raw.length > 0 ? raw : null;
305
+ }
306
+ async function emitSegment(dsn, options, state2, event, segmentId, timestamp) {
307
+ const recordingText = `{"segment_id":${segmentId}}
308
+ ${JSON.stringify(event ?? [])}`;
309
+ const sessionId = resolveSessionId(options);
310
+ const items = [
311
+ {
312
+ type: "replay_event",
313
+ payload: {
314
+ segment_id: segmentId,
315
+ replay_id: state2.replayId,
316
+ timestamp,
317
+ ...sessionId ? { session_id: sessionId } : {},
318
+ ...options.environment ? { environment: options.environment } : {},
319
+ ...options.release ? { release: options.release } : {}
320
+ }
321
+ },
322
+ {
323
+ type: "replay_recording",
324
+ payload: recordingText,
325
+ // Byte length (not UTF-16 code units) — the server validates the
326
+ // declared length against payload bytes.
327
+ length: new TextEncoder().encode(recordingText).length
328
+ }
329
+ ];
330
+ const serialized = buildEnvelope(
331
+ {
332
+ event_id: eventId(),
333
+ sent_at: timestamp,
334
+ sdk: { name: SDK_NAME, version: "0.1.0" }
335
+ },
336
+ items
337
+ );
338
+ if (!serialized) return false;
339
+ const result = await sendEnvelope({
340
+ url: dsn.envelopeUrl,
341
+ authHeader: `Sentry sentry_key=${dsn.key}, sentry_version=7, sentry_client=onus.javascript/0.1.0`,
342
+ body: serialized,
343
+ fetchImpl: options.fetchImpl
344
+ });
345
+ return result.ok;
346
+ }
347
+ var active, segmentCounter;
348
+ var init_replay = __esm({
349
+ "src/replay.ts"() {
350
+ "use strict";
351
+ init_envelope();
352
+ init_transport();
353
+ init_dsn();
354
+ init_client();
355
+ init_envelope();
356
+ init_dsn();
357
+ active = null;
358
+ segmentCounter = 0;
359
+ }
360
+ });
361
+
362
+ // src/client.ts
363
+ function debugLog(...args) {
364
+ if (false) {
365
+ console.debug("[onus]", ...args);
366
+ }
367
+ }
368
+ function dispatch(items, eventIdValue) {
369
+ if (!state?.dsn) return Promise.resolve(false);
370
+ const serialized = buildEnvelope(
371
+ {
372
+ event_id: eventIdValue,
373
+ sent_at: (/* @__PURE__ */ new Date()).toISOString(),
374
+ sdk: { name: SDK_NAME, version: SDK_VERSION }
375
+ },
376
+ items
377
+ );
378
+ if (!serialized) {
379
+ debugLog("envelope exceeded 1 MiB cap; dropped", eventIdValue);
380
+ return Promise.resolve(false);
381
+ }
382
+ const send = sendEnvelope({
383
+ url: state.dsn.envelopeUrl,
384
+ authHeader: state.authHeader,
385
+ body: serialized,
386
+ fetchImpl: state.options.fetchImpl
387
+ });
388
+ const tracked = send.then((r) => {
389
+ if (!r.ok) debugLog("send failed", r.status, r.error);
390
+ return r.ok;
391
+ });
392
+ pending.push(tracked);
393
+ if (pending.length > MAX_PENDING) pending.shift();
394
+ return tracked;
395
+ }
396
+ function init(options) {
397
+ const dsn = parseDsn(options.dsn);
398
+ if (!dsn) {
399
+ debugLog("no valid dsn; SDK is disabled (no network sends)");
400
+ }
401
+ state = {
402
+ dsn,
403
+ options,
404
+ authHeader: dsn ? buildSentryAuthHeader(dsn, SDK_VERSION) : "",
405
+ replayId: null
406
+ };
407
+ autoReplayStart = null;
408
+ if (options.replays && options.replays.enabled !== false && dsn) {
409
+ autoReplayStart = startAutoReplay(options.replays, options);
410
+ }
411
+ }
412
+ async function startAutoReplay(replays, options) {
413
+ try {
414
+ const rate = typeof replays.sampleRate === "number" && replays.sampleRate >= 0 && replays.sampleRate <= 1 ? replays.sampleRate : 1;
415
+ if (Math.random() >= rate) return;
416
+ const replay = await Promise.resolve().then(() => (init_replay(), replay_exports));
417
+ const replayId = await replay.startSessionReplay({
418
+ dsn: options.dsn,
419
+ environment: replays.environment ?? options.environment,
420
+ release: replays.release ?? options.release,
421
+ sessionId: replays.sessionId,
422
+ segmentIntervalMs: replays.segmentIntervalMs,
423
+ maxEventsPerSegment: replays.maxEventsPerSegment,
424
+ recordOptions: replays.recordOptions,
425
+ recorderFactory: replays.recorderFactory,
426
+ fetchImpl: replays.fetchImpl ?? options.fetchImpl
427
+ });
428
+ if (replayId) setActiveReplayId(replayId);
429
+ } catch (e) {
430
+ debugLog("replay auto-start failed", e);
431
+ }
432
+ }
433
+ function isEnabled() {
434
+ return state?.dsn !== null && state?.dsn !== void 0;
435
+ }
436
+ function resetForTest() {
437
+ state = null;
438
+ pending.length = 0;
439
+ autoReplayStart = null;
440
+ }
441
+ function baseEvent(error) {
442
+ return {
443
+ platform: "javascript",
444
+ environment: state?.options.environment ?? "production",
445
+ ...state?.options.release ? { release: state.options.release } : {},
446
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
447
+ ...error
448
+ };
449
+ }
450
+ function captureException(exception, hint) {
451
+ const id = eventId();
452
+ const event = withReplayContext(baseEvent({ level: "error", ...hint }), state?.replayId ?? null);
453
+ event["event_id"] = id;
454
+ event["exception"] = exceptionPayload(exception);
455
+ void dispatch([{ type: "event", payload: event }], id);
456
+ return id;
457
+ }
458
+ function exceptionPayload(exception) {
459
+ if (exception instanceof Error) {
460
+ return {
461
+ values: [
462
+ {
463
+ type: exception.name,
464
+ value: exception.message,
465
+ stacktrace: exception.stack ? { frames: parseStackFrames(exception.stack) } : void 0
466
+ }
467
+ ]
468
+ };
469
+ }
470
+ return { values: [{ value: String(exception) }] };
471
+ }
472
+ function parseStackFrames(stack) {
473
+ const frames = [];
474
+ for (const line of stack.split("\n")) {
475
+ const m = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?\s*$/.exec(line);
476
+ if (!m) continue;
477
+ frames.push({
478
+ function: m[1] ?? void 0,
479
+ filename: m[2],
480
+ lineno: Number(m[3]),
481
+ colno: Number(m[4])
482
+ });
483
+ }
484
+ return frames.reverse();
485
+ }
486
+ function captureMessage(message, hint) {
487
+ const id = eventId();
488
+ const event = withReplayContext(
489
+ baseEvent({ message, level: "info", ...hint }),
490
+ state?.replayId ?? null
491
+ );
492
+ event["event_id"] = id;
493
+ void dispatch([{ type: "event", payload: event }], id);
494
+ return id;
495
+ }
496
+ function captureEvent(event) {
497
+ const id = eventId();
498
+ const payload = withReplayContext(baseEvent(event), state?.replayId ?? null);
499
+ payload["event_id"] = id;
500
+ void dispatch([{ type: "event", payload }], id);
501
+ return id;
502
+ }
503
+ function captureCustomEvent(name, props) {
504
+ const id = eventId();
505
+ const event = baseEvent({
506
+ message: name,
507
+ level: "info",
508
+ tags: { __onus_custom: name },
509
+ extra: props ?? {}
510
+ });
511
+ event["event_id"] = id;
512
+ void dispatch([{ type: "event", payload: event }], id);
513
+ return id;
514
+ }
515
+ function setActiveReplayId(replayId) {
516
+ if (state) state.replayId = replayId && /^[a-zA-Z0-9_-]{1,64}$/.test(replayId) ? replayId : null;
517
+ }
518
+ function getActiveReplayId() {
519
+ return state?.replayId ?? null;
520
+ }
521
+ async function flush() {
522
+ if (autoReplayStart) await autoReplayStart;
523
+ await Promise.allSettled(pending.splice(0, pending.length));
524
+ return isEnabled();
525
+ }
526
+ var SDK_NAME, SDK_VERSION, autoReplayStart, state, pending, MAX_PENDING;
527
+ var init_client = __esm({
528
+ "src/client.ts"() {
529
+ "use strict";
530
+ init_dsn();
531
+ init_envelope();
532
+ init_transport();
533
+ SDK_NAME = "onus.javascript";
534
+ SDK_VERSION = "0.1.0";
535
+ autoReplayStart = null;
536
+ state = null;
537
+ pending = [];
538
+ MAX_PENDING = 100;
539
+ }
540
+ });
541
+
542
+ // src/index.ts
543
+ var src_exports = {};
544
+ __export(src_exports, {
545
+ MAX_ENVELOPE_BYTES: () => MAX_ENVELOPE_BYTES,
546
+ REPLAY_ID_RE: () => REPLAY_ID_RE,
547
+ SDK_NAME: () => SDK_NAME,
548
+ SDK_VERSION: () => SDK_VERSION,
549
+ buildEnvelope: () => buildEnvelope,
550
+ buildSentryAuthHeader: () => buildSentryAuthHeader,
551
+ captureCustomEvent: () => captureCustomEvent,
552
+ captureEvent: () => captureEvent,
553
+ captureException: () => captureException,
554
+ captureMessage: () => captureMessage,
555
+ eventId: () => eventId,
556
+ flush: () => flush,
557
+ getActiveReplayId: () => getActiveReplayId,
558
+ init: () => init,
559
+ isEnabled: () => isEnabled,
560
+ parseDsn: () => parseDsn,
561
+ resetForTest: () => resetForTest,
562
+ setActiveReplayId: () => setActiveReplayId
563
+ });
564
+ module.exports = __toCommonJS(src_exports);
565
+ init_client();
566
+ init_dsn();
567
+ init_envelope();
568
+ // Annotate the CommonJS export names for ESM import in node:
569
+ 0 && (module.exports = {
570
+ MAX_ENVELOPE_BYTES,
571
+ REPLAY_ID_RE,
572
+ SDK_NAME,
573
+ SDK_VERSION,
574
+ buildEnvelope,
575
+ buildSentryAuthHeader,
576
+ captureCustomEvent,
577
+ captureEvent,
578
+ captureException,
579
+ captureMessage,
580
+ eventId,
581
+ flush,
582
+ getActiveReplayId,
583
+ init,
584
+ isEnabled,
585
+ parseDsn,
586
+ resetForTest,
587
+ setActiveReplayId
588
+ });
@@ -0,0 +1,102 @@
1
+ import { R as Recorder } from './replay-D7ejwI0s.js';
2
+ export { E as EnvelopeItem, M as MAX_ENVELOPE_BYTES, P as ParsedDsn, a as REPLAY_ID_RE, b as buildEnvelope, c as buildSentryAuthHeader, e as eventId, p as parseDsn } from './replay-D7ejwI0s.js';
3
+
4
+ declare const SDK_NAME = "onus.javascript";
5
+ declare const SDK_VERSION = "0.1.0";
6
+ /**
7
+ * Session-replay capture config (ONUS-155). Passing this block opts the app
8
+ * into automatic replay recording from `init()`; within the block, capture
9
+ * defaults to **all sessions** (`sampleRate` 1) — sampling is an opt-down.
10
+ * rrweb itself stays behind the lazy `@onus/sdk/replay` subpath, so the
11
+ * recorder chunk only downloads once recording actually starts.
12
+ */
13
+ interface ReplaysOptions {
14
+ /** Auto-start replay with `init()` (default true when the block is given). */
15
+ enabled?: boolean;
16
+ /** Fraction of sessions recorded, 0..1 (default 1 — capture all users). */
17
+ sampleRate?: number;
18
+ /**
19
+ * @onus/analytics session id (or getter) attached to each replay_event so
20
+ * the sessions surface can deep-link playback. A getter is evaluated per
21
+ * segment flush so rotated session ids stay fresh.
22
+ */
23
+ sessionId?: string | (() => string | null | undefined);
24
+ /** Override the replay environment (defaults to the core `environment`). */
25
+ environment?: string;
26
+ /** Override the replay release (defaults to the core `release`). */
27
+ release?: string;
28
+ /** Buffered events flushed as one segment after this long (default 5000ms). */
29
+ segmentIntervalMs?: number;
30
+ /** Buffered events that force an immediate segment flush (default 100). */
31
+ maxEventsPerSegment?: number;
32
+ /** rrweb record options passthrough (privacy masking, etc.). */
33
+ recordOptions?: Record<string, unknown>;
34
+ /** Test seam: recorder injection (defaults to lazy rrweb import). */
35
+ recorderFactory?: () => Promise<Recorder | null>;
36
+ /** Test seam: fetch implementation (defaults to the core `fetchImpl`). */
37
+ fetchImpl?: typeof fetch;
38
+ }
39
+ interface OnusSdkOptions {
40
+ /** Flat DSN `https://{key}@{host}/{numericProjectId}` — required to send. */
41
+ dsn?: string;
42
+ release?: string;
43
+ environment?: string;
44
+ /** Session-replay capture (see {@link ReplaysOptions}); omit to opt out. */
45
+ replays?: ReplaysOptions;
46
+ /** Test seam: fetch implementation (defaults to globalThis.fetch). */
47
+ fetchImpl?: typeof fetch;
48
+ }
49
+ interface OnusErrorEvent {
50
+ message?: string;
51
+ level?: 'error' | 'warning' | 'info' | 'debug';
52
+ exception?: unknown;
53
+ tags?: Record<string, string | number | boolean>;
54
+ extra?: Record<string, unknown>;
55
+ contexts?: Record<string, unknown>;
56
+ breadcrumbs?: unknown[];
57
+ request?: Record<string, unknown>;
58
+ }
59
+ /** Initialize the SDK. Safe to call once; later calls reconfigure the DSN. */
60
+ declare function init(options: OnusSdkOptions): void;
61
+ /** Whether the SDK has a usable DSN and will actually send. */
62
+ declare function isEnabled(): boolean;
63
+ /** Test seam: reset module state. */
64
+ declare function resetForTest(): void;
65
+ /** Capture a handled/unhandled exception. Returns the event id. */
66
+ declare function captureException(exception: unknown, hint?: OnusErrorEvent): string;
67
+ /** Capture a textual message (level defaults to info). */
68
+ declare function captureMessage(message: string, hint?: OnusErrorEvent): string;
69
+ /** Capture a fully-formed event (Sentry captureEvent). */
70
+ declare function captureEvent(event: OnusErrorEvent): string;
71
+ /**
72
+ * Capture a custom analytics event. Routed through the error envelope as an
73
+ * `event` item with `tags.__onus_custom` marker so the same ingest path
74
+ * serves both surfaces (the @onus/analytics SDK remains the PostHog-style
75
+ * events pipeline; this is for lightweight app-level breadcrumbs).
76
+ */
77
+ declare function captureCustomEvent(name: string, props?: Record<string, unknown>): string;
78
+ /** Register the active replay session id for error↔replay linking. */
79
+ declare function setActiveReplayId(replayId: string | null): void;
80
+ /** Active replay id (null when replay is off). */
81
+ declare function getActiveReplayId(): string | null;
82
+ /** Drain in-flight sends; resolves true when the SDK is enabled. */
83
+ declare function flush(): Promise<boolean>;
84
+
85
+ /**
86
+ * Envelope transport for @onus/sdk (ONUS-68).
87
+ *
88
+ * POSTs a serialized envelope to the ingest route with `X-Sentry-Auth`.
89
+ * Honors the server contract: 429 + `retry-after` (rate limits, migration 34),
90
+ * gzip via CompressionStream when available (the route sniffs magic bytes and
91
+ * also accepts identity bodies). Retries 429s with capped backoff; network
92
+ * failures are reported, never thrown, so instrumentation can't crash the app.
93
+ */
94
+ interface SendResult {
95
+ ok: boolean;
96
+ status?: number;
97
+ /** Server-provided retry hint (seconds) from a 429, when present. */
98
+ retryAfterSeconds?: number;
99
+ error?: string;
100
+ }
101
+
102
+ export { type OnusErrorEvent, type OnusSdkOptions, type ReplaysOptions, SDK_NAME, SDK_VERSION, type SendResult, captureCustomEvent, captureEvent, captureException, captureMessage, flush, getActiveReplayId, init, isEnabled, resetForTest, setActiveReplayId };