@multiplayer-app/session-recorder-react-native 1.3.15 → 1.3.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/lib/module/config/constants.js +3 -0
- package/lib/module/config/constants.js.map +1 -1
- package/lib/module/config/defaults.js +5 -1
- package/lib/module/config/defaults.js.map +1 -1
- package/lib/module/config/session-recorder.js +5 -1
- package/lib/module/config/session-recorder.js.map +1 -1
- package/lib/module/otel/CrashBufferSpanProcessor.js +41 -0
- package/lib/module/otel/CrashBufferSpanProcessor.js.map +1 -0
- package/lib/module/otel/index.js +28 -9
- package/lib/module/otel/index.js.map +1 -1
- package/lib/module/patch/fetch.js +71 -11
- package/lib/module/patch/fetch.js.map +1 -1
- package/lib/module/recorder/index.js +15 -1
- package/lib/module/recorder/index.js.map +1 -1
- package/lib/module/services/api.service.js +24 -2
- package/lib/module/services/api.service.js.map +1 -1
- package/lib/module/services/crashBuffer.service.js +248 -0
- package/lib/module/services/crashBuffer.service.js.map +1 -0
- package/lib/module/services/socket.service.js +9 -2
- package/lib/module/services/socket.service.js.map +1 -1
- package/lib/module/session-recorder.js +152 -6
- package/lib/module/session-recorder.js.map +1 -1
- package/lib/module/types/session-recorder.js.map +1 -1
- package/lib/typescript/src/config/constants.d.ts +1 -0
- package/lib/typescript/src/config/constants.d.ts.map +1 -1
- package/lib/typescript/src/config/defaults.d.ts.map +1 -1
- package/lib/typescript/src/config/session-recorder.d.ts.map +1 -1
- package/lib/typescript/src/otel/CrashBufferSpanProcessor.d.ts +18 -0
- package/lib/typescript/src/otel/CrashBufferSpanProcessor.d.ts.map +1 -0
- package/lib/typescript/src/otel/index.d.ts +8 -0
- package/lib/typescript/src/otel/index.d.ts.map +1 -1
- package/lib/typescript/src/recorder/index.d.ts +8 -1
- package/lib/typescript/src/recorder/index.d.ts.map +1 -1
- package/lib/typescript/src/services/api.service.d.ts +27 -2
- package/lib/typescript/src/services/api.service.d.ts.map +1 -1
- package/lib/typescript/src/services/crashBuffer.service.d.ts +46 -0
- package/lib/typescript/src/services/crashBuffer.service.d.ts.map +1 -0
- package/lib/typescript/src/services/socket.service.d.ts +4 -3
- package/lib/typescript/src/services/socket.service.d.ts.map +1 -1
- package/lib/typescript/src/session-recorder.d.ts +8 -0
- package/lib/typescript/src/session-recorder.d.ts.map +1 -1
- package/lib/typescript/src/types/session-recorder.d.ts +18 -0
- package/lib/typescript/src/types/session-recorder.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/config/constants.ts +3 -0
- package/src/config/defaults.ts +5 -0
- package/src/config/session-recorder.ts +5 -0
- package/src/otel/CrashBufferSpanProcessor.ts +61 -0
- package/src/otel/index.ts +90 -34
- package/src/patch/fetch.ts +73 -11
- package/src/recorder/index.ts +30 -3
- package/src/services/api.service.ts +68 -13
- package/src/services/crashBuffer.service.ts +327 -0
- package/src/services/socket.service.ts +36 -22
- package/src/session-recorder.ts +226 -19
- package/src/types/session-recorder.ts +18 -0
package/src/otel/index.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
W3CTraceContextPropagator,
|
|
4
|
+
type ExportResult,
|
|
5
|
+
} from '@opentelemetry/core';
|
|
6
|
+
import {
|
|
7
|
+
AlwaysOnSampler,
|
|
8
|
+
BatchSpanProcessor,
|
|
9
|
+
type ReadableSpan,
|
|
10
|
+
} from '@opentelemetry/sdk-trace-base';
|
|
4
11
|
import * as SemanticAttributes from '@opentelemetry/semantic-conventions';
|
|
5
12
|
import { registerInstrumentations } from '@opentelemetry/instrumentation';
|
|
6
13
|
import {
|
|
7
14
|
SessionType,
|
|
8
|
-
|
|
15
|
+
SessionRecorderSdk,
|
|
9
16
|
SessionRecorderIdGenerator,
|
|
10
|
-
SessionRecorderTraceIdRatioBasedSampler,
|
|
11
17
|
SessionRecorderBrowserTraceExporter,
|
|
18
|
+
ATTR_MULTIPLAYER_SESSION_ID,
|
|
19
|
+
MULTIPLAYER_TRACE_CLIENT_ID_LENGTH,
|
|
12
20
|
} from '@multiplayer-app/session-recorder-common';
|
|
13
21
|
import { type TracerReactNativeConfig } from '../types';
|
|
14
22
|
import { getInstrumentations } from './instrumentations';
|
|
@@ -17,28 +25,37 @@ import { trace, SpanStatusCode, context, type Span } from '@opentelemetry/api';
|
|
|
17
25
|
|
|
18
26
|
import { getPlatformAttributes } from '../utils/platform';
|
|
19
27
|
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
|
|
28
|
+
import { CrashBufferService } from '../services/crashBuffer.service';
|
|
29
|
+
import { CrashBufferSpanProcessor } from './CrashBufferSpanProcessor';
|
|
20
30
|
|
|
21
31
|
export class TracerReactNativeSDK {
|
|
32
|
+
clientId = '';
|
|
22
33
|
private tracerProvider?: WebTracerProvider;
|
|
23
34
|
private config?: TracerReactNativeConfig;
|
|
24
35
|
|
|
25
36
|
private sessionId = '';
|
|
26
37
|
private idGenerator?: SessionRecorderIdGenerator;
|
|
27
|
-
private exporter?:
|
|
38
|
+
private exporter?: SessionRecorderBrowserTraceExporter;
|
|
39
|
+
private batchSpanProcessor?: BatchSpanProcessor;
|
|
28
40
|
private globalErrorHandlerRegistered = false;
|
|
41
|
+
private crashBuffer?: CrashBufferService;
|
|
29
42
|
|
|
30
|
-
constructor() {
|
|
43
|
+
constructor() {}
|
|
31
44
|
|
|
32
45
|
private _setSessionId(
|
|
33
46
|
sessionId: string,
|
|
34
47
|
sessionType: SessionType = SessionType.MANUAL
|
|
35
48
|
) {
|
|
36
49
|
this.sessionId = sessionId;
|
|
37
|
-
this.idGenerator?.setSessionId(sessionId, sessionType);
|
|
50
|
+
this.idGenerator?.setSessionId(sessionId, sessionType, this.clientId);
|
|
38
51
|
}
|
|
39
52
|
|
|
40
53
|
init(options: TracerReactNativeConfig): void {
|
|
41
54
|
this.config = options;
|
|
55
|
+
const clientIdGenerator = SessionRecorderSdk.getIdGenerator(
|
|
56
|
+
MULTIPLAYER_TRACE_CLIENT_ID_LENGTH
|
|
57
|
+
);
|
|
58
|
+
this.clientId = clientIdGenerator();
|
|
42
59
|
|
|
43
60
|
const { application, version, environment } = this.config;
|
|
44
61
|
|
|
@@ -49,6 +66,8 @@ export class TracerReactNativeSDK {
|
|
|
49
66
|
url: getExporterEndpoint(options.exporterEndpoint),
|
|
50
67
|
});
|
|
51
68
|
|
|
69
|
+
this.batchSpanProcessor = new BatchSpanProcessor(this.exporter);
|
|
70
|
+
|
|
52
71
|
this.tracerProvider = new WebTracerProvider({
|
|
53
72
|
resource: resourceFromAttributes({
|
|
54
73
|
[SemanticAttributes.SEMRESATTRS_SERVICE_NAME]: application,
|
|
@@ -57,12 +76,14 @@ export class TracerReactNativeSDK {
|
|
|
57
76
|
...getPlatformAttributes(),
|
|
58
77
|
}),
|
|
59
78
|
idGenerator: this.idGenerator,
|
|
60
|
-
sampler: new
|
|
61
|
-
this.config.sampleTraceRatio || 0.15
|
|
62
|
-
),
|
|
79
|
+
sampler: new AlwaysOnSampler(),
|
|
63
80
|
spanProcessors: [
|
|
64
81
|
this._getSpanSessionIdProcessor(),
|
|
65
|
-
new
|
|
82
|
+
new CrashBufferSpanProcessor(
|
|
83
|
+
this.batchSpanProcessor,
|
|
84
|
+
this.crashBuffer,
|
|
85
|
+
this.exporter.serializeSpan.bind(this.exporter)
|
|
86
|
+
),
|
|
66
87
|
],
|
|
67
88
|
});
|
|
68
89
|
|
|
@@ -79,17 +100,41 @@ export class TracerReactNativeSDK {
|
|
|
79
100
|
this._registerGlobalErrorHandlers();
|
|
80
101
|
}
|
|
81
102
|
|
|
103
|
+
setCrashBuffer(
|
|
104
|
+
crashBuffer: CrashBufferService | undefined,
|
|
105
|
+
windowMs?: number
|
|
106
|
+
): void {
|
|
107
|
+
this.crashBuffer = crashBuffer;
|
|
108
|
+
if (
|
|
109
|
+
crashBuffer &&
|
|
110
|
+
typeof windowMs === 'number' &&
|
|
111
|
+
Number.isFinite(windowMs)
|
|
112
|
+
) {
|
|
113
|
+
crashBuffer.setDefaultWindowMs(windowMs);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async exportTraces(
|
|
118
|
+
spans: ReadableSpan[]
|
|
119
|
+
): Promise<ExportResult | undefined | void> {
|
|
120
|
+
if (this.batchSpanProcessor) {
|
|
121
|
+
spans.map((span) => {
|
|
122
|
+
this.batchSpanProcessor?.onEnd(span);
|
|
123
|
+
});
|
|
124
|
+
return Promise.resolve();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
throw new Error('Buffer span processor not initialized');
|
|
128
|
+
}
|
|
129
|
+
|
|
82
130
|
private _getSpanSessionIdProcessor() {
|
|
83
131
|
return {
|
|
84
132
|
onStart: (span: any) => {
|
|
85
133
|
if (this.sessionId) {
|
|
86
134
|
span.setAttribute(ATTR_MULTIPLAYER_SESSION_ID, this.sessionId);
|
|
87
135
|
}
|
|
88
|
-
// Add React Native specific attributes
|
|
89
|
-
span.setAttribute('platform', 'react-native');
|
|
90
|
-
span.setAttribute('timestamp', Date.now());
|
|
91
136
|
},
|
|
92
|
-
onEnd: () => {
|
|
137
|
+
onEnd: () => {},
|
|
93
138
|
shutdown: () => Promise.resolve(),
|
|
94
139
|
forceFlush: () => Promise.resolve(),
|
|
95
140
|
};
|
|
@@ -143,32 +188,36 @@ export class TracerReactNativeSDK {
|
|
|
143
188
|
if (!error) return;
|
|
144
189
|
// Prefer attaching to the active span to keep correlation intact
|
|
145
190
|
try {
|
|
146
|
-
const activeSpan = trace.getSpan(context.active())
|
|
191
|
+
const activeSpan = trace.getSpan(context.active());
|
|
147
192
|
if (activeSpan) {
|
|
148
|
-
this._recordException(activeSpan, error, errorInfo)
|
|
149
|
-
return
|
|
193
|
+
this._recordException(activeSpan, error, errorInfo);
|
|
194
|
+
return;
|
|
150
195
|
}
|
|
151
|
-
} catch (_ignored) {
|
|
196
|
+
} catch (_ignored) {}
|
|
152
197
|
|
|
153
198
|
// Fallback: create a short-lived span to hold the exception details
|
|
154
199
|
try {
|
|
155
|
-
const tracer = trace.getTracer('exception')
|
|
156
|
-
const span = tracer.startSpan(error.name || 'Error')
|
|
157
|
-
this._recordException(span, error, errorInfo)
|
|
158
|
-
span.end()
|
|
159
|
-
} catch (_ignored) {
|
|
200
|
+
const tracer = trace.getTracer('exception');
|
|
201
|
+
const span = tracer.startSpan(error.name || 'Error');
|
|
202
|
+
this._recordException(span, error, errorInfo);
|
|
203
|
+
span.end();
|
|
204
|
+
} catch (_ignored) {}
|
|
160
205
|
}
|
|
161
206
|
|
|
162
|
-
private _recordException(
|
|
163
|
-
span
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
span.
|
|
207
|
+
private _recordException(
|
|
208
|
+
span: Span,
|
|
209
|
+
error: Error,
|
|
210
|
+
errorInfo?: Record<string, any>
|
|
211
|
+
): void {
|
|
212
|
+
span.recordException(error);
|
|
213
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
214
|
+
span.setAttribute('exception.type', error.name || 'Error');
|
|
215
|
+
span.setAttribute('exception.message', error.message);
|
|
216
|
+
span.setAttribute('exception.stacktrace', error.stack || '');
|
|
168
217
|
if (errorInfo) {
|
|
169
218
|
Object.entries(errorInfo).forEach(([key, value]) => {
|
|
170
|
-
span.setAttribute(`error_info.${key}`, value)
|
|
171
|
-
})
|
|
219
|
+
span.setAttribute(`error_info.${key}`, value);
|
|
220
|
+
});
|
|
172
221
|
}
|
|
173
222
|
}
|
|
174
223
|
|
|
@@ -182,11 +231,18 @@ export class TracerReactNativeSDK {
|
|
|
182
231
|
const previous = ErrorUtilsRef.getGlobalHandler?.();
|
|
183
232
|
ErrorUtilsRef.setGlobalHandler((error: any, isFatal?: boolean) => {
|
|
184
233
|
try {
|
|
185
|
-
const err =
|
|
234
|
+
const err =
|
|
235
|
+
error instanceof Error
|
|
236
|
+
? error
|
|
237
|
+
: new Error(String(error?.message || error));
|
|
186
238
|
this.captureException(err);
|
|
187
239
|
} finally {
|
|
188
240
|
if (typeof previous === 'function') {
|
|
189
|
-
try {
|
|
241
|
+
try {
|
|
242
|
+
previous(error, isFatal);
|
|
243
|
+
} catch (_e) {
|
|
244
|
+
/* ignore */
|
|
245
|
+
}
|
|
190
246
|
}
|
|
191
247
|
}
|
|
192
248
|
});
|
package/src/patch/fetch.ts
CHANGED
|
@@ -35,28 +35,90 @@ function _tryReadFetchBody({
|
|
|
35
35
|
return `[Fetch] Cannot read body of type ${Object.prototype.toString.call(body)}`
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Detects if a response is a streaming response that should NOT have its body read.
|
|
40
|
+
* Reading the body of streaming responses (SSE, chunked streams, etc.) will either:
|
|
41
|
+
* - Block forever (SSE streams never end)
|
|
42
|
+
* - Corrupt the stream for the actual consumer
|
|
43
|
+
*/
|
|
44
|
+
function _isStreamingResponse(response: Response): boolean {
|
|
45
|
+
const contentType = response.headers.get('content-type')?.toLowerCase() ?? ''
|
|
46
|
+
|
|
47
|
+
// SSE - Server-Sent Events (infinite stream)
|
|
48
|
+
if (contentType.includes('text/event-stream')) {
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Binary streams that are typically long-running
|
|
53
|
+
if (contentType.includes('application/octet-stream')) {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// NDJSON streaming (newline-delimited JSON, common in streaming APIs)
|
|
58
|
+
if (contentType.includes('application/x-ndjson') || contentType.includes('application/ndjson')) {
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// gRPC-web streaming
|
|
63
|
+
if (contentType.includes('application/grpc')) {
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check for chunked transfer encoding (often indicates streaming)
|
|
68
|
+
const transferEncoding = response.headers.get('transfer-encoding')?.toLowerCase()
|
|
69
|
+
if (transferEncoding?.includes('chunked')) {
|
|
70
|
+
// Chunked alone isn't definitive, but combined with no content-length = streaming
|
|
71
|
+
const contentLength = response.headers.get('content-length')
|
|
72
|
+
if (!contentLength) {
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Safely reads response body for non-streaming responses.
|
|
82
|
+
* Returns null for streaming responses to avoid blocking/corruption.
|
|
83
|
+
*/
|
|
38
84
|
async function _tryReadResponseBody(response: Response): Promise<string | null> {
|
|
85
|
+
// CRITICAL: Never attempt to read streaming response bodies
|
|
86
|
+
if (_isStreamingResponse(response)) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
|
|
39
90
|
try {
|
|
40
91
|
// Clone the response to avoid consuming the original stream.
|
|
41
92
|
const clonedResponse = response.clone()
|
|
93
|
+
const contentType = response.headers.get('content-type')?.toLowerCase() ?? ''
|
|
94
|
+
|
|
95
|
+
// Check content-length to avoid reading massive responses
|
|
96
|
+
const contentLength = response.headers.get('content-length')
|
|
97
|
+
if (contentLength) {
|
|
98
|
+
const length = parseInt(contentLength, 10)
|
|
99
|
+
if (!isNaN(length) && length > configs.maxCapturingHttpPayloadSize) {
|
|
100
|
+
return `[Fetch] Response too large (${length} bytes)`
|
|
101
|
+
}
|
|
102
|
+
}
|
|
42
103
|
|
|
43
|
-
const contentType = response.headers.get('content-type') || ''
|
|
44
104
|
if (contentType.includes('application/json')) {
|
|
45
105
|
const json = await clonedResponse.json()
|
|
46
106
|
return JSON.stringify(json)
|
|
47
|
-
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (contentType.includes('text/')) {
|
|
48
110
|
return await clonedResponse.text()
|
|
49
|
-
}
|
|
50
|
-
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// For unknown types, attempt text read
|
|
114
|
+
try {
|
|
115
|
+
return await clonedResponse.text()
|
|
116
|
+
} catch {
|
|
51
117
|
try {
|
|
52
|
-
|
|
118
|
+
const arrayBuffer = await clonedResponse.arrayBuffer()
|
|
119
|
+
return `[Fetch] Binary data (${arrayBuffer.byteLength} bytes)`
|
|
53
120
|
} catch {
|
|
54
|
-
|
|
55
|
-
const arrayBuffer = await clonedResponse.arrayBuffer()
|
|
56
|
-
return `[Fetch] Binary data (${arrayBuffer.byteLength} bytes)`
|
|
57
|
-
} catch {
|
|
58
|
-
return '[Fetch] Unable to read response body'
|
|
59
|
-
}
|
|
121
|
+
return '[Fetch] Unable to read response body'
|
|
60
122
|
}
|
|
61
123
|
}
|
|
62
124
|
} catch (error) {
|
package/src/recorder/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SessionType } from '@multiplayer-app/session-recorder-common';
|
|
2
2
|
// import { pack } from '@rrweb/packer' // Removed to avoid blob creation issues in Hermes
|
|
3
3
|
import { SocketService } from '../services/socket.service';
|
|
4
|
+
import { CrashBufferService } from '../services/crashBuffer.service';
|
|
4
5
|
import { logger } from '../utils';
|
|
5
6
|
import { ScreenRecorder } from './screenRecorder';
|
|
6
7
|
import { GestureRecorder } from './gestureRecorder';
|
|
@@ -15,6 +16,9 @@ export class RecorderReactNativeSDK implements EventRecorder {
|
|
|
15
16
|
private navigationTracker: NavigationTracker;
|
|
16
17
|
private recordedEvents: eventWithTime[] = [];
|
|
17
18
|
private socketService!: SocketService;
|
|
19
|
+
private crashBuffer?: CrashBufferService;
|
|
20
|
+
private bufferingEnabled: boolean = false;
|
|
21
|
+
private bufferWindowMs: number = 2 * 60 * 1000;
|
|
18
22
|
private sessionId: string | null = null;
|
|
19
23
|
private sessionType: SessionType = SessionType.MANUAL;
|
|
20
24
|
|
|
@@ -24,15 +28,25 @@ export class RecorderReactNativeSDK implements EventRecorder {
|
|
|
24
28
|
this.navigationTracker = new NavigationTracker();
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
init(
|
|
31
|
+
init(
|
|
32
|
+
config: RecorderConfig,
|
|
33
|
+
socketService: SocketService,
|
|
34
|
+
crashBuffer?: CrashBufferService,
|
|
35
|
+
buffering?: { enabled: boolean; windowMs: number }
|
|
36
|
+
): void {
|
|
28
37
|
this.config = config;
|
|
29
38
|
this.socketService = socketService;
|
|
39
|
+
this.crashBuffer = crashBuffer;
|
|
40
|
+
this.bufferingEnabled = Boolean(buffering?.enabled);
|
|
41
|
+
this.bufferWindowMs = Math.max(
|
|
42
|
+
10_000,
|
|
43
|
+
buffering?.windowMs || 1 * 60 * 1000
|
|
44
|
+
);
|
|
30
45
|
this.screenRecorder.init(config, this);
|
|
31
46
|
this.navigationTracker.init(config, this.screenRecorder);
|
|
32
47
|
this.gestureRecorder.init(config, this, this.screenRecorder);
|
|
33
48
|
}
|
|
34
49
|
|
|
35
|
-
|
|
36
50
|
start(sessionId: string | null, sessionType: SessionType): void {
|
|
37
51
|
if (!this.config) {
|
|
38
52
|
throw new Error(
|
|
@@ -88,8 +102,21 @@ export class RecorderReactNativeSDK implements EventRecorder {
|
|
|
88
102
|
return;
|
|
89
103
|
}
|
|
90
104
|
|
|
105
|
+
// Buffer-only mode (no active debug session): persist locally.
|
|
106
|
+
if (!this.sessionId && this.crashBuffer && this.bufferingEnabled) {
|
|
107
|
+
void this.crashBuffer.appendEvent(
|
|
108
|
+
{ ts: event.timestamp, event },
|
|
109
|
+
this.bufferWindowMs
|
|
110
|
+
);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
91
114
|
if (this.socketService) {
|
|
92
|
-
logger.debug(
|
|
115
|
+
logger.debug(
|
|
116
|
+
'RecorderReactNativeSDK',
|
|
117
|
+
'Sending to socket service',
|
|
118
|
+
event
|
|
119
|
+
);
|
|
93
120
|
// Skip packing to avoid blob creation issues in Hermes
|
|
94
121
|
// const packedEvent = pack(event)
|
|
95
122
|
this.socketService.send({
|
|
@@ -3,10 +3,8 @@ import type {
|
|
|
3
3
|
ISessionAttributes,
|
|
4
4
|
IResourceAttributes,
|
|
5
5
|
} from '@multiplayer-app/session-recorder-common';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from '../types';
|
|
6
|
+
import { type ApiServiceConfig } from '../types';
|
|
7
|
+
import type { eventWithTime } from '@rrweb/types';
|
|
10
8
|
|
|
11
9
|
export interface StartSessionRequest {
|
|
12
10
|
name?: string;
|
|
@@ -23,11 +21,14 @@ export interface StopSessionRequest {
|
|
|
23
21
|
stoppedAt: string | number;
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
|
|
27
24
|
export interface CheckRemoteSessionRequest {
|
|
28
|
-
sessionAttributes?: ISessionAttributes
|
|
29
|
-
resourceAttributes?: IResourceAttributes
|
|
30
|
-
userAttributes?: IUserAttributes
|
|
25
|
+
sessionAttributes?: ISessionAttributes;
|
|
26
|
+
resourceAttributes?: IResourceAttributes;
|
|
27
|
+
userAttributes?: IUserAttributes;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CreateErrorSpanSessionRequest {
|
|
31
|
+
span: any;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export class ApiService {
|
|
@@ -72,6 +73,60 @@ export class ApiService {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Create a new error span session
|
|
78
|
+
* @param request - Session create error span request data
|
|
79
|
+
* @param signal - Optional AbortSignal for request cancellation
|
|
80
|
+
*/
|
|
81
|
+
async createErrorSession(
|
|
82
|
+
request: CreateErrorSpanSessionRequest,
|
|
83
|
+
signal?: AbortSignal
|
|
84
|
+
): Promise<any> {
|
|
85
|
+
return this.makeRequest(
|
|
86
|
+
'/debug-sessions/error-span/start',
|
|
87
|
+
'POST',
|
|
88
|
+
request,
|
|
89
|
+
signal
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async updateSessionAttributes(
|
|
94
|
+
sessionId: string,
|
|
95
|
+
requestBody: {
|
|
96
|
+
name?: string;
|
|
97
|
+
userAttributes?: IUserAttributes;
|
|
98
|
+
sessionAttributes?: ISessionAttributes;
|
|
99
|
+
resourceAttributes?: IResourceAttributes;
|
|
100
|
+
},
|
|
101
|
+
signal?: AbortSignal
|
|
102
|
+
): Promise<any> {
|
|
103
|
+
return this.makeRequest(
|
|
104
|
+
`/debug-sessions/${sessionId}`,
|
|
105
|
+
'PATCH',
|
|
106
|
+
requestBody,
|
|
107
|
+
signal
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Export events to the session debugger API
|
|
113
|
+
* @param sessionId - ID of the session to export events
|
|
114
|
+
* @param requestBody - Request body containing events
|
|
115
|
+
* @param signal - Optional AbortSignal for request cancellation
|
|
116
|
+
*/
|
|
117
|
+
async exportEvents(
|
|
118
|
+
sessionId: string,
|
|
119
|
+
requestBody: { events: eventWithTime[] },
|
|
120
|
+
signal?: AbortSignal
|
|
121
|
+
): Promise<any> {
|
|
122
|
+
return this.makeRequest(
|
|
123
|
+
`/debug-sessions/${sessionId}/rrweb-events`,
|
|
124
|
+
'POST',
|
|
125
|
+
requestBody,
|
|
126
|
+
signal
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
75
130
|
/**
|
|
76
131
|
* Make a request to the session debugger API
|
|
77
132
|
* @param path - API endpoint path (relative to the base URL)
|
|
@@ -212,17 +267,17 @@ export class ApiService {
|
|
|
212
267
|
}
|
|
213
268
|
|
|
214
269
|
/**
|
|
215
|
-
|
|
216
|
-
|
|
270
|
+
* Check debug session should be started remotely
|
|
271
|
+
*/
|
|
217
272
|
async checkRemoteSession(
|
|
218
273
|
requestBody: CheckRemoteSessionRequest,
|
|
219
|
-
signal?: AbortSignal
|
|
274
|
+
signal?: AbortSignal
|
|
220
275
|
): Promise<{ state: 'START' | 'STOP' }> {
|
|
221
276
|
return this.makeRequest(
|
|
222
277
|
'/remote-debug-session/check',
|
|
223
278
|
'POST',
|
|
224
279
|
requestBody,
|
|
225
|
-
signal
|
|
226
|
-
)
|
|
280
|
+
signal
|
|
281
|
+
);
|
|
227
282
|
}
|
|
228
283
|
}
|