@safaricom-mxl/web-sdk 0.0.20 → 0.0.21
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/DATA_EXPORT.md +2 -1
- package/dist/instrumentations/replay/session-recording/SessionRecording.cjs +1 -0
- package/dist/instrumentations/replay/session-recording/SessionRecording.cjs.map +1 -1
- package/dist/instrumentations/replay/session-recording/SessionRecording.js +1 -0
- package/dist/instrumentations/replay/session-recording/SessionRecording.js.map +1 -1
- package/dist/instrumentations/replay/transport/replayTransport.cjs +21 -4
- package/dist/instrumentations/replay/transport/replayTransport.cjs.map +1 -1
- package/dist/instrumentations/replay/transport/replayTransport.d.cts +1 -1
- package/dist/instrumentations/replay/transport/replayTransport.d.cts.map +1 -1
- package/dist/instrumentations/replay/transport/replayTransport.d.ts +1 -1
- package/dist/instrumentations/replay/transport/replayTransport.d.ts.map +1 -1
- package/dist/instrumentations/replay/transport/replayTransport.js +21 -4
- package/dist/instrumentations/replay/transport/replayTransport.js.map +1 -1
- package/dist/instrumentations/replay/transport/types.d.cts +2 -1
- package/dist/instrumentations/replay/transport/types.d.cts.map +1 -1
- package/dist/instrumentations/replay/transport/types.d.ts +2 -1
- package/dist/instrumentations/replay/transport/types.d.ts.map +1 -1
- package/dist/mxl-web-sdk.js +2 -2
- package/dist/mxl-web-sdk.js.map +1 -1
- package/dist/resources/sdkConstants.cjs +1 -1
- package/dist/resources/sdkConstants.cjs.map +1 -1
- package/dist/resources/sdkConstants.d.cts +1 -1
- package/dist/resources/sdkConstants.d.ts +1 -1
- package/dist/resources/sdkConstants.js +1 -1
- package/dist/resources/sdkConstants.js.map +1 -1
- package/dist/sdk/initSDK.cjs +3 -3
- package/dist/sdk/initSDK.cjs.map +1 -1
- package/dist/sdk/initSDK.d.cts +1 -0
- package/dist/sdk/initSDK.d.cts.map +1 -1
- package/dist/sdk/initSDK.d.ts +1 -0
- package/dist/sdk/initSDK.d.ts.map +1 -1
- package/dist/sdk/initSDK.js +3 -3
- package/dist/sdk/initSDK.js.map +1 -1
- package/dist/sdk/types.d.cts +6 -0
- package/dist/sdk/types.d.cts.map +1 -1
- package/dist/sdk/types.d.ts +6 -0
- package/dist/sdk/types.d.ts.map +1 -1
- package/package.json +1 -1
package/DATA_EXPORT.md
CHANGED
|
@@ -22,11 +22,12 @@ failures following an exponential backoff. See [FetchTransport](./src/transport/
|
|
|
22
22
|
|
|
23
23
|
## Session replay
|
|
24
24
|
|
|
25
|
-
When `recordUserSessions` is enabled (default) and the SDK sends data to MXL, session replay uses **
|
|
25
|
+
When `recordUserSessions` is enabled (default) and the SDK sends data to MXL, session replay uses **dedicated HTTP ingest paths** (gzip NDJSON or octet-stream), separate from OTLP spans and logs. All replay ingest requests include the `mxl-client-id` header set to the init `appID`.
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
| Endpoint | Payload |
|
|
29
29
|
| --------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `POST /replay` | Session registration at session start when `apiKey` is configured (`mxl-api-key` header, body `null`) |
|
|
30
31
|
| `POST /sessionReplays/meta` | Session metadata (`active` on start, `ended` on finalize with `trace_ids`, `click_count`, `duration_ms`) |
|
|
31
32
|
| `POST /sessionReplays/blob` | Gzip-compressed rrweb event chunks (`x-mxl-session-id`, `x-mxl-chunk-seq`, `x-mxl-is-checkpoint`, …) |
|
|
32
33
|
| `POST /sessionEvents` | Distilled timeline events (console, network, errors, navigation, interactions) |
|
|
@@ -136,6 +136,7 @@ var SessionRecording = class {
|
|
|
136
136
|
sessionId,
|
|
137
137
|
transport: this._context.transport
|
|
138
138
|
});
|
|
139
|
+
this._context.transport.postReplay();
|
|
139
140
|
this._context.transport.postSessionMeta(require_instrumentations_replay_session_recording_lifecycle.sessionMetaRow(this._config, sessionId, startedAt, 1, "active", null));
|
|
140
141
|
const removeLifecycle = globalThis.__MXL_TEST_SKIP_REPLAY_LIFECYCLE__ === true ? () => void 0 : require_instrumentations_replay_session_recording_lifecycle.installReplayLifecycleHandlers({
|
|
141
142
|
config: this._config,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionRecording.cjs","names":["createChunkSeqStore","resolveReplayConfig","loadRrwebModule","getSessionStartedAt","startRecording","startEventCapture","sessionMetaRow","installReplayLifecycleHandlers"],"sources":["../../../../src/instrumentations/replay/session-recording/SessionRecording.ts"],"sourcesContent":["import type { DiagLogger } from '@opentelemetry/api';\nimport type { UserSessionManagerInternal } from '../../../managers/MXLUserSessionManager/types';\nimport type { NamespacedStorage } from '../../../utils/NamespacedStorage/NamespacedStorage';\nimport { startEventCapture } from '../events/startEventCapture';\nimport { loadRrwebModule } from '../rrwebLoader/loadRrwebModule';\nimport type { RrwebModule } from '../rrwebLoader/types';\nimport { startRecording } from '../recorder/startRecording';\nimport type { Recorder } from '../recorder/types';\nimport {\n clearObservedTraceIds,\n clearReplaySessionSink,\n publishReplaySessionSink,\n} from '../trace-sink/traceSink';\nimport { createChunkSeqStore } from './chunkSeq';\nimport {\n type ReplayContext,\n type ResolvedReplayConfig,\n resolveReplayConfig,\n} from './config';\nimport type { EventCapture } from '../events/startEventCapture';\nimport {\n getSessionStartedAt,\n installReplayLifecycleHandlers,\n sessionMetaRow,\n} from './lifecycle';\n\nexport interface SessionRecordingArgs {\n context: ReplayContext;\n userSessionManager: UserSessionManagerInternal;\n sessionStorage: NamespacedStorage;\n rrwebOptions?: ResolvedReplayConfig['rrwebOptions'] & {\n debug?: boolean;\n enabled?: boolean;\n sampleRate?: number;\n maskAllInputs?: boolean;\n maskAllText?: boolean;\n flushIntervalMs?: number;\n };\n recordUserSessions?: boolean;\n diag?: DiagLogger;\n}\n\ninterface ActiveRecording {\n recorder: Recorder;\n events: EventCapture;\n rrwebModule: RrwebModule;\n removeLifecycle: () => void;\n removeSessionEndListener?: () => void;\n}\n\nexport class SessionRecording {\n private readonly _context: ReplayContext;\n private readonly _userSessionManager: UserSessionManagerInternal;\n private readonly _chunkSeqStore: ReturnType<typeof createChunkSeqStore>;\n private readonly _config: ResolvedReplayConfig;\n private readonly _diag: DiagLogger;\n private readonly _recordUserSessions: boolean;\n\n private _active: ActiveRecording | undefined;\n private _paused = false;\n private _enableRequested = false;\n private _startInProgress = false;\n private _stopInProgress = false;\n private _removeSessionStartedListener?: () => void;\n\n public constructor({\n context,\n userSessionManager,\n sessionStorage,\n rrwebOptions,\n recordUserSessions = true,\n diag,\n }: SessionRecordingArgs) {\n this._context = context;\n this._userSessionManager = userSessionManager;\n this._chunkSeqStore = createChunkSeqStore(sessionStorage);\n this._recordUserSessions = recordUserSessions;\n this._config = resolveReplayConfig(\n context,\n rrwebOptions,\n recordUserSessions,\n );\n this._diag = diag ?? {\n debug: () => undefined,\n error: () => undefined,\n info: () => undefined,\n verbose: () => undefined,\n warn: () => undefined,\n };\n }\n\n public isRecording(): boolean {\n return Boolean(this._active) && !this._paused;\n }\n\n public enable(options?: { force?: boolean }): void {\n if (!this._recordUserSessions && !options?.force) {\n this._diag.debug(\n 'recordUserSessions disabled, waiting for manual enable',\n );\n return;\n }\n\n this._enableRequested = true;\n this._paused = false;\n void this._tryStart();\n }\n\n public pause(): void {\n if (!this._active) {\n return;\n }\n this._enableRequested = false;\n this._paused = true;\n this._active.recorder.pause();\n this._active.events.pause();\n }\n\n public resume(): void {\n if (!this._active) {\n this.enable();\n return;\n }\n\n const wasPaused = this._paused;\n this._enableRequested = true;\n this._paused = false;\n this._active.recorder.resume();\n this._active.events.resume();\n if (wasPaused) {\n this._active.rrwebModule.record.takeFullSnapshot();\n }\n }\n\n public async stop(): Promise<void> {\n this._enableRequested = false;\n this._paused = false;\n this._removeSessionStartedListener?.();\n this._removeSessionStartedListener = undefined;\n\n if (!this._active) {\n clearReplaySessionSink();\n clearObservedTraceIds();\n return;\n }\n\n if (this._stopInProgress) {\n return;\n }\n\n this._stopInProgress = true;\n\n const active = this._active;\n this._active = undefined;\n\n try {\n active.removeSessionEndListener?.();\n active.removeLifecycle();\n\n await active.recorder.flush(true);\n await active.events.flush(true);\n active.recorder.stop();\n active.events.stop();\n } finally {\n clearReplaySessionSink();\n clearObservedTraceIds();\n this._stopInProgress = false;\n }\n }\n\n private async _tryStart(): Promise<void> {\n if (\n !this._enableRequested ||\n this._active ||\n this._startInProgress ||\n this._stopInProgress\n ) {\n return;\n }\n\n if (typeof window === 'undefined') {\n this._diag.warn('session replay cannot run in non-browser environments');\n return;\n }\n\n const sessionId = this._userSessionManager.getUserSessionId();\n if (sessionId === null) {\n if (!this._removeSessionStartedListener) {\n this._removeSessionStartedListener =\n this._userSessionManager.addSessionPartStartedListener(() => {\n this._removeSessionStartedListener?.();\n this._removeSessionStartedListener = undefined;\n void this._tryStart();\n });\n }\n return;\n }\n\n if (!this._config.replayEnabled) {\n return;\n }\n\n if (Math.random() >= this._config.replaySampleRate) {\n this._diag.debug('session replay skipped by sample rate');\n return;\n }\n\n this._startInProgress = true;\n\n try {\n const rrwebModule = await loadRrwebModule();\n if (!this._enableRequested || this._active) {\n return;\n }\n\n publishReplaySessionSink(sessionId);\n clearObservedTraceIds();\n\n const startedAt = getSessionStartedAt(this._userSessionManager);\n const onActivity = () => {\n // Activity is tracked via MXL user session manager; no-op hook for now.\n };\n\n const recorder = startRecording({\n chunkSeqStore: this._chunkSeqStore,\n config: this._config,\n flushIntervalMs: this._config.flushIntervalMs,\n onActivity,\n rrwebModule,\n sessionId,\n transport: this._context.transport,\n });\n\n const events = startEventCapture({\n config: this._config,\n flushIntervalMs: this._config.flushIntervalMs,\n onActivity,\n sessionId,\n transport: this._context.transport,\n });\n\n void this._context.transport.postSessionMeta(\n sessionMetaRow(this._config, sessionId, startedAt, 1, 'active', null),\n );\n\n const skipLifecycle = (\n globalThis as Record<string, unknown>\n ).__MXL_TEST_SKIP_REPLAY_LIFECYCLE__;\n\n const removeLifecycle =\n skipLifecycle === true\n ? () => undefined\n : installReplayLifecycleHandlers({\n config: this._config,\n events,\n onFinalize: () => {\n this._active = undefined;\n clearReplaySessionSink();\n clearObservedTraceIds();\n },\n recorder,\n sessionId,\n startedAt,\n transport: this._context.transport,\n });\n\n const removeSessionEndListener =\n this._userSessionManager.addSessionPartEndedListener(() => {\n void recorder.flush(true);\n void events.flush(true);\n });\n\n this._active = {\n events,\n recorder,\n removeLifecycle,\n removeSessionEndListener,\n rrwebModule,\n };\n\n this._diag.debug('session recording started');\n } catch (error) {\n this._diag.error('failed to start session recording', error);\n } finally {\n this._startInProgress = false;\n }\n }\n}\n"],"mappings":";;;;;;;;;AAkDA,IAAa,mBAAb,MAA8B;CAC5B;CACA;CACA;CACA;CACA;CACA;CAEA;CACA,UAAkB;CAClB,mBAA2B;CAC3B,mBAA2B;CAC3B,kBAA0B;CAC1B;CAEA,YAAmB,EACjB,SACA,oBACA,gBACA,cACA,qBAAqB,MACrB,QACuB;AACvB,OAAK,WAAW;AAChB,OAAK,sBAAsB;AAC3B,OAAK,iBAAiBA,2DAAAA,oBAAoB,eAAe;AACzD,OAAK,sBAAsB;AAC3B,OAAK,UAAUC,yDAAAA,oBACb,SACA,cACA,mBACD;AACD,OAAK,QAAQ,QAAQ;GACnB,aAAa,KAAA;GACb,aAAa,KAAA;GACb,YAAY,KAAA;GACZ,eAAe,KAAA;GACf,YAAY,KAAA;GACb;;CAGH,cAA8B;AAC5B,SAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,KAAK;;CAGxC,OAAc,SAAqC;AACjD,MAAI,CAAC,KAAK,uBAAuB,CAAC,SAAS,OAAO;AAChD,QAAK,MAAM,MACT,yDACD;AACD;;AAGF,OAAK,mBAAmB;AACxB,OAAK,UAAU;AACV,OAAK,WAAW;;CAGvB,QAAqB;AACnB,MAAI,CAAC,KAAK,QACR;AAEF,OAAK,mBAAmB;AACxB,OAAK,UAAU;AACf,OAAK,QAAQ,SAAS,OAAO;AAC7B,OAAK,QAAQ,OAAO,OAAO;;CAG7B,SAAsB;AACpB,MAAI,CAAC,KAAK,SAAS;AACjB,QAAK,QAAQ;AACb;;EAGF,MAAM,YAAY,KAAK;AACvB,OAAK,mBAAmB;AACxB,OAAK,UAAU;AACf,OAAK,QAAQ,SAAS,QAAQ;AAC9B,OAAK,QAAQ,OAAO,QAAQ;AAC5B,MAAI,UACF,MAAK,QAAQ,YAAY,OAAO,kBAAkB;;CAItD,MAAa,OAAsB;AACjC,OAAK,mBAAmB;AACxB,OAAK,UAAU;AACf,OAAK,iCAAiC;AACtC,OAAK,gCAAgC,KAAA;AAErC,MAAI,CAAC,KAAK,SAAS;AACjB,wDAAA,wBAAwB;AACxB,wDAAA,uBAAuB;AACvB;;AAGF,MAAI,KAAK,gBACP;AAGF,OAAK,kBAAkB;EAEvB,MAAM,SAAS,KAAK;AACpB,OAAK,UAAU,KAAA;AAEf,MAAI;AACF,UAAO,4BAA4B;AACnC,UAAO,iBAAiB;AAExB,SAAM,OAAO,SAAS,MAAM,KAAK;AACjC,SAAM,OAAO,OAAO,MAAM,KAAK;AAC/B,UAAO,SAAS,MAAM;AACtB,UAAO,OAAO,MAAM;YACZ;AACR,wDAAA,wBAAwB;AACxB,wDAAA,uBAAuB;AACvB,QAAK,kBAAkB;;;CAI3B,MAAc,YAA2B;AACvC,MACE,CAAC,KAAK,oBACN,KAAK,WACL,KAAK,oBACL,KAAK,gBAEL;AAGF,MAAI,OAAO,WAAW,aAAa;AACjC,QAAK,MAAM,KAAK,wDAAwD;AACxE;;EAGF,MAAM,YAAY,KAAK,oBAAoB,kBAAkB;AAC7D,MAAI,cAAc,MAAM;AACtB,OAAI,CAAC,KAAK,8BACR,MAAK,gCACH,KAAK,oBAAoB,oCAAoC;AAC3D,SAAK,iCAAiC;AACtC,SAAK,gCAAgC,KAAA;AAChC,SAAK,WAAW;KACrB;AAEN;;AAGF,MAAI,CAAC,KAAK,QAAQ,cAChB;AAGF,MAAI,KAAK,QAAQ,IAAI,KAAK,QAAQ,kBAAkB;AAClD,QAAK,MAAM,MAAM,wCAAwC;AACzD;;AAGF,OAAK,mBAAmB;AAExB,MAAI;GACF,MAAM,cAAc,MAAMC,gEAAAA,iBAAiB;AAC3C,OAAI,CAAC,KAAK,oBAAoB,KAAK,QACjC;AAGF,wDAAA,yBAAyB,UAAU;AACnC,wDAAA,uBAAuB;GAEvB,MAAM,YAAYC,4DAAAA,oBAAoB,KAAK,oBAAoB;GAC/D,MAAM,mBAAmB;GAIzB,MAAM,WAAWC,wDAAAA,eAAe;IAC9B,eAAe,KAAK;IACpB,QAAQ,KAAK;IACb,iBAAiB,KAAK,QAAQ;IAC9B;IACA;IACA;IACA,WAAW,KAAK,SAAS;IAC1B,CAAC;GAEF,MAAM,SAASC,yDAAAA,kBAAkB;IAC/B,QAAQ,KAAK;IACb,iBAAiB,KAAK,QAAQ;IAC9B;IACA;IACA,WAAW,KAAK,SAAS;IAC1B,CAAC;AAEG,QAAK,SAAS,UAAU,gBAC3BC,4DAAAA,eAAe,KAAK,SAAS,WAAW,WAAW,GAAG,UAAU,KAAK,CACtE;GAMD,MAAM,kBAHJ,WACA,uCAGkB,aACR,KAAA,IACNC,4DAAAA,+BAA+B;IAC7B,QAAQ,KAAK;IACb;IACA,kBAAkB;AAChB,UAAK,UAAU,KAAA;AACf,0DAAA,wBAAwB;AACxB,0DAAA,uBAAuB;;IAEzB;IACA;IACA;IACA,WAAW,KAAK,SAAS;IAC1B,CAAC;GAER,MAAM,2BACJ,KAAK,oBAAoB,kCAAkC;AACpD,aAAS,MAAM,KAAK;AACpB,WAAO,MAAM,KAAK;KACvB;AAEJ,QAAK,UAAU;IACb;IACA;IACA;IACA;IACA;IACD;AAED,QAAK,MAAM,MAAM,4BAA4B;WACtC,OAAO;AACd,QAAK,MAAM,MAAM,qCAAqC,MAAM;YACpD;AACR,QAAK,mBAAmB"}
|
|
1
|
+
{"version":3,"file":"SessionRecording.cjs","names":["createChunkSeqStore","resolveReplayConfig","loadRrwebModule","getSessionStartedAt","startRecording","startEventCapture","sessionMetaRow","installReplayLifecycleHandlers"],"sources":["../../../../src/instrumentations/replay/session-recording/SessionRecording.ts"],"sourcesContent":["import type { DiagLogger } from '@opentelemetry/api';\nimport type { UserSessionManagerInternal } from '../../../managers/MXLUserSessionManager/types';\nimport type { NamespacedStorage } from '../../../utils/NamespacedStorage/NamespacedStorage';\nimport { startEventCapture } from '../events/startEventCapture';\nimport { loadRrwebModule } from '../rrwebLoader/loadRrwebModule';\nimport type { RrwebModule } from '../rrwebLoader/types';\nimport { startRecording } from '../recorder/startRecording';\nimport type { Recorder } from '../recorder/types';\nimport {\n clearObservedTraceIds,\n clearReplaySessionSink,\n publishReplaySessionSink,\n} from '../trace-sink/traceSink';\nimport { createChunkSeqStore } from './chunkSeq';\nimport {\n type ReplayContext,\n type ResolvedReplayConfig,\n resolveReplayConfig,\n} from './config';\nimport type { EventCapture } from '../events/startEventCapture';\nimport {\n getSessionStartedAt,\n installReplayLifecycleHandlers,\n sessionMetaRow,\n} from './lifecycle';\n\nexport interface SessionRecordingArgs {\n context: ReplayContext;\n userSessionManager: UserSessionManagerInternal;\n sessionStorage: NamespacedStorage;\n rrwebOptions?: ResolvedReplayConfig['rrwebOptions'] & {\n debug?: boolean;\n enabled?: boolean;\n sampleRate?: number;\n maskAllInputs?: boolean;\n maskAllText?: boolean;\n flushIntervalMs?: number;\n };\n recordUserSessions?: boolean;\n diag?: DiagLogger;\n}\n\ninterface ActiveRecording {\n recorder: Recorder;\n events: EventCapture;\n rrwebModule: RrwebModule;\n removeLifecycle: () => void;\n removeSessionEndListener?: () => void;\n}\n\nexport class SessionRecording {\n private readonly _context: ReplayContext;\n private readonly _userSessionManager: UserSessionManagerInternal;\n private readonly _chunkSeqStore: ReturnType<typeof createChunkSeqStore>;\n private readonly _config: ResolvedReplayConfig;\n private readonly _diag: DiagLogger;\n private readonly _recordUserSessions: boolean;\n\n private _active: ActiveRecording | undefined;\n private _paused = false;\n private _enableRequested = false;\n private _startInProgress = false;\n private _stopInProgress = false;\n private _removeSessionStartedListener?: () => void;\n\n public constructor({\n context,\n userSessionManager,\n sessionStorage,\n rrwebOptions,\n recordUserSessions = true,\n diag,\n }: SessionRecordingArgs) {\n this._context = context;\n this._userSessionManager = userSessionManager;\n this._chunkSeqStore = createChunkSeqStore(sessionStorage);\n this._recordUserSessions = recordUserSessions;\n this._config = resolveReplayConfig(\n context,\n rrwebOptions,\n recordUserSessions,\n );\n this._diag = diag ?? {\n debug: () => undefined,\n error: () => undefined,\n info: () => undefined,\n verbose: () => undefined,\n warn: () => undefined,\n };\n }\n\n public isRecording(): boolean {\n return Boolean(this._active) && !this._paused;\n }\n\n public enable(options?: { force?: boolean }): void {\n if (!this._recordUserSessions && !options?.force) {\n this._diag.debug(\n 'recordUserSessions disabled, waiting for manual enable',\n );\n return;\n }\n\n this._enableRequested = true;\n this._paused = false;\n void this._tryStart();\n }\n\n public pause(): void {\n if (!this._active) {\n return;\n }\n this._enableRequested = false;\n this._paused = true;\n this._active.recorder.pause();\n this._active.events.pause();\n }\n\n public resume(): void {\n if (!this._active) {\n this.enable();\n return;\n }\n\n const wasPaused = this._paused;\n this._enableRequested = true;\n this._paused = false;\n this._active.recorder.resume();\n this._active.events.resume();\n if (wasPaused) {\n this._active.rrwebModule.record.takeFullSnapshot();\n }\n }\n\n public async stop(): Promise<void> {\n this._enableRequested = false;\n this._paused = false;\n this._removeSessionStartedListener?.();\n this._removeSessionStartedListener = undefined;\n\n if (!this._active) {\n clearReplaySessionSink();\n clearObservedTraceIds();\n return;\n }\n\n if (this._stopInProgress) {\n return;\n }\n\n this._stopInProgress = true;\n\n const active = this._active;\n this._active = undefined;\n\n try {\n active.removeSessionEndListener?.();\n active.removeLifecycle();\n\n await active.recorder.flush(true);\n await active.events.flush(true);\n active.recorder.stop();\n active.events.stop();\n } finally {\n clearReplaySessionSink();\n clearObservedTraceIds();\n this._stopInProgress = false;\n }\n }\n\n private async _tryStart(): Promise<void> {\n if (\n !this._enableRequested ||\n this._active ||\n this._startInProgress ||\n this._stopInProgress\n ) {\n return;\n }\n\n if (typeof window === 'undefined') {\n this._diag.warn('session replay cannot run in non-browser environments');\n return;\n }\n\n const sessionId = this._userSessionManager.getUserSessionId();\n if (sessionId === null) {\n if (!this._removeSessionStartedListener) {\n this._removeSessionStartedListener =\n this._userSessionManager.addSessionPartStartedListener(() => {\n this._removeSessionStartedListener?.();\n this._removeSessionStartedListener = undefined;\n void this._tryStart();\n });\n }\n return;\n }\n\n if (!this._config.replayEnabled) {\n return;\n }\n\n if (Math.random() >= this._config.replaySampleRate) {\n this._diag.debug('session replay skipped by sample rate');\n return;\n }\n\n this._startInProgress = true;\n\n try {\n const rrwebModule = await loadRrwebModule();\n if (!this._enableRequested || this._active) {\n return;\n }\n\n publishReplaySessionSink(sessionId);\n clearObservedTraceIds();\n\n const startedAt = getSessionStartedAt(this._userSessionManager);\n const onActivity = () => {\n // Activity is tracked via MXL user session manager; no-op hook for now.\n };\n\n const recorder = startRecording({\n chunkSeqStore: this._chunkSeqStore,\n config: this._config,\n flushIntervalMs: this._config.flushIntervalMs,\n onActivity,\n rrwebModule,\n sessionId,\n transport: this._context.transport,\n });\n\n const events = startEventCapture({\n config: this._config,\n flushIntervalMs: this._config.flushIntervalMs,\n onActivity,\n sessionId,\n transport: this._context.transport,\n });\n\n void this._context.transport.postReplay();\n void this._context.transport.postSessionMeta(\n sessionMetaRow(this._config, sessionId, startedAt, 1, 'active', null),\n );\n\n const skipLifecycle = (\n globalThis as Record<string, unknown>\n ).__MXL_TEST_SKIP_REPLAY_LIFECYCLE__;\n\n const removeLifecycle =\n skipLifecycle === true\n ? () => undefined\n : installReplayLifecycleHandlers({\n config: this._config,\n events,\n onFinalize: () => {\n this._active = undefined;\n clearReplaySessionSink();\n clearObservedTraceIds();\n },\n recorder,\n sessionId,\n startedAt,\n transport: this._context.transport,\n });\n\n const removeSessionEndListener =\n this._userSessionManager.addSessionPartEndedListener(() => {\n void recorder.flush(true);\n void events.flush(true);\n });\n\n this._active = {\n events,\n recorder,\n removeLifecycle,\n removeSessionEndListener,\n rrwebModule,\n };\n\n this._diag.debug('session recording started');\n } catch (error) {\n this._diag.error('failed to start session recording', error);\n } finally {\n this._startInProgress = false;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -135,6 +135,7 @@ var SessionRecording = class {
|
|
|
135
135
|
sessionId,
|
|
136
136
|
transport: this._context.transport
|
|
137
137
|
});
|
|
138
|
+
this._context.transport.postReplay();
|
|
138
139
|
this._context.transport.postSessionMeta(sessionMetaRow(this._config, sessionId, startedAt, 1, "active", null));
|
|
139
140
|
const removeLifecycle = globalThis.__MXL_TEST_SKIP_REPLAY_LIFECYCLE__ === true ? () => void 0 : installReplayLifecycleHandlers({
|
|
140
141
|
config: this._config,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionRecording.js","names":[],"sources":["../../../../src/instrumentations/replay/session-recording/SessionRecording.ts"],"sourcesContent":["import type { DiagLogger } from '@opentelemetry/api';\nimport type { UserSessionManagerInternal } from '../../../managers/MXLUserSessionManager/types';\nimport type { NamespacedStorage } from '../../../utils/NamespacedStorage/NamespacedStorage';\nimport { startEventCapture } from '../events/startEventCapture';\nimport { loadRrwebModule } from '../rrwebLoader/loadRrwebModule';\nimport type { RrwebModule } from '../rrwebLoader/types';\nimport { startRecording } from '../recorder/startRecording';\nimport type { Recorder } from '../recorder/types';\nimport {\n clearObservedTraceIds,\n clearReplaySessionSink,\n publishReplaySessionSink,\n} from '../trace-sink/traceSink';\nimport { createChunkSeqStore } from './chunkSeq';\nimport {\n type ReplayContext,\n type ResolvedReplayConfig,\n resolveReplayConfig,\n} from './config';\nimport type { EventCapture } from '../events/startEventCapture';\nimport {\n getSessionStartedAt,\n installReplayLifecycleHandlers,\n sessionMetaRow,\n} from './lifecycle';\n\nexport interface SessionRecordingArgs {\n context: ReplayContext;\n userSessionManager: UserSessionManagerInternal;\n sessionStorage: NamespacedStorage;\n rrwebOptions?: ResolvedReplayConfig['rrwebOptions'] & {\n debug?: boolean;\n enabled?: boolean;\n sampleRate?: number;\n maskAllInputs?: boolean;\n maskAllText?: boolean;\n flushIntervalMs?: number;\n };\n recordUserSessions?: boolean;\n diag?: DiagLogger;\n}\n\ninterface ActiveRecording {\n recorder: Recorder;\n events: EventCapture;\n rrwebModule: RrwebModule;\n removeLifecycle: () => void;\n removeSessionEndListener?: () => void;\n}\n\nexport class SessionRecording {\n private readonly _context: ReplayContext;\n private readonly _userSessionManager: UserSessionManagerInternal;\n private readonly _chunkSeqStore: ReturnType<typeof createChunkSeqStore>;\n private readonly _config: ResolvedReplayConfig;\n private readonly _diag: DiagLogger;\n private readonly _recordUserSessions: boolean;\n\n private _active: ActiveRecording | undefined;\n private _paused = false;\n private _enableRequested = false;\n private _startInProgress = false;\n private _stopInProgress = false;\n private _removeSessionStartedListener?: () => void;\n\n public constructor({\n context,\n userSessionManager,\n sessionStorage,\n rrwebOptions,\n recordUserSessions = true,\n diag,\n }: SessionRecordingArgs) {\n this._context = context;\n this._userSessionManager = userSessionManager;\n this._chunkSeqStore = createChunkSeqStore(sessionStorage);\n this._recordUserSessions = recordUserSessions;\n this._config = resolveReplayConfig(\n context,\n rrwebOptions,\n recordUserSessions,\n );\n this._diag = diag ?? {\n debug: () => undefined,\n error: () => undefined,\n info: () => undefined,\n verbose: () => undefined,\n warn: () => undefined,\n };\n }\n\n public isRecording(): boolean {\n return Boolean(this._active) && !this._paused;\n }\n\n public enable(options?: { force?: boolean }): void {\n if (!this._recordUserSessions && !options?.force) {\n this._diag.debug(\n 'recordUserSessions disabled, waiting for manual enable',\n );\n return;\n }\n\n this._enableRequested = true;\n this._paused = false;\n void this._tryStart();\n }\n\n public pause(): void {\n if (!this._active) {\n return;\n }\n this._enableRequested = false;\n this._paused = true;\n this._active.recorder.pause();\n this._active.events.pause();\n }\n\n public resume(): void {\n if (!this._active) {\n this.enable();\n return;\n }\n\n const wasPaused = this._paused;\n this._enableRequested = true;\n this._paused = false;\n this._active.recorder.resume();\n this._active.events.resume();\n if (wasPaused) {\n this._active.rrwebModule.record.takeFullSnapshot();\n }\n }\n\n public async stop(): Promise<void> {\n this._enableRequested = false;\n this._paused = false;\n this._removeSessionStartedListener?.();\n this._removeSessionStartedListener = undefined;\n\n if (!this._active) {\n clearReplaySessionSink();\n clearObservedTraceIds();\n return;\n }\n\n if (this._stopInProgress) {\n return;\n }\n\n this._stopInProgress = true;\n\n const active = this._active;\n this._active = undefined;\n\n try {\n active.removeSessionEndListener?.();\n active.removeLifecycle();\n\n await active.recorder.flush(true);\n await active.events.flush(true);\n active.recorder.stop();\n active.events.stop();\n } finally {\n clearReplaySessionSink();\n clearObservedTraceIds();\n this._stopInProgress = false;\n }\n }\n\n private async _tryStart(): Promise<void> {\n if (\n !this._enableRequested ||\n this._active ||\n this._startInProgress ||\n this._stopInProgress\n ) {\n return;\n }\n\n if (typeof window === 'undefined') {\n this._diag.warn('session replay cannot run in non-browser environments');\n return;\n }\n\n const sessionId = this._userSessionManager.getUserSessionId();\n if (sessionId === null) {\n if (!this._removeSessionStartedListener) {\n this._removeSessionStartedListener =\n this._userSessionManager.addSessionPartStartedListener(() => {\n this._removeSessionStartedListener?.();\n this._removeSessionStartedListener = undefined;\n void this._tryStart();\n });\n }\n return;\n }\n\n if (!this._config.replayEnabled) {\n return;\n }\n\n if (Math.random() >= this._config.replaySampleRate) {\n this._diag.debug('session replay skipped by sample rate');\n return;\n }\n\n this._startInProgress = true;\n\n try {\n const rrwebModule = await loadRrwebModule();\n if (!this._enableRequested || this._active) {\n return;\n }\n\n publishReplaySessionSink(sessionId);\n clearObservedTraceIds();\n\n const startedAt = getSessionStartedAt(this._userSessionManager);\n const onActivity = () => {\n // Activity is tracked via MXL user session manager; no-op hook for now.\n };\n\n const recorder = startRecording({\n chunkSeqStore: this._chunkSeqStore,\n config: this._config,\n flushIntervalMs: this._config.flushIntervalMs,\n onActivity,\n rrwebModule,\n sessionId,\n transport: this._context.transport,\n });\n\n const events = startEventCapture({\n config: this._config,\n flushIntervalMs: this._config.flushIntervalMs,\n onActivity,\n sessionId,\n transport: this._context.transport,\n });\n\n void this._context.transport.postSessionMeta(\n sessionMetaRow(this._config, sessionId, startedAt, 1, 'active', null),\n );\n\n const skipLifecycle = (\n globalThis as Record<string, unknown>\n ).__MXL_TEST_SKIP_REPLAY_LIFECYCLE__;\n\n const removeLifecycle =\n skipLifecycle === true\n ? () => undefined\n : installReplayLifecycleHandlers({\n config: this._config,\n events,\n onFinalize: () => {\n this._active = undefined;\n clearReplaySessionSink();\n clearObservedTraceIds();\n },\n recorder,\n sessionId,\n startedAt,\n transport: this._context.transport,\n });\n\n const removeSessionEndListener =\n this._userSessionManager.addSessionPartEndedListener(() => {\n void recorder.flush(true);\n void events.flush(true);\n });\n\n this._active = {\n events,\n recorder,\n removeLifecycle,\n removeSessionEndListener,\n rrwebModule,\n };\n\n this._diag.debug('session recording started');\n } catch (error) {\n this._diag.error('failed to start session recording', error);\n } finally {\n this._startInProgress = false;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionRecording.js","names":[],"sources":["../../../../src/instrumentations/replay/session-recording/SessionRecording.ts"],"sourcesContent":["import type { DiagLogger } from '@opentelemetry/api';\nimport type { UserSessionManagerInternal } from '../../../managers/MXLUserSessionManager/types';\nimport type { NamespacedStorage } from '../../../utils/NamespacedStorage/NamespacedStorage';\nimport { startEventCapture } from '../events/startEventCapture';\nimport { loadRrwebModule } from '../rrwebLoader/loadRrwebModule';\nimport type { RrwebModule } from '../rrwebLoader/types';\nimport { startRecording } from '../recorder/startRecording';\nimport type { Recorder } from '../recorder/types';\nimport {\n clearObservedTraceIds,\n clearReplaySessionSink,\n publishReplaySessionSink,\n} from '../trace-sink/traceSink';\nimport { createChunkSeqStore } from './chunkSeq';\nimport {\n type ReplayContext,\n type ResolvedReplayConfig,\n resolveReplayConfig,\n} from './config';\nimport type { EventCapture } from '../events/startEventCapture';\nimport {\n getSessionStartedAt,\n installReplayLifecycleHandlers,\n sessionMetaRow,\n} from './lifecycle';\n\nexport interface SessionRecordingArgs {\n context: ReplayContext;\n userSessionManager: UserSessionManagerInternal;\n sessionStorage: NamespacedStorage;\n rrwebOptions?: ResolvedReplayConfig['rrwebOptions'] & {\n debug?: boolean;\n enabled?: boolean;\n sampleRate?: number;\n maskAllInputs?: boolean;\n maskAllText?: boolean;\n flushIntervalMs?: number;\n };\n recordUserSessions?: boolean;\n diag?: DiagLogger;\n}\n\ninterface ActiveRecording {\n recorder: Recorder;\n events: EventCapture;\n rrwebModule: RrwebModule;\n removeLifecycle: () => void;\n removeSessionEndListener?: () => void;\n}\n\nexport class SessionRecording {\n private readonly _context: ReplayContext;\n private readonly _userSessionManager: UserSessionManagerInternal;\n private readonly _chunkSeqStore: ReturnType<typeof createChunkSeqStore>;\n private readonly _config: ResolvedReplayConfig;\n private readonly _diag: DiagLogger;\n private readonly _recordUserSessions: boolean;\n\n private _active: ActiveRecording | undefined;\n private _paused = false;\n private _enableRequested = false;\n private _startInProgress = false;\n private _stopInProgress = false;\n private _removeSessionStartedListener?: () => void;\n\n public constructor({\n context,\n userSessionManager,\n sessionStorage,\n rrwebOptions,\n recordUserSessions = true,\n diag,\n }: SessionRecordingArgs) {\n this._context = context;\n this._userSessionManager = userSessionManager;\n this._chunkSeqStore = createChunkSeqStore(sessionStorage);\n this._recordUserSessions = recordUserSessions;\n this._config = resolveReplayConfig(\n context,\n rrwebOptions,\n recordUserSessions,\n );\n this._diag = diag ?? {\n debug: () => undefined,\n error: () => undefined,\n info: () => undefined,\n verbose: () => undefined,\n warn: () => undefined,\n };\n }\n\n public isRecording(): boolean {\n return Boolean(this._active) && !this._paused;\n }\n\n public enable(options?: { force?: boolean }): void {\n if (!this._recordUserSessions && !options?.force) {\n this._diag.debug(\n 'recordUserSessions disabled, waiting for manual enable',\n );\n return;\n }\n\n this._enableRequested = true;\n this._paused = false;\n void this._tryStart();\n }\n\n public pause(): void {\n if (!this._active) {\n return;\n }\n this._enableRequested = false;\n this._paused = true;\n this._active.recorder.pause();\n this._active.events.pause();\n }\n\n public resume(): void {\n if (!this._active) {\n this.enable();\n return;\n }\n\n const wasPaused = this._paused;\n this._enableRequested = true;\n this._paused = false;\n this._active.recorder.resume();\n this._active.events.resume();\n if (wasPaused) {\n this._active.rrwebModule.record.takeFullSnapshot();\n }\n }\n\n public async stop(): Promise<void> {\n this._enableRequested = false;\n this._paused = false;\n this._removeSessionStartedListener?.();\n this._removeSessionStartedListener = undefined;\n\n if (!this._active) {\n clearReplaySessionSink();\n clearObservedTraceIds();\n return;\n }\n\n if (this._stopInProgress) {\n return;\n }\n\n this._stopInProgress = true;\n\n const active = this._active;\n this._active = undefined;\n\n try {\n active.removeSessionEndListener?.();\n active.removeLifecycle();\n\n await active.recorder.flush(true);\n await active.events.flush(true);\n active.recorder.stop();\n active.events.stop();\n } finally {\n clearReplaySessionSink();\n clearObservedTraceIds();\n this._stopInProgress = false;\n }\n }\n\n private async _tryStart(): Promise<void> {\n if (\n !this._enableRequested ||\n this._active ||\n this._startInProgress ||\n this._stopInProgress\n ) {\n return;\n }\n\n if (typeof window === 'undefined') {\n this._diag.warn('session replay cannot run in non-browser environments');\n return;\n }\n\n const sessionId = this._userSessionManager.getUserSessionId();\n if (sessionId === null) {\n if (!this._removeSessionStartedListener) {\n this._removeSessionStartedListener =\n this._userSessionManager.addSessionPartStartedListener(() => {\n this._removeSessionStartedListener?.();\n this._removeSessionStartedListener = undefined;\n void this._tryStart();\n });\n }\n return;\n }\n\n if (!this._config.replayEnabled) {\n return;\n }\n\n if (Math.random() >= this._config.replaySampleRate) {\n this._diag.debug('session replay skipped by sample rate');\n return;\n }\n\n this._startInProgress = true;\n\n try {\n const rrwebModule = await loadRrwebModule();\n if (!this._enableRequested || this._active) {\n return;\n }\n\n publishReplaySessionSink(sessionId);\n clearObservedTraceIds();\n\n const startedAt = getSessionStartedAt(this._userSessionManager);\n const onActivity = () => {\n // Activity is tracked via MXL user session manager; no-op hook for now.\n };\n\n const recorder = startRecording({\n chunkSeqStore: this._chunkSeqStore,\n config: this._config,\n flushIntervalMs: this._config.flushIntervalMs,\n onActivity,\n rrwebModule,\n sessionId,\n transport: this._context.transport,\n });\n\n const events = startEventCapture({\n config: this._config,\n flushIntervalMs: this._config.flushIntervalMs,\n onActivity,\n sessionId,\n transport: this._context.transport,\n });\n\n void this._context.transport.postReplay();\n void this._context.transport.postSessionMeta(\n sessionMetaRow(this._config, sessionId, startedAt, 1, 'active', null),\n );\n\n const skipLifecycle = (\n globalThis as Record<string, unknown>\n ).__MXL_TEST_SKIP_REPLAY_LIFECYCLE__;\n\n const removeLifecycle =\n skipLifecycle === true\n ? () => undefined\n : installReplayLifecycleHandlers({\n config: this._config,\n events,\n onFinalize: () => {\n this._active = undefined;\n clearReplaySessionSink();\n clearObservedTraceIds();\n },\n recorder,\n sessionId,\n startedAt,\n transport: this._context.transport,\n });\n\n const removeSessionEndListener =\n this._userSessionManager.addSessionPartEndedListener(() => {\n void recorder.flush(true);\n void events.flush(true);\n });\n\n this._active = {\n events,\n recorder,\n removeLifecycle,\n removeSessionEndListener,\n rrwebModule,\n };\n\n this._diag.debug('session recording started');\n } catch (error) {\n this._diag.error('failed to start session recording', error);\n } finally {\n this._startInProgress = false;\n }\n }\n}\n"],"mappings":";;;;;;;;AAkDA,IAAa,mBAAb,MAA8B;CAC5B;CACA;CACA;CACA;CACA;CACA;CAEA;CACA,UAAkB;CAClB,mBAA2B;CAC3B,mBAA2B;CAC3B,kBAA0B;CAC1B;CAEA,YAAmB,EACjB,SACA,oBACA,gBACA,cACA,qBAAqB,MACrB,QACuB;AACvB,OAAK,WAAW;AAChB,OAAK,sBAAsB;AAC3B,OAAK,iBAAiB,oBAAoB,eAAe;AACzD,OAAK,sBAAsB;AAC3B,OAAK,UAAU,oBACb,SACA,cACA,mBACD;AACD,OAAK,QAAQ,QAAQ;GACnB,aAAa,KAAA;GACb,aAAa,KAAA;GACb,YAAY,KAAA;GACZ,eAAe,KAAA;GACf,YAAY,KAAA;GACb;;CAGH,cAA8B;AAC5B,SAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,KAAK;;CAGxC,OAAc,SAAqC;AACjD,MAAI,CAAC,KAAK,uBAAuB,CAAC,SAAS,OAAO;AAChD,QAAK,MAAM,MACT,yDACD;AACD;;AAGF,OAAK,mBAAmB;AACxB,OAAK,UAAU;AACV,OAAK,WAAW;;CAGvB,QAAqB;AACnB,MAAI,CAAC,KAAK,QACR;AAEF,OAAK,mBAAmB;AACxB,OAAK,UAAU;AACf,OAAK,QAAQ,SAAS,OAAO;AAC7B,OAAK,QAAQ,OAAO,OAAO;;CAG7B,SAAsB;AACpB,MAAI,CAAC,KAAK,SAAS;AACjB,QAAK,QAAQ;AACb;;EAGF,MAAM,YAAY,KAAK;AACvB,OAAK,mBAAmB;AACxB,OAAK,UAAU;AACf,OAAK,QAAQ,SAAS,QAAQ;AAC9B,OAAK,QAAQ,OAAO,QAAQ;AAC5B,MAAI,UACF,MAAK,QAAQ,YAAY,OAAO,kBAAkB;;CAItD,MAAa,OAAsB;AACjC,OAAK,mBAAmB;AACxB,OAAK,UAAU;AACf,OAAK,iCAAiC;AACtC,OAAK,gCAAgC,KAAA;AAErC,MAAI,CAAC,KAAK,SAAS;AACjB,2BAAwB;AACxB,0BAAuB;AACvB;;AAGF,MAAI,KAAK,gBACP;AAGF,OAAK,kBAAkB;EAEvB,MAAM,SAAS,KAAK;AACpB,OAAK,UAAU,KAAA;AAEf,MAAI;AACF,UAAO,4BAA4B;AACnC,UAAO,iBAAiB;AAExB,SAAM,OAAO,SAAS,MAAM,KAAK;AACjC,SAAM,OAAO,OAAO,MAAM,KAAK;AAC/B,UAAO,SAAS,MAAM;AACtB,UAAO,OAAO,MAAM;YACZ;AACR,2BAAwB;AACxB,0BAAuB;AACvB,QAAK,kBAAkB;;;CAI3B,MAAc,YAA2B;AACvC,MACE,CAAC,KAAK,oBACN,KAAK,WACL,KAAK,oBACL,KAAK,gBAEL;AAGF,MAAI,OAAO,WAAW,aAAa;AACjC,QAAK,MAAM,KAAK,wDAAwD;AACxE;;EAGF,MAAM,YAAY,KAAK,oBAAoB,kBAAkB;AAC7D,MAAI,cAAc,MAAM;AACtB,OAAI,CAAC,KAAK,8BACR,MAAK,gCACH,KAAK,oBAAoB,oCAAoC;AAC3D,SAAK,iCAAiC;AACtC,SAAK,gCAAgC,KAAA;AAChC,SAAK,WAAW;KACrB;AAEN;;AAGF,MAAI,CAAC,KAAK,QAAQ,cAChB;AAGF,MAAI,KAAK,QAAQ,IAAI,KAAK,QAAQ,kBAAkB;AAClD,QAAK,MAAM,MAAM,wCAAwC;AACzD;;AAGF,OAAK,mBAAmB;AAExB,MAAI;GACF,MAAM,cAAc,MAAM,iBAAiB;AAC3C,OAAI,CAAC,KAAK,oBAAoB,KAAK,QACjC;AAGF,4BAAyB,UAAU;AACnC,0BAAuB;GAEvB,MAAM,YAAY,oBAAoB,KAAK,oBAAoB;GAC/D,MAAM,mBAAmB;GAIzB,MAAM,WAAW,eAAe;IAC9B,eAAe,KAAK;IACpB,QAAQ,KAAK;IACb,iBAAiB,KAAK,QAAQ;IAC9B;IACA;IACA;IACA,WAAW,KAAK,SAAS;IAC1B,CAAC;GAEF,MAAM,SAAS,kBAAkB;IAC/B,QAAQ,KAAK;IACb,iBAAiB,KAAK,QAAQ;IAC9B;IACA;IACA,WAAW,KAAK,SAAS;IAC1B,CAAC;AAEG,QAAK,SAAS,UAAU,YAAY;AACpC,QAAK,SAAS,UAAU,gBAC3B,eAAe,KAAK,SAAS,WAAW,WAAW,GAAG,UAAU,KAAK,CACtE;GAMD,MAAM,kBAHJ,WACA,uCAGkB,aACR,KAAA,IACN,+BAA+B;IAC7B,QAAQ,KAAK;IACb;IACA,kBAAkB;AAChB,UAAK,UAAU,KAAA;AACf,6BAAwB;AACxB,4BAAuB;;IAEzB;IACA;IACA;IACA,WAAW,KAAK,SAAS;IAC1B,CAAC;GAER,MAAM,2BACJ,KAAK,oBAAoB,kCAAkC;AACpD,aAAS,MAAM,KAAK;AACpB,WAAO,MAAM,KAAK;KACvB;AAEJ,QAAK,UAAU;IACb;IACA;IACA;IACA;IACA;IACD;AAED,QAAK,MAAM,MAAM,4BAA4B;WACtC,OAAO;AACd,QAAK,MAAM,MAAM,qCAAqC,MAAM;YACpD;AACR,QAAK,mBAAmB"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_instrumentations_replay_transport_gzip = require("./gzip.cjs");
|
|
3
|
-
const require_exporters_utils = require("../../../exporters/utils.cjs");
|
|
4
3
|
const require_managers_MXLConfigManager_utils = require("../../../managers/MXLConfigManager/utils.cjs");
|
|
5
4
|
//#region src/instrumentations/replay/transport/replayTransport.ts
|
|
6
5
|
/**
|
|
7
6
|
* Ported from Maple browser SDK (MIT), adapted for MXL auth and endpoints.
|
|
8
7
|
* @see https://github.com/Makisuo/maple/tree/main/packages/browser/src/replay/transport.ts
|
|
9
8
|
*/
|
|
9
|
+
const getReplayHeaders = (appID) => ({ "mxl-client-id": appID });
|
|
10
10
|
let lastWarnAt = 0;
|
|
11
11
|
function warnDropped(what, error) {
|
|
12
12
|
const now = Date.now();
|
|
@@ -15,16 +15,32 @@ function warnDropped(what, error) {
|
|
|
15
15
|
console.warn(`[mxl] session replay ${what} failed (dropping; will retry on next chunk):`, error);
|
|
16
16
|
}
|
|
17
17
|
function createReplayTransport(config) {
|
|
18
|
-
const headers =
|
|
18
|
+
const headers = getReplayHeaders(config.appID);
|
|
19
19
|
const baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
20
20
|
const otelCollectorURL = config.otelCollectorURL.replace(/\/$/, "");
|
|
21
21
|
const ignoredPathPrefixes = [
|
|
22
22
|
`${baseUrl}/sessionReplays/`,
|
|
23
23
|
`${baseUrl}/sessionEvents`,
|
|
24
|
+
`${baseUrl}/replay`,
|
|
24
25
|
`${otelCollectorURL}/v1/logs`,
|
|
25
26
|
`${otelCollectorURL}/v1/traces`
|
|
26
27
|
];
|
|
27
28
|
const shouldIgnoreUrl = (url) => ignoredPathPrefixes.some((prefix) => url.startsWith(prefix));
|
|
29
|
+
const postReplay = async (keepalive = false) => {
|
|
30
|
+
if (!config.apiKey) return;
|
|
31
|
+
await fetch(`${baseUrl}/replay`, {
|
|
32
|
+
body: "null",
|
|
33
|
+
headers: {
|
|
34
|
+
...headers,
|
|
35
|
+
"content-type": "application/json",
|
|
36
|
+
"mxl-api-key": config.apiKey
|
|
37
|
+
},
|
|
38
|
+
keepalive,
|
|
39
|
+
method: "POST"
|
|
40
|
+
}).catch((error) => {
|
|
41
|
+
warnDropped("replay POST", error);
|
|
42
|
+
});
|
|
43
|
+
};
|
|
28
44
|
const postSessionMeta = async (row, keepalive = false) => {
|
|
29
45
|
const body = `${JSON.stringify(row)}\n`;
|
|
30
46
|
await fetch(`${baseUrl}/sessionReplays/meta`, {
|
|
@@ -73,6 +89,7 @@ function createReplayTransport(config) {
|
|
|
73
89
|
});
|
|
74
90
|
};
|
|
75
91
|
return {
|
|
92
|
+
postReplay,
|
|
76
93
|
postSessionBlob,
|
|
77
94
|
postSessionEvents,
|
|
78
95
|
postSessionMeta,
|
|
@@ -81,10 +98,10 @@ function createReplayTransport(config) {
|
|
|
81
98
|
}
|
|
82
99
|
function createReplayTransportFromInit(args) {
|
|
83
100
|
return createReplayTransport({
|
|
101
|
+
apiKey: args.apiKey,
|
|
84
102
|
appID: args.appID,
|
|
85
103
|
baseUrl: require_managers_MXLConfigManager_utils.getApiURL(args.apiURL),
|
|
86
|
-
otelCollectorURL: require_managers_MXLConfigManager_utils.getOtelCollectorURL(args.otelCollectorURL)
|
|
87
|
-
userID: args.userID
|
|
104
|
+
otelCollectorURL: require_managers_MXLConfigManager_utils.getOtelCollectorURL(args.otelCollectorURL)
|
|
88
105
|
});
|
|
89
106
|
}
|
|
90
107
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replayTransport.cjs","names":["
|
|
1
|
+
{"version":3,"file":"replayTransport.cjs","names":["getApiURL","getOtelCollectorURL"],"sources":["../../../../src/instrumentations/replay/transport/replayTransport.ts"],"sourcesContent":["/**\n * Ported from Maple browser SDK (MIT), adapted for MXL auth and endpoints.\n * @see https://github.com/Makisuo/maple/tree/main/packages/browser/src/replay/transport.ts\n */\n\nimport {\n getApiURL,\n getOtelCollectorURL,\n} from '../../../managers/MXLConfigManager/utils';\nimport { gzip } from './gzip';\nimport type { ChunkMeta, ReplayTransport, ReplayTransportConfig } from './types';\n\nexport { gzip };\n\nconst getReplayHeaders = (appID: string): Record<string, string> => ({\n 'mxl-client-id': appID,\n});\n\nlet lastWarnAt = 0;\n\nfunction warnDropped(what: string, error: unknown): void {\n const now = Date.now();\n if (now - lastWarnAt < 30_000) {\n return;\n }\n lastWarnAt = now;\n // oxlint-disable-next-line no-console -- best-effort replay diagnostics\n console.warn(\n `[mxl] session replay ${what} failed (dropping; will retry on next chunk):`,\n error,\n );\n}\n\nexport function createReplayTransport(\n config: ReplayTransportConfig,\n): ReplayTransport {\n const headers = getReplayHeaders(config.appID);\n const baseUrl = config.baseUrl.replace(/\\/$/, '');\n const otelCollectorURL = config.otelCollectorURL.replace(/\\/$/, '');\n\n const ignoredPathPrefixes = [\n `${baseUrl}/sessionReplays/`,\n `${baseUrl}/sessionEvents`,\n `${baseUrl}/replay`,\n `${otelCollectorURL}/v1/logs`,\n `${otelCollectorURL}/v1/traces`,\n ];\n\n const shouldIgnoreUrl = (url: string): boolean =>\n ignoredPathPrefixes.some((prefix) => url.startsWith(prefix));\n\n const postReplay = async (keepalive = false): Promise<void> => {\n if (!config.apiKey) {\n return;\n }\n await fetch(`${baseUrl}/replay`, {\n body: 'null',\n headers: {\n ...headers,\n 'content-type': 'application/json',\n 'mxl-api-key': config.apiKey,\n },\n keepalive,\n method: 'POST',\n }).catch((error) => {\n warnDropped('replay POST', error);\n });\n };\n\n const postSessionMeta = async (\n row: Record<string, unknown>,\n keepalive = false,\n ): Promise<void> => {\n const body = `${JSON.stringify(row)}\\n`;\n await fetch(`${baseUrl}/sessionReplays/meta`, {\n body,\n headers: {\n ...headers,\n 'content-type': 'application/x-ndjson',\n },\n keepalive,\n method: 'POST',\n }).catch((error) => {\n warnDropped('metadata POST', error);\n });\n };\n\n const postSessionEvents = async (\n rows: ReadonlyArray<Record<string, unknown>>,\n keepalive = false,\n ): Promise<void> => {\n if (rows.length === 0) {\n return;\n }\n const body = `${rows.map((row) => JSON.stringify(row)).join('\\n')}\\n`;\n await fetch(`${baseUrl}/sessionEvents`, {\n body,\n headers: {\n ...headers,\n 'content-type': 'application/x-ndjson',\n },\n keepalive,\n method: 'POST',\n }).catch((error) => {\n warnDropped('events POST', error);\n });\n };\n\n const postSessionBlob = async (\n meta: ChunkMeta,\n gzipped: Uint8Array,\n keepalive = false,\n ): Promise<void> => {\n await fetch(`${baseUrl}/sessionReplays/blob`, {\n body: gzipped as unknown as BodyInit,\n headers: {\n ...headers,\n 'content-type': 'application/octet-stream',\n 'x-mxl-chunk-seq': String(meta.chunkSeq),\n 'x-mxl-duration-ms': String(meta.durationMs),\n 'x-mxl-event-count': String(meta.eventCount),\n 'x-mxl-is-checkpoint': meta.isCheckpoint ? '1' : '0',\n 'x-mxl-session-id': meta.sessionId,\n },\n keepalive,\n method: 'POST',\n }).catch((error) => {\n warnDropped('blob POST', error);\n });\n };\n\n return {\n postReplay,\n postSessionBlob,\n postSessionEvents,\n postSessionMeta,\n shouldIgnoreUrl,\n };\n}\n\nexport function createReplayTransportFromInit(args: {\n appID: string;\n apiKey?: string;\n apiURL?: string;\n otelCollectorURL?: string;\n}): ReplayTransport {\n return createReplayTransport({\n apiKey: args.apiKey,\n appID: args.appID,\n baseUrl: getApiURL(args.apiURL),\n otelCollectorURL: getOtelCollectorURL(args.otelCollectorURL),\n });\n}\n"],"mappings":";;;;;;;;AAcA,MAAM,oBAAoB,WAA2C,EACnE,iBAAiB,OAClB;AAED,IAAI,aAAa;AAEjB,SAAS,YAAY,MAAc,OAAsB;CACvD,MAAM,MAAM,KAAK,KAAK;AACtB,KAAI,MAAM,aAAa,IACrB;AAEF,cAAa;AAEb,SAAQ,KACN,wBAAwB,KAAK,gDAC7B,MACD;;AAGH,SAAgB,sBACd,QACiB;CACjB,MAAM,UAAU,iBAAiB,OAAO,MAAM;CAC9C,MAAM,UAAU,OAAO,QAAQ,QAAQ,OAAO,GAAG;CACjD,MAAM,mBAAmB,OAAO,iBAAiB,QAAQ,OAAO,GAAG;CAEnE,MAAM,sBAAsB;EAC1B,GAAG,QAAQ;EACX,GAAG,QAAQ;EACX,GAAG,QAAQ;EACX,GAAG,iBAAiB;EACpB,GAAG,iBAAiB;EACrB;CAED,MAAM,mBAAmB,QACvB,oBAAoB,MAAM,WAAW,IAAI,WAAW,OAAO,CAAC;CAE9D,MAAM,aAAa,OAAO,YAAY,UAAyB;AAC7D,MAAI,CAAC,OAAO,OACV;AAEF,QAAM,MAAM,GAAG,QAAQ,UAAU;GAC/B,MAAM;GACN,SAAS;IACP,GAAG;IACH,gBAAgB;IAChB,eAAe,OAAO;IACvB;GACD;GACA,QAAQ;GACT,CAAC,CAAC,OAAO,UAAU;AAClB,eAAY,eAAe,MAAM;IACjC;;CAGJ,MAAM,kBAAkB,OACtB,KACA,YAAY,UACM;EAClB,MAAM,OAAO,GAAG,KAAK,UAAU,IAAI,CAAC;AACpC,QAAM,MAAM,GAAG,QAAQ,uBAAuB;GAC5C;GACA,SAAS;IACP,GAAG;IACH,gBAAgB;IACjB;GACD;GACA,QAAQ;GACT,CAAC,CAAC,OAAO,UAAU;AAClB,eAAY,iBAAiB,MAAM;IACnC;;CAGJ,MAAM,oBAAoB,OACxB,MACA,YAAY,UACM;AAClB,MAAI,KAAK,WAAW,EAClB;EAEF,MAAM,OAAO,GAAG,KAAK,KAAK,QAAQ,KAAK,UAAU,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC;AAClE,QAAM,MAAM,GAAG,QAAQ,iBAAiB;GACtC;GACA,SAAS;IACP,GAAG;IACH,gBAAgB;IACjB;GACD;GACA,QAAQ;GACT,CAAC,CAAC,OAAO,UAAU;AAClB,eAAY,eAAe,MAAM;IACjC;;CAGJ,MAAM,kBAAkB,OACtB,MACA,SACA,YAAY,UACM;AAClB,QAAM,MAAM,GAAG,QAAQ,uBAAuB;GAC5C,MAAM;GACN,SAAS;IACP,GAAG;IACH,gBAAgB;IAChB,mBAAmB,OAAO,KAAK,SAAS;IACxC,qBAAqB,OAAO,KAAK,WAAW;IAC5C,qBAAqB,OAAO,KAAK,WAAW;IAC5C,uBAAuB,KAAK,eAAe,MAAM;IACjD,oBAAoB,KAAK;IAC1B;GACD;GACA,QAAQ;GACT,CAAC,CAAC,OAAO,UAAU;AAClB,eAAY,aAAa,MAAM;IAC/B;;AAGJ,QAAO;EACL;EACA;EACA;EACA;EACA;EACD;;AAGH,SAAgB,8BAA8B,MAK1B;AAClB,QAAO,sBAAsB;EAC3B,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,SAASA,wCAAAA,UAAU,KAAK,OAAO;EAC/B,kBAAkBC,wCAAAA,oBAAoB,KAAK,iBAAiB;EAC7D,CAAC"}
|
|
@@ -5,7 +5,7 @@ import { gzip } from "./gzip.cjs";
|
|
|
5
5
|
declare function createReplayTransport(config: ReplayTransportConfig): ReplayTransport;
|
|
6
6
|
declare function createReplayTransportFromInit(args: {
|
|
7
7
|
appID: string;
|
|
8
|
-
|
|
8
|
+
apiKey?: string;
|
|
9
9
|
apiURL?: string;
|
|
10
10
|
otelCollectorURL?: string;
|
|
11
11
|
}): ReplayTransport;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replayTransport.d.cts","names":[],"sources":["../../../../src/instrumentations/replay/transport/replayTransport.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"replayTransport.d.cts","names":[],"sources":["../../../../src/instrumentations/replay/transport/replayTransport.ts"],"mappings":";;;;iBAiCgB,qBAAA,CACd,MAAA,EAAQ,qBAAA,GACP,eAAA;AAAA,iBAyGa,6BAAA,CAA8B,IAAA;EAC5C,KAAA;EACA,MAAA;EACA,MAAA;EACA,gBAAA;AAAA,IACE,eAAA"}
|
|
@@ -5,7 +5,7 @@ import { gzip } from "./gzip.js";
|
|
|
5
5
|
declare function createReplayTransport(config: ReplayTransportConfig): ReplayTransport;
|
|
6
6
|
declare function createReplayTransportFromInit(args: {
|
|
7
7
|
appID: string;
|
|
8
|
-
|
|
8
|
+
apiKey?: string;
|
|
9
9
|
apiURL?: string;
|
|
10
10
|
otelCollectorURL?: string;
|
|
11
11
|
}): ReplayTransport;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replayTransport.d.ts","names":[],"sources":["../../../../src/instrumentations/replay/transport/replayTransport.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"replayTransport.d.ts","names":[],"sources":["../../../../src/instrumentations/replay/transport/replayTransport.ts"],"mappings":";;;;iBAiCgB,qBAAA,CACd,MAAA,EAAQ,qBAAA,GACP,eAAA;AAAA,iBAyGa,6BAAA,CAA8B,IAAA;EAC5C,KAAA;EACA,MAAA;EACA,MAAA;EACA,gBAAA;AAAA,IACE,eAAA"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { gzip } from "./gzip.js";
|
|
2
|
-
import { getMXLHeaders } from "../../../exporters/utils.js";
|
|
3
2
|
import { getApiURL, getOtelCollectorURL } from "../../../managers/MXLConfigManager/utils.js";
|
|
4
3
|
//#region src/instrumentations/replay/transport/replayTransport.ts
|
|
5
4
|
/**
|
|
6
5
|
* Ported from Maple browser SDK (MIT), adapted for MXL auth and endpoints.
|
|
7
6
|
* @see https://github.com/Makisuo/maple/tree/main/packages/browser/src/replay/transport.ts
|
|
8
7
|
*/
|
|
8
|
+
const getReplayHeaders = (appID) => ({ "mxl-client-id": appID });
|
|
9
9
|
let lastWarnAt = 0;
|
|
10
10
|
function warnDropped(what, error) {
|
|
11
11
|
const now = Date.now();
|
|
@@ -14,16 +14,32 @@ function warnDropped(what, error) {
|
|
|
14
14
|
console.warn(`[mxl] session replay ${what} failed (dropping; will retry on next chunk):`, error);
|
|
15
15
|
}
|
|
16
16
|
function createReplayTransport(config) {
|
|
17
|
-
const headers =
|
|
17
|
+
const headers = getReplayHeaders(config.appID);
|
|
18
18
|
const baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
19
19
|
const otelCollectorURL = config.otelCollectorURL.replace(/\/$/, "");
|
|
20
20
|
const ignoredPathPrefixes = [
|
|
21
21
|
`${baseUrl}/sessionReplays/`,
|
|
22
22
|
`${baseUrl}/sessionEvents`,
|
|
23
|
+
`${baseUrl}/replay`,
|
|
23
24
|
`${otelCollectorURL}/v1/logs`,
|
|
24
25
|
`${otelCollectorURL}/v1/traces`
|
|
25
26
|
];
|
|
26
27
|
const shouldIgnoreUrl = (url) => ignoredPathPrefixes.some((prefix) => url.startsWith(prefix));
|
|
28
|
+
const postReplay = async (keepalive = false) => {
|
|
29
|
+
if (!config.apiKey) return;
|
|
30
|
+
await fetch(`${baseUrl}/replay`, {
|
|
31
|
+
body: "null",
|
|
32
|
+
headers: {
|
|
33
|
+
...headers,
|
|
34
|
+
"content-type": "application/json",
|
|
35
|
+
"mxl-api-key": config.apiKey
|
|
36
|
+
},
|
|
37
|
+
keepalive,
|
|
38
|
+
method: "POST"
|
|
39
|
+
}).catch((error) => {
|
|
40
|
+
warnDropped("replay POST", error);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
27
43
|
const postSessionMeta = async (row, keepalive = false) => {
|
|
28
44
|
const body = `${JSON.stringify(row)}\n`;
|
|
29
45
|
await fetch(`${baseUrl}/sessionReplays/meta`, {
|
|
@@ -72,6 +88,7 @@ function createReplayTransport(config) {
|
|
|
72
88
|
});
|
|
73
89
|
};
|
|
74
90
|
return {
|
|
91
|
+
postReplay,
|
|
75
92
|
postSessionBlob,
|
|
76
93
|
postSessionEvents,
|
|
77
94
|
postSessionMeta,
|
|
@@ -80,10 +97,10 @@ function createReplayTransport(config) {
|
|
|
80
97
|
}
|
|
81
98
|
function createReplayTransportFromInit(args) {
|
|
82
99
|
return createReplayTransport({
|
|
100
|
+
apiKey: args.apiKey,
|
|
83
101
|
appID: args.appID,
|
|
84
102
|
baseUrl: getApiURL(args.apiURL),
|
|
85
|
-
otelCollectorURL: getOtelCollectorURL(args.otelCollectorURL)
|
|
86
|
-
userID: args.userID
|
|
103
|
+
otelCollectorURL: getOtelCollectorURL(args.otelCollectorURL)
|
|
87
104
|
});
|
|
88
105
|
}
|
|
89
106
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replayTransport.js","names":[],"sources":["../../../../src/instrumentations/replay/transport/replayTransport.ts"],"sourcesContent":["/**\n * Ported from Maple browser SDK (MIT), adapted for MXL auth and endpoints.\n * @see https://github.com/Makisuo/maple/tree/main/packages/browser/src/replay/transport.ts\n */\n\nimport {
|
|
1
|
+
{"version":3,"file":"replayTransport.js","names":[],"sources":["../../../../src/instrumentations/replay/transport/replayTransport.ts"],"sourcesContent":["/**\n * Ported from Maple browser SDK (MIT), adapted for MXL auth and endpoints.\n * @see https://github.com/Makisuo/maple/tree/main/packages/browser/src/replay/transport.ts\n */\n\nimport {\n getApiURL,\n getOtelCollectorURL,\n} from '../../../managers/MXLConfigManager/utils';\nimport { gzip } from './gzip';\nimport type { ChunkMeta, ReplayTransport, ReplayTransportConfig } from './types';\n\nexport { gzip };\n\nconst getReplayHeaders = (appID: string): Record<string, string> => ({\n 'mxl-client-id': appID,\n});\n\nlet lastWarnAt = 0;\n\nfunction warnDropped(what: string, error: unknown): void {\n const now = Date.now();\n if (now - lastWarnAt < 30_000) {\n return;\n }\n lastWarnAt = now;\n // oxlint-disable-next-line no-console -- best-effort replay diagnostics\n console.warn(\n `[mxl] session replay ${what} failed (dropping; will retry on next chunk):`,\n error,\n );\n}\n\nexport function createReplayTransport(\n config: ReplayTransportConfig,\n): ReplayTransport {\n const headers = getReplayHeaders(config.appID);\n const baseUrl = config.baseUrl.replace(/\\/$/, '');\n const otelCollectorURL = config.otelCollectorURL.replace(/\\/$/, '');\n\n const ignoredPathPrefixes = [\n `${baseUrl}/sessionReplays/`,\n `${baseUrl}/sessionEvents`,\n `${baseUrl}/replay`,\n `${otelCollectorURL}/v1/logs`,\n `${otelCollectorURL}/v1/traces`,\n ];\n\n const shouldIgnoreUrl = (url: string): boolean =>\n ignoredPathPrefixes.some((prefix) => url.startsWith(prefix));\n\n const postReplay = async (keepalive = false): Promise<void> => {\n if (!config.apiKey) {\n return;\n }\n await fetch(`${baseUrl}/replay`, {\n body: 'null',\n headers: {\n ...headers,\n 'content-type': 'application/json',\n 'mxl-api-key': config.apiKey,\n },\n keepalive,\n method: 'POST',\n }).catch((error) => {\n warnDropped('replay POST', error);\n });\n };\n\n const postSessionMeta = async (\n row: Record<string, unknown>,\n keepalive = false,\n ): Promise<void> => {\n const body = `${JSON.stringify(row)}\\n`;\n await fetch(`${baseUrl}/sessionReplays/meta`, {\n body,\n headers: {\n ...headers,\n 'content-type': 'application/x-ndjson',\n },\n keepalive,\n method: 'POST',\n }).catch((error) => {\n warnDropped('metadata POST', error);\n });\n };\n\n const postSessionEvents = async (\n rows: ReadonlyArray<Record<string, unknown>>,\n keepalive = false,\n ): Promise<void> => {\n if (rows.length === 0) {\n return;\n }\n const body = `${rows.map((row) => JSON.stringify(row)).join('\\n')}\\n`;\n await fetch(`${baseUrl}/sessionEvents`, {\n body,\n headers: {\n ...headers,\n 'content-type': 'application/x-ndjson',\n },\n keepalive,\n method: 'POST',\n }).catch((error) => {\n warnDropped('events POST', error);\n });\n };\n\n const postSessionBlob = async (\n meta: ChunkMeta,\n gzipped: Uint8Array,\n keepalive = false,\n ): Promise<void> => {\n await fetch(`${baseUrl}/sessionReplays/blob`, {\n body: gzipped as unknown as BodyInit,\n headers: {\n ...headers,\n 'content-type': 'application/octet-stream',\n 'x-mxl-chunk-seq': String(meta.chunkSeq),\n 'x-mxl-duration-ms': String(meta.durationMs),\n 'x-mxl-event-count': String(meta.eventCount),\n 'x-mxl-is-checkpoint': meta.isCheckpoint ? '1' : '0',\n 'x-mxl-session-id': meta.sessionId,\n },\n keepalive,\n method: 'POST',\n }).catch((error) => {\n warnDropped('blob POST', error);\n });\n };\n\n return {\n postReplay,\n postSessionBlob,\n postSessionEvents,\n postSessionMeta,\n shouldIgnoreUrl,\n };\n}\n\nexport function createReplayTransportFromInit(args: {\n appID: string;\n apiKey?: string;\n apiURL?: string;\n otelCollectorURL?: string;\n}): ReplayTransport {\n return createReplayTransport({\n apiKey: args.apiKey,\n appID: args.appID,\n baseUrl: getApiURL(args.apiURL),\n otelCollectorURL: getOtelCollectorURL(args.otelCollectorURL),\n });\n}\n"],"mappings":";;;;;;;AAcA,MAAM,oBAAoB,WAA2C,EACnE,iBAAiB,OAClB;AAED,IAAI,aAAa;AAEjB,SAAS,YAAY,MAAc,OAAsB;CACvD,MAAM,MAAM,KAAK,KAAK;AACtB,KAAI,MAAM,aAAa,IACrB;AAEF,cAAa;AAEb,SAAQ,KACN,wBAAwB,KAAK,gDAC7B,MACD;;AAGH,SAAgB,sBACd,QACiB;CACjB,MAAM,UAAU,iBAAiB,OAAO,MAAM;CAC9C,MAAM,UAAU,OAAO,QAAQ,QAAQ,OAAO,GAAG;CACjD,MAAM,mBAAmB,OAAO,iBAAiB,QAAQ,OAAO,GAAG;CAEnE,MAAM,sBAAsB;EAC1B,GAAG,QAAQ;EACX,GAAG,QAAQ;EACX,GAAG,QAAQ;EACX,GAAG,iBAAiB;EACpB,GAAG,iBAAiB;EACrB;CAED,MAAM,mBAAmB,QACvB,oBAAoB,MAAM,WAAW,IAAI,WAAW,OAAO,CAAC;CAE9D,MAAM,aAAa,OAAO,YAAY,UAAyB;AAC7D,MAAI,CAAC,OAAO,OACV;AAEF,QAAM,MAAM,GAAG,QAAQ,UAAU;GAC/B,MAAM;GACN,SAAS;IACP,GAAG;IACH,gBAAgB;IAChB,eAAe,OAAO;IACvB;GACD;GACA,QAAQ;GACT,CAAC,CAAC,OAAO,UAAU;AAClB,eAAY,eAAe,MAAM;IACjC;;CAGJ,MAAM,kBAAkB,OACtB,KACA,YAAY,UACM;EAClB,MAAM,OAAO,GAAG,KAAK,UAAU,IAAI,CAAC;AACpC,QAAM,MAAM,GAAG,QAAQ,uBAAuB;GAC5C;GACA,SAAS;IACP,GAAG;IACH,gBAAgB;IACjB;GACD;GACA,QAAQ;GACT,CAAC,CAAC,OAAO,UAAU;AAClB,eAAY,iBAAiB,MAAM;IACnC;;CAGJ,MAAM,oBAAoB,OACxB,MACA,YAAY,UACM;AAClB,MAAI,KAAK,WAAW,EAClB;EAEF,MAAM,OAAO,GAAG,KAAK,KAAK,QAAQ,KAAK,UAAU,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC;AAClE,QAAM,MAAM,GAAG,QAAQ,iBAAiB;GACtC;GACA,SAAS;IACP,GAAG;IACH,gBAAgB;IACjB;GACD;GACA,QAAQ;GACT,CAAC,CAAC,OAAO,UAAU;AAClB,eAAY,eAAe,MAAM;IACjC;;CAGJ,MAAM,kBAAkB,OACtB,MACA,SACA,YAAY,UACM;AAClB,QAAM,MAAM,GAAG,QAAQ,uBAAuB;GAC5C,MAAM;GACN,SAAS;IACP,GAAG;IACH,gBAAgB;IAChB,mBAAmB,OAAO,KAAK,SAAS;IACxC,qBAAqB,OAAO,KAAK,WAAW;IAC5C,qBAAqB,OAAO,KAAK,WAAW;IAC5C,uBAAuB,KAAK,eAAe,MAAM;IACjD,oBAAoB,KAAK;IAC1B;GACD;GACA,QAAQ;GACT,CAAC,CAAC,OAAO,UAAU;AAClB,eAAY,aAAa,MAAM;IAC/B;;AAGJ,QAAO;EACL;EACA;EACA;EACA;EACA;EACD;;AAGH,SAAgB,8BAA8B,MAK1B;AAClB,QAAO,sBAAsB;EAC3B,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,SAAS,UAAU,KAAK,OAAO;EAC/B,kBAAkB,oBAAoB,KAAK,iBAAiB;EAC7D,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/instrumentations/replay/transport/types.d.ts
|
|
2
2
|
interface ReplayTransportConfig {
|
|
3
3
|
readonly appID: string;
|
|
4
|
-
readonly
|
|
4
|
+
readonly apiKey?: string;
|
|
5
5
|
readonly baseUrl: string;
|
|
6
6
|
readonly otelCollectorURL: string;
|
|
7
7
|
}
|
|
@@ -13,6 +13,7 @@ interface ChunkMeta {
|
|
|
13
13
|
readonly durationMs: number;
|
|
14
14
|
}
|
|
15
15
|
interface ReplayTransport {
|
|
16
|
+
postReplay: (keepalive?: boolean) => Promise<void>;
|
|
16
17
|
postSessionMeta: (row: Record<string, unknown>, keepalive?: boolean) => Promise<void>;
|
|
17
18
|
postSessionEvents: (rows: ReadonlyArray<Record<string, unknown>>, keepalive?: boolean) => Promise<void>;
|
|
18
19
|
postSessionBlob: (meta: ChunkMeta, gzipped: Uint8Array, keepalive?: boolean) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.cts","names":[],"sources":["../../../../src/instrumentations/replay/transport/types.ts"],"mappings":";UAAiB,qBAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA;EAAA,SACA,OAAA;EAAA,SACA,gBAAA;AAAA;AAAA,UAGM,SAAA;EAAA,SACN,SAAA;EAAA,SACA,QAAA;EAAA,SACA,YAAA;EAAA,SACA,UAAA;EAAA,SACA,UAAA;AAAA;AAAA,UAGM,eAAA;EACf,eAAA,GACE,GAAA,EAAK,MAAA,mBACL,SAAA,eACG,OAAA;EACL,iBAAA,GACE,IAAA,EAAM,aAAA,CAAc,MAAA,oBACpB,SAAA,eACG,OAAA;EACL,eAAA,GACE,IAAA,EAAM,SAAA,EACN,OAAA,EAAS,UAAA,EACT,SAAA,eACG,OAAA;EACL,eAAA,GAAkB,GAAA;AAAA"}
|
|
1
|
+
{"version":3,"file":"types.d.cts","names":[],"sources":["../../../../src/instrumentations/replay/transport/types.ts"],"mappings":";UAAiB,qBAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA;EAAA,SACA,OAAA;EAAA,SACA,gBAAA;AAAA;AAAA,UAGM,SAAA;EAAA,SACN,SAAA;EAAA,SACA,QAAA;EAAA,SACA,YAAA;EAAA,SACA,UAAA;EAAA,SACA,UAAA;AAAA;AAAA,UAGM,eAAA;EACf,UAAA,GAAa,SAAA,eAAwB,OAAA;EACrC,eAAA,GACE,GAAA,EAAK,MAAA,mBACL,SAAA,eACG,OAAA;EACL,iBAAA,GACE,IAAA,EAAM,aAAA,CAAc,MAAA,oBACpB,SAAA,eACG,OAAA;EACL,eAAA,GACE,IAAA,EAAM,SAAA,EACN,OAAA,EAAS,UAAA,EACT,SAAA,eACG,OAAA;EACL,eAAA,GAAkB,GAAA;AAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/instrumentations/replay/transport/types.d.ts
|
|
2
2
|
interface ReplayTransportConfig {
|
|
3
3
|
readonly appID: string;
|
|
4
|
-
readonly
|
|
4
|
+
readonly apiKey?: string;
|
|
5
5
|
readonly baseUrl: string;
|
|
6
6
|
readonly otelCollectorURL: string;
|
|
7
7
|
}
|
|
@@ -13,6 +13,7 @@ interface ChunkMeta {
|
|
|
13
13
|
readonly durationMs: number;
|
|
14
14
|
}
|
|
15
15
|
interface ReplayTransport {
|
|
16
|
+
postReplay: (keepalive?: boolean) => Promise<void>;
|
|
16
17
|
postSessionMeta: (row: Record<string, unknown>, keepalive?: boolean) => Promise<void>;
|
|
17
18
|
postSessionEvents: (rows: ReadonlyArray<Record<string, unknown>>, keepalive?: boolean) => Promise<void>;
|
|
18
19
|
postSessionBlob: (meta: ChunkMeta, gzipped: Uint8Array, keepalive?: boolean) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/instrumentations/replay/transport/types.ts"],"mappings":";UAAiB,qBAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA;EAAA,SACA,OAAA;EAAA,SACA,gBAAA;AAAA;AAAA,UAGM,SAAA;EAAA,SACN,SAAA;EAAA,SACA,QAAA;EAAA,SACA,YAAA;EAAA,SACA,UAAA;EAAA,SACA,UAAA;AAAA;AAAA,UAGM,eAAA;EACf,eAAA,GACE,GAAA,EAAK,MAAA,mBACL,SAAA,eACG,OAAA;EACL,iBAAA,GACE,IAAA,EAAM,aAAA,CAAc,MAAA,oBACpB,SAAA,eACG,OAAA;EACL,eAAA,GACE,IAAA,EAAM,SAAA,EACN,OAAA,EAAS,UAAA,EACT,SAAA,eACG,OAAA;EACL,eAAA,GAAkB,GAAA;AAAA"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/instrumentations/replay/transport/types.ts"],"mappings":";UAAiB,qBAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA;EAAA,SACA,OAAA;EAAA,SACA,gBAAA;AAAA;AAAA,UAGM,SAAA;EAAA,SACN,SAAA;EAAA,SACA,QAAA;EAAA,SACA,YAAA;EAAA,SACA,UAAA;EAAA,SACA,UAAA;AAAA;AAAA,UAGM,eAAA;EACf,UAAA,GAAa,SAAA,eAAwB,OAAA;EACrC,eAAA,GACE,GAAA,EAAK,MAAA,mBACL,SAAA,eACG,OAAA;EACL,iBAAA,GACE,IAAA,EAAM,aAAA,CAAc,MAAA,oBACpB,SAAA,eACG,OAAA;EACL,eAAA,GACE,IAAA,EAAM,SAAA,EACN,OAAA,EAAS,UAAA,EACT,SAAA,eACG,OAAA;EACL,eAAA,GAAkB,GAAA;AAAA"}
|