@anyreplay/browser 0.1.1 → 0.1.3
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 +23 -6
- package/dist/index.cjs +301 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -6
- package/dist/index.d.ts +53 -6
- package/dist/index.js +300 -93
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3641,10 +3641,12 @@ record.mirror = mirror;
|
|
|
3641
3641
|
var DEFAULTS = {
|
|
3642
3642
|
ingestUrl: "https://in.anyreplay.com",
|
|
3643
3643
|
sampleRate: 1,
|
|
3644
|
-
//
|
|
3645
|
-
//
|
|
3646
|
-
//
|
|
3647
|
-
|
|
3644
|
+
// Both masking switches are opt-in: a recording shows what people typed,
|
|
3645
|
+
// which is the point of watching one. What that never includes is the floor
|
|
3646
|
+
// in masking.ts — passwords, payment fields and the rest — which no option
|
|
3647
|
+
// can switch off.
|
|
3648
|
+
maskAllInputs: false,
|
|
3649
|
+
maskAllTyping: false,
|
|
3648
3650
|
maskClass: "ar-mask",
|
|
3649
3651
|
blockClass: "ar-block",
|
|
3650
3652
|
requireConsent: false,
|
|
@@ -3677,11 +3679,18 @@ function resolveOptions(input) {
|
|
|
3677
3679
|
projectKey: input.projectKey,
|
|
3678
3680
|
sampleRate,
|
|
3679
3681
|
ingestUrl: ((_c = input.ingestUrl) != null ? _c : DEFAULTS.ingestUrl).replace(/\/+$/, ""),
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
maskAllInputs: input.maskAllInputs === false ? false : true
|
|
3682
|
+
maskAllInputs: readSwitch(input, "maskAllInputs"),
|
|
3683
|
+
maskAllTyping: readSwitch(input, "maskAllTyping")
|
|
3683
3684
|
};
|
|
3684
3685
|
}
|
|
3686
|
+
function readSwitch(input, name) {
|
|
3687
|
+
const value = input[name];
|
|
3688
|
+
if (typeof value === "boolean") return value;
|
|
3689
|
+
if (value !== void 0 && typeof console !== "undefined") {
|
|
3690
|
+
console.warn(`[anyreplay] ${name} ignored: expected true or false, got ${typeof value}. Nothing is masked by this option.`);
|
|
3691
|
+
}
|
|
3692
|
+
return DEFAULTS[name];
|
|
3693
|
+
}
|
|
3685
3694
|
|
|
3686
3695
|
// src/events.ts
|
|
3687
3696
|
var TRACK_TAG = "anyreplay.track";
|
|
@@ -3711,6 +3720,170 @@ function byteLength(text) {
|
|
|
3711
3720
|
return typeof TextEncoder === "function" ? new TextEncoder().encode(text).length : text.length * 3;
|
|
3712
3721
|
}
|
|
3713
3722
|
|
|
3723
|
+
// src/masking.ts
|
|
3724
|
+
var maskValue = (text) => "*".repeat(text.length);
|
|
3725
|
+
var MASK_EVERY_FIELD = {
|
|
3726
|
+
color: true,
|
|
3727
|
+
date: true,
|
|
3728
|
+
"datetime-local": true,
|
|
3729
|
+
email: true,
|
|
3730
|
+
month: true,
|
|
3731
|
+
number: true,
|
|
3732
|
+
range: true,
|
|
3733
|
+
search: true,
|
|
3734
|
+
tel: true,
|
|
3735
|
+
text: true,
|
|
3736
|
+
time: true,
|
|
3737
|
+
url: true,
|
|
3738
|
+
week: true,
|
|
3739
|
+
textarea: true,
|
|
3740
|
+
select: true,
|
|
3741
|
+
password: true,
|
|
3742
|
+
input: true
|
|
3743
|
+
};
|
|
3744
|
+
var SENSITIVE_AUTOCOMPLETE = /* @__PURE__ */ new Set([
|
|
3745
|
+
"cc-number",
|
|
3746
|
+
"cc-csc",
|
|
3747
|
+
"cc-exp",
|
|
3748
|
+
"cc-exp-month",
|
|
3749
|
+
"cc-exp-year",
|
|
3750
|
+
"cc-name",
|
|
3751
|
+
"cc-type",
|
|
3752
|
+
"cc-given-name",
|
|
3753
|
+
"cc-family-name",
|
|
3754
|
+
"cc-additional-name",
|
|
3755
|
+
"current-password",
|
|
3756
|
+
"new-password",
|
|
3757
|
+
"one-time-code"
|
|
3758
|
+
]);
|
|
3759
|
+
var PAYMENT_FIELD = new RegExp(
|
|
3760
|
+
"(?:credit|debit|payment)[\\s_-]*card|card[\\s_-]*(?:number|num|no|nr|code|pin|cvv|cvc|csc)|(?:^|[^a-z])cc[\\s_-]*(?:number|num|no|nr|csc|cvv|cvc|exp)|(?:^|[^a-z])(?:cvv|cvc|cvn|csc)(?:[^a-z]|$)|security[\\s_-]*code|(?:^|[^a-z])iban(?:[^a-z]|$)|routing[\\s_-]*number|sort[\\s_-]*code",
|
|
3761
|
+
"i"
|
|
3762
|
+
);
|
|
3763
|
+
var CREDENTIAL_FIELD = /pass(?:word|wd|phrase)|(?:^|[^a-z])pwd(?:[^a-z]|$)|(?:^|[^a-z])otp(?:[^a-z]|$)|one[\s_-]*time[\s_-]*code/i;
|
|
3764
|
+
var SENSITIVE_TYPES = /* @__PURE__ */ new Set(["password"]);
|
|
3765
|
+
var MASK_SENSITIVE_FIELDS = Object.fromEntries(
|
|
3766
|
+
[...SENSITIVE_TYPES].map((type) => [type, true])
|
|
3767
|
+
);
|
|
3768
|
+
var NAMING_ATTRIBUTES = ["name", "id", "autocomplete", "placeholder", "aria-label", "data-testid"];
|
|
3769
|
+
function isSensitiveByAttributes(read, type) {
|
|
3770
|
+
var _a;
|
|
3771
|
+
if (read("data-rr-is-password") !== null) return true;
|
|
3772
|
+
if (SENSITIVE_TYPES.has(type)) return true;
|
|
3773
|
+
const autocomplete = ((_a = read("autocomplete")) != null ? _a : "").toLowerCase();
|
|
3774
|
+
for (const token of autocomplete.split(/\s+/)) {
|
|
3775
|
+
if (SENSITIVE_AUTOCOMPLETE.has(token)) return true;
|
|
3776
|
+
}
|
|
3777
|
+
for (const attribute of NAMING_ATTRIBUTES) {
|
|
3778
|
+
const value = read(attribute);
|
|
3779
|
+
if (value && (PAYMENT_FIELD.test(value) || CREDENTIAL_FIELD.test(value))) return true;
|
|
3780
|
+
}
|
|
3781
|
+
return false;
|
|
3782
|
+
}
|
|
3783
|
+
var asElement = (node) => node && typeof node.getAttribute === "function" ? node : null;
|
|
3784
|
+
function isSensitiveField(node) {
|
|
3785
|
+
const element = asElement(node);
|
|
3786
|
+
if (!element) return false;
|
|
3787
|
+
const type = (element.type || element.getAttribute("type") || "").toLowerCase();
|
|
3788
|
+
return isSensitiveByAttributes((name) => element.getAttribute(name), type);
|
|
3789
|
+
}
|
|
3790
|
+
function isSensitiveSerialised(attributes) {
|
|
3791
|
+
var _a;
|
|
3792
|
+
const read = (name) => typeof attributes[name] === "string" ? attributes[name] : null;
|
|
3793
|
+
return isSensitiveByAttributes(read, ((_a = read("type")) != null ? _a : "").toLowerCase());
|
|
3794
|
+
}
|
|
3795
|
+
function looksLikeCardNumber(text) {
|
|
3796
|
+
if (text.length < 13 || text.length > 24 || !/^[0-9][0-9 -]*[0-9]$/.test(text)) return false;
|
|
3797
|
+
const digits = text.replace(/[ -]/g, "");
|
|
3798
|
+
if (digits.length < 13 || digits.length > 19) return false;
|
|
3799
|
+
let sum = 0;
|
|
3800
|
+
let double = false;
|
|
3801
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
3802
|
+
let digit = digits.charCodeAt(i) - 48;
|
|
3803
|
+
if (double) {
|
|
3804
|
+
digit *= 2;
|
|
3805
|
+
if (digit > 9) digit -= 9;
|
|
3806
|
+
}
|
|
3807
|
+
sum += digit;
|
|
3808
|
+
double = !double;
|
|
3809
|
+
}
|
|
3810
|
+
return sum % 10 === 0;
|
|
3811
|
+
}
|
|
3812
|
+
function isMarkedByCustomer(node, maskClass) {
|
|
3813
|
+
var _a;
|
|
3814
|
+
let element = asElement(node);
|
|
3815
|
+
while (element) {
|
|
3816
|
+
if ((_a = element.classList) == null ? void 0 : _a.contains(maskClass)) return true;
|
|
3817
|
+
element = element.parentElement;
|
|
3818
|
+
}
|
|
3819
|
+
return false;
|
|
3820
|
+
}
|
|
3821
|
+
var hasClass = (attributes, maskClass) => typeof attributes.class === "string" && attributes.class.split(/\s+/).includes(maskClass);
|
|
3822
|
+
function shouldMaskValue(text, node, policy) {
|
|
3823
|
+
if (policy.maskEveryField) return true;
|
|
3824
|
+
if (isSensitiveField(node)) return true;
|
|
3825
|
+
if (isMarkedByCustomer(node, policy.maskClass)) return true;
|
|
3826
|
+
return looksLikeCardNumber(text);
|
|
3827
|
+
}
|
|
3828
|
+
var MUTATION = 0;
|
|
3829
|
+
var INPUT = 5;
|
|
3830
|
+
var ELEMENT_NODE = 2;
|
|
3831
|
+
var TEXT_NODE = 3;
|
|
3832
|
+
var FIELD_TAGS = /* @__PURE__ */ new Set(["input", "textarea", "select"]);
|
|
3833
|
+
function maskTextChildren(node) {
|
|
3834
|
+
var _a;
|
|
3835
|
+
for (const child of (_a = node.childNodes) != null ? _a : []) {
|
|
3836
|
+
if (child.type === TEXT_NODE && typeof child.textContent === "string") {
|
|
3837
|
+
child.textContent = child.textContent.replace(/\S/g, "*");
|
|
3838
|
+
}
|
|
3839
|
+
maskTextChildren(child);
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
function scrubTree(node, policy, masked) {
|
|
3843
|
+
var _a, _b;
|
|
3844
|
+
if (!node) return;
|
|
3845
|
+
const attributes = (_a = node.attributes) != null ? _a : {};
|
|
3846
|
+
const inheritedMask = masked || hasClass(attributes, policy.maskClass);
|
|
3847
|
+
if (node.type === ELEMENT_NODE && node.tagName && FIELD_TAGS.has(node.tagName.toLowerCase())) {
|
|
3848
|
+
const value = attributes.value;
|
|
3849
|
+
const shouldMask = policy.maskEveryField || inheritedMask || isSensitiveSerialised(attributes) || typeof value === "string" && looksLikeCardNumber(value);
|
|
3850
|
+
if (shouldMask) {
|
|
3851
|
+
if (typeof value === "string" && value !== "") attributes.value = maskValue(value);
|
|
3852
|
+
if (node.tagName.toLowerCase() === "textarea") maskTextChildren(node);
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
for (const child of (_b = node.childNodes) != null ? _b : []) scrubTree(child, policy, inheritedMask);
|
|
3856
|
+
}
|
|
3857
|
+
function scrubEvent(event, getNode, policy) {
|
|
3858
|
+
var _a, _b, _c, _d, _e;
|
|
3859
|
+
if (!event.data) return;
|
|
3860
|
+
if (event.type === 2) {
|
|
3861
|
+
scrubTree(event.data.node, policy, false);
|
|
3862
|
+
return;
|
|
3863
|
+
}
|
|
3864
|
+
if (event.type !== 3) return;
|
|
3865
|
+
const data = event.data;
|
|
3866
|
+
if (data.source === INPUT) {
|
|
3867
|
+
if (typeof data.text !== "string" || data.text === "") return;
|
|
3868
|
+
const node = typeof data.id === "number" ? getNode(data.id) : null;
|
|
3869
|
+
const type = ((_b = (_a = asElement(node)) == null ? void 0 : _a.type) != null ? _b : "").toLowerCase();
|
|
3870
|
+
if (type === "checkbox" || type === "radio") return;
|
|
3871
|
+
if (shouldMaskValue(data.text, node, policy)) data.text = maskValue(data.text);
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
if (data.source !== MUTATION) return;
|
|
3875
|
+
for (const entry of (_c = data.attributes) != null ? _c : []) {
|
|
3876
|
+
const value = (_d = entry == null ? void 0 : entry.attributes) == null ? void 0 : _d.value;
|
|
3877
|
+
if (typeof value !== "string" || value === "") continue;
|
|
3878
|
+
const node = typeof entry.id === "number" ? getNode(entry.id) : null;
|
|
3879
|
+
if (shouldMaskValue(value, node, policy)) entry.attributes.value = maskValue(value);
|
|
3880
|
+
}
|
|
3881
|
+
for (const add of (_e = data.adds) != null ? _e : []) {
|
|
3882
|
+
const parent = typeof (add == null ? void 0 : add.parentId) === "number" ? getNode(add.parentId) : null;
|
|
3883
|
+
scrubTree(add == null ? void 0 : add.node, policy, isMarkedByCustomer(parent, policy.maskClass));
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3714
3887
|
// src/sampling.ts
|
|
3715
3888
|
function hashToUnitInterval(input) {
|
|
3716
3889
|
let hash = 2166136261;
|
|
@@ -3806,7 +3979,14 @@ var Transport = class {
|
|
|
3806
3979
|
seq: this.seq,
|
|
3807
3980
|
events,
|
|
3808
3981
|
...this.pendingMeta ? { meta: this.pendingMeta } : {},
|
|
3809
|
-
...Object.keys(this.flags).length > 0 ? { flags: this.flags } : {}
|
|
3982
|
+
...Object.keys(this.flags).length > 0 ? { flags: this.flags } : {},
|
|
3983
|
+
// This device's clock as the request leaves, on the same clock as the
|
|
3984
|
+
// event timestamps. Ingest compares it with its own to catch a device
|
|
3985
|
+
// that runs ahead — a test phone three weeks fast would otherwise list
|
|
3986
|
+
// its sessions as starting "in 3 weeks". Stamped here, when the body is
|
|
3987
|
+
// built, so a retry or a queue flushed after a spell offline carries
|
|
3988
|
+
// the time it was actually sent rather than when it was recorded.
|
|
3989
|
+
sentAt: Date.now()
|
|
3810
3990
|
});
|
|
3811
3991
|
}
|
|
3812
3992
|
/** Sends whatever is buffered. Safe to call when there is nothing to send. */
|
|
@@ -4007,47 +4187,37 @@ function reserveSeq(session, next) {
|
|
|
4007
4187
|
function touchSession(session, now = Date.now()) {
|
|
4008
4188
|
session.set(SESSION_TS_KEY, String(now));
|
|
4009
4189
|
}
|
|
4190
|
+
function forgetVisitor(local, session) {
|
|
4191
|
+
local.remove(VISITOR_KEY);
|
|
4192
|
+
for (const key of [SESSION_KEY, SESSION_TS_KEY, SEQ_KEY, PAGE_KEY, COOLDOWN_KEY]) session.remove(key);
|
|
4193
|
+
}
|
|
4010
4194
|
|
|
4011
4195
|
// src/recorder.ts
|
|
4012
4196
|
var RAGE_CLICK_COUNT = 3;
|
|
4013
4197
|
var RAGE_CLICK_WINDOW_MS = 1e3;
|
|
4014
4198
|
var RAGE_CLICK_RADIUS_PX = 30;
|
|
4015
4199
|
var MAX_PENDING_EVENTS = 32;
|
|
4200
|
+
var EDITABLE_SELECTOR = '[contenteditable]:not([contenteditable="false"])';
|
|
4016
4201
|
function createRecorder(rawOptions) {
|
|
4017
4202
|
const options = resolveOptions(rawOptions);
|
|
4018
4203
|
const local = safeStorage(() => globalThis.localStorage);
|
|
4019
4204
|
const session = safeStorage(() => globalThis.sessionStorage);
|
|
4020
|
-
const identity = resolveIdentity(local, session);
|
|
4021
4205
|
let status = "idle";
|
|
4206
|
+
let identity;
|
|
4207
|
+
let transport;
|
|
4022
4208
|
let stopRecording;
|
|
4023
|
-
let pageCount =
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
sessionId: identity.sessionId,
|
|
4036
|
-
visitorId: identity.visitorId,
|
|
4037
|
-
initialSeq: identity.nextSeq
|
|
4038
|
-
},
|
|
4039
|
-
{
|
|
4040
|
-
onSeqReserved: (next) => reserveSeq(session, next),
|
|
4041
|
-
onStopped: (reason) => {
|
|
4042
|
-
status = "stopped";
|
|
4043
|
-
stopRecording == null ? void 0 : stopRecording();
|
|
4044
|
-
stopRecording = void 0;
|
|
4045
|
-
if (reason === "rejected_402") startCooldown(session);
|
|
4046
|
-
if (options.debug) console.warn(`[anyreplay] recording stopped: ${reason}`);
|
|
4047
|
-
}
|
|
4048
|
-
}
|
|
4049
|
-
);
|
|
4050
|
-
const collectMeta = () => ({
|
|
4209
|
+
let pageCount = 0;
|
|
4210
|
+
let pendingTraits;
|
|
4211
|
+
const sendIdentity = (who, traits) => {
|
|
4212
|
+
void fetch(`${options.ingestUrl}/v1/ingest/identify`, {
|
|
4213
|
+
method: "POST",
|
|
4214
|
+
headers: { "Content-Type": "application/json" },
|
|
4215
|
+
credentials: "omit",
|
|
4216
|
+
body: JSON.stringify({ projectKey: options.projectKey, sessionId: who.sessionId, ...traits })
|
|
4217
|
+
}).catch(() => {
|
|
4218
|
+
});
|
|
4219
|
+
};
|
|
4220
|
+
const collectMeta = (who) => ({
|
|
4051
4221
|
startedAt: Date.now(),
|
|
4052
4222
|
url: safeLocation(),
|
|
4053
4223
|
lang: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
@@ -4057,10 +4227,10 @@ function createRecorder(rawOptions) {
|
|
|
4057
4227
|
referrer: typeof document !== "undefined" ? document.referrer || void 0 : void 0,
|
|
4058
4228
|
// The cap is a decision about starting a session, so it goes only where
|
|
4059
4229
|
// ingest makes that decision: chunk 0. A resumed page starts later.
|
|
4060
|
-
...options.maxSessionsPerMonth !== void 0 &&
|
|
4230
|
+
...options.maxSessionsPerMonth !== void 0 && who.nextSeq === 0 ? { sessionCap: options.maxSessionsPerMonth } : {}
|
|
4061
4231
|
});
|
|
4062
4232
|
const recentClicks = [];
|
|
4063
|
-
const noteInteraction = (event) => {
|
|
4233
|
+
const noteInteraction = (event, channel) => {
|
|
4064
4234
|
var _a;
|
|
4065
4235
|
const data = event.data;
|
|
4066
4236
|
if (event.type !== 3 || (data == null ? void 0 : data.source) !== 2 || typeof data.x !== "number") return;
|
|
@@ -4072,7 +4242,7 @@ function createRecorder(rawOptions) {
|
|
|
4072
4242
|
const clustered = recentClicks.every(
|
|
4073
4243
|
(c) => Math.abs(c.x - first.x) < RAGE_CLICK_RADIUS_PX && Math.abs(c.y - first.y) < RAGE_CLICK_RADIUS_PX
|
|
4074
4244
|
);
|
|
4075
|
-
if (clustered)
|
|
4245
|
+
if (clustered) channel.mergeFlags({ hasRageClick: true });
|
|
4076
4246
|
}
|
|
4077
4247
|
};
|
|
4078
4248
|
const pending = [];
|
|
@@ -4100,7 +4270,7 @@ function createRecorder(rawOptions) {
|
|
|
4100
4270
|
}
|
|
4101
4271
|
};
|
|
4102
4272
|
const emitCustom = (tag, payload) => {
|
|
4103
|
-
if (status === "stopped") return;
|
|
4273
|
+
if (status === "stopped" || status === "sampled-out") return;
|
|
4104
4274
|
if (status === "recording") {
|
|
4105
4275
|
try {
|
|
4106
4276
|
record.addCustomEvent(tag, payload);
|
|
@@ -4114,63 +4284,104 @@ function createRecorder(rawOptions) {
|
|
|
4114
4284
|
}
|
|
4115
4285
|
pending.push({ tag, payload });
|
|
4116
4286
|
};
|
|
4117
|
-
let lastUrl
|
|
4118
|
-
const noteNavigation = () => {
|
|
4287
|
+
let lastUrl;
|
|
4288
|
+
const noteNavigation = (channel) => {
|
|
4119
4289
|
const url = safeLocation();
|
|
4120
4290
|
if (!url || url === lastUrl) return;
|
|
4121
4291
|
lastUrl = url;
|
|
4122
4292
|
pageCount += 1;
|
|
4123
4293
|
notePageCount(session, pageCount);
|
|
4124
|
-
|
|
4294
|
+
channel.mergeFlags({ pageCount });
|
|
4125
4295
|
emitCustom(NAVIGATE_TAG, { url, title: typeof document !== "undefined" ? document.title : "" });
|
|
4126
4296
|
};
|
|
4127
4297
|
const begin = () => {
|
|
4128
|
-
if (status
|
|
4298
|
+
if (status !== "idle" && status !== "awaiting-consent") return;
|
|
4299
|
+
const who = resolveIdentity(local, session);
|
|
4300
|
+
identity = who;
|
|
4301
|
+
pageCount = who.pageCount;
|
|
4302
|
+
if (!isSampledIn(who.visitorId, options.sampleRate)) {
|
|
4303
|
+
status = "sampled-out";
|
|
4304
|
+
return;
|
|
4305
|
+
}
|
|
4306
|
+
if (inCooldown(session)) {
|
|
4307
|
+
status = "stopped";
|
|
4308
|
+
if (options.debug) console.warn("[anyreplay] not recording: ingest refused a new session recently");
|
|
4309
|
+
return;
|
|
4310
|
+
}
|
|
4311
|
+
const channel = new Transport(
|
|
4312
|
+
options,
|
|
4313
|
+
{
|
|
4314
|
+
projectKey: options.projectKey,
|
|
4315
|
+
sessionId: who.sessionId,
|
|
4316
|
+
visitorId: who.visitorId,
|
|
4317
|
+
initialSeq: who.nextSeq
|
|
4318
|
+
},
|
|
4319
|
+
{
|
|
4320
|
+
onSeqReserved: (next) => reserveSeq(session, next),
|
|
4321
|
+
onStopped: (reason) => {
|
|
4322
|
+
status = "stopped";
|
|
4323
|
+
stopRecording == null ? void 0 : stopRecording();
|
|
4324
|
+
stopRecording = void 0;
|
|
4325
|
+
if (reason === "rejected_402") startCooldown(session);
|
|
4326
|
+
if (options.debug) console.warn(`[anyreplay] recording stopped: ${reason}`);
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
);
|
|
4330
|
+
transport = channel;
|
|
4129
4331
|
status = "recording";
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4332
|
+
lastUrl = safeLocation();
|
|
4333
|
+
channel.setMeta(collectMeta(who));
|
|
4334
|
+
channel.mergeFlags({ pageCount });
|
|
4335
|
+
channel.start();
|
|
4336
|
+
const policy = {
|
|
4337
|
+
maskEveryField: options.maskAllInputs || options.maskAllTyping,
|
|
4338
|
+
maskClass: options.maskClass
|
|
4339
|
+
};
|
|
4133
4340
|
stopRecording = record({
|
|
4134
4341
|
emit: (event) => {
|
|
4135
4342
|
const typed = event;
|
|
4136
|
-
|
|
4343
|
+
scrubEvent(typed, (id) => record.mirror.getNode(id), policy);
|
|
4344
|
+
noteInteraction(typed, channel);
|
|
4137
4345
|
touchSession(session, typed.timestamp);
|
|
4138
|
-
|
|
4346
|
+
channel.push(typed);
|
|
4139
4347
|
drainPending();
|
|
4140
4348
|
},
|
|
4141
|
-
|
|
4349
|
+
maskInputOptions: policy.maskEveryField ? MASK_EVERY_FIELD : MASK_SENSITIVE_FIELDS,
|
|
4142
4350
|
maskTextClass: options.maskClass,
|
|
4351
|
+
maskTextSelector: options.maskAllTyping ? EDITABLE_SELECTOR : void 0,
|
|
4143
4352
|
blockClass: options.blockClass,
|
|
4144
|
-
// Passwords are never recorded, regardless of configuration.
|
|
4145
|
-
maskInputOptions: { password: true, email: true, tel: true },
|
|
4146
4353
|
// Canvas capture is expensive and off unless a customer opts in later.
|
|
4147
4354
|
recordCanvas: false,
|
|
4148
4355
|
collectFonts: false
|
|
4149
4356
|
});
|
|
4150
4357
|
drainPending();
|
|
4151
|
-
|
|
4358
|
+
if (pendingTraits) {
|
|
4359
|
+
sendIdentity(who, pendingTraits);
|
|
4360
|
+
pendingTraits = void 0;
|
|
4361
|
+
}
|
|
4362
|
+
attachLifecycleHooks(channel);
|
|
4152
4363
|
};
|
|
4153
|
-
const attachLifecycleHooks = () => {
|
|
4364
|
+
const attachLifecycleHooks = (channel) => {
|
|
4154
4365
|
if (typeof window === "undefined") return;
|
|
4155
4366
|
window.addEventListener("pagehide", () => {
|
|
4156
|
-
|
|
4367
|
+
channel.flushWithBeacon();
|
|
4157
4368
|
}, { capture: true });
|
|
4158
4369
|
document.addEventListener("visibilitychange", () => {
|
|
4159
|
-
if (document.visibilityState === "hidden")
|
|
4370
|
+
if (document.visibilityState === "hidden") channel.flushWithBeacon();
|
|
4160
4371
|
});
|
|
4161
|
-
window.addEventListener("error", () =>
|
|
4162
|
-
window.addEventListener("unhandledrejection", () =>
|
|
4372
|
+
window.addEventListener("error", () => channel.mergeFlags({ hasError: true }));
|
|
4373
|
+
window.addEventListener("unhandledrejection", () => channel.mergeFlags({ hasError: true }));
|
|
4163
4374
|
const patch2 = (method) => {
|
|
4164
4375
|
const original = history[method].bind(history);
|
|
4165
4376
|
history[method] = function patched(...args) {
|
|
4166
4377
|
const result = original(...args);
|
|
4167
|
-
noteNavigation();
|
|
4378
|
+
noteNavigation(channel);
|
|
4168
4379
|
return result;
|
|
4169
4380
|
};
|
|
4170
4381
|
};
|
|
4171
4382
|
patch2("pushState");
|
|
4172
4383
|
patch2("replaceState");
|
|
4173
|
-
window.addEventListener("popstate", noteNavigation);
|
|
4384
|
+
window.addEventListener("popstate", () => noteNavigation(channel));
|
|
4174
4385
|
};
|
|
4175
4386
|
if (options.requireConsent) {
|
|
4176
4387
|
status = "awaiting-consent";
|
|
@@ -4179,24 +4390,28 @@ function createRecorder(rawOptions) {
|
|
|
4179
4390
|
}
|
|
4180
4391
|
return {
|
|
4181
4392
|
status: () => status,
|
|
4182
|
-
sessionId: () =>
|
|
4393
|
+
sessionId: () => {
|
|
4394
|
+
var _a;
|
|
4395
|
+
return (_a = identity == null ? void 0 : identity.sessionId) != null ? _a : null;
|
|
4396
|
+
},
|
|
4183
4397
|
consent: (granted) => {
|
|
4184
|
-
if (granted)
|
|
4185
|
-
|
|
4398
|
+
if (granted) {
|
|
4399
|
+
begin();
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
transport == null ? void 0 : transport.stop("consent_withdrawn");
|
|
4403
|
+
if (status === "idle" || status === "awaiting-consent") status = "stopped";
|
|
4404
|
+
pendingTraits = void 0;
|
|
4405
|
+
forgetVisitor(local, session);
|
|
4186
4406
|
},
|
|
4187
4407
|
identify: (traits) => {
|
|
4188
4408
|
if (!traits.userId && !traits.email) return;
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
sessionId: identity.sessionId,
|
|
4196
|
-
...traits
|
|
4197
|
-
})
|
|
4198
|
-
}).catch(() => {
|
|
4199
|
-
});
|
|
4409
|
+
if (identity && transport) {
|
|
4410
|
+
sendIdentity(identity, traits);
|
|
4411
|
+
return;
|
|
4412
|
+
}
|
|
4413
|
+
if (status === "idle" || status === "awaiting-consent") pendingTraits = traits;
|
|
4414
|
+
else if (options.debug) console.warn("[anyreplay] identify() ignored: not recording");
|
|
4200
4415
|
},
|
|
4201
4416
|
track: (name, properties) => {
|
|
4202
4417
|
const payload = validateTrack(name, properties);
|
|
@@ -4206,8 +4421,15 @@ function createRecorder(rawOptions) {
|
|
|
4206
4421
|
}
|
|
4207
4422
|
emitCustom(TRACK_TAG, payload);
|
|
4208
4423
|
},
|
|
4209
|
-
stop: () =>
|
|
4210
|
-
|
|
4424
|
+
stop: () => {
|
|
4425
|
+
transport == null ? void 0 : transport.stop("stopped_by_page");
|
|
4426
|
+
if (status === "idle" || status === "awaiting-consent") status = "stopped";
|
|
4427
|
+
pendingTraits = void 0;
|
|
4428
|
+
},
|
|
4429
|
+
flush: () => {
|
|
4430
|
+
var _a;
|
|
4431
|
+
return (_a = transport == null ? void 0 : transport.flush()) != null ? _a : Promise.resolve();
|
|
4432
|
+
}
|
|
4211
4433
|
};
|
|
4212
4434
|
}
|
|
4213
4435
|
function safeLocation() {
|
|
@@ -4217,22 +4439,6 @@ function safeLocation() {
|
|
|
4217
4439
|
return void 0;
|
|
4218
4440
|
}
|
|
4219
4441
|
}
|
|
4220
|
-
function inertHandle(status, sessionId) {
|
|
4221
|
-
return {
|
|
4222
|
-
status: () => status,
|
|
4223
|
-
sessionId: () => sessionId,
|
|
4224
|
-
consent: () => {
|
|
4225
|
-
},
|
|
4226
|
-
identify: () => {
|
|
4227
|
-
},
|
|
4228
|
-
track: () => {
|
|
4229
|
-
},
|
|
4230
|
-
stop: () => {
|
|
4231
|
-
},
|
|
4232
|
-
flush: async () => {
|
|
4233
|
-
}
|
|
4234
|
-
};
|
|
4235
|
-
}
|
|
4236
4442
|
export {
|
|
4237
4443
|
ConfigError,
|
|
4238
4444
|
DEFAULTS,
|
|
@@ -4242,6 +4448,7 @@ export {
|
|
|
4242
4448
|
TRACK_TAG,
|
|
4243
4449
|
Transport,
|
|
4244
4450
|
createRecorder,
|
|
4451
|
+
forgetVisitor,
|
|
4245
4452
|
hashToUnitInterval,
|
|
4246
4453
|
isSampledIn,
|
|
4247
4454
|
resolveIdentity,
|