@multiplayer-app/session-recorder-browser 2.0.85 → 2.0.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/constants.d.ts +2 -1
- package/dist/config/constants.js +3 -1
- package/dist/config/defaults.js +9 -0
- package/dist/config/session-recorder.js +10 -1
- package/dist/index.js +167 -57
- package/dist/index.umd.js +167 -57
- package/dist/session-recorder.js +83 -38
- package/dist/sessionWidget/UIManager.js +7 -4
- package/dist/sessionWidget/buttonStateConfigs.d.ts +15 -0
- package/dist/sessionWidget/buttonStateConfigs.js +40 -0
- package/dist/sessionWidget/index.js +17 -18
- package/dist/types/session-recorder.d.ts +18 -0
- package/package.json +6 -4
package/dist/session-recorder.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Observable } from './observable';
|
|
2
|
-
import { SessionType } from '@multiplayer-app/session-recorder-common';
|
|
2
|
+
import { SessionType, } from '@multiplayer-app/session-recorder-common';
|
|
3
3
|
import { TracerBrowserSDK } from './otel';
|
|
4
4
|
import { RecorderBrowserSDK } from './rrweb';
|
|
5
5
|
import { getStoredItem, setStoredItem, getNavigatorInfo, getFormattedDate, getTimeDifferenceInSeconds, isSessionActive, getOrCreateTabId, } from './utils';
|
|
6
|
-
import { SessionState } from './types';
|
|
7
|
-
import { BASE_CONFIG, SESSION_PROP_NAME, SESSION_ID_PROP_NAME, SESSION_TYPE_PROP_NAME, SESSION_STATE_PROP_NAME, DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE, getSessionRecorderConfig, SESSION_AUTO_CREATED, SESSION_STOPPED_EVENT, REMOTE_SESSION_RECORDING_START, REMOTE_SESSION_RECORDING_STOP, SESSION_SAVE_BUFFER_EVENT, SESSION_READY_EVENT, } from './config';
|
|
8
|
-
import { setShouldRecordHttpData, setMaxCapturingHttpPayloadSize } from './patch';
|
|
6
|
+
import { SessionState, } from './types';
|
|
7
|
+
import { BASE_CONFIG, SESSION_PROP_NAME, SESSION_ID_PROP_NAME, SESSION_TYPE_PROP_NAME, SESSION_STATE_PROP_NAME, DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE, getSessionRecorderConfig, SESSION_AUTO_CREATED, SESSION_STOPPED_EVENT, REMOTE_SESSION_RECORDING_START, REMOTE_SESSION_RECORDING_STOP, SESSION_SAVE_BUFFER_EVENT, SESSION_READY_EVENT_LEGACY, SESSION_READY_EVENT, } from './config';
|
|
8
|
+
import { setShouldRecordHttpData, setMaxCapturingHttpPayloadSize, } from './patch';
|
|
9
9
|
import { recorderEventBus } from './eventBus';
|
|
10
10
|
import { SessionWidget } from './sessionWidget';
|
|
11
11
|
import messagingService from './services/messaging.service';
|
|
12
|
-
import { ApiService } from './services/api.service';
|
|
12
|
+
import { ApiService, } from './services/api.service';
|
|
13
13
|
import { SocketService } from './services/socket.service';
|
|
14
14
|
import { IndexedDBService } from './services/indexedDb.service';
|
|
15
15
|
import { CrashBufferService } from './services/crashBuffer.service';
|
|
@@ -51,7 +51,10 @@ export class SessionRecorder extends Observable {
|
|
|
51
51
|
messagingService.sendMessage('state-change', this._sessionState);
|
|
52
52
|
setStoredItem(SESSION_STATE_PROP_NAME, state);
|
|
53
53
|
// Emit observable event to support React wrapper
|
|
54
|
-
this.emit('state-change', [
|
|
54
|
+
this.emit('state-change', [
|
|
55
|
+
this._sessionState || SessionState.stopped,
|
|
56
|
+
this.sessionType,
|
|
57
|
+
]);
|
|
55
58
|
}
|
|
56
59
|
get session() {
|
|
57
60
|
return this._session;
|
|
@@ -122,10 +125,18 @@ export class SessionRecorder extends Observable {
|
|
|
122
125
|
this._error = '';
|
|
123
126
|
// Safety: avoid accessing storage in SSR/non-browser environments
|
|
124
127
|
const isBrowser = typeof window !== 'undefined';
|
|
125
|
-
const sessionLocal = isBrowser
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
128
|
+
const sessionLocal = isBrowser
|
|
129
|
+
? getStoredItem(SESSION_PROP_NAME, true)
|
|
130
|
+
: null;
|
|
131
|
+
const sessionIdLocal = isBrowser
|
|
132
|
+
? getStoredItem(SESSION_ID_PROP_NAME)
|
|
133
|
+
: null;
|
|
134
|
+
const sessionStateLocal = isBrowser
|
|
135
|
+
? getStoredItem(SESSION_STATE_PROP_NAME)
|
|
136
|
+
: null;
|
|
137
|
+
const sessionTypeLocal = isBrowser
|
|
138
|
+
? getStoredItem(SESSION_TYPE_PROP_NAME)
|
|
139
|
+
: null;
|
|
129
140
|
if (isSessionActive(sessionLocal, sessionTypeLocal)) {
|
|
130
141
|
this.session = sessionLocal;
|
|
131
142
|
this.sessionId = sessionIdLocal;
|
|
@@ -157,7 +168,8 @@ export class SessionRecorder extends Observable {
|
|
|
157
168
|
// GC: remove orphaned crash buffers from old tabs.
|
|
158
169
|
// Keep TTL large to avoid any accidental data loss.
|
|
159
170
|
void this._bufferDb.sweepStaleTabs(10 * 60 * 60 * 1000);
|
|
160
|
-
setMaxCapturingHttpPayloadSize(this._configs.maxCapturingHttpPayloadSize ||
|
|
171
|
+
setMaxCapturingHttpPayloadSize(this._configs.maxCapturingHttpPayloadSize ||
|
|
172
|
+
DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE);
|
|
161
173
|
setShouldRecordHttpData(this._configs.captureBody, this._configs.captureHeaders);
|
|
162
174
|
this._setupCrashBuffer();
|
|
163
175
|
this._tracer.init(this._configs);
|
|
@@ -179,7 +191,9 @@ export class SessionRecorder extends Observable {
|
|
|
179
191
|
if (this._configs.apiKey) {
|
|
180
192
|
this._recorder.init(this._configs, this._socketService);
|
|
181
193
|
}
|
|
182
|
-
if (this.sessionId &&
|
|
194
|
+
if (this.sessionId &&
|
|
195
|
+
(this.sessionState === SessionState.started ||
|
|
196
|
+
this.sessionState === SessionState.paused)) {
|
|
183
197
|
this._start();
|
|
184
198
|
}
|
|
185
199
|
else {
|
|
@@ -233,7 +247,8 @@ export class SessionRecorder extends Observable {
|
|
|
233
247
|
return;
|
|
234
248
|
let hasFocus = true;
|
|
235
249
|
try {
|
|
236
|
-
hasFocus =
|
|
250
|
+
hasFocus =
|
|
251
|
+
typeof document.hasFocus === 'function' ? document.hasFocus() : true;
|
|
237
252
|
}
|
|
238
253
|
catch (_e) {
|
|
239
254
|
hasFocus = true;
|
|
@@ -255,7 +270,9 @@ export class SessionRecorder extends Observable {
|
|
|
255
270
|
// stop, leaving the buffer with no FullSnapshot and silently breaking
|
|
256
271
|
// exception-triggered flushBuffer. `_recorder.restart(null, ...)` passes
|
|
257
272
|
// null explicitly, so it's safe regardless of `this.sessionId`.
|
|
258
|
-
if (!this._crashBuffer ||
|
|
273
|
+
if (!this._crashBuffer ||
|
|
274
|
+
!((_b = (_a = this._configs) === null || _a === void 0 ? void 0 : _a.buffering) === null || _b === void 0 ? void 0 : _b.enabled) ||
|
|
275
|
+
this.sessionState !== SessionState.stopped) {
|
|
259
276
|
return;
|
|
260
277
|
}
|
|
261
278
|
void this._recorder.restart(null, SessionType.MANUAL);
|
|
@@ -306,7 +323,8 @@ export class SessionRecorder extends Observable {
|
|
|
306
323
|
start(type = SessionType.MANUAL, session) {
|
|
307
324
|
this._checkOperation('start');
|
|
308
325
|
// If continuous recording is disabled, force plain mode
|
|
309
|
-
if (type === SessionType.CONTINUOUS &&
|
|
326
|
+
if (type === SessionType.CONTINUOUS &&
|
|
327
|
+
!this._configs.showContinuousRecording) {
|
|
310
328
|
type = SessionType.MANUAL;
|
|
311
329
|
}
|
|
312
330
|
this.sessionType = type;
|
|
@@ -337,7 +355,8 @@ export class SessionRecorder extends Observable {
|
|
|
337
355
|
stoppedAt: this._recorder.stoppedAt,
|
|
338
356
|
};
|
|
339
357
|
const response = await this._apiService.stopSession(sid, request);
|
|
340
|
-
recorderEventBus.emit(
|
|
358
|
+
recorderEventBus.emit(SESSION_READY_EVENT_LEGACY, response._id);
|
|
359
|
+
recorderEventBus.emit(SESSION_READY_EVENT, response);
|
|
341
360
|
}
|
|
342
361
|
}
|
|
343
362
|
catch (error) {
|
|
@@ -433,8 +452,8 @@ export class SessionRecorder extends Observable {
|
|
|
433
452
|
this.error = (e === null || e === void 0 ? void 0 : e.message) || 'Failed to capture exception';
|
|
434
453
|
}
|
|
435
454
|
}
|
|
436
|
-
async _flushBuffer(
|
|
437
|
-
if (!
|
|
455
|
+
async _flushBuffer(session, force = false) {
|
|
456
|
+
if (!session || !this._crashBuffer || this._isFlushingBuffer) {
|
|
438
457
|
return null;
|
|
439
458
|
}
|
|
440
459
|
this._isFlushingBuffer = true;
|
|
@@ -445,8 +464,10 @@ export class SessionRecorder extends Observable {
|
|
|
445
464
|
}
|
|
446
465
|
await Promise.all([
|
|
447
466
|
this._tracer.exportTraces(spans.map((s) => s.span)),
|
|
448
|
-
this._apiService.exportEvents(
|
|
449
|
-
|
|
467
|
+
this._apiService.exportEvents(session._id, {
|
|
468
|
+
events: events.map((e) => e.event),
|
|
469
|
+
}),
|
|
470
|
+
this._apiService.updateSessionAttributes(session._id, {
|
|
450
471
|
startedAt: this._toCrashBufferSessionIso(startedAt),
|
|
451
472
|
stoppedAt: this._toCrashBufferSessionIso(stoppedAt),
|
|
452
473
|
sessionAttributes: this.sessionAttributes,
|
|
@@ -461,7 +482,8 @@ export class SessionRecorder extends Observable {
|
|
|
461
482
|
finally {
|
|
462
483
|
await this._crashBuffer.clear();
|
|
463
484
|
this._isFlushingBuffer = false;
|
|
464
|
-
recorderEventBus.emit(
|
|
485
|
+
recorderEventBus.emit(SESSION_READY_EVENT_LEGACY, session._id);
|
|
486
|
+
recorderEventBus.emit(SESSION_READY_EVENT, session);
|
|
465
487
|
}
|
|
466
488
|
}
|
|
467
489
|
_toCrashBufferSessionIso(ts) {
|
|
@@ -550,7 +572,8 @@ export class SessionRecorder extends Observable {
|
|
|
550
572
|
* Handle the safe stop event
|
|
551
573
|
*/
|
|
552
574
|
_handleStop(comment) {
|
|
553
|
-
if (this.sessionState === SessionState.started ||
|
|
575
|
+
if (this.sessionState === SessionState.started ||
|
|
576
|
+
this.sessionState === SessionState.paused) {
|
|
554
577
|
this.stop(comment);
|
|
555
578
|
}
|
|
556
579
|
}
|
|
@@ -574,7 +597,8 @@ export class SessionRecorder extends Observable {
|
|
|
574
597
|
* Handle the safe cancel event
|
|
575
598
|
*/
|
|
576
599
|
_handleCancel() {
|
|
577
|
-
if (this.sessionState === SessionState.started ||
|
|
600
|
+
if (this.sessionState === SessionState.started ||
|
|
601
|
+
this.sessionState === SessionState.paused) {
|
|
578
602
|
this.cancel();
|
|
579
603
|
}
|
|
580
604
|
}
|
|
@@ -582,7 +606,8 @@ export class SessionRecorder extends Observable {
|
|
|
582
606
|
* Handle the safe save event
|
|
583
607
|
*/
|
|
584
608
|
_handleSave() {
|
|
585
|
-
if (this.sessionState === SessionState.started &&
|
|
609
|
+
if (this.sessionState === SessionState.started &&
|
|
610
|
+
this.continuousRecording) {
|
|
586
611
|
this.save();
|
|
587
612
|
}
|
|
588
613
|
}
|
|
@@ -627,12 +652,12 @@ export class SessionRecorder extends Observable {
|
|
|
627
652
|
}
|
|
628
653
|
});
|
|
629
654
|
this._socketService.on(SESSION_SAVE_BUFFER_EVENT, (payload) => {
|
|
630
|
-
var _a, _b
|
|
655
|
+
var _a, _b;
|
|
631
656
|
if (this.sessionState !== SessionState.stopped)
|
|
632
657
|
return;
|
|
633
|
-
this._flushBuffer(
|
|
634
|
-
if ((
|
|
635
|
-
recorderEventBus.emit(SESSION_AUTO_CREATED, (
|
|
658
|
+
this._flushBuffer(payload === null || payload === void 0 ? void 0 : payload.debugSession, true);
|
|
659
|
+
if ((_a = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _a === void 0 ? void 0 : _a.url) {
|
|
660
|
+
recorderEventBus.emit(SESSION_AUTO_CREATED, (_b = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _b === void 0 ? void 0 : _b.url);
|
|
636
661
|
}
|
|
637
662
|
});
|
|
638
663
|
}
|
|
@@ -640,7 +665,7 @@ export class SessionRecorder extends Observable {
|
|
|
640
665
|
try {
|
|
641
666
|
const session = await this._apiService.createErrorSession({ span });
|
|
642
667
|
if (session === null || session === void 0 ? void 0 : session._id) {
|
|
643
|
-
this._flushBuffer(session
|
|
668
|
+
this._flushBuffer(session);
|
|
644
669
|
}
|
|
645
670
|
if (session === null || session === void 0 ? void 0 : session.url) {
|
|
646
671
|
recorderEventBus.emit(SESSION_AUTO_CREATED, session.url);
|
|
@@ -660,14 +685,20 @@ export class SessionRecorder extends Observable {
|
|
|
660
685
|
sessionAttributes: this.sessionAttributes,
|
|
661
686
|
resourceAttributes: getNavigatorInfo(),
|
|
662
687
|
name: this._getSessionName(),
|
|
663
|
-
...(this._userAttributes
|
|
688
|
+
...(this._userAttributes
|
|
689
|
+
? { userAttributes: this._userAttributes }
|
|
690
|
+
: {}),
|
|
664
691
|
};
|
|
665
|
-
const request = !this.continuousRecording
|
|
692
|
+
const request = !this.continuousRecording
|
|
693
|
+
? payload
|
|
694
|
+
: { debugSessionData: payload };
|
|
666
695
|
const session = this.continuousRecording
|
|
667
696
|
? await this._apiService.startContinuousDebugSession(request, signal)
|
|
668
697
|
: await this._apiService.startSession(request, signal);
|
|
669
698
|
if (session) {
|
|
670
|
-
session.sessionType = this.continuousRecording
|
|
699
|
+
session.sessionType = this.continuousRecording
|
|
700
|
+
? SessionType.CONTINUOUS
|
|
701
|
+
: SessionType.MANUAL;
|
|
671
702
|
this._setupSessionAndStart(session, false);
|
|
672
703
|
}
|
|
673
704
|
}
|
|
@@ -690,7 +721,10 @@ export class SessionRecorder extends Observable {
|
|
|
690
721
|
this._tracer.start(this.sessionId, this.sessionType);
|
|
691
722
|
// Ensure we switch from buffer-only recording to session recording cleanly.
|
|
692
723
|
void this._recorder.restart(this.sessionId, this.sessionType);
|
|
693
|
-
this._navigationRecorder.start({
|
|
724
|
+
this._navigationRecorder.start({
|
|
725
|
+
sessionId: this.sessionId,
|
|
726
|
+
sessionType: this.sessionType,
|
|
727
|
+
});
|
|
694
728
|
if (this.session) {
|
|
695
729
|
this._socketService.subscribeToSession(this.session);
|
|
696
730
|
this._sessionWidget.seconds = getTimeDifferenceInSeconds((_a = this.session) === null || _a === void 0 ? void 0 : _a.startedAt);
|
|
@@ -718,8 +752,12 @@ export class SessionRecorder extends Observable {
|
|
|
718
752
|
// rrweb assigns new node IDs on each record() call, so the next buffer
|
|
719
753
|
// segment must not carry events from the previous generation. Await the
|
|
720
754
|
// clear so its IDB tx can't race past the fresh FullSnapshot.
|
|
721
|
-
const cleared = this._crashBuffer
|
|
722
|
-
|
|
755
|
+
const cleared = this._crashBuffer
|
|
756
|
+
? this._crashBuffer.clear()
|
|
757
|
+
: Promise.resolve();
|
|
758
|
+
void cleared
|
|
759
|
+
.catch(() => undefined)
|
|
760
|
+
.then(() => this._startBufferOnlyRecording());
|
|
723
761
|
}
|
|
724
762
|
/**
|
|
725
763
|
* Pause the session tracing and recording
|
|
@@ -755,7 +793,10 @@ export class SessionRecorder extends Observable {
|
|
|
755
793
|
* @param sessionId - the session ID to set or clear
|
|
756
794
|
*/
|
|
757
795
|
_setSession(session) {
|
|
758
|
-
this.session = {
|
|
796
|
+
this.session = {
|
|
797
|
+
...session,
|
|
798
|
+
startedAt: session.startedAt || new Date().toISOString(),
|
|
799
|
+
};
|
|
759
800
|
this.sessionId = (session === null || session === void 0 ? void 0 : session.shortId) || (session === null || session === void 0 ? void 0 : session._id);
|
|
760
801
|
}
|
|
761
802
|
_clearSession() {
|
|
@@ -778,7 +819,8 @@ export class SessionRecorder extends Observable {
|
|
|
778
819
|
}
|
|
779
820
|
break;
|
|
780
821
|
case 'stop':
|
|
781
|
-
if (this.sessionState !== SessionState.paused &&
|
|
822
|
+
if (this.sessionState !== SessionState.paused &&
|
|
823
|
+
this.sessionState !== SessionState.started) {
|
|
782
824
|
throw new Error('Cannot stop. Session is not currently started.');
|
|
783
825
|
}
|
|
784
826
|
break;
|
|
@@ -856,7 +898,10 @@ export class SessionRecorder extends Observable {
|
|
|
856
898
|
*/
|
|
857
899
|
_getSessionName(date = new Date()) {
|
|
858
900
|
var _a, _b, _c;
|
|
859
|
-
const userName = ((_a = this.sessionAttributes) === null || _a === void 0 ? void 0 : _a.userName) ||
|
|
901
|
+
const userName = ((_a = this.sessionAttributes) === null || _a === void 0 ? void 0 : _a.userName) ||
|
|
902
|
+
((_b = this._userAttributes) === null || _b === void 0 ? void 0 : _b.userName) ||
|
|
903
|
+
((_c = this._userAttributes) === null || _c === void 0 ? void 0 : _c.name) ||
|
|
904
|
+
'';
|
|
860
905
|
return userName
|
|
861
906
|
? `${userName}'s session on ${getFormattedDate(date, { month: 'short', day: 'numeric' })}`
|
|
862
907
|
: `Session on ${getFormattedDate(date)}`;
|
|
@@ -34,8 +34,9 @@ export class UIManager {
|
|
|
34
34
|
* tooltip, and inner HTML content (Record icon)
|
|
35
35
|
*/
|
|
36
36
|
setRecorderButtonProps() {
|
|
37
|
+
var _a;
|
|
37
38
|
this.recorderButton.className = 'mp-session-debugger-button';
|
|
38
|
-
this.recorderButton.dataset.tooltip = 'Record an issue';
|
|
39
|
+
this.recorderButton.dataset.tooltip = (_a = this.widgetTextOverrides.buttonTooltipIdle) !== null && _a !== void 0 ? _a : 'Record an issue';
|
|
39
40
|
insertTrustedHTML(this.recorderButton, `${RecordIcon}`);
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
@@ -60,8 +61,7 @@ export class UIManager {
|
|
|
60
61
|
* The popover includes a logo, heading, and start recording button.
|
|
61
62
|
*/
|
|
62
63
|
setInitialPopoverProps() {
|
|
63
|
-
this.initialPopover.className =
|
|
64
|
-
'mp-session-debugger-popover mp-initial-popover hidden';
|
|
64
|
+
this.initialPopover.className = 'mp-session-debugger-popover mp-initial-popover hidden';
|
|
65
65
|
insertTrustedHTML(this.initialPopover, initialPopoverTemplate(this.widgetTextOverrides, this.showContinuousRecording));
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
@@ -81,12 +81,15 @@ export class UIManager {
|
|
|
81
81
|
* @param isLoading - Whether the popover button should show a loading state.
|
|
82
82
|
*/
|
|
83
83
|
setPopoverLoadingState(isLoading) {
|
|
84
|
+
var _a, _b;
|
|
84
85
|
const button = this.initialPopover.querySelector('.mp-session-debugger-popover-button');
|
|
85
86
|
if (!button) {
|
|
86
87
|
return;
|
|
87
88
|
}
|
|
88
89
|
button.classList.toggle('disabled', isLoading);
|
|
89
|
-
button.textContent = isLoading
|
|
90
|
+
button.textContent = isLoading
|
|
91
|
+
? ((_a = this.widgetTextOverrides.buttonTooltipLoading) !== null && _a !== void 0 ? _a : 'Starting to record...')
|
|
92
|
+
: ((_b = this.widgetTextOverrides.startRecordingButtonText) !== null && _b !== void 0 ? _b : 'Start recording');
|
|
90
93
|
}
|
|
91
94
|
setTimerValue(time) {
|
|
92
95
|
const timerElement = this.recordingOverlay.querySelector('.timer');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WidgetTextOverridesConfig } from '../types';
|
|
1
2
|
/**
|
|
2
3
|
* ButtonState defines the possible states of the recorder button.
|
|
3
4
|
* It includes IDLE, RECORDING, CANCEL, SENT and LOADING states.
|
|
@@ -16,6 +17,17 @@ export declare enum ContinuousRecordingSaveButtonState {
|
|
|
16
17
|
SAVED = "SAVED",
|
|
17
18
|
ERROR = "ERROR"
|
|
18
19
|
}
|
|
20
|
+
type ButtonStateConfig = {
|
|
21
|
+
icon: string;
|
|
22
|
+
tooltip: string;
|
|
23
|
+
classes: string[];
|
|
24
|
+
excludeClasses: string[];
|
|
25
|
+
};
|
|
26
|
+
type ContinuousRecordingSaveButtonConfig = {
|
|
27
|
+
textContent: string;
|
|
28
|
+
disabled: boolean;
|
|
29
|
+
classes: string[];
|
|
30
|
+
};
|
|
19
31
|
/**
|
|
20
32
|
* buttonStates object provides properties for each button state:
|
|
21
33
|
* IDLE, RECORDING, CANCEL, and SENT.
|
|
@@ -80,4 +92,7 @@ export declare const continuousRecordingSaveButtonStates: {
|
|
|
80
92
|
classes: never[];
|
|
81
93
|
};
|
|
82
94
|
};
|
|
95
|
+
export declare function getButtonStates(overrides: WidgetTextOverridesConfig): Record<ButtonState, ButtonStateConfig>;
|
|
96
|
+
export declare function getContinuousRecordingSaveButtonStates(overrides: WidgetTextOverridesConfig): Record<ContinuousRecordingSaveButtonState, ContinuousRecordingSaveButtonConfig>;
|
|
97
|
+
export {};
|
|
83
98
|
//# sourceMappingURL=buttonStateConfigs.d.ts.map
|
|
@@ -83,4 +83,44 @@ export const continuousRecordingSaveButtonStates = {
|
|
|
83
83
|
classes: [],
|
|
84
84
|
},
|
|
85
85
|
};
|
|
86
|
+
const buttonTooltipOverrideKeys = {
|
|
87
|
+
[ButtonState.IDLE]: 'buttonTooltipIdle',
|
|
88
|
+
[ButtonState.RECORDING]: 'buttonTooltipRecording',
|
|
89
|
+
[ButtonState.CANCEL]: 'buttonTooltipCancel',
|
|
90
|
+
[ButtonState.SENT]: 'buttonTooltipSent',
|
|
91
|
+
[ButtonState.LOADING]: 'buttonTooltipLoading',
|
|
92
|
+
[ButtonState.CONTINUOUS_DEBUGGING]: 'buttonTooltipContinuousDebugging',
|
|
93
|
+
};
|
|
94
|
+
const continuousSaveTextOverrideKeys = {
|
|
95
|
+
[ContinuousRecordingSaveButtonState.IDLE]: 'saveLastSnapshotButtonText',
|
|
96
|
+
[ContinuousRecordingSaveButtonState.SAVING]: 'saveContinuousRecordingSavingText',
|
|
97
|
+
[ContinuousRecordingSaveButtonState.SAVED]: 'saveContinuousRecordingSavedText',
|
|
98
|
+
[ContinuousRecordingSaveButtonState.ERROR]: 'saveContinuousRecordingErrorText',
|
|
99
|
+
};
|
|
100
|
+
export function getButtonStates(overrides) {
|
|
101
|
+
var _a;
|
|
102
|
+
const resolved = { ...buttonStates };
|
|
103
|
+
for (const state of Object.keys(buttonStates)) {
|
|
104
|
+
const overrideKey = buttonTooltipOverrideKeys[state];
|
|
105
|
+
resolved[state] = {
|
|
106
|
+
...buttonStates[state],
|
|
107
|
+
tooltip: (_a = overrides[overrideKey]) !== null && _a !== void 0 ? _a : buttonStates[state].tooltip,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
export function getContinuousRecordingSaveButtonStates(overrides) {
|
|
113
|
+
var _a;
|
|
114
|
+
const resolved = {
|
|
115
|
+
...continuousRecordingSaveButtonStates,
|
|
116
|
+
};
|
|
117
|
+
for (const state of Object.keys(continuousRecordingSaveButtonStates)) {
|
|
118
|
+
const overrideKey = continuousSaveTextOverrideKeys[state];
|
|
119
|
+
resolved[state] = {
|
|
120
|
+
...continuousRecordingSaveButtonStates[state],
|
|
121
|
+
textContent: (_a = overrides[overrideKey]) !== null && _a !== void 0 ? _a : continuousRecordingSaveButtonStates[state].textContent,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return resolved;
|
|
125
|
+
}
|
|
86
126
|
//# sourceMappingURL=buttonStateConfigs.js.map
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Observable } from '../observable';
|
|
2
2
|
import { insertTrustedHTML, injectStylesIntoShadowRoot, formatTimeForSessionTimer } from '../utils';
|
|
3
3
|
import { SessionState } from '../types';
|
|
4
|
-
import { POPOVER_WIDTH, NON_DRAGGABLE_OFFSET, POPOVER_DISTANCE_FROM_BUTTON
|
|
4
|
+
import { POPOVER_WIDTH, NON_DRAGGABLE_OFFSET, POPOVER_DISTANCE_FROM_BUTTON } from './constants';
|
|
5
5
|
import { DEFAULT_WIDGET_TEXT_CONFIG } from '../config';
|
|
6
6
|
import { isBrowser, isBrowserExtension } from '../global';
|
|
7
7
|
import { UIManager } from './UIManager';
|
|
8
8
|
import { DragManager } from './dragManager';
|
|
9
|
-
import { ButtonState,
|
|
9
|
+
import { ButtonState, getButtonStates, getContinuousRecordingSaveButtonStates, } from './buttonStateConfigs';
|
|
10
10
|
// Import styles as string for shadow DOM injection
|
|
11
11
|
import widgetStyles from './styles/index.scss?raw';
|
|
12
12
|
import './styles/button.scss';
|
|
@@ -16,7 +16,7 @@ export class SessionWidget extends Observable {
|
|
|
16
16
|
this._buttonState = newState;
|
|
17
17
|
if (!this.isBrowser)
|
|
18
18
|
return;
|
|
19
|
-
const { icon, tooltip, classes, excludeClasses } =
|
|
19
|
+
const { icon, tooltip, classes, excludeClasses } = getButtonStates(this._widgetTextOverrides)[newState];
|
|
20
20
|
if (newState === ButtonState.CANCEL) {
|
|
21
21
|
(_a = this.buttonDraggabilityObserver) === null || _a === void 0 ? void 0 : _a.observe(this.recorderButton, {
|
|
22
22
|
attributes: true,
|
|
@@ -54,7 +54,8 @@ export class SessionWidget extends Observable {
|
|
|
54
54
|
type: 'error',
|
|
55
55
|
message: this._error,
|
|
56
56
|
button: {
|
|
57
|
-
text: 'Close',
|
|
57
|
+
text: 'Close',
|
|
58
|
+
onClick: () => this.hideToast(),
|
|
58
59
|
},
|
|
59
60
|
});
|
|
60
61
|
}
|
|
@@ -201,7 +202,7 @@ export class SessionWidget extends Observable {
|
|
|
201
202
|
return;
|
|
202
203
|
const saveButton = this.initialPopover.querySelector('#mp-save-continuous-debug-session');
|
|
203
204
|
if (saveButton) {
|
|
204
|
-
const { textContent, disabled } =
|
|
205
|
+
const { textContent, disabled } = getContinuousRecordingSaveButtonStates(this._widgetTextOverrides)[state];
|
|
205
206
|
saveButton.disabled = disabled;
|
|
206
207
|
saveButton.textContent = textContent;
|
|
207
208
|
}
|
|
@@ -229,14 +230,11 @@ export class SessionWidget extends Observable {
|
|
|
229
230
|
return;
|
|
230
231
|
this.buttonDraggabilityObserver = new MutationObserver((mutationsList) => {
|
|
231
232
|
for (const mutation of mutationsList) {
|
|
232
|
-
if (mutation.type === 'attributes' &&
|
|
233
|
-
mutation.attributeName === 'class') {
|
|
233
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
|
234
234
|
const oldClassName = mutation.oldValue;
|
|
235
235
|
const newClassName = mutation.target['className'];
|
|
236
|
-
if (((oldClassName === null || oldClassName === void 0 ? void 0 : oldClassName.includes('no-draggable')) &&
|
|
237
|
-
|
|
238
|
-
((newClassName === null || newClassName === void 0 ? void 0 : newClassName.includes('no-draggable')) &&
|
|
239
|
-
!(oldClassName === null || oldClassName === void 0 ? void 0 : oldClassName.includes('no-draggable')))) {
|
|
236
|
+
if (((oldClassName === null || oldClassName === void 0 ? void 0 : oldClassName.includes('no-draggable')) && !newClassName.includes('no-draggable')) ||
|
|
237
|
+
((newClassName === null || newClassName === void 0 ? void 0 : newClassName.includes('no-draggable')) && !(oldClassName === null || oldClassName === void 0 ? void 0 : oldClassName.includes('no-draggable')))) {
|
|
240
238
|
// draggable mode was changed
|
|
241
239
|
this.initialPopoverVisible = false;
|
|
242
240
|
this.finalPopoverVisible = false;
|
|
@@ -330,7 +328,12 @@ export class SessionWidget extends Observable {
|
|
|
330
328
|
popoverBottom = VIEWPORT_HEIGHT - top + POPOVER_DISTANCE_FROM_BUTTON + (isDraggable ? 0 : NON_DRAGGABLE_OFFSET);
|
|
331
329
|
popoverRight = VIEWPORT_WIDTH - right;
|
|
332
330
|
if (popoverBottom + POPOVER_HEIGHT > VIEWPORT_HEIGHT) {
|
|
333
|
-
popoverBottom =
|
|
331
|
+
popoverBottom =
|
|
332
|
+
VIEWPORT_HEIGHT -
|
|
333
|
+
bottom -
|
|
334
|
+
POPOVER_HEIGHT -
|
|
335
|
+
POPOVER_DISTANCE_FROM_BUTTON -
|
|
336
|
+
(isDraggable ? 0 : NON_DRAGGABLE_OFFSET);
|
|
334
337
|
}
|
|
335
338
|
if (popoverRight + POPOVER_WIDTH > VIEWPORT_WIDTH) {
|
|
336
339
|
popoverRight = VIEWPORT_WIDTH - left - POPOVER_WIDTH;
|
|
@@ -433,9 +436,7 @@ export class SessionWidget extends Observable {
|
|
|
433
436
|
this.onCancel();
|
|
434
437
|
}
|
|
435
438
|
this.initialPopoverVisible = false;
|
|
436
|
-
this.buttonState = this._continuousRecording
|
|
437
|
-
? ButtonState.CONTINUOUS_DEBUGGING
|
|
438
|
-
: ButtonState.IDLE;
|
|
439
|
+
this.buttonState = this._continuousRecording ? ButtonState.CONTINUOUS_DEBUGGING : ButtonState.IDLE;
|
|
439
440
|
if (typeof document !== 'undefined') {
|
|
440
441
|
document.removeEventListener('click', this.handleClickOutside);
|
|
441
442
|
}
|
|
@@ -502,9 +503,7 @@ export class SessionWidget extends Observable {
|
|
|
502
503
|
}
|
|
503
504
|
}
|
|
504
505
|
else {
|
|
505
|
-
this.buttonState = this._initialPopoverVisible
|
|
506
|
-
? ButtonState.IDLE
|
|
507
|
-
: ButtonState.CANCEL;
|
|
506
|
+
this.buttonState = this._initialPopoverVisible ? ButtonState.IDLE : ButtonState.CANCEL;
|
|
508
507
|
this.initialPopoverVisible = !this._initialPopoverVisible;
|
|
509
508
|
}
|
|
510
509
|
if (typeof document !== 'undefined') {
|
|
@@ -305,6 +305,24 @@ export interface WidgetTextOverridesConfig {
|
|
|
305
305
|
submitDialogSubmitText?: string;
|
|
306
306
|
/** Text for the cancel button in dialog */
|
|
307
307
|
submitDialogCancelText?: string;
|
|
308
|
+
/** Tooltip when the recorder button is idle */
|
|
309
|
+
buttonTooltipIdle?: string;
|
|
310
|
+
/** Tooltip while a session is recording */
|
|
311
|
+
buttonTooltipRecording?: string;
|
|
312
|
+
/** Tooltip when cancel is available */
|
|
313
|
+
buttonTooltipCancel?: string;
|
|
314
|
+
/** Tooltip after the recording was sent */
|
|
315
|
+
buttonTooltipSent?: string;
|
|
316
|
+
/** Tooltip while the recorder is starting */
|
|
317
|
+
buttonTooltipLoading?: string;
|
|
318
|
+
/** Tooltip during continuous debugging */
|
|
319
|
+
buttonTooltipContinuousDebugging?: string;
|
|
320
|
+
/** Label while a continuous recording is being saved */
|
|
321
|
+
saveContinuousRecordingSavingText?: string;
|
|
322
|
+
/** Label after a continuous recording was saved */
|
|
323
|
+
saveContinuousRecordingSavedText?: string;
|
|
324
|
+
/** Label when saving a continuous recording failed */
|
|
325
|
+
saveContinuousRecordingErrorText?: string;
|
|
308
326
|
}
|
|
309
327
|
/**
|
|
310
328
|
* Configuration interface for the ApiService class
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplayer-app/session-recorder-browser",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.88",
|
|
4
4
|
"description": "Multiplayer Fullstack Session Recorder for Browser",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Multiplayer Software, Inc.",
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"types": "./dist/index.d.ts",
|
|
29
29
|
"import": "./dist/index.js",
|
|
30
30
|
"require": "./dist/index.umd.js"
|
|
31
|
-
}
|
|
31
|
+
},
|
|
32
|
+
"./browser": "./dist/browser/index.js",
|
|
33
|
+
"./exporters": "./dist/exporters/index.js"
|
|
32
34
|
},
|
|
33
35
|
"engines": {
|
|
34
36
|
"node": ">=18",
|
|
@@ -49,7 +51,7 @@
|
|
|
49
51
|
"preversion": "npm run lint",
|
|
50
52
|
"postversion:skip": "git push && git push --tags",
|
|
51
53
|
"build": "rm -rf dist tsconfig.tsbuildinfo && tsc && webpack --config-name esm --config-name umd && node scripts/sync-exports.js",
|
|
52
|
-
"build:browser": "webpack --config-name browser && node scripts/sync-exports.js",
|
|
54
|
+
"build:browser": "webpack --config-name browser && node scripts/patch-canvas-worker.mjs && node scripts/sync-exports.js",
|
|
53
55
|
"build:exporters": "webpack --config-name exporters && node scripts/sync-exports.js",
|
|
54
56
|
"build:extras": "webpack --config-name browser --config-name exporters && node scripts/sync-exports.js",
|
|
55
57
|
"build:all": "rm -rf dist tsconfig.tsbuildinfo && tsc && webpack && node scripts/sync-exports.js",
|
|
@@ -73,7 +75,7 @@
|
|
|
73
75
|
"webpack-cli": "5.1.4"
|
|
74
76
|
},
|
|
75
77
|
"dependencies": {
|
|
76
|
-
"@multiplayer-app/session-recorder-common": "2.0.
|
|
78
|
+
"@multiplayer-app/session-recorder-common": "2.0.88",
|
|
77
79
|
"@opentelemetry/core": "2.0.1",
|
|
78
80
|
"@opentelemetry/exporter-trace-otlp-http": "0.203.0",
|
|
79
81
|
"@opentelemetry/instrumentation": "0.203.0",
|