@multiplayer-app/session-recorder-react-native 1.2.37 → 1.3.2
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 +2 -1
- package/lib/module/config/defaults.js.map +1 -1
- package/lib/module/config/session-recorder.js +2 -1
- package/lib/module/config/session-recorder.js.map +1 -1
- package/lib/module/recorder/index.js +7 -18
- package/lib/module/recorder/index.js.map +1 -1
- package/lib/module/services/api.service.js +2 -2
- package/lib/module/services/api.service.js.map +1 -1
- package/lib/module/services/socket.service.js +176 -0
- package/lib/module/services/socket.service.js.map +1 -0
- package/lib/module/session-recorder.js +67 -11
- package/lib/module/session-recorder.js.map +1 -1
- package/lib/module/types/session-recorder.js.map +1 -1
- package/lib/module/types/session.js.map +1 -1
- package/lib/module/utils/logger.js +1 -1
- package/lib/typescript/src/config/constants.d.ts +3 -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/recorder/index.d.ts +3 -4
- package/lib/typescript/src/recorder/index.d.ts.map +1 -1
- package/lib/typescript/src/services/api.service.d.ts +10 -4
- package/lib/typescript/src/services/api.service.d.ts.map +1 -1
- package/lib/typescript/src/services/socket.service.d.ts +39 -0
- package/lib/typescript/src/services/socket.service.d.ts.map +1 -0
- package/lib/typescript/src/session-recorder.d.ts +17 -1
- package/lib/typescript/src/session-recorder.d.ts.map +1 -1
- package/lib/typescript/src/types/session-recorder.d.ts +12 -1
- package/lib/typescript/src/types/session-recorder.d.ts.map +1 -1
- package/lib/typescript/src/types/session.d.ts +1 -2
- package/lib/typescript/src/types/session.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/config/constants.ts +6 -0
- package/src/config/defaults.ts +29 -27
- package/src/config/session-recorder.ts +2 -0
- package/src/recorder/index.ts +8 -21
- package/src/services/api.service.ts +15 -6
- package/src/services/socket.service.ts +225 -0
- package/src/session-recorder.ts +71 -11
- package/src/types/session-recorder.ts +14 -1
- package/src/types/session.ts +3 -2
- package/src/utils/logger.ts +1 -1
- package/lib/module/recorder/eventExporter.js +0 -130
- package/lib/module/recorder/eventExporter.js.map +0 -1
- package/lib/module/types/client-type.enum.js +0 -9
- package/lib/module/types/client-type.enum.js.map +0 -1
- package/lib/typescript/src/recorder/eventExporter.d.ts +0 -25
- package/lib/typescript/src/recorder/eventExporter.d.ts.map +0 -1
- package/lib/typescript/src/types/client-type.enum.d.ts +0 -6
- package/lib/typescript/src/types/client-type.enum.d.ts.map +0 -1
- package/src/recorder/eventExporter.ts +0 -150
- package/src/types/client-type.enum.ts +0 -5
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type IResourceAttributes,
|
|
3
3
|
type ISessionAttributes,
|
|
4
4
|
type ApiServiceConfig,
|
|
5
|
+
type IUserAttributes,
|
|
5
6
|
} from '../types';
|
|
6
7
|
|
|
7
8
|
export interface StartSessionRequest {
|
|
@@ -9,6 +10,7 @@ export interface StartSessionRequest {
|
|
|
9
10
|
stoppedAt?: string | number;
|
|
10
11
|
sessionAttributes?: ISessionAttributes;
|
|
11
12
|
resourceAttributes?: IResourceAttributes;
|
|
13
|
+
userAttributes?: IUserAttributes;
|
|
12
14
|
debugSessionData?: Record<string, any>;
|
|
13
15
|
tags?: { key?: string; value: string }[];
|
|
14
16
|
}
|
|
@@ -18,6 +20,13 @@ export interface StopSessionRequest {
|
|
|
18
20
|
stoppedAt: string | number;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
|
|
24
|
+
export interface CheckRemoteSessionRequest {
|
|
25
|
+
sessionAttributes?: ISessionAttributes
|
|
26
|
+
resourceAttributes?: IResourceAttributes
|
|
27
|
+
userAttributes?: IUserAttributes
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
export class ApiService {
|
|
22
31
|
private config?: ApiServiceConfig;
|
|
23
32
|
private baseUrl: string = 'https://api.multiplayer.app';
|
|
@@ -200,17 +209,17 @@ export class ApiService {
|
|
|
200
209
|
}
|
|
201
210
|
|
|
202
211
|
/**
|
|
203
|
-
|
|
204
|
-
|
|
212
|
+
* Check debug session should be started remotely
|
|
213
|
+
*/
|
|
205
214
|
async checkRemoteSession(
|
|
206
|
-
requestBody:
|
|
207
|
-
signal?: AbortSignal
|
|
215
|
+
requestBody: CheckRemoteSessionRequest,
|
|
216
|
+
signal?: AbortSignal,
|
|
208
217
|
): Promise<{ state: 'START' | 'STOP' }> {
|
|
209
218
|
return this.makeRequest(
|
|
210
219
|
'/remote-debug-session/check',
|
|
211
220
|
'POST',
|
|
212
221
|
requestBody,
|
|
213
|
-
signal
|
|
214
|
-
)
|
|
222
|
+
signal,
|
|
223
|
+
)
|
|
215
224
|
}
|
|
216
225
|
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import io, { Socket } from 'socket.io-client';
|
|
2
|
+
import { Observable } from 'lib0/observable';
|
|
3
|
+
|
|
4
|
+
import { type ISession, type IUserAttributes } from '../types';
|
|
5
|
+
import { logger } from '../utils';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
SESSION_ADD_EVENT,
|
|
9
|
+
SESSION_AUTO_CREATED,
|
|
10
|
+
SESSION_STOPPED_EVENT,
|
|
11
|
+
SESSION_SUBSCRIBE_EVENT,
|
|
12
|
+
SESSION_UNSUBSCRIBE_EVENT,
|
|
13
|
+
SOCKET_SET_USER_EVENT,
|
|
14
|
+
REMOTE_SESSION_RECORDING_START,
|
|
15
|
+
REMOTE_SESSION_RECORDING_STOP,
|
|
16
|
+
SESSION_STARTED_EVENT,
|
|
17
|
+
} from '../config';
|
|
18
|
+
|
|
19
|
+
const MAX_RECONNECTION_ATTEMPTS = 2;
|
|
20
|
+
|
|
21
|
+
export type SocketServiceEvents =
|
|
22
|
+
| typeof SESSION_STOPPED_EVENT
|
|
23
|
+
| typeof SESSION_AUTO_CREATED
|
|
24
|
+
| typeof REMOTE_SESSION_RECORDING_START
|
|
25
|
+
| typeof REMOTE_SESSION_RECORDING_STOP;
|
|
26
|
+
|
|
27
|
+
export interface SocketServiceOptions {
|
|
28
|
+
apiKey: string;
|
|
29
|
+
socketUrl: string;
|
|
30
|
+
keepAlive?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class SocketService extends Observable<SocketServiceEvents> {
|
|
34
|
+
private socket: Socket | null = null;
|
|
35
|
+
private queue: any[] = [];
|
|
36
|
+
private isConnecting: boolean = false;
|
|
37
|
+
private isConnected: boolean = false;
|
|
38
|
+
private attempts: number = 0;
|
|
39
|
+
private sessionId: string | null = null;
|
|
40
|
+
private options: SocketServiceOptions;
|
|
41
|
+
|
|
42
|
+
constructor() {
|
|
43
|
+
super();
|
|
44
|
+
this.options = {
|
|
45
|
+
apiKey: '',
|
|
46
|
+
socketUrl: '',
|
|
47
|
+
keepAlive: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Initialize the socket service
|
|
53
|
+
* @param config - Socket service configuration
|
|
54
|
+
*/
|
|
55
|
+
public init(config: SocketServiceOptions): void {
|
|
56
|
+
this.options = {
|
|
57
|
+
...this.options,
|
|
58
|
+
...config,
|
|
59
|
+
};
|
|
60
|
+
if (
|
|
61
|
+
this.options.keepAlive &&
|
|
62
|
+
this.options.socketUrl &&
|
|
63
|
+
this.options.apiKey
|
|
64
|
+
) {
|
|
65
|
+
this._initConnection();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Update the socket service configuration
|
|
71
|
+
* @param config - Partial configuration to update
|
|
72
|
+
*/
|
|
73
|
+
public updateConfigs(config: Partial<SocketServiceOptions>): void {
|
|
74
|
+
// If any config changed, reconnect if connected
|
|
75
|
+
const hasChanges = Object.keys(config).some(
|
|
76
|
+
(key) => {
|
|
77
|
+
const typedKey = key as keyof SocketServiceOptions;
|
|
78
|
+
return (
|
|
79
|
+
config[typedKey] !== undefined &&
|
|
80
|
+
config[typedKey] !== this.options[typedKey]
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (hasChanges) {
|
|
86
|
+
this.options = { ...this.options, ...config };
|
|
87
|
+
if (this.socket?.connected) {
|
|
88
|
+
this.close().then(() => {
|
|
89
|
+
if (
|
|
90
|
+
this.options.keepAlive &&
|
|
91
|
+
this.options.socketUrl &&
|
|
92
|
+
this.options.apiKey
|
|
93
|
+
) {
|
|
94
|
+
this._initConnection();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private _initConnection(): void {
|
|
102
|
+
if (this.isConnecting || this.isConnected) return;
|
|
103
|
+
this.attempts++;
|
|
104
|
+
this.isConnecting = true;
|
|
105
|
+
this.socket = io(this.options.socketUrl, {
|
|
106
|
+
path: '/v0/radar/ws',
|
|
107
|
+
auth: {
|
|
108
|
+
'x-api-key': this.options.apiKey,
|
|
109
|
+
},
|
|
110
|
+
reconnectionAttempts: 2,
|
|
111
|
+
transports: ['websocket'],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this.socket.on('ready', () => {
|
|
115
|
+
this.isConnecting = false;
|
|
116
|
+
this.isConnected = true;
|
|
117
|
+
logger.info('SocketService', 'Connected to server');
|
|
118
|
+
this.flushQueue();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
this.socket.on('disconnect', (_err: any) => {
|
|
122
|
+
this.isConnecting = false;
|
|
123
|
+
this.isConnected = false;
|
|
124
|
+
logger.info('SocketService', 'Disconnected from server');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
this.socket.on('connect_error', (err: any) => {
|
|
128
|
+
this.isConnecting = false;
|
|
129
|
+
this.isConnected = false;
|
|
130
|
+
this.checkReconnectionAttempts();
|
|
131
|
+
logger.error('SocketService', 'Error connecting to server', err);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
this.socket.on(SESSION_STOPPED_EVENT, (data: any) => {
|
|
135
|
+
this.emit(SESSION_STOPPED_EVENT, [data]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
this.socket.on(SESSION_AUTO_CREATED, (data: any) => {
|
|
139
|
+
this.emit(SESSION_AUTO_CREATED, [data]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this.socket.on(REMOTE_SESSION_RECORDING_START, (data: any) => {
|
|
143
|
+
this.emit(REMOTE_SESSION_RECORDING_START, [data]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
this.socket.on(REMOTE_SESSION_RECORDING_STOP, (data: any) => {
|
|
147
|
+
this.emit(REMOTE_SESSION_RECORDING_STOP, [data]);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private checkReconnectionAttempts(): void {
|
|
152
|
+
if (this.attempts >= MAX_RECONNECTION_ATTEMPTS) {
|
|
153
|
+
this.flushQueue();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private emitSocketEvent(name: string, data: any): void {
|
|
158
|
+
if (this.socket?.connected) {
|
|
159
|
+
this.socket.emit(name, data)
|
|
160
|
+
} else {
|
|
161
|
+
this.queue.push({ data, name })
|
|
162
|
+
this._initConnection()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private flushQueue(): void {
|
|
167
|
+
while (this.queue.length > 0 && this.socket?.connected) {
|
|
168
|
+
const event = this.queue.shift();
|
|
169
|
+
if (!event) continue;
|
|
170
|
+
|
|
171
|
+
if (this.socket?.connected) {
|
|
172
|
+
this.socket.emit(event.name, event.data);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
public send(event: any): void {
|
|
179
|
+
this.emitSocketEvent(SESSION_ADD_EVENT, event)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
public subscribeToSession(session: ISession): void {
|
|
184
|
+
this.sessionId = session.shortId || session._id;
|
|
185
|
+
const payload = {
|
|
186
|
+
projectId: session.project,
|
|
187
|
+
workspaceId: session.workspace,
|
|
188
|
+
debugSessionId: this.sessionId,
|
|
189
|
+
sessionType: session.creationType,
|
|
190
|
+
};
|
|
191
|
+
this.emitSocketEvent(SESSION_SUBSCRIBE_EVENT, payload)
|
|
192
|
+
this.emitSocketEvent(SESSION_STARTED_EVENT, { debugSessionId: this.sessionId })
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public unsubscribeFromSession(stopSession?: boolean) {
|
|
196
|
+
if (this.sessionId) {
|
|
197
|
+
this.emitSocketEvent(SESSION_UNSUBSCRIBE_EVENT, { debugSessionId: this.sessionId })
|
|
198
|
+
if (stopSession) {
|
|
199
|
+
this.emitSocketEvent(SESSION_STOPPED_EVENT, {})
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public setUser(userAttributes: IUserAttributes | undefined): void {
|
|
205
|
+
this.emitSocketEvent(SOCKET_SET_USER_EVENT, userAttributes)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public close(): Promise<void> {
|
|
209
|
+
return new Promise((resolve) => {
|
|
210
|
+
if (this.socket?.connected) {
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
this.unsubscribeFromSession();
|
|
213
|
+
this.attempts = 0;
|
|
214
|
+
this.isConnected = false;
|
|
215
|
+
this.isConnecting = false;
|
|
216
|
+
this.socket?.disconnect();
|
|
217
|
+
this.socket = null;
|
|
218
|
+
resolve();
|
|
219
|
+
}, 500);
|
|
220
|
+
} else {
|
|
221
|
+
resolve();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
package/src/session-recorder.ts
CHANGED
|
@@ -13,7 +13,13 @@ import {
|
|
|
13
13
|
type SessionRecorderConfigs,
|
|
14
14
|
type SessionRecorderOptions,
|
|
15
15
|
type EventRecorder,
|
|
16
|
+
type IUserAttributes,
|
|
16
17
|
} from './types';
|
|
18
|
+
import {
|
|
19
|
+
SESSION_STOPPED_EVENT,
|
|
20
|
+
REMOTE_SESSION_RECORDING_START,
|
|
21
|
+
REMOTE_SESSION_RECORDING_STOP
|
|
22
|
+
} from './config';
|
|
17
23
|
import { getFormattedDate, isSessionActive, getNavigatorInfo } from './utils';
|
|
18
24
|
import {
|
|
19
25
|
setShouldRecordHttpData,
|
|
@@ -28,6 +34,7 @@ import {
|
|
|
28
34
|
type StartSessionRequest,
|
|
29
35
|
type StopSessionRequest,
|
|
30
36
|
} from './services/api.service';
|
|
37
|
+
import { SocketService } from './services/socket.service';
|
|
31
38
|
|
|
32
39
|
type SessionRecorderEvents = 'state-change' | 'init';
|
|
33
40
|
|
|
@@ -36,6 +43,7 @@ class SessionRecorder
|
|
|
36
43
|
implements ISessionRecorder, EventRecorder {
|
|
37
44
|
private _configs: SessionRecorderConfigs;
|
|
38
45
|
private _apiService = new ApiService();
|
|
46
|
+
private _socketService = new SocketService();
|
|
39
47
|
private _tracer = new TracerReactNativeSDK();
|
|
40
48
|
private _recorder = new RecorderReactNativeSDK();
|
|
41
49
|
private _storageService = StorageService.getInstance();
|
|
@@ -110,6 +118,8 @@ class SessionRecorder
|
|
|
110
118
|
this._sessionAttributes = attributes;
|
|
111
119
|
}
|
|
112
120
|
|
|
121
|
+
private _userAttributes: IUserAttributes | undefined = undefined;
|
|
122
|
+
|
|
113
123
|
/**
|
|
114
124
|
* Error message getter and setter
|
|
115
125
|
*/
|
|
@@ -200,10 +210,16 @@ class SessionRecorder
|
|
|
200
210
|
);
|
|
201
211
|
|
|
202
212
|
this._tracer.init(this._configs);
|
|
203
|
-
this._recorder.init(this._configs);
|
|
204
213
|
this._apiService.init(this._configs);
|
|
214
|
+
this._socketService.init({
|
|
215
|
+
apiKey: this._configs.apiKey,
|
|
216
|
+
socketUrl: this._configs.apiBaseUrl,
|
|
217
|
+
keepAlive: this._configs.useWebsocket
|
|
218
|
+
});
|
|
219
|
+
this._recorder.init(this._configs, this._socketService);
|
|
205
220
|
await this._networkService.init();
|
|
206
221
|
this._setupNetworkCallbacks();
|
|
222
|
+
this._registerSocketServiceListeners();
|
|
207
223
|
|
|
208
224
|
if (
|
|
209
225
|
this.sessionId &&
|
|
@@ -215,6 +231,30 @@ class SessionRecorder
|
|
|
215
231
|
this.emit('init', []);
|
|
216
232
|
}
|
|
217
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Register socket service event listeners
|
|
236
|
+
*/
|
|
237
|
+
private _registerSocketServiceListeners(): void {
|
|
238
|
+
this._socketService.on(SESSION_STOPPED_EVENT, () => {
|
|
239
|
+
this._stop();
|
|
240
|
+
this._clearSession();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
this._socketService.on(REMOTE_SESSION_RECORDING_START, (payload: any) => {
|
|
244
|
+
logger.info('SessionRecorder', 'Remote session recording started', payload);
|
|
245
|
+
if (this.sessionState === SessionState.stopped) {
|
|
246
|
+
this.start();
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
this._socketService.on(REMOTE_SESSION_RECORDING_STOP, (payload: any) => {
|
|
251
|
+
logger.info('SessionRecorder', 'Remote session recording stopped', payload);
|
|
252
|
+
if (this.sessionState !== SessionState.stopped) {
|
|
253
|
+
this.stop();
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
218
258
|
/**
|
|
219
259
|
* Setup network state change callbacks
|
|
220
260
|
*/
|
|
@@ -361,13 +401,9 @@ class SessionRecorder
|
|
|
361
401
|
{
|
|
362
402
|
sessionAttributes: this.sessionAttributes,
|
|
363
403
|
resourceAttributes: getNavigatorInfo(),
|
|
404
|
+
userAttributes: this._userAttributes,
|
|
364
405
|
stoppedAt: Date.now(),
|
|
365
|
-
name: this.
|
|
366
|
-
? `${this.sessionAttributes.userName}'s session on ${getFormattedDate(
|
|
367
|
-
Date.now(),
|
|
368
|
-
{ month: 'short', day: 'numeric' }
|
|
369
|
-
)}`
|
|
370
|
-
: `Session on ${getFormattedDate(Date.now())}`,
|
|
406
|
+
name: this._getSessionName()
|
|
371
407
|
}
|
|
372
408
|
);
|
|
373
409
|
|
|
@@ -385,6 +421,15 @@ class SessionRecorder
|
|
|
385
421
|
this._sessionAttributes = attributes;
|
|
386
422
|
}
|
|
387
423
|
|
|
424
|
+
/**
|
|
425
|
+
* Set the user attributes
|
|
426
|
+
* @param userAttributes - the user attributes to set
|
|
427
|
+
*/
|
|
428
|
+
public setUserAttributes(userAttributes: IUserAttributes | undefined): void {
|
|
429
|
+
this._userAttributes = userAttributes;
|
|
430
|
+
this._socketService.setUser(this._userAttributes);
|
|
431
|
+
}
|
|
432
|
+
|
|
388
433
|
/**
|
|
389
434
|
* @description Check if session should be started/stopped automatically
|
|
390
435
|
* @param {ISession} [sessionPayload]
|
|
@@ -406,6 +451,7 @@ class SessionRecorder
|
|
|
406
451
|
...getNavigatorInfo(),
|
|
407
452
|
...(sessionPayload?.resourceAttributes || {}),
|
|
408
453
|
},
|
|
454
|
+
userAttributes: this._userAttributes,
|
|
409
455
|
};
|
|
410
456
|
|
|
411
457
|
const { state } = await this._apiService.checkRemoteSession(payload);
|
|
@@ -430,9 +476,8 @@ class SessionRecorder
|
|
|
430
476
|
const payload = {
|
|
431
477
|
sessionAttributes: this.sessionAttributes,
|
|
432
478
|
resourceAttributes: getNavigatorInfo(),
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
: `Session on ${getFormattedDate(Date.now())}`,
|
|
479
|
+
userAttributes: this._userAttributes,
|
|
480
|
+
name: this._getSessionName()
|
|
436
481
|
};
|
|
437
482
|
const request: StartSessionRequest = !this.continuousRecording
|
|
438
483
|
? payload
|
|
@@ -467,6 +512,9 @@ class SessionRecorder
|
|
|
467
512
|
if (this.sessionId) {
|
|
468
513
|
this._tracer.start(this.sessionId, this.sessionType);
|
|
469
514
|
this._recorder.start(this.sessionId, this.sessionType);
|
|
515
|
+
if (this.session) {
|
|
516
|
+
this._socketService.subscribeToSession(this.session);
|
|
517
|
+
}
|
|
470
518
|
}
|
|
471
519
|
}
|
|
472
520
|
|
|
@@ -475,6 +523,7 @@ class SessionRecorder
|
|
|
475
523
|
*/
|
|
476
524
|
private _stop(): void {
|
|
477
525
|
this.sessionState = SessionState.stopped;
|
|
526
|
+
this._socketService.unsubscribeFromSession(true);
|
|
478
527
|
this._tracer.shutdown();
|
|
479
528
|
this._recorder.stop();
|
|
480
529
|
}
|
|
@@ -506,8 +555,8 @@ class SessionRecorder
|
|
|
506
555
|
if (configureExporters && session.tempApiKey) {
|
|
507
556
|
this._configs.apiKey = session.tempApiKey;
|
|
508
557
|
this._tracer.setApiKey(session.tempApiKey);
|
|
509
|
-
this._recorder.setApiKey(session.tempApiKey);
|
|
510
558
|
this._apiService.setApiKey(session.tempApiKey);
|
|
559
|
+
this._socketService.updateConfigs({ apiKey: session.tempApiKey });
|
|
511
560
|
}
|
|
512
561
|
|
|
513
562
|
this._setSession(session);
|
|
@@ -685,6 +734,17 @@ class SessionRecorder
|
|
|
685
734
|
return { errorInfo: String(errorInfo) }
|
|
686
735
|
}
|
|
687
736
|
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Get the session name
|
|
740
|
+
* @returns the session name
|
|
741
|
+
*/
|
|
742
|
+
private _getSessionName(date: Date = new Date()): string {
|
|
743
|
+
const userName = this.sessionAttributes?.userName || this._userAttributes?.userName || this._userAttributes?.name || '';
|
|
744
|
+
return userName
|
|
745
|
+
? `${userName}'s session on ${getFormattedDate(date, { month: 'short', day: 'numeric' })}`
|
|
746
|
+
: `Session on ${getFormattedDate(date)}`;
|
|
747
|
+
}
|
|
688
748
|
}
|
|
689
749
|
|
|
690
750
|
export default new SessionRecorder();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Span } from '@opentelemetry/api';
|
|
2
2
|
import { SessionType } from '@multiplayer-app/session-recorder-common';
|
|
3
3
|
import { type PropagateTraceHeaderCorsUrls } from '@opentelemetry/sdk-trace-web';
|
|
4
|
-
import type { ISession } from './session';
|
|
4
|
+
import type { ISession, IUserAttributes } from './session';
|
|
5
5
|
|
|
6
6
|
// WidgetButtonPlacement moved to configs.ts
|
|
7
7
|
|
|
@@ -155,6 +155,13 @@ export interface SessionRecorderOptions {
|
|
|
155
155
|
level?: number;
|
|
156
156
|
enabled?: boolean;
|
|
157
157
|
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @description
|
|
161
|
+
* If true, webSocket will be used to manage remote recording sessions.
|
|
162
|
+
* @default true
|
|
163
|
+
*/
|
|
164
|
+
useWebsocket?: boolean
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
/**
|
|
@@ -332,6 +339,12 @@ export interface ISessionRecorder {
|
|
|
332
339
|
*/
|
|
333
340
|
setSessionAttributes(attributes: Record<string, any>): void;
|
|
334
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Set the user attributes
|
|
344
|
+
* @param userAttributes - the user attributes to set
|
|
345
|
+
*/
|
|
346
|
+
setUserAttributes(userAttributes: IUserAttributes | undefined): void;
|
|
347
|
+
|
|
335
348
|
/**
|
|
336
349
|
* Capture an exception and send it as an error trace
|
|
337
350
|
*/
|
package/src/types/session.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { SessionType } from '@multiplayer-app/session-recorder-common';
|
|
2
|
-
|
|
1
|
+
import { SessionType, UserType } from '@multiplayer-app/session-recorder-common';
|
|
2
|
+
|
|
3
|
+
|
|
3
4
|
|
|
4
5
|
export interface IResourceAttributes {
|
|
5
6
|
browserInfo?: string;
|
package/src/utils/logger.ts
CHANGED
|
@@ -24,7 +24,7 @@ class Logger {
|
|
|
24
24
|
['ScreenRecorder', '📸'],
|
|
25
25
|
['NativeGestureRecorder', '👆'],
|
|
26
26
|
['SessionRecorderContext', '🎯'],
|
|
27
|
-
['
|
|
27
|
+
['SocketService', '📤'],
|
|
28
28
|
['NavigationTracker', '📸'],
|
|
29
29
|
['RecorderReactNativeSDK', '📤'],
|
|
30
30
|
['DEBUGGER_LIB', '🔍'],
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import io from 'socket.io-client';
|
|
4
|
-
import { logger } from "../utils/index.js";
|
|
5
|
-
import { SESSION_ADD_EVENT, SESSION_AUTO_CREATED, SESSION_STOPPED_EVENT, SESSION_SUBSCRIBE_EVENT, SESSION_UNSUBSCRIBE_EVENT } from "../config/index.js";
|
|
6
|
-
const MAX_RECONNECTION_ATTEMPTS = 2;
|
|
7
|
-
export class EventExporter {
|
|
8
|
-
socket = null;
|
|
9
|
-
queue = [];
|
|
10
|
-
isConnecting = false;
|
|
11
|
-
isConnected = false;
|
|
12
|
-
attempts = 0;
|
|
13
|
-
sessionId = null;
|
|
14
|
-
constructor(options) {
|
|
15
|
-
this.socketUrl = options.socketUrl;
|
|
16
|
-
this.apiKey = options.apiKey;
|
|
17
|
-
}
|
|
18
|
-
init() {
|
|
19
|
-
if (this.isConnecting || this.isConnected) return;
|
|
20
|
-
this.attempts++;
|
|
21
|
-
this.isConnecting = true;
|
|
22
|
-
this.socket = io(this.socketUrl, {
|
|
23
|
-
path: '/v0/radar/ws',
|
|
24
|
-
auth: {
|
|
25
|
-
'x-api-key': this.apiKey
|
|
26
|
-
},
|
|
27
|
-
reconnectionAttempts: 2,
|
|
28
|
-
transports: ['websocket']
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// this.socket.on('connect', () => {
|
|
32
|
-
// this.isConnecting = false
|
|
33
|
-
// this.isConnected = true
|
|
34
|
-
// this.usePostMessage = false
|
|
35
|
-
// this.flushQueue()
|
|
36
|
-
// })
|
|
37
|
-
|
|
38
|
-
this.socket.on('ready', () => {
|
|
39
|
-
this.isConnecting = false;
|
|
40
|
-
this.isConnected = true;
|
|
41
|
-
logger.info('EventExporter', 'Connected to server');
|
|
42
|
-
this.flushQueue();
|
|
43
|
-
});
|
|
44
|
-
this.socket.on('disconnect', _err => {
|
|
45
|
-
this.isConnecting = false;
|
|
46
|
-
this.isConnected = false;
|
|
47
|
-
logger.info('EventExporter', 'Disconnected from server');
|
|
48
|
-
});
|
|
49
|
-
this.socket.on('connect_error', err => {
|
|
50
|
-
this.isConnecting = false;
|
|
51
|
-
this.isConnected = false;
|
|
52
|
-
this.checkReconnectionAttempts();
|
|
53
|
-
logger.error('EventExporter', 'Error connecting to server', err);
|
|
54
|
-
});
|
|
55
|
-
this.socket.on(SESSION_STOPPED_EVENT, _ => {
|
|
56
|
-
this.unsubscribeFromSession();
|
|
57
|
-
});
|
|
58
|
-
this.socket.on(SESSION_AUTO_CREATED, _ => {});
|
|
59
|
-
}
|
|
60
|
-
setApiKey(apiKey) {
|
|
61
|
-
this.apiKey = apiKey;
|
|
62
|
-
}
|
|
63
|
-
setSocketUrl(socketUrl) {
|
|
64
|
-
this.socketUrl = socketUrl;
|
|
65
|
-
}
|
|
66
|
-
checkReconnectionAttempts() {
|
|
67
|
-
if (this.attempts >= MAX_RECONNECTION_ATTEMPTS) {
|
|
68
|
-
this.flushQueue();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
flushQueue() {
|
|
72
|
-
while (this.queue.length > 0 && this.socket?.connected) {
|
|
73
|
-
const event = this.queue.shift();
|
|
74
|
-
if (!event) continue;
|
|
75
|
-
if (this.socket?.connected) {
|
|
76
|
-
this.socket.emit(event.name, event.data);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
unsubscribeFromSession() {
|
|
81
|
-
const payload = {
|
|
82
|
-
debugSessionId: this.sessionId
|
|
83
|
-
};
|
|
84
|
-
if (this.socket?.connected) {
|
|
85
|
-
this.socket.emit(SESSION_UNSUBSCRIBE_EVENT, payload);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
send(event) {
|
|
89
|
-
if (this.socket?.connected) {
|
|
90
|
-
this.socket.emit(SESSION_ADD_EVENT, event);
|
|
91
|
-
} else {
|
|
92
|
-
this.queue.push({
|
|
93
|
-
data: event,
|
|
94
|
-
name: SESSION_ADD_EVENT
|
|
95
|
-
});
|
|
96
|
-
this.init();
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
subscribeToSession(session) {
|
|
100
|
-
this.sessionId = session.shortId || session._id;
|
|
101
|
-
const payload = {
|
|
102
|
-
projectId: session.project,
|
|
103
|
-
workspaceId: session.workspace,
|
|
104
|
-
debugSessionId: this.sessionId,
|
|
105
|
-
sessionType: session.creationType
|
|
106
|
-
};
|
|
107
|
-
if (this.socket?.connected) {
|
|
108
|
-
this.socket.emit(SESSION_SUBSCRIBE_EVENT, payload);
|
|
109
|
-
} else {
|
|
110
|
-
this.queue.push({
|
|
111
|
-
data: payload,
|
|
112
|
-
name: SESSION_SUBSCRIBE_EVENT
|
|
113
|
-
});
|
|
114
|
-
this.init();
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
close() {
|
|
118
|
-
if (this.socket?.connected) {
|
|
119
|
-
setTimeout(() => {
|
|
120
|
-
this.unsubscribeFromSession();
|
|
121
|
-
this.attempts = 0;
|
|
122
|
-
this.isConnected = false;
|
|
123
|
-
this.isConnecting = false;
|
|
124
|
-
this.socket?.disconnect();
|
|
125
|
-
this.socket = null;
|
|
126
|
-
}, 500);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
//# sourceMappingURL=eventExporter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["io","logger","SESSION_ADD_EVENT","SESSION_AUTO_CREATED","SESSION_STOPPED_EVENT","SESSION_SUBSCRIBE_EVENT","SESSION_UNSUBSCRIBE_EVENT","MAX_RECONNECTION_ATTEMPTS","EventExporter","socket","queue","isConnecting","isConnected","attempts","sessionId","constructor","options","socketUrl","apiKey","init","path","auth","reconnectionAttempts","transports","on","info","flushQueue","_err","err","checkReconnectionAttempts","error","_","unsubscribeFromSession","setApiKey","setSocketUrl","length","connected","event","shift","emit","name","data","payload","debugSessionId","send","push","subscribeToSession","session","shortId","_id","projectId","project","workspaceId","workspace","sessionType","creationType","close","setTimeout","disconnect"],"sourceRoot":"../../../src","sources":["recorder/eventExporter.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["UserType"],"sourceRoot":"../../../src","sources":["types/client-type.enum.ts"],"mappings":";;AAAA,WAAYA,QAAQ,0BAARA,QAAQ;EAARA,QAAQ;EAARA,QAAQ;EAARA,QAAQ;EAAA,OAARA,QAAQ;AAAA","ignoreList":[]}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { type ISession } from '../types';
|
|
2
|
-
export declare class EventExporter {
|
|
3
|
-
private socket;
|
|
4
|
-
private queue;
|
|
5
|
-
private isConnecting;
|
|
6
|
-
private isConnected;
|
|
7
|
-
private attempts;
|
|
8
|
-
private sessionId;
|
|
9
|
-
private socketUrl;
|
|
10
|
-
private apiKey;
|
|
11
|
-
constructor(options: {
|
|
12
|
-
socketUrl: string;
|
|
13
|
-
apiKey: string;
|
|
14
|
-
});
|
|
15
|
-
private init;
|
|
16
|
-
setApiKey(apiKey: string): void;
|
|
17
|
-
setSocketUrl(socketUrl: string): void;
|
|
18
|
-
private checkReconnectionAttempts;
|
|
19
|
-
private flushQueue;
|
|
20
|
-
private unsubscribeFromSession;
|
|
21
|
-
send(event: any): void;
|
|
22
|
-
subscribeToSession(session: ISession): void;
|
|
23
|
-
close(): void;
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=eventExporter.d.ts.map
|