@anyreplay/browser 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +105 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -5
- package/dist/index.d.ts +18 -5
- package/dist/index.js +104 -82
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ __export(src_exports, {
|
|
|
30
30
|
TRACK_TAG: () => TRACK_TAG,
|
|
31
31
|
Transport: () => Transport,
|
|
32
32
|
createRecorder: () => createRecorder,
|
|
33
|
+
forgetVisitor: () => forgetVisitor,
|
|
33
34
|
hashToUnitInterval: () => hashToUnitInterval,
|
|
34
35
|
isSampledIn: () => isSampledIn,
|
|
35
36
|
resolveIdentity: () => resolveIdentity,
|
|
@@ -4039,6 +4040,10 @@ function reserveSeq(session, next) {
|
|
|
4039
4040
|
function touchSession(session, now = Date.now()) {
|
|
4040
4041
|
session.set(SESSION_TS_KEY, String(now));
|
|
4041
4042
|
}
|
|
4043
|
+
function forgetVisitor(local, session) {
|
|
4044
|
+
local.remove(VISITOR_KEY);
|
|
4045
|
+
for (const key of [SESSION_KEY, SESSION_TS_KEY, SEQ_KEY, PAGE_KEY, COOLDOWN_KEY]) session.remove(key);
|
|
4046
|
+
}
|
|
4042
4047
|
|
|
4043
4048
|
// src/recorder.ts
|
|
4044
4049
|
var RAGE_CLICK_COUNT = 3;
|
|
@@ -4049,37 +4054,22 @@ function createRecorder(rawOptions) {
|
|
|
4049
4054
|
const options = resolveOptions(rawOptions);
|
|
4050
4055
|
const local = safeStorage(() => globalThis.localStorage);
|
|
4051
4056
|
const session = safeStorage(() => globalThis.sessionStorage);
|
|
4052
|
-
const identity = resolveIdentity(local, session);
|
|
4053
4057
|
let status = "idle";
|
|
4058
|
+
let identity;
|
|
4059
|
+
let transport;
|
|
4054
4060
|
let stopRecording;
|
|
4055
|
-
let pageCount =
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
sessionId: identity.sessionId,
|
|
4068
|
-
visitorId: identity.visitorId,
|
|
4069
|
-
initialSeq: identity.nextSeq
|
|
4070
|
-
},
|
|
4071
|
-
{
|
|
4072
|
-
onSeqReserved: (next) => reserveSeq(session, next),
|
|
4073
|
-
onStopped: (reason) => {
|
|
4074
|
-
status = "stopped";
|
|
4075
|
-
stopRecording == null ? void 0 : stopRecording();
|
|
4076
|
-
stopRecording = void 0;
|
|
4077
|
-
if (reason === "rejected_402") startCooldown(session);
|
|
4078
|
-
if (options.debug) console.warn(`[anyreplay] recording stopped: ${reason}`);
|
|
4079
|
-
}
|
|
4080
|
-
}
|
|
4081
|
-
);
|
|
4082
|
-
const collectMeta = () => ({
|
|
4061
|
+
let pageCount = 0;
|
|
4062
|
+
let pendingTraits;
|
|
4063
|
+
const sendIdentity = (who, traits) => {
|
|
4064
|
+
void fetch(`${options.ingestUrl}/v1/ingest/identify`, {
|
|
4065
|
+
method: "POST",
|
|
4066
|
+
headers: { "Content-Type": "application/json" },
|
|
4067
|
+
credentials: "omit",
|
|
4068
|
+
body: JSON.stringify({ projectKey: options.projectKey, sessionId: who.sessionId, ...traits })
|
|
4069
|
+
}).catch(() => {
|
|
4070
|
+
});
|
|
4071
|
+
};
|
|
4072
|
+
const collectMeta = (who) => ({
|
|
4083
4073
|
startedAt: Date.now(),
|
|
4084
4074
|
url: safeLocation(),
|
|
4085
4075
|
lang: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
@@ -4089,10 +4079,10 @@ function createRecorder(rawOptions) {
|
|
|
4089
4079
|
referrer: typeof document !== "undefined" ? document.referrer || void 0 : void 0,
|
|
4090
4080
|
// The cap is a decision about starting a session, so it goes only where
|
|
4091
4081
|
// ingest makes that decision: chunk 0. A resumed page starts later.
|
|
4092
|
-
...options.maxSessionsPerMonth !== void 0 &&
|
|
4082
|
+
...options.maxSessionsPerMonth !== void 0 && who.nextSeq === 0 ? { sessionCap: options.maxSessionsPerMonth } : {}
|
|
4093
4083
|
});
|
|
4094
4084
|
const recentClicks = [];
|
|
4095
|
-
const noteInteraction = (event) => {
|
|
4085
|
+
const noteInteraction = (event, channel) => {
|
|
4096
4086
|
var _a;
|
|
4097
4087
|
const data = event.data;
|
|
4098
4088
|
if (event.type !== 3 || (data == null ? void 0 : data.source) !== 2 || typeof data.x !== "number") return;
|
|
@@ -4104,7 +4094,7 @@ function createRecorder(rawOptions) {
|
|
|
4104
4094
|
const clustered = recentClicks.every(
|
|
4105
4095
|
(c) => Math.abs(c.x - first.x) < RAGE_CLICK_RADIUS_PX && Math.abs(c.y - first.y) < RAGE_CLICK_RADIUS_PX
|
|
4106
4096
|
);
|
|
4107
|
-
if (clustered)
|
|
4097
|
+
if (clustered) channel.mergeFlags({ hasRageClick: true });
|
|
4108
4098
|
}
|
|
4109
4099
|
};
|
|
4110
4100
|
const pending = [];
|
|
@@ -4132,7 +4122,7 @@ function createRecorder(rawOptions) {
|
|
|
4132
4122
|
}
|
|
4133
4123
|
};
|
|
4134
4124
|
const emitCustom = (tag, payload) => {
|
|
4135
|
-
if (status === "stopped") return;
|
|
4125
|
+
if (status === "stopped" || status === "sampled-out") return;
|
|
4136
4126
|
if (status === "recording") {
|
|
4137
4127
|
try {
|
|
4138
4128
|
record.addCustomEvent(tag, payload);
|
|
@@ -4146,28 +4136,61 @@ function createRecorder(rawOptions) {
|
|
|
4146
4136
|
}
|
|
4147
4137
|
pending.push({ tag, payload });
|
|
4148
4138
|
};
|
|
4149
|
-
let lastUrl
|
|
4150
|
-
const noteNavigation = () => {
|
|
4139
|
+
let lastUrl;
|
|
4140
|
+
const noteNavigation = (channel) => {
|
|
4151
4141
|
const url = safeLocation();
|
|
4152
4142
|
if (!url || url === lastUrl) return;
|
|
4153
4143
|
lastUrl = url;
|
|
4154
4144
|
pageCount += 1;
|
|
4155
4145
|
notePageCount(session, pageCount);
|
|
4156
|
-
|
|
4146
|
+
channel.mergeFlags({ pageCount });
|
|
4157
4147
|
emitCustom(NAVIGATE_TAG, { url, title: typeof document !== "undefined" ? document.title : "" });
|
|
4158
4148
|
};
|
|
4159
4149
|
const begin = () => {
|
|
4160
|
-
if (status
|
|
4150
|
+
if (status !== "idle" && status !== "awaiting-consent") return;
|
|
4151
|
+
const who = resolveIdentity(local, session);
|
|
4152
|
+
identity = who;
|
|
4153
|
+
pageCount = who.pageCount;
|
|
4154
|
+
if (!isSampledIn(who.visitorId, options.sampleRate)) {
|
|
4155
|
+
status = "sampled-out";
|
|
4156
|
+
return;
|
|
4157
|
+
}
|
|
4158
|
+
if (inCooldown(session)) {
|
|
4159
|
+
status = "stopped";
|
|
4160
|
+
if (options.debug) console.warn("[anyreplay] not recording: ingest refused a new session recently");
|
|
4161
|
+
return;
|
|
4162
|
+
}
|
|
4163
|
+
const channel = new Transport(
|
|
4164
|
+
options,
|
|
4165
|
+
{
|
|
4166
|
+
projectKey: options.projectKey,
|
|
4167
|
+
sessionId: who.sessionId,
|
|
4168
|
+
visitorId: who.visitorId,
|
|
4169
|
+
initialSeq: who.nextSeq
|
|
4170
|
+
},
|
|
4171
|
+
{
|
|
4172
|
+
onSeqReserved: (next) => reserveSeq(session, next),
|
|
4173
|
+
onStopped: (reason) => {
|
|
4174
|
+
status = "stopped";
|
|
4175
|
+
stopRecording == null ? void 0 : stopRecording();
|
|
4176
|
+
stopRecording = void 0;
|
|
4177
|
+
if (reason === "rejected_402") startCooldown(session);
|
|
4178
|
+
if (options.debug) console.warn(`[anyreplay] recording stopped: ${reason}`);
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
);
|
|
4182
|
+
transport = channel;
|
|
4161
4183
|
status = "recording";
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4184
|
+
lastUrl = safeLocation();
|
|
4185
|
+
channel.setMeta(collectMeta(who));
|
|
4186
|
+
channel.mergeFlags({ pageCount });
|
|
4187
|
+
channel.start();
|
|
4165
4188
|
stopRecording = record({
|
|
4166
4189
|
emit: (event) => {
|
|
4167
4190
|
const typed = event;
|
|
4168
|
-
noteInteraction(typed);
|
|
4191
|
+
noteInteraction(typed, channel);
|
|
4169
4192
|
touchSession(session, typed.timestamp);
|
|
4170
|
-
|
|
4193
|
+
channel.push(typed);
|
|
4171
4194
|
drainPending();
|
|
4172
4195
|
},
|
|
4173
4196
|
maskAllInputs: options.maskAllInputs,
|
|
@@ -4180,29 +4203,33 @@ function createRecorder(rawOptions) {
|
|
|
4180
4203
|
collectFonts: false
|
|
4181
4204
|
});
|
|
4182
4205
|
drainPending();
|
|
4183
|
-
|
|
4206
|
+
if (pendingTraits) {
|
|
4207
|
+
sendIdentity(who, pendingTraits);
|
|
4208
|
+
pendingTraits = void 0;
|
|
4209
|
+
}
|
|
4210
|
+
attachLifecycleHooks(channel);
|
|
4184
4211
|
};
|
|
4185
|
-
const attachLifecycleHooks = () => {
|
|
4212
|
+
const attachLifecycleHooks = (channel) => {
|
|
4186
4213
|
if (typeof window === "undefined") return;
|
|
4187
4214
|
window.addEventListener("pagehide", () => {
|
|
4188
|
-
|
|
4215
|
+
channel.flushWithBeacon();
|
|
4189
4216
|
}, { capture: true });
|
|
4190
4217
|
document.addEventListener("visibilitychange", () => {
|
|
4191
|
-
if (document.visibilityState === "hidden")
|
|
4218
|
+
if (document.visibilityState === "hidden") channel.flushWithBeacon();
|
|
4192
4219
|
});
|
|
4193
|
-
window.addEventListener("error", () =>
|
|
4194
|
-
window.addEventListener("unhandledrejection", () =>
|
|
4220
|
+
window.addEventListener("error", () => channel.mergeFlags({ hasError: true }));
|
|
4221
|
+
window.addEventListener("unhandledrejection", () => channel.mergeFlags({ hasError: true }));
|
|
4195
4222
|
const patch2 = (method) => {
|
|
4196
4223
|
const original = history[method].bind(history);
|
|
4197
4224
|
history[method] = function patched(...args) {
|
|
4198
4225
|
const result = original(...args);
|
|
4199
|
-
noteNavigation();
|
|
4226
|
+
noteNavigation(channel);
|
|
4200
4227
|
return result;
|
|
4201
4228
|
};
|
|
4202
4229
|
};
|
|
4203
4230
|
patch2("pushState");
|
|
4204
4231
|
patch2("replaceState");
|
|
4205
|
-
window.addEventListener("popstate", noteNavigation);
|
|
4232
|
+
window.addEventListener("popstate", () => noteNavigation(channel));
|
|
4206
4233
|
};
|
|
4207
4234
|
if (options.requireConsent) {
|
|
4208
4235
|
status = "awaiting-consent";
|
|
@@ -4211,24 +4238,28 @@ function createRecorder(rawOptions) {
|
|
|
4211
4238
|
}
|
|
4212
4239
|
return {
|
|
4213
4240
|
status: () => status,
|
|
4214
|
-
sessionId: () =>
|
|
4241
|
+
sessionId: () => {
|
|
4242
|
+
var _a;
|
|
4243
|
+
return (_a = identity == null ? void 0 : identity.sessionId) != null ? _a : null;
|
|
4244
|
+
},
|
|
4215
4245
|
consent: (granted) => {
|
|
4216
|
-
if (granted)
|
|
4217
|
-
|
|
4246
|
+
if (granted) {
|
|
4247
|
+
begin();
|
|
4248
|
+
return;
|
|
4249
|
+
}
|
|
4250
|
+
transport == null ? void 0 : transport.stop("consent_withdrawn");
|
|
4251
|
+
if (status === "idle" || status === "awaiting-consent") status = "stopped";
|
|
4252
|
+
pendingTraits = void 0;
|
|
4253
|
+
forgetVisitor(local, session);
|
|
4218
4254
|
},
|
|
4219
4255
|
identify: (traits) => {
|
|
4220
4256
|
if (!traits.userId && !traits.email) return;
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
sessionId: identity.sessionId,
|
|
4228
|
-
...traits
|
|
4229
|
-
})
|
|
4230
|
-
}).catch(() => {
|
|
4231
|
-
});
|
|
4257
|
+
if (identity && transport) {
|
|
4258
|
+
sendIdentity(identity, traits);
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
4261
|
+
if (status === "idle" || status === "awaiting-consent") pendingTraits = traits;
|
|
4262
|
+
else if (options.debug) console.warn("[anyreplay] identify() ignored: not recording");
|
|
4232
4263
|
},
|
|
4233
4264
|
track: (name, properties) => {
|
|
4234
4265
|
const payload = validateTrack(name, properties);
|
|
@@ -4238,8 +4269,15 @@ function createRecorder(rawOptions) {
|
|
|
4238
4269
|
}
|
|
4239
4270
|
emitCustom(TRACK_TAG, payload);
|
|
4240
4271
|
},
|
|
4241
|
-
stop: () =>
|
|
4242
|
-
|
|
4272
|
+
stop: () => {
|
|
4273
|
+
transport == null ? void 0 : transport.stop("stopped_by_page");
|
|
4274
|
+
if (status === "idle" || status === "awaiting-consent") status = "stopped";
|
|
4275
|
+
pendingTraits = void 0;
|
|
4276
|
+
},
|
|
4277
|
+
flush: () => {
|
|
4278
|
+
var _a;
|
|
4279
|
+
return (_a = transport == null ? void 0 : transport.flush()) != null ? _a : Promise.resolve();
|
|
4280
|
+
}
|
|
4243
4281
|
};
|
|
4244
4282
|
}
|
|
4245
4283
|
function safeLocation() {
|
|
@@ -4249,22 +4287,6 @@ function safeLocation() {
|
|
|
4249
4287
|
return void 0;
|
|
4250
4288
|
}
|
|
4251
4289
|
}
|
|
4252
|
-
function inertHandle(status, sessionId) {
|
|
4253
|
-
return {
|
|
4254
|
-
status: () => status,
|
|
4255
|
-
sessionId: () => sessionId,
|
|
4256
|
-
consent: () => {
|
|
4257
|
-
},
|
|
4258
|
-
identify: () => {
|
|
4259
|
-
},
|
|
4260
|
-
track: () => {
|
|
4261
|
-
},
|
|
4262
|
-
stop: () => {
|
|
4263
|
-
},
|
|
4264
|
-
flush: async () => {
|
|
4265
|
-
}
|
|
4266
|
-
};
|
|
4267
|
-
}
|
|
4268
4290
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4269
4291
|
0 && (module.exports = {
|
|
4270
4292
|
ConfigError,
|
|
@@ -4275,6 +4297,7 @@ function inertHandle(status, sessionId) {
|
|
|
4275
4297
|
TRACK_TAG,
|
|
4276
4298
|
Transport,
|
|
4277
4299
|
createRecorder,
|
|
4300
|
+
forgetVisitor,
|
|
4278
4301
|
hashToUnitInterval,
|
|
4279
4302
|
isSampledIn,
|
|
4280
4303
|
resolveIdentity,
|