@oneuptime/react-native-replay 14.0.11 → 14.0.12
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/README.md +8 -2
- package/dist/index.cjs +217 -49
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +217 -49
- package/dist/index.js.map +2 -2
- package/dist/types/Contract.d.ts +1 -0
- package/dist/types/Contract.d.ts.map +1 -1
- package/dist/types/MobileReplayRecorder.d.ts +11 -1
- package/dist/types/MobileReplayRecorder.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ var SESSION_REPLAY_MAX_OFFLINE_DELAY_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
|
59
59
|
var MOBILE_CAPTURE_INTERVAL_MS = 500;
|
|
60
60
|
var MOBILE_RECORDER_KIND = "rn-view-tree";
|
|
61
61
|
var SYNTHETIC_RRWEB_VERSION = "synthetic-1";
|
|
62
|
-
var RECORDER_VERSION = true ? "14.0.
|
|
62
|
+
var RECORDER_VERSION = true ? "14.0.12" : "0.0.0-dev";
|
|
63
63
|
var SESSION_REPLAY_MOBILE_RECORDER_CAPABILITIES = [
|
|
64
64
|
"mobile-view-tree",
|
|
65
65
|
"mobile-touch-events",
|
|
@@ -111,6 +111,7 @@ var IDENTIFY_CUSTOM_EVENT_TAG = "oneuptime.identify";
|
|
|
111
111
|
var TAGS_CUSTOM_EVENT_TAG = "oneuptime.tags";
|
|
112
112
|
var VISIBILITY_CUSTOM_EVENT_TAG = "oneuptime.visibility";
|
|
113
113
|
var TOUCH_CUSTOM_EVENT_TAG = "oneuptime.touch";
|
|
114
|
+
var SESSION_ROTATED_CUSTOM_EVENT_TAG = "oneuptime.session-rotated";
|
|
114
115
|
|
|
115
116
|
// src/Sanitize.ts
|
|
116
117
|
var MAX_TRAIT_KEYS = 20;
|
|
@@ -3081,6 +3082,19 @@ var MobileReplayRecorder = class {
|
|
|
3081
3082
|
__publicField(this, "pendingStartCancellation", null);
|
|
3082
3083
|
__publicField(this, "identityPersisted", false);
|
|
3083
3084
|
__publicField(this, "sessionLastActivityUnixMs", 0);
|
|
3085
|
+
/*
|
|
3086
|
+
* The session went the idle window without activity, was sealed where it
|
|
3087
|
+
* ended, and nobody has come back since.
|
|
3088
|
+
*
|
|
3089
|
+
* Rotating on the spot instead (the old behaviour) started a session
|
|
3090
|
+
* nobody was in: an app left open in the foreground with the screen on
|
|
3091
|
+
* re-snapshots every minute, so it filed a new session every half hour
|
|
3092
|
+
* for as long as it stayed open. Now nothing is captured, uploaded or
|
|
3093
|
+
* handed to the host as a session id until the user is back - a touch,
|
|
3094
|
+
* the app returning to the foreground, or an explicit captureSession() -
|
|
3095
|
+
* and that starts the next session (see maybeRotateSession).
|
|
3096
|
+
*/
|
|
3097
|
+
__publicField(this, "sealedForIdle", false);
|
|
3084
3098
|
__publicField(this, "rotatingSession", false);
|
|
3085
3099
|
__publicField(this, "rotationPromise", null);
|
|
3086
3100
|
__publicField(this, "appStateQueue", Promise.resolve());
|
|
@@ -3168,6 +3182,7 @@ var MobileReplayRecorder = class {
|
|
|
3168
3182
|
this.appStateQueue = Promise.resolve();
|
|
3169
3183
|
this.rotationPromise = null;
|
|
3170
3184
|
this.rotatingSession = false;
|
|
3185
|
+
this.sealedForIdle = false;
|
|
3171
3186
|
this.policyRefreshPromise = null;
|
|
3172
3187
|
this.lastPolicyRefreshAtUnixMs = 0;
|
|
3173
3188
|
this.forceFullSnapshot = true;
|
|
@@ -3340,12 +3355,14 @@ var MobileReplayRecorder = class {
|
|
|
3340
3355
|
this.diagnosticsStatus = "stopped";
|
|
3341
3356
|
return;
|
|
3342
3357
|
}
|
|
3343
|
-
this.
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3358
|
+
if (this.rotationReason(this.now()) !== "idle") {
|
|
3359
|
+
this.appendCustom(
|
|
3360
|
+
VISIBILITY_CUSTOM_EVENT_TAG,
|
|
3361
|
+
{ state: "stopped" },
|
|
3362
|
+
void 0,
|
|
3363
|
+
true
|
|
3364
|
+
);
|
|
3365
|
+
}
|
|
3349
3366
|
this.haltCapture();
|
|
3350
3367
|
await this.closeCurrent(true);
|
|
3351
3368
|
if (this.uploadActive) {
|
|
@@ -3508,6 +3525,9 @@ var MobileReplayRecorder = class {
|
|
|
3508
3525
|
return;
|
|
3509
3526
|
}
|
|
3510
3527
|
this.route = next;
|
|
3528
|
+
if (this.sealedForIdle) {
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3511
3531
|
this.appendCustom(
|
|
3512
3532
|
ROUTE_CUSTOM_EVENT_TAG,
|
|
3513
3533
|
{
|
|
@@ -3526,7 +3546,7 @@ var MobileReplayRecorder = class {
|
|
|
3526
3546
|
if (!this.running || !this.acceptingEvents) {
|
|
3527
3547
|
return;
|
|
3528
3548
|
}
|
|
3529
|
-
await this.
|
|
3549
|
+
await this.maybeRotateForUser(this.now());
|
|
3530
3550
|
const safeReason2 = truncate(
|
|
3531
3551
|
(typeof reason === "string" ? reason.trim() : "") || "manual",
|
|
3532
3552
|
MAX_CAPTURE_REASON_LENGTH
|
|
@@ -3543,6 +3563,9 @@ var MobileReplayRecorder = class {
|
|
|
3543
3563
|
return;
|
|
3544
3564
|
}
|
|
3545
3565
|
await this.maybeRotateSession(this.now());
|
|
3566
|
+
if (this.sealedForIdle) {
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3546
3569
|
const rawName = error instanceof Error && typeof error.name === "string" ? error.name.trim() : "";
|
|
3547
3570
|
const name = SAFE_ERROR_NAMES.has(rawName) ? rawName : "Error";
|
|
3548
3571
|
this.appendCustom(
|
|
@@ -3593,9 +3616,9 @@ var MobileReplayRecorder = class {
|
|
|
3593
3616
|
this.lastTouchMoveAtUnixMs = touch.timestamp;
|
|
3594
3617
|
}
|
|
3595
3618
|
const nowUnixMs = this.now();
|
|
3596
|
-
if (this.rotatingSession || this.rotationReason(nowUnixMs)) {
|
|
3619
|
+
if (this.rotatingSession || this.sealedForIdle || this.rotationReason(nowUnixMs)) {
|
|
3597
3620
|
this.diagnostic("touch-dropped-session-rotation");
|
|
3598
|
-
void this.
|
|
3621
|
+
void this.maybeRotateForUser(nowUnixMs);
|
|
3599
3622
|
return;
|
|
3600
3623
|
}
|
|
3601
3624
|
this.appendEvent(
|
|
@@ -3643,9 +3666,14 @@ var MobileReplayRecorder = class {
|
|
|
3643
3666
|
)
|
|
3644
3667
|
};
|
|
3645
3668
|
}
|
|
3646
|
-
/**
|
|
3669
|
+
/**
|
|
3670
|
+
* Current consented replay session for host trace/log correlation. None
|
|
3671
|
+
* while the session has ended idle and nobody is back: a background poll
|
|
3672
|
+
* on an idle app is no part of a recording that ended at the user's last
|
|
3673
|
+
* activity.
|
|
3674
|
+
*/
|
|
3647
3675
|
getSessionId() {
|
|
3648
|
-
return this.running && this.identityPersisted ? this.identity?.sessionId ?? null : null;
|
|
3676
|
+
return this.running && this.identityPersisted && !this.sealedForIdle ? this.identity?.sessionId ?? null : null;
|
|
3649
3677
|
}
|
|
3650
3678
|
/** Fires immediately when a session exists and again on rotate/clear. */
|
|
3651
3679
|
onSessionChange(listener) {
|
|
@@ -3700,7 +3728,7 @@ var MobileReplayRecorder = class {
|
|
|
3700
3728
|
}
|
|
3701
3729
|
async captureTick(force = false) {
|
|
3702
3730
|
const generation = this.lifecycleGeneration;
|
|
3703
|
-
if (!this.running || !this.acceptingEvents || !this.foreground || !this.rootTag || this.captureGeneration === generation || !this.options) {
|
|
3731
|
+
if (!this.running || !this.acceptingEvents || !this.foreground || !this.rootTag || this.sealedForIdle || this.captureGeneration === generation || !this.options) {
|
|
3704
3732
|
return;
|
|
3705
3733
|
}
|
|
3706
3734
|
this.captureGeneration = generation;
|
|
@@ -3715,7 +3743,7 @@ var MobileReplayRecorder = class {
|
|
|
3715
3743
|
return;
|
|
3716
3744
|
}
|
|
3717
3745
|
await this.maybeRotateSession(nowUnixMs);
|
|
3718
|
-
if (!this.running || !this.acceptingEvents || !this.foreground || !this.isCurrentGeneration(generation)) {
|
|
3746
|
+
if (!this.running || !this.acceptingEvents || !this.foreground || !this.isCurrentGeneration(generation) || this.sealedForIdle) {
|
|
3719
3747
|
return;
|
|
3720
3748
|
}
|
|
3721
3749
|
const capturedSessionId = this.identity?.sessionId;
|
|
@@ -3724,7 +3752,6 @@ var MobileReplayRecorder = class {
|
|
|
3724
3752
|
return;
|
|
3725
3753
|
}
|
|
3726
3754
|
this.touchPrivacyMap = deriveTouchPrivacyMap(tree);
|
|
3727
|
-
const size = getViewport();
|
|
3728
3755
|
const shouldForce = force || this.forceFullSnapshot || nowUnixMs - this.lastFullSnapshotAtUnixMs >= SESSION_REPLAY_CHECKOUT_INTERVAL_MS;
|
|
3729
3756
|
if (shouldForce && !this.chunkBuffer.isEmpty()) {
|
|
3730
3757
|
await this.closeCurrent(false);
|
|
@@ -3732,22 +3759,7 @@ var MobileReplayRecorder = class {
|
|
|
3732
3759
|
if (!this.running || !this.acceptingEvents || !this.foreground || !this.isCurrentGeneration(generation) || this.identity?.sessionId !== capturedSessionId) {
|
|
3733
3760
|
return;
|
|
3734
3761
|
}
|
|
3735
|
-
|
|
3736
|
-
timestamp: nowUnixMs,
|
|
3737
|
-
url: toAppUrl(this.options.mobileAppIdentifier, this.route),
|
|
3738
|
-
viewportWidth: size.width,
|
|
3739
|
-
viewportHeight: size.height,
|
|
3740
|
-
forceFullSnapshot: shouldForce
|
|
3741
|
-
});
|
|
3742
|
-
this.droppedEvents += capture.droppedNodes;
|
|
3743
|
-
this.chunkBuffer.appendMany(capture.events, {
|
|
3744
|
-
route: toAppUrl(this.options.mobileAppIdentifier, this.route),
|
|
3745
|
-
fidelityNotices: capture.fidelityNotices
|
|
3746
|
-
});
|
|
3747
|
-
if (capture.hasFullSnapshot) {
|
|
3748
|
-
this.lastFullSnapshotAtUnixMs = nowUnixMs;
|
|
3749
|
-
this.forceFullSnapshot = false;
|
|
3750
|
-
}
|
|
3762
|
+
this.appendCapture(tree, this.options, nowUnixMs, shouldForce);
|
|
3751
3763
|
if (this.chunkBuffer.shouldFlush()) {
|
|
3752
3764
|
await this.closeCurrent(false);
|
|
3753
3765
|
}
|
|
@@ -3764,6 +3776,26 @@ var MobileReplayRecorder = class {
|
|
|
3764
3776
|
}
|
|
3765
3777
|
}
|
|
3766
3778
|
}
|
|
3779
|
+
appendCapture(tree, options, nowUnixMs, forceFullSnapshot) {
|
|
3780
|
+
const size = getViewport();
|
|
3781
|
+
const url = toAppUrl(options.mobileAppIdentifier, this.route);
|
|
3782
|
+
const capture = this.serializer.capture(tree, {
|
|
3783
|
+
timestamp: nowUnixMs,
|
|
3784
|
+
url,
|
|
3785
|
+
viewportWidth: size.width,
|
|
3786
|
+
viewportHeight: size.height,
|
|
3787
|
+
forceFullSnapshot
|
|
3788
|
+
});
|
|
3789
|
+
this.droppedEvents += capture.droppedNodes;
|
|
3790
|
+
this.chunkBuffer.appendMany(capture.events, {
|
|
3791
|
+
route: url,
|
|
3792
|
+
fidelityNotices: capture.fidelityNotices
|
|
3793
|
+
});
|
|
3794
|
+
if (capture.hasFullSnapshot) {
|
|
3795
|
+
this.lastFullSnapshotAtUnixMs = nowUnixMs;
|
|
3796
|
+
this.forceFullSnapshot = false;
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3767
3799
|
appendCustom(tag, payload, signal, allowWhenQuiesced = false) {
|
|
3768
3800
|
this.appendEvent(
|
|
3769
3801
|
createCustomEvent(tag, payload, this.now()),
|
|
@@ -3775,6 +3807,9 @@ var MobileReplayRecorder = class {
|
|
|
3775
3807
|
if (!this.running || !this.acceptingEvents && !allowWhenQuiesced) {
|
|
3776
3808
|
return;
|
|
3777
3809
|
}
|
|
3810
|
+
if (this.sealedForIdle) {
|
|
3811
|
+
return;
|
|
3812
|
+
}
|
|
3778
3813
|
if (!Number.isFinite(event.timestamp)) {
|
|
3779
3814
|
this.droppedEvents += 1;
|
|
3780
3815
|
this.diagnostic("invalid-event-timestamp");
|
|
@@ -3803,7 +3838,7 @@ var MobileReplayRecorder = class {
|
|
|
3803
3838
|
}
|
|
3804
3839
|
}
|
|
3805
3840
|
async closeCurrent(isFinal) {
|
|
3806
|
-
const closesFinalChunkSlot = !isFinal && !this.rotatingSession && this.uploadActive && this.sessionStore?.getNextChunkIndex() === MAX_SESSION_REPLAY_CHUNKS_PER_SESSION - 1;
|
|
3841
|
+
const closesFinalChunkSlot = !isFinal && !this.rotatingSession && !this.sealedForIdle && this.uploadActive && this.sessionStore?.getNextChunkIndex() === MAX_SESSION_REPLAY_CHUNKS_PER_SESSION - 1;
|
|
3807
3842
|
if (closesFinalChunkSlot) {
|
|
3808
3843
|
this.rotatingSession = true;
|
|
3809
3844
|
const rotationOptions = {};
|
|
@@ -3815,7 +3850,7 @@ var MobileReplayRecorder = class {
|
|
|
3815
3850
|
}
|
|
3816
3851
|
this.chunkBuffer.append(
|
|
3817
3852
|
createCustomEvent(
|
|
3818
|
-
|
|
3853
|
+
SESSION_ROTATED_CUSTOM_EVENT_TAG,
|
|
3819
3854
|
{ reason: "chunk-cap" },
|
|
3820
3855
|
this.now()
|
|
3821
3856
|
),
|
|
@@ -4015,15 +4050,19 @@ var MobileReplayRecorder = class {
|
|
|
4015
4050
|
if (!policyReady) {
|
|
4016
4051
|
return;
|
|
4017
4052
|
}
|
|
4018
|
-
|
|
4053
|
+
const sessionIdBeforeResume = this.identity?.sessionId;
|
|
4054
|
+
await this.maybeRotateSession(this.now(), true);
|
|
4019
4055
|
if (!this.running || !this.isCurrentGeneration(generation)) {
|
|
4020
4056
|
return;
|
|
4021
4057
|
}
|
|
4022
4058
|
this.appendCustom(VISIBILITY_CUSTOM_EVENT_TAG, { state: "visible" });
|
|
4023
|
-
this.
|
|
4059
|
+
const openedOnSnapshot = this.identity?.sessionId !== sessionIdBeforeResume && this.lastFullSnapshotAtUnixMs > 0;
|
|
4060
|
+
if (!openedOnSnapshot) {
|
|
4061
|
+
this.forceFullSnapshot = true;
|
|
4062
|
+
}
|
|
4024
4063
|
this.startTimers();
|
|
4025
4064
|
this.diagnosticsStatus = this.consentState() === "Unknown" ? "consent-required" : "recording";
|
|
4026
|
-
await this.captureTick(
|
|
4065
|
+
await this.captureTick(!openedOnSnapshot);
|
|
4027
4066
|
if (this.uploadActive && this.running) {
|
|
4028
4067
|
this.transport?.resume();
|
|
4029
4068
|
}
|
|
@@ -4303,9 +4342,12 @@ var MobileReplayRecorder = class {
|
|
|
4303
4342
|
if (!this.running || identityEpoch !== this.identityCancellationEpoch) {
|
|
4304
4343
|
return;
|
|
4305
4344
|
}
|
|
4345
|
+
if (!this.rotationPromise && !this.sealedForIdle && this.rotationReason(this.now()) === "idle") {
|
|
4346
|
+
await this.maybeRotateSession(this.now());
|
|
4347
|
+
}
|
|
4306
4348
|
if (this.rotationPromise) {
|
|
4307
4349
|
await this.rotationPromise;
|
|
4308
|
-
} else {
|
|
4350
|
+
} else if (!this.sealedForIdle) {
|
|
4309
4351
|
this.rotatingSession = true;
|
|
4310
4352
|
const rotation = this.performSessionRotation(
|
|
4311
4353
|
this.now(),
|
|
@@ -4640,16 +4682,24 @@ var MobileReplayRecorder = class {
|
|
|
4640
4682
|
chunkIndex: 0
|
|
4641
4683
|
};
|
|
4642
4684
|
}
|
|
4685
|
+
/*
|
|
4686
|
+
* Idle before the duration cap, the order Common's SessionIdentity decides
|
|
4687
|
+
* them in. A session left for the idle window ended where it was left,
|
|
4688
|
+
* even when it has outlived the cap by the time anything notices, and
|
|
4689
|
+
* only an idle rotation dates its end there (see performSessionRotation):
|
|
4690
|
+
* called "duration", a session three hours old put in the background for
|
|
4691
|
+
* two more was sealed two hours after its footage.
|
|
4692
|
+
*/
|
|
4643
4693
|
rotationReason(nowUnixMs) {
|
|
4644
4694
|
if (!this.identity) {
|
|
4645
4695
|
return null;
|
|
4646
4696
|
}
|
|
4647
|
-
if (nowUnixMs - this.identity.sessionStartUnixMs >= SESSION_REPLAY_MAX_SESSION_MS) {
|
|
4648
|
-
return "duration";
|
|
4649
|
-
}
|
|
4650
4697
|
if (nowUnixMs - this.sessionLastActivityUnixMs >= SESSION_REPLAY_IDLE_ROLLOVER_MS) {
|
|
4651
4698
|
return "idle";
|
|
4652
4699
|
}
|
|
4700
|
+
if (nowUnixMs - this.identity.sessionStartUnixMs >= SESSION_REPLAY_MAX_SESSION_MS) {
|
|
4701
|
+
return "duration";
|
|
4702
|
+
}
|
|
4653
4703
|
return null;
|
|
4654
4704
|
}
|
|
4655
4705
|
runExternalActivity(nowUnixMs, action) {
|
|
@@ -4684,23 +4734,37 @@ var MobileReplayRecorder = class {
|
|
|
4684
4734
|
}
|
|
4685
4735
|
});
|
|
4686
4736
|
}
|
|
4687
|
-
|
|
4688
|
-
|
|
4737
|
+
/*
|
|
4738
|
+
* isUserBack: the caller knows the user is here - a touch, the app
|
|
4739
|
+
* returning to the foreground, an explicit captureSession(). An idle
|
|
4740
|
+
* session then rotates at once. Anything else (a capture tick, a route
|
|
4741
|
+
* change, a developer event) only finds out the session went idle, and
|
|
4742
|
+
* seals it to wait for them (see sealedForIdle).
|
|
4743
|
+
*/
|
|
4744
|
+
async maybeRotateSession(nowUnixMs, isUserBack = false) {
|
|
4745
|
+
while (this.rotationPromise) {
|
|
4689
4746
|
await this.rotationPromise;
|
|
4690
|
-
|
|
4747
|
+
if (!isUserBack || !this.sealedForIdle) {
|
|
4748
|
+
return;
|
|
4749
|
+
}
|
|
4691
4750
|
}
|
|
4692
4751
|
if (!this.running || this.rotatingSession || !this.identity || !this.config || !this.sessionStore || !this.options) {
|
|
4693
4752
|
return;
|
|
4694
4753
|
}
|
|
4695
|
-
|
|
4754
|
+
let reason;
|
|
4755
|
+
if (this.sealedForIdle) {
|
|
4756
|
+
if (!isUserBack) {
|
|
4757
|
+
return;
|
|
4758
|
+
}
|
|
4759
|
+
reason = "idle";
|
|
4760
|
+
} else {
|
|
4761
|
+
reason = this.rotationReason(nowUnixMs);
|
|
4762
|
+
}
|
|
4696
4763
|
if (!reason) {
|
|
4697
4764
|
return;
|
|
4698
4765
|
}
|
|
4699
4766
|
this.rotatingSession = true;
|
|
4700
|
-
const operation = this.performSessionRotation(
|
|
4701
|
-
nowUnixMs,
|
|
4702
|
-
reason
|
|
4703
|
-
);
|
|
4767
|
+
const operation = reason === "idle" && !isUserBack ? this.sealForIdle() : this.performSessionRotation(nowUnixMs, reason);
|
|
4704
4768
|
this.rotationPromise = operation;
|
|
4705
4769
|
try {
|
|
4706
4770
|
await operation;
|
|
@@ -4711,8 +4775,60 @@ var MobileReplayRecorder = class {
|
|
|
4711
4775
|
this.rotatingSession = false;
|
|
4712
4776
|
}
|
|
4713
4777
|
}
|
|
4778
|
+
/*
|
|
4779
|
+
* maybeRotateSession for a user who is here. On an app whose session
|
|
4780
|
+
* ended idle, the policy is brought up to date first: no capture tick has
|
|
4781
|
+
* refreshed it while the app sat idle, and the next session must not open
|
|
4782
|
+
* under one that has since changed (or turned replay off).
|
|
4783
|
+
*/
|
|
4784
|
+
async maybeRotateForUser(nowUnixMs) {
|
|
4785
|
+
if (this.sealedForIdle && !this.rotatingSession) {
|
|
4786
|
+
const policyReady = await this.refreshRuntimePolicy(
|
|
4787
|
+
this.lifecycleGeneration,
|
|
4788
|
+
false,
|
|
4789
|
+
"periodic"
|
|
4790
|
+
);
|
|
4791
|
+
if (!policyReady) {
|
|
4792
|
+
return;
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
await this.maybeRotateSession(nowUnixMs, true);
|
|
4796
|
+
}
|
|
4797
|
+
/*
|
|
4798
|
+
* End the session as idle, where it really ended, and wait for the user.
|
|
4799
|
+
*
|
|
4800
|
+
* Sealed with whatever footage is still buffered - dated by that footage,
|
|
4801
|
+
* never by now - and with no marker of its own: a marker stamped now
|
|
4802
|
+
* would date the session's end half an hour or more after anything the
|
|
4803
|
+
* player can show. With nothing buffered nothing is sent, and the server
|
|
4804
|
+
* ends the session at its last chunk.
|
|
4805
|
+
*/
|
|
4806
|
+
async sealForIdle() {
|
|
4807
|
+
this.sealedForIdle = true;
|
|
4808
|
+
this.diagnostic("session-ended-idle", {
|
|
4809
|
+
idleForMs: Math.max(0, this.now() - this.sessionLastActivityUnixMs)
|
|
4810
|
+
});
|
|
4811
|
+
this.notifySessionChange();
|
|
4812
|
+
await this.closeCurrent(true);
|
|
4813
|
+
await this.closeQueue;
|
|
4814
|
+
if (!this.running) {
|
|
4815
|
+
return;
|
|
4816
|
+
}
|
|
4817
|
+
if (this.uploadActive) {
|
|
4818
|
+
await this.transport?.drain();
|
|
4819
|
+
} else {
|
|
4820
|
+
this.rollingBuffer.clear();
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4714
4823
|
async performSessionRotation(nowUnixMs, reason) {
|
|
4715
|
-
|
|
4824
|
+
if (reason !== "idle") {
|
|
4825
|
+
this.appendCustom(
|
|
4826
|
+
SESSION_ROTATED_CUSTOM_EVENT_TAG,
|
|
4827
|
+
{ reason },
|
|
4828
|
+
void 0,
|
|
4829
|
+
true
|
|
4830
|
+
);
|
|
4831
|
+
}
|
|
4716
4832
|
await this.closeCurrent(true);
|
|
4717
4833
|
await this.closeQueue;
|
|
4718
4834
|
if (!this.running) {
|
|
@@ -4727,12 +4843,64 @@ var MobileReplayRecorder = class {
|
|
|
4727
4843
|
return;
|
|
4728
4844
|
}
|
|
4729
4845
|
await this.completeSessionIdentityRotation(nowUnixMs, reason);
|
|
4846
|
+
if (reason === "idle" && this.running) {
|
|
4847
|
+
await this.openSessionAfterIdle();
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
/*
|
|
4851
|
+
* The first things a session after an idle one records: a full snapshot
|
|
4852
|
+
* of the screen the user came back to, then the rotation marker - the
|
|
4853
|
+
* snapshot first, so chunk 0 is a seek anchor rather than a lone marker.
|
|
4854
|
+
*
|
|
4855
|
+
* Taken here, inside the rotation, rather than left to the next capture
|
|
4856
|
+
* tick: until the rotation settles, touches are dropped and every other
|
|
4857
|
+
* event waits for it (rotatingSession), so nothing can land ahead of the
|
|
4858
|
+
* snapshot. A user who has just come back is touching the screen, and
|
|
4859
|
+
* half a second of that was otherwise a chunk 0 with no screen in it.
|
|
4860
|
+
*
|
|
4861
|
+
* In the background or without a registered root there is no screen to
|
|
4862
|
+
* capture yet; the marker opens the session alone.
|
|
4863
|
+
*/
|
|
4864
|
+
async openSessionAfterIdle() {
|
|
4865
|
+
const generation = this.lifecycleGeneration;
|
|
4866
|
+
const rootTag = this.rootTag;
|
|
4867
|
+
const options = this.options;
|
|
4868
|
+
const sessionId = this.identity?.sessionId;
|
|
4869
|
+
if (this.foreground && rootTag && options && sessionId) {
|
|
4870
|
+
const capturedAtUnixMs = this.now();
|
|
4871
|
+
let tree;
|
|
4872
|
+
try {
|
|
4873
|
+
tree = await this.nativeViewTree.captureViewTree(rootTag);
|
|
4874
|
+
} catch (error) {
|
|
4875
|
+
if (this.running && this.isCurrentGeneration(generation)) {
|
|
4876
|
+
this.disable("native-capture-failed", {
|
|
4877
|
+
name: error instanceof Error ? error.name : "Error"
|
|
4878
|
+
});
|
|
4879
|
+
this.haltCapture();
|
|
4880
|
+
}
|
|
4881
|
+
return;
|
|
4882
|
+
}
|
|
4883
|
+
if (!this.running || !this.isCurrentGeneration(generation) || this.identity?.sessionId !== sessionId) {
|
|
4884
|
+
return;
|
|
4885
|
+
}
|
|
4886
|
+
if (this.foreground) {
|
|
4887
|
+
this.touchPrivacyMap = deriveTouchPrivacyMap(tree);
|
|
4888
|
+
this.appendCapture(tree, options, capturedAtUnixMs, true);
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
this.appendCustom(
|
|
4892
|
+
SESSION_ROTATED_CUSTOM_EVENT_TAG,
|
|
4893
|
+
{ reason: "idle" },
|
|
4894
|
+
void 0,
|
|
4895
|
+
true
|
|
4896
|
+
);
|
|
4730
4897
|
}
|
|
4731
4898
|
async completeSessionIdentityRotation(nowUnixMs, reason) {
|
|
4732
4899
|
if (!this.sessionStore || !this.config || !this.options) {
|
|
4733
4900
|
return;
|
|
4734
4901
|
}
|
|
4735
4902
|
this.identity = this.identityPersisted ? await this.sessionStore.rotate(nowUnixMs) : this.ephemeralIdentity(nowUnixMs);
|
|
4903
|
+
this.sealedForIdle = false;
|
|
4736
4904
|
this.notifySessionChange();
|
|
4737
4905
|
this.sampled = this.identityPersisted ? deterministicSample(
|
|
4738
4906
|
this.identity.sessionId,
|