@leanbase-giangnd/js 0.2.2 → 0.2.4
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/index.cjs +272 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +272 -116
- package/dist/index.mjs.map +1 -1
- package/dist/leanbase.iife.js +273 -117
- package/dist/leanbase.iife.js.map +1 -1
- package/package.json +47 -46
- package/src/extensions/replay/external/lazy-loaded-session-recorder.ts +176 -88
- package/src/extensions/replay/external/mutation-throttler.ts +40 -27
- package/src/extensions/replay/session-recording.ts +19 -12
- package/src/leanbase.ts +68 -6
- package/src/version.ts +1 -1
- package/LICENSE +0 -37
package/package.json
CHANGED
|
@@ -1,47 +1,48 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
2
|
+
"name": "@leanbase-giangnd/js",
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"description": "Leanbase browser SDK - event tracking, autocapture, and session replay",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"directory": "packages/leanbase"
|
|
8
|
+
},
|
|
9
|
+
"author": "leanbase",
|
|
10
|
+
"license": "Copyrighted by Leanflag Limited",
|
|
11
|
+
"main": "dist/index.cjs",
|
|
12
|
+
"module": "dist/index.mjs",
|
|
13
|
+
"types": "dist/index.d.ts",
|
|
14
|
+
"unpkg": "dist/leanbase.iife.js",
|
|
15
|
+
"jsdelivr": "dist/leanbase.iife.js",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"clean": "rimraf dist coverage",
|
|
18
|
+
"test:unit": "jest -c jest.config.js",
|
|
19
|
+
"lint": "eslint src test",
|
|
20
|
+
"lint:fix": "eslint src test --fix",
|
|
21
|
+
"prebuild": "node -p \"'export const version = \\'' + require('./package.json').version + '\\''\" > src/version.ts",
|
|
22
|
+
"build": "rollup -c",
|
|
23
|
+
"dev": "rollup -c -w",
|
|
24
|
+
"prepublishOnly": "pnpm lint && pnpm test:unit && pnpm build",
|
|
25
|
+
"package": "mkdir -p ../../target && pnpm pack --pack-destination ../../target"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@posthog/core": "workspace:*",
|
|
37
|
+
"@rrweb/record": "2.0.0-alpha.17",
|
|
38
|
+
"fflate": "^0.4.8"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@posthog-tooling/tsconfig-base": "workspace:*",
|
|
42
|
+
"@posthog-tooling/rollup-utils": "workspace:*",
|
|
43
|
+
"jest": "catalog:",
|
|
44
|
+
"jest-environment-jsdom": "catalog:",
|
|
45
|
+
"rollup": "catalog:",
|
|
46
|
+
"rimraf": "^6.0.1"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -331,6 +331,10 @@ export class LazyLoadedSessionRecording {
|
|
|
331
331
|
private _queuedRRWebEvents: QueuedRRWebEvent[] = []
|
|
332
332
|
private _isIdle: boolean | 'unknown' = 'unknown'
|
|
333
333
|
|
|
334
|
+
// Replay should not process rrweb emits until all critical dependencies are ready.
|
|
335
|
+
private _isFullyReady: boolean = false
|
|
336
|
+
private _loggedMissingEndpointFor: boolean = false
|
|
337
|
+
|
|
334
338
|
private _linkedFlagMatching: LinkedFlagMatching
|
|
335
339
|
private _urlTriggerMatching: URLTriggerMatching
|
|
336
340
|
private _eventTriggerMatching: EventTriggerMatching
|
|
@@ -575,7 +579,12 @@ export class LazyLoadedSessionRecording {
|
|
|
575
579
|
}
|
|
576
580
|
|
|
577
581
|
private _tryAddCustomEvent(tag: string, payload: any): boolean {
|
|
578
|
-
|
|
582
|
+
const rrwebRecord = getRRWebRecord()
|
|
583
|
+
if (!rrwebRecord || typeof (rrwebRecord as any).addCustomEvent !== 'function') {
|
|
584
|
+
return false
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return this._tryRRWebMethod(newQueuedEvent(() => (rrwebRecord as any).addCustomEvent(tag, payload)))
|
|
579
588
|
}
|
|
580
589
|
|
|
581
590
|
private _pageViewFallBack() {
|
|
@@ -622,7 +631,12 @@ export class LazyLoadedSessionRecording {
|
|
|
622
631
|
}
|
|
623
632
|
|
|
624
633
|
private _tryTakeFullSnapshot(): boolean {
|
|
625
|
-
|
|
634
|
+
const rrwebRecord = getRRWebRecord()
|
|
635
|
+
if (!rrwebRecord || typeof (rrwebRecord as any).takeFullSnapshot !== 'function') {
|
|
636
|
+
return false
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return this._tryRRWebMethod(newQueuedEvent(() => (rrwebRecord as any).takeFullSnapshot()))
|
|
626
640
|
}
|
|
627
641
|
|
|
628
642
|
private get _fullSnapshotIntervalMillis(): number {
|
|
@@ -717,6 +731,7 @@ export class LazyLoadedSessionRecording {
|
|
|
717
731
|
}
|
|
718
732
|
|
|
719
733
|
async start(startReason?: SessionStartReason) {
|
|
734
|
+
this._isFullyReady = false
|
|
720
735
|
const config = this._remoteConfig
|
|
721
736
|
if (!config) {
|
|
722
737
|
logger.info('remote config must be stored in persistence before recording can start')
|
|
@@ -759,6 +774,16 @@ export class LazyLoadedSessionRecording {
|
|
|
759
774
|
this._makeSamplingDecision(this.sessionId)
|
|
760
775
|
await this._startRecorder()
|
|
761
776
|
|
|
777
|
+
// If rrweb failed to load/start, do not proceed further.
|
|
778
|
+
// This prevents installing listeners that assume rrweb is active.
|
|
779
|
+
if (!this.isStarted) {
|
|
780
|
+
return
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Only start processing rrweb emits once the ingestion endpoint is available.
|
|
784
|
+
// If it isn't available, we must degrade to a no-op (never crash the host app).
|
|
785
|
+
this._isFullyReady = this._canCaptureSnapshots()
|
|
786
|
+
|
|
762
787
|
// calling addEventListener multiple times is safe and will not add duplicates
|
|
763
788
|
addEventListener(window, 'beforeunload', this._onBeforeUnload)
|
|
764
789
|
addEventListener(window, 'offline', this._onOffline)
|
|
@@ -864,101 +889,130 @@ export class LazyLoadedSessionRecording {
|
|
|
864
889
|
this._stopRrweb?.()
|
|
865
890
|
this._stopRrweb = undefined
|
|
866
891
|
|
|
892
|
+
this._isFullyReady = false
|
|
893
|
+
|
|
867
894
|
logger.info('stopped')
|
|
868
895
|
}
|
|
869
896
|
|
|
870
|
-
|
|
871
|
-
this.
|
|
897
|
+
private _snapshotIngestionUrl(): string | null {
|
|
898
|
+
const endpointFor = (this._instance as any)?.requestRouter?.endpointFor
|
|
899
|
+
if (typeof endpointFor !== 'function') {
|
|
900
|
+
return null
|
|
901
|
+
}
|
|
902
|
+
try {
|
|
903
|
+
return endpointFor('api', this._endpoint)
|
|
904
|
+
} catch {
|
|
905
|
+
return null
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
private _canCaptureSnapshots(): boolean {
|
|
910
|
+
return !!this._snapshotIngestionUrl()
|
|
911
|
+
}
|
|
872
912
|
|
|
873
|
-
|
|
913
|
+
onRRwebEmit(rawEvent: eventWithTime) {
|
|
914
|
+
// Never process rrweb emits until we're fully ready.
|
|
915
|
+
if (!this._isFullyReady || !this.isStarted) {
|
|
874
916
|
return
|
|
875
917
|
}
|
|
876
918
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
if (!
|
|
919
|
+
try {
|
|
920
|
+
this._processQueuedEvents()
|
|
921
|
+
|
|
922
|
+
if (!rawEvent || !isObject(rawEvent)) {
|
|
881
923
|
return
|
|
882
924
|
}
|
|
883
|
-
rawEvent.data.href = href
|
|
884
|
-
} else {
|
|
885
|
-
this._pageViewFallBack()
|
|
886
|
-
}
|
|
887
925
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
}
|
|
926
|
+
if (rawEvent.type === EventType.Meta) {
|
|
927
|
+
const href = this._maskUrl(rawEvent.data.href)
|
|
928
|
+
this._lastHref = href
|
|
929
|
+
if (!href) {
|
|
930
|
+
return
|
|
931
|
+
}
|
|
932
|
+
rawEvent.data.href = href
|
|
933
|
+
} else {
|
|
934
|
+
this._pageViewFallBack()
|
|
935
|
+
}
|
|
899
936
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
937
|
+
// Check if the URL matches any trigger patterns
|
|
938
|
+
this._urlTriggerMatching.checkUrlTriggerConditions(
|
|
939
|
+
() => this._pauseRecording(),
|
|
940
|
+
() => this._resumeRecording(),
|
|
941
|
+
(triggerType) => this._activateTrigger(triggerType)
|
|
942
|
+
)
|
|
943
|
+
// always have to check if the URL is blocked really early,
|
|
944
|
+
// or you risk getting stuck in a loop
|
|
945
|
+
if (this._urlTriggerMatching.urlBlocked && !isRecordingPausedEvent(rawEvent)) {
|
|
946
|
+
return
|
|
947
|
+
}
|
|
906
948
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
this._clearBufferBeforeMostRecentMeta()
|
|
914
|
-
}
|
|
949
|
+
// we're processing a full snapshot, so we should reset the timer
|
|
950
|
+
if (rawEvent.type === EventType.FullSnapshot) {
|
|
951
|
+
this._scheduleFullSnapshot()
|
|
952
|
+
// Full snapshots reset rrweb's node IDs, so clear any logged node tracking
|
|
953
|
+
this._mutationThrottler?.reset()
|
|
954
|
+
}
|
|
915
955
|
|
|
916
|
-
|
|
956
|
+
// Clear the buffer if waiting for a trigger and only keep data from after the current full snapshot
|
|
957
|
+
// we always start trigger pending so need to wait for flags before we know if we're really pending
|
|
958
|
+
if (
|
|
959
|
+
rawEvent.type === EventType.FullSnapshot &&
|
|
960
|
+
this._triggerMatching.triggerStatus(this.sessionId) === TRIGGER_PENDING
|
|
961
|
+
) {
|
|
962
|
+
this._clearBufferBeforeMostRecentMeta()
|
|
963
|
+
}
|
|
917
964
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
965
|
+
const throttledEvent = this._mutationThrottler
|
|
966
|
+
? this._mutationThrottler.throttleMutations(rawEvent)
|
|
967
|
+
: rawEvent
|
|
921
968
|
|
|
922
|
-
|
|
923
|
-
|
|
969
|
+
if (!throttledEvent) {
|
|
970
|
+
return
|
|
971
|
+
}
|
|
924
972
|
|
|
925
|
-
|
|
973
|
+
// TODO: Re-add ensureMaxMessageSize once we are confident in it
|
|
974
|
+
const event = truncateLargeConsoleLogs(throttledEvent)
|
|
926
975
|
|
|
927
|
-
|
|
928
|
-
// we don't want to return early if idle is 'unknown'
|
|
929
|
-
if (this._isIdle === true && !isSessionIdleEvent(event)) {
|
|
930
|
-
return
|
|
931
|
-
}
|
|
976
|
+
this._updateWindowAndSessionIds(event)
|
|
932
977
|
|
|
933
|
-
|
|
934
|
-
//
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
const payload = event.data.payload as SessionIdlePayload
|
|
938
|
-
if (payload) {
|
|
939
|
-
const lastActivity = payload.lastActivityTimestamp
|
|
940
|
-
const threshold = payload.threshold
|
|
941
|
-
event.timestamp = lastActivity + threshold
|
|
978
|
+
// When in an idle state we keep recording but don't capture the events,
|
|
979
|
+
// we don't want to return early if idle is 'unknown'
|
|
980
|
+
if (this._isIdle === true && !isSessionIdleEvent(event)) {
|
|
981
|
+
return
|
|
942
982
|
}
|
|
943
|
-
}
|
|
944
983
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
984
|
+
if (isSessionIdleEvent(event)) {
|
|
985
|
+
// session idle events have a timestamp when rrweb sees them
|
|
986
|
+
// which can artificially lengthen a session
|
|
987
|
+
// we know when we detected it based on the payload and can correct the timestamp
|
|
988
|
+
const payload = event.data.payload as SessionIdlePayload
|
|
989
|
+
if (payload) {
|
|
990
|
+
const lastActivity = payload.lastActivityTimestamp
|
|
991
|
+
const threshold = payload.threshold
|
|
992
|
+
event.timestamp = lastActivity + threshold
|
|
993
|
+
}
|
|
994
|
+
}
|
|
948
995
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
$session_id: this._sessionId,
|
|
953
|
-
$window_id: this._windowId,
|
|
954
|
-
}
|
|
996
|
+
const eventToSend =
|
|
997
|
+
(this._instance.config.session_recording?.compress_events ?? true) ? compressEvent(event) : event
|
|
998
|
+
const size = estimateSize(eventToSend)
|
|
955
999
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1000
|
+
const properties = {
|
|
1001
|
+
$snapshot_bytes: size,
|
|
1002
|
+
$snapshot_data: eventToSend,
|
|
1003
|
+
$session_id: this._sessionId,
|
|
1004
|
+
$window_id: this._windowId,
|
|
1005
|
+
}
|
|
960
1006
|
|
|
961
|
-
|
|
1007
|
+
if (this.status === DISABLED) {
|
|
1008
|
+
this._clearBuffer()
|
|
1009
|
+
return
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
this._captureSnapshotBuffered(properties)
|
|
1013
|
+
} catch (e) {
|
|
1014
|
+
logger.error('error processing rrweb event', e)
|
|
1015
|
+
}
|
|
962
1016
|
}
|
|
963
1017
|
|
|
964
1018
|
get status(): SessionRecordingStatus {
|
|
@@ -1047,6 +1101,17 @@ export class LazyLoadedSessionRecording {
|
|
|
1047
1101
|
return this._buffer
|
|
1048
1102
|
}
|
|
1049
1103
|
|
|
1104
|
+
if (!this._canCaptureSnapshots()) {
|
|
1105
|
+
if (!this._loggedMissingEndpointFor) {
|
|
1106
|
+
this._loggedMissingEndpointFor = true
|
|
1107
|
+
logger.warn('snapshot capture skipped because requestRouter.endpointFor is unavailable')
|
|
1108
|
+
}
|
|
1109
|
+
this._flushBufferTimer = setTimeout(() => {
|
|
1110
|
+
this._flushBuffer()
|
|
1111
|
+
}, RECORDING_BUFFER_TIMEOUT)
|
|
1112
|
+
return this._buffer
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1050
1115
|
if (this._buffer.data.length > 0) {
|
|
1051
1116
|
const snapshotEvents = splitBuffer(this._buffer)
|
|
1052
1117
|
snapshotEvents.forEach((snapshotBuffer) => {
|
|
@@ -1086,13 +1151,26 @@ export class LazyLoadedSessionRecording {
|
|
|
1086
1151
|
}
|
|
1087
1152
|
|
|
1088
1153
|
private _captureSnapshot(properties: Properties) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1154
|
+
const url = this._snapshotIngestionUrl()
|
|
1155
|
+
if (!url) {
|
|
1156
|
+
if (!this._loggedMissingEndpointFor) {
|
|
1157
|
+
this._loggedMissingEndpointFor = true
|
|
1158
|
+
logger.warn('snapshot capture skipped because requestRouter.endpointFor is unavailable')
|
|
1159
|
+
}
|
|
1160
|
+
return
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
try {
|
|
1164
|
+
// :TRICKY: Make sure we batch these requests, use a custom endpoint and don't truncate the strings.
|
|
1165
|
+
this._instance.capture('$snapshot', properties, {
|
|
1166
|
+
_url: url,
|
|
1167
|
+
_noTruncate: true,
|
|
1168
|
+
_batchKey: SESSION_RECORDING_BATCH_KEY,
|
|
1169
|
+
skip_client_rate_limiting: true,
|
|
1170
|
+
})
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
logger.error('failed to capture snapshot', e)
|
|
1173
|
+
}
|
|
1096
1174
|
}
|
|
1097
1175
|
|
|
1098
1176
|
private _snapshotUrl(): string {
|
|
@@ -1418,13 +1496,23 @@ export class LazyLoadedSessionRecording {
|
|
|
1418
1496
|
})
|
|
1419
1497
|
|
|
1420
1498
|
const activePlugins = this._gatherRRWebPlugins()
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1499
|
+
try {
|
|
1500
|
+
this._stopRrweb = rrwebRecord({
|
|
1501
|
+
emit: (event) => {
|
|
1502
|
+
try {
|
|
1503
|
+
this.onRRwebEmit(event)
|
|
1504
|
+
} catch (e) {
|
|
1505
|
+
logger.error('error in rrweb emit handler', e)
|
|
1506
|
+
}
|
|
1507
|
+
},
|
|
1508
|
+
plugins: activePlugins,
|
|
1509
|
+
...sessionRecordingOptions,
|
|
1510
|
+
})
|
|
1511
|
+
} catch (e) {
|
|
1512
|
+
logger.error('failed to start rrweb recorder', e)
|
|
1513
|
+
this._stopRrweb = undefined
|
|
1514
|
+
return
|
|
1515
|
+
}
|
|
1428
1516
|
|
|
1429
1517
|
// We reset the last activity timestamp, resetting the idle timer
|
|
1430
1518
|
this._lastActivityTimestamp = Date.now()
|
|
@@ -54,7 +54,17 @@ export class MutationThrottler {
|
|
|
54
54
|
return [id, node]
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
private _getNode = (id: number) =>
|
|
57
|
+
private _getNode = (id: number | null | undefined): Node | null => {
|
|
58
|
+
// eslint-disable-next-line posthog-js/no-direct-undefined-check, posthog-js/no-direct-null-check
|
|
59
|
+
if (id === null || id === undefined) {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return this._rrweb.mirror.getNode(id) ?? null
|
|
64
|
+
} catch {
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
}
|
|
58
68
|
|
|
59
69
|
private _numberOfChanges = (data: Partial<mutationCallbackParam>) => {
|
|
60
70
|
return (
|
|
@@ -66,36 +76,39 @@ export class MutationThrottler {
|
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
public throttleMutations = (event: eventWithTime) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const data = event.data as Partial<mutationCallbackParam>
|
|
74
|
-
const initialMutationCount = this._numberOfChanges(data)
|
|
75
|
-
|
|
76
|
-
if (data.attributes) {
|
|
77
|
-
// Most problematic mutations come from attrs where the style or minor properties are changed rapidly
|
|
78
|
-
data.attributes = data.attributes.filter((attr) => {
|
|
79
|
-
const [nodeId] = this._getNodeOrRelevantParent(attr.id)
|
|
80
|
-
|
|
81
|
-
const isRateLimited = this._rateLimiter.consumeRateLimit(nodeId)
|
|
82
|
-
|
|
83
|
-
if (isRateLimited) {
|
|
84
|
-
return false
|
|
85
|
-
}
|
|
79
|
+
try {
|
|
80
|
+
if (event.type !== INCREMENTAL_SNAPSHOT_EVENT_TYPE || event.data.source !== MUTATION_SOURCE_TYPE) {
|
|
81
|
+
return event
|
|
82
|
+
}
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
const data = event.data as Partial<mutationCallbackParam>
|
|
85
|
+
const initialMutationCount = this._numberOfChanges(data)
|
|
86
|
+
|
|
87
|
+
if (data.attributes) {
|
|
88
|
+
// Most problematic mutations come from attrs where the style or minor properties are changed rapidly
|
|
89
|
+
data.attributes = data.attributes.filter((attr: any) => {
|
|
90
|
+
const id = (attr as any)?.id
|
|
91
|
+
if (typeof id !== 'number') {
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const [nodeId] = this._getNodeOrRelevantParent(id)
|
|
96
|
+
const isRateLimited = this._rateLimiter.consumeRateLimit(nodeId)
|
|
97
|
+
return !isRateLimited
|
|
98
|
+
})
|
|
99
|
+
}
|
|
90
100
|
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
// Check if every part of the mutation is empty in which case there is nothing to do
|
|
102
|
+
const mutationCount = this._numberOfChanges(data)
|
|
93
103
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
if (mutationCount === 0 && initialMutationCount !== mutationCount) {
|
|
105
|
+
// If we have modified the mutation count and the remaining count is 0, then we don't need the event.
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
return event
|
|
109
|
+
} catch {
|
|
110
|
+
return event
|
|
97
111
|
}
|
|
98
|
-
return event
|
|
99
112
|
}
|
|
100
113
|
|
|
101
114
|
public reset() {
|
|
@@ -213,22 +213,29 @@ export class SessionRecording {
|
|
|
213
213
|
// If extensions provide an init function, use it. Otherwise, fall back to the local LazyLoadedSessionRecording
|
|
214
214
|
if (assignableWindow.__PosthogExtensions__?.initSessionRecording) {
|
|
215
215
|
if (!this._lazyLoadedSessionRecording) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
216
|
+
const maybeRecording = assignableWindow.__PosthogExtensions__?.initSessionRecording(this._instance)
|
|
217
|
+
if (maybeRecording && typeof (maybeRecording as any).start === 'function') {
|
|
218
|
+
this._lazyLoadedSessionRecording = maybeRecording
|
|
219
|
+
;(this._lazyLoadedSessionRecording as any)._forceAllowLocalhostNetworkCapture =
|
|
220
|
+
this._forceAllowLocalhostNetworkCapture
|
|
221
|
+
} else {
|
|
222
|
+
log.warn(
|
|
223
|
+
'initSessionRecording was present but did not return a recorder instance; falling back to local recorder'
|
|
224
|
+
)
|
|
225
|
+
}
|
|
221
226
|
}
|
|
222
227
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
maybePromise
|
|
228
|
+
if (this._lazyLoadedSessionRecording) {
|
|
229
|
+
try {
|
|
230
|
+
const maybePromise: any = this._lazyLoadedSessionRecording.start(startReason)
|
|
231
|
+
if (maybePromise && typeof maybePromise.catch === 'function') {
|
|
232
|
+
maybePromise.catch((e: any) => logger.error('error starting session recording', e))
|
|
233
|
+
}
|
|
234
|
+
} catch (e: any) {
|
|
235
|
+
logger.error('error starting session recording', e)
|
|
227
236
|
}
|
|
228
|
-
|
|
229
|
-
logger.error('error starting session recording', e)
|
|
237
|
+
return
|
|
230
238
|
}
|
|
231
|
-
return
|
|
232
239
|
}
|
|
233
240
|
|
|
234
241
|
if (!this._lazyLoadedSessionRecording) {
|