@multiplayer-app/session-recorder-common 1.3.31 → 1.3.33
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/esm/sdk/capture-exception.d.ts +1 -0
- package/dist/esm/sdk/capture-exception.d.ts.map +1 -1
- package/dist/esm/sdk/capture-exception.js +60 -4
- package/dist/esm/sdk/capture-exception.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/type/crash-buffer.d.ts +4 -7
- package/dist/esm/type/crash-buffer.d.ts.map +1 -1
- package/dist/esm/type/crash-buffer.js.map +1 -1
- package/dist/esnext/sdk/capture-exception.d.ts +1 -0
- package/dist/esnext/sdk/capture-exception.d.ts.map +1 -1
- package/dist/esnext/sdk/capture-exception.js +39 -5
- package/dist/esnext/sdk/capture-exception.js.map +1 -1
- package/dist/esnext/tsconfig.esnext.tsbuildinfo +1 -1
- package/dist/esnext/type/crash-buffer.d.ts +4 -7
- package/dist/esnext/type/crash-buffer.d.ts.map +1 -1
- package/dist/esnext/type/crash-buffer.js.map +1 -1
- package/dist/src/sdk/capture-exception.d.ts +1 -0
- package/dist/src/sdk/capture-exception.d.ts.map +1 -1
- package/dist/src/sdk/capture-exception.js +40 -5
- package/dist/src/sdk/capture-exception.js.map +1 -1
- package/dist/src/type/crash-buffer.d.ts +4 -7
- package/dist/src/type/crash-buffer.d.ts.map +1 -1
- package/dist/src/type/crash-buffer.js.map +1 -1
- package/package.json +1 -1
- package/src/sdk/capture-exception.ts +57 -19
- package/src/type/crash-buffer.ts +6 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crash-buffer.d.ts","sourceRoot":"","sources":["../../../src/type/crash-buffer.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACxC,cAAc,CAAC,EAAE,GAAG,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,4BAA4B,GAAG;IACzC,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,KAAK,EAAE,GAAG,CAAA;CACX,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,GAAG,CAAA;CACV,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,+BAA+B,GAAG,0BAA0B,EAAE,CAAA;AAE1E,MAAM,MAAM,iCAAiC,GAAG,0BAA0B,CAAA;AAE1E,MAAM,MAAM,oBAAoB,GAAG,qBAAqB,CAAA;AAExD,MAAM,MAAM,mBAAmB,GAAG;IAChC,qBAAqB,EAAE,iCAAiC,CAAA;CACzD,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,
|
|
1
|
+
{"version":3,"file":"crash-buffer.d.ts","sourceRoot":"","sources":["../../../src/type/crash-buffer.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACxC,cAAc,CAAC,EAAE,GAAG,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,4BAA4B,GAAG;IACzC,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,KAAK,EAAE,GAAG,CAAA;CACX,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,GAAG,CAAA;CACV,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,+BAA+B,GAAG,0BAA0B,EAAE,CAAA;AAE1E,MAAM,MAAM,iCAAiC,GAAG,0BAA0B,CAAA;AAE1E,MAAM,MAAM,oBAAoB,GAAG,qBAAqB,CAAA;AAExD,MAAM,MAAM,mBAAmB,GAAG;IAChC,qBAAqB,EAAE,iCAAiC,CAAA;CACzD,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,4BAA4B,EAAE,CAAA;IACtC,KAAK,EAAE,0BAA0B,EAAE,CAAA;CACpC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,OAAO,EAAE,4BAA4B,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpF,WAAW,CAAC,OAAO,EAAE,+BAA+B,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;IACvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,cAAc,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD,EAAE,CAAC,CAAC,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,mBAAmB,CAAC,oBAAoB,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;IACpH,GAAG,CAAC,CAAC,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,mBAAmB,CAAC,oBAAoB,CAAC,KAAK,IAAI,GAAG,IAAI,CAAA;CAChH;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,WAAW;IACvD,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAA;IAChC,iBAAiB,IAAI,OAAO,CAAA;CAC7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crash-buffer.js","sourceRoot":"","sources":["../../../src/type/crash-buffer.ts"],"names":[],"mappings":"","sourcesContent":["export type CrashBufferAttrs = {\n sessionAttributes?: Record<string, any>\n resourceAttributes?: Record<string, any>\n userAttributes?: any\n}\n\nexport type CrashBufferRrwebEventPayload = {\n ts: number\n isFullSnapshot?: boolean\n event: any\n}\n\nexport type CrashBufferOtelSpanPayload = {\n ts: number\n span: any\n}\n\n/**\n * Batch append payload for OTEL spans.\n * This is intentionally the same per-span shape as `CrashBufferOtelSpanPayload`,\n * just provided as an array to allow implementations to persist efficiently.\n */\nexport type CrashBufferOtelSpanBatchPayload = CrashBufferOtelSpanPayload[]\n\nexport type CrashBufferErrorSpanAppendedEvent = CrashBufferOtelSpanPayload\n\nexport type CrashBufferEventName = 'error-span-appended'\n\nexport type CrashBufferEventMap = {\n 'error-span-appended': CrashBufferErrorSpanAppendedEvent\n}\n\nexport type CrashBufferSnapshot = {\n
|
|
1
|
+
{"version":3,"file":"crash-buffer.js","sourceRoot":"","sources":["../../../src/type/crash-buffer.ts"],"names":[],"mappings":"","sourcesContent":["export type CrashBufferAttrs = {\n sessionAttributes?: Record<string, any>\n resourceAttributes?: Record<string, any>\n userAttributes?: any\n}\n\nexport type CrashBufferRrwebEventPayload = {\n ts: number\n isFullSnapshot?: boolean\n event: any\n}\n\nexport type CrashBufferOtelSpanPayload = {\n ts: number\n span: any\n}\n\n/**\n * Batch append payload for OTEL spans.\n * This is intentionally the same per-span shape as `CrashBufferOtelSpanPayload`,\n * just provided as an array to allow implementations to persist efficiently.\n */\nexport type CrashBufferOtelSpanBatchPayload = CrashBufferOtelSpanPayload[]\n\nexport type CrashBufferErrorSpanAppendedEvent = CrashBufferOtelSpanPayload\n\nexport type CrashBufferEventName = 'error-span-appended'\n\nexport type CrashBufferEventMap = {\n 'error-span-appended': CrashBufferErrorSpanAppendedEvent\n}\n\nexport type CrashBufferSnapshot = {\n startedAt: number\n stoppedAt: number\n events: CrashBufferRrwebEventPayload[]\n spans: CrashBufferOtelSpanPayload[]\n}\n\n/**\n * Shared CrashBuffer contract used across browser + react-native implementations.\n *\n * Notes:\n * - `windowMs` is optional because browser implementations usually bake the window into the instance,\n * while React Native typically passes it per call.\n * - `pruneOlderThan` is optional because browser implementations can handle pruning internally.\n */\nexport interface CrashBuffer {\n appendEvent(payload: CrashBufferRrwebEventPayload, windowMs?: number): Promise<void>\n appendSpans(payload: CrashBufferOtelSpanBatchPayload, windowMs?: number): Promise<void>\n snapshot(windowMs?: number, now?: number): Promise<CrashBufferSnapshot>\n clear(): Promise<void>\n pruneOlderThan?(cutoffTs: number): Promise<void>\n on?(event: CrashBufferEventName, listener: (payload: CrashBufferEventMap[CrashBufferEventName]) => void): () => void\n off?(event: CrashBufferEventName, listener: (payload: CrashBufferEventMap[CrashBufferEventName]) => void): void\n}\n\n/**\n * Optional lifecycle controls supported by some CrashBuffer implementations (e.g. browser tabs).\n */\nexport interface CrashBufferLifecycle extends CrashBuffer {\n setActive(active: boolean): void\n needsFullSnapshot(): boolean\n}\n"]}
|
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* @returns {void}
|
|
5
5
|
*/
|
|
6
6
|
export declare const captureException: (error: Error, errorInfo?: Record<string, any>) => void;
|
|
7
|
+
export declare const shouldCaptureException: (error: Error, _errorInfo?: Record<string, any>) => boolean;
|
|
7
8
|
//# sourceMappingURL=capture-exception.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture-exception.d.ts","sourceRoot":"","sources":["../../../src/sdk/capture-exception.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"capture-exception.d.ts","sourceRoot":"","sources":["../../../src/sdk/capture-exception.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,KAAK,EAAE,YAAY,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,SA4C7E,CAAA;AAUD,eAAO,MAAM,sBAAsB,GAAI,OAAO,KAAK,EAAE,aAAa,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAG,OAkCvF,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { context, trace, SpanStatusCode } from '@opentelemetry/api';
|
|
2
|
-
import { ATTR_EXCEPTION_MESSAGE, ATTR_EXCEPTION_STACKTRACE, ATTR_EXCEPTION_TYPE
|
|
2
|
+
import { ATTR_EXCEPTION_MESSAGE, ATTR_EXCEPTION_STACKTRACE, ATTR_EXCEPTION_TYPE } from '@opentelemetry/semantic-conventions';
|
|
3
3
|
import { getResourceAttributes } from './set-resource-attributes';
|
|
4
4
|
/**
|
|
5
5
|
* @description Add error to current span
|
|
@@ -7,7 +7,7 @@ import { getResourceAttributes } from './set-resource-attributes';
|
|
|
7
7
|
* @returns {void}
|
|
8
8
|
*/
|
|
9
9
|
export const captureException = (error, errorInfo) => {
|
|
10
|
-
if (!error) {
|
|
10
|
+
if (!error || !shouldCaptureException(error)) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
const activeContext = context.active();
|
|
@@ -15,7 +15,7 @@ export const captureException = (error, errorInfo) => {
|
|
|
15
15
|
let isNewSpan = false;
|
|
16
16
|
if (!span || !span.isRecording()) {
|
|
17
17
|
span = trace.getTracer('exception').startSpan(error.name || 'Error', {
|
|
18
|
-
attributes: Object.assign({ [ATTR_EXCEPTION_MESSAGE]: error.message, [ATTR_EXCEPTION_STACKTRACE]: error.stack, [ATTR_EXCEPTION_TYPE]: error.name }, getResourceAttributes())
|
|
18
|
+
attributes: Object.assign({ [ATTR_EXCEPTION_MESSAGE]: error.message, [ATTR_EXCEPTION_STACKTRACE]: error.stack, [ATTR_EXCEPTION_TYPE]: error.name }, getResourceAttributes())
|
|
19
19
|
});
|
|
20
20
|
trace.setSpan(activeContext, span);
|
|
21
21
|
isNewSpan = true;
|
|
@@ -24,7 +24,7 @@ export const captureException = (error, errorInfo) => {
|
|
|
24
24
|
span.setAttributes({
|
|
25
25
|
[ATTR_EXCEPTION_MESSAGE]: error.message,
|
|
26
26
|
[ATTR_EXCEPTION_STACKTRACE]: error.stack,
|
|
27
|
-
[ATTR_EXCEPTION_TYPE]: error.name
|
|
27
|
+
[ATTR_EXCEPTION_TYPE]: error.name
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
if (errorInfo) {
|
|
@@ -35,10 +35,44 @@ export const captureException = (error, errorInfo) => {
|
|
|
35
35
|
span.recordException(error);
|
|
36
36
|
span.setStatus({
|
|
37
37
|
code: SpanStatusCode.ERROR,
|
|
38
|
-
message: error.message
|
|
38
|
+
message: error.message
|
|
39
39
|
});
|
|
40
40
|
if (isNewSpan) {
|
|
41
41
|
span.end();
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Best-effort deduplication of exceptions that fire multiple times
|
|
46
|
+
* (e.g. framework handler + global handlers) within a short time window.
|
|
47
|
+
*/
|
|
48
|
+
const exceptionDedupeWindowMs = 2000;
|
|
49
|
+
const recentExceptionFingerprints = new Map();
|
|
50
|
+
export const shouldCaptureException = (error, _errorInfo) => {
|
|
51
|
+
if (!error)
|
|
52
|
+
return false;
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
// Build a fingerprint that is stable enough across repeated emissions
|
|
55
|
+
// but not so broad that different errors collapse into one.
|
|
56
|
+
const keyParts = [];
|
|
57
|
+
keyParts.push(error.name || 'Error');
|
|
58
|
+
keyParts.push(error.message || '');
|
|
59
|
+
// First stack line tends to include file/line where it originated.
|
|
60
|
+
if (typeof error.stack === 'string') {
|
|
61
|
+
const firstFrame = error.stack.split('\n')[1] || '';
|
|
62
|
+
keyParts.push(firstFrame.trim());
|
|
63
|
+
}
|
|
64
|
+
const fingerprint = keyParts.join('|').slice(0, 500);
|
|
65
|
+
const lastSeen = recentExceptionFingerprints.get(fingerprint);
|
|
66
|
+
if (lastSeen && now - lastSeen < exceptionDedupeWindowMs) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
recentExceptionFingerprints.set(fingerprint, now);
|
|
70
|
+
// Cheap cleanup of old entries to avoid unbounded growth.
|
|
71
|
+
for (const [key, ts] of recentExceptionFingerprints) {
|
|
72
|
+
if (now - ts > exceptionDedupeWindowMs * 5) {
|
|
73
|
+
recentExceptionFingerprints.delete(key);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
};
|
|
44
78
|
//# sourceMappingURL=capture-exception.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture-exception.js","sourceRoot":"","sources":["../../../src/sdk/capture-exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnE,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"capture-exception.js","sourceRoot":"","sources":["../../../src/sdk/capture-exception.ts"],"names":[],"mappings":"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","sourcesContent":["import { context, trace, SpanStatusCode } from '@opentelemetry/api'\nimport {\n ATTR_EXCEPTION_MESSAGE,\n ATTR_EXCEPTION_STACKTRACE,\n ATTR_EXCEPTION_TYPE\n} from '@opentelemetry/semantic-conventions'\nimport { getResourceAttributes } from './set-resource-attributes'\n\n/**\n * @description Add error to current span\n * @param {Error} error\n * @returns {void}\n */\nexport const captureException = (error: Error, errorInfo?: Record<string, any>) => {\n if (!error || !shouldCaptureException(error)) {\n return\n }\n\n const activeContext = context.active()\n\n let span = trace.getSpan(activeContext)\n let isNewSpan = false\n\n if (!span || !span.isRecording()) {\n span = trace.getTracer('exception').startSpan(error.name || 'Error', {\n attributes: {\n [ATTR_EXCEPTION_MESSAGE]: error.message,\n [ATTR_EXCEPTION_STACKTRACE]: error.stack,\n [ATTR_EXCEPTION_TYPE]: error.name,\n ...getResourceAttributes()\n }\n })\n trace.setSpan(activeContext, span)\n isNewSpan = true\n } else {\n span.setAttributes({\n [ATTR_EXCEPTION_MESSAGE]: error.message,\n [ATTR_EXCEPTION_STACKTRACE]: error.stack,\n [ATTR_EXCEPTION_TYPE]: error.name\n })\n }\n\n if (errorInfo) {\n Object.entries(errorInfo).forEach(([key, value]) => {\n span.setAttribute(`error_info.${key}`, value)\n })\n }\n\n span.recordException(error)\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message\n })\n\n if (isNewSpan) {\n span.end()\n }\n}\n\n/**\n * Best-effort deduplication of exceptions that fire multiple times\n * (e.g. framework handler + global handlers) within a short time window.\n */\n\nconst exceptionDedupeWindowMs = 2000\nconst recentExceptionFingerprints = new Map<string, number>()\n\nexport const shouldCaptureException = (error: Error, _errorInfo?: Record<string, any>): boolean => {\n if (!error) return false\n\n const now = Date.now()\n\n // Build a fingerprint that is stable enough across repeated emissions\n // but not so broad that different errors collapse into one.\n const keyParts: string[] = []\n keyParts.push(error.name || 'Error')\n keyParts.push(error.message || '')\n\n // First stack line tends to include file/line where it originated.\n if (typeof error.stack === 'string') {\n const firstFrame = error.stack.split('\\n')[1] || ''\n keyParts.push(firstFrame.trim())\n }\n\n const fingerprint = keyParts.join('|').slice(0, 500)\n\n const lastSeen = recentExceptionFingerprints.get(fingerprint)\n if (lastSeen && now - lastSeen < exceptionDedupeWindowMs) {\n return false\n }\n\n recentExceptionFingerprints.set(fingerprint, now)\n\n // Cheap cleanup of old entries to avoid unbounded growth.\n for (const [key, ts] of recentExceptionFingerprints) {\n if (now - ts > exceptionDedupeWindowMs * 5) {\n recentExceptionFingerprints.delete(key)\n }\n }\n\n return true\n}\n"]}
|