@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.d.cts
CHANGED
|
@@ -40,14 +40,15 @@ declare class ConfigError extends Error {
|
|
|
40
40
|
declare function resolveOptions(input: AnyReplayOptions): ResolvedOptions;
|
|
41
41
|
|
|
42
42
|
type RecorderStatus = 'idle' | 'awaiting-consent' | 'recording' | 'sampled-out' | 'stopped';
|
|
43
|
+
interface IdentifyTraits {
|
|
44
|
+
userId?: string;
|
|
45
|
+
email?: string;
|
|
46
|
+
}
|
|
43
47
|
interface RecorderHandle {
|
|
44
48
|
status: () => RecorderStatus;
|
|
45
49
|
sessionId: () => string | null;
|
|
46
50
|
consent: (granted: boolean) => void;
|
|
47
|
-
identify: (traits:
|
|
48
|
-
userId?: string;
|
|
49
|
-
email?: string;
|
|
50
|
-
}) => void;
|
|
51
|
+
identify: (traits: IdentifyTraits) => void;
|
|
51
52
|
/**
|
|
52
53
|
* Tags this moment of the recording so it shows on the player's timeline.
|
|
53
54
|
*
|
|
@@ -199,6 +200,18 @@ interface Identity {
|
|
|
199
200
|
* id resets after a period of inactivity.
|
|
200
201
|
*/
|
|
201
202
|
declare function resolveIdentity(local: Storage, session: Storage, now?: number): Identity;
|
|
203
|
+
/**
|
|
204
|
+
* Removes everything the recorder has stored in this browser.
|
|
205
|
+
*
|
|
206
|
+
* Called when consent is refused. A refusal has to undo an earlier acceptance,
|
|
207
|
+
* not merely stop the current page: the visitor id written last week is
|
|
208
|
+
* exactly what the visitor is now saying no to, and leaving it in place would
|
|
209
|
+
* make "we store nothing without consent" untrue for as long as it sat there.
|
|
210
|
+
* The cool-down goes too — it is a fact about a session that no longer exists.
|
|
211
|
+
* Removing a key that is not there creates nothing, so this is safe to call
|
|
212
|
+
* before anything was ever written.
|
|
213
|
+
*/
|
|
214
|
+
declare function forgetVisitor(local: Storage, session: Storage): void;
|
|
202
215
|
|
|
203
216
|
/**
|
|
204
217
|
* Custom events: the moments a page tags with `track()`.
|
|
@@ -225,4 +238,4 @@ interface TrackPayload {
|
|
|
225
238
|
*/
|
|
226
239
|
declare function validateTrack(name: unknown, properties: unknown): TrackPayload | string;
|
|
227
240
|
|
|
228
|
-
export { type AnyReplayOptions, ConfigError, DEFAULTS, EVENT_NAME_PATTERN, MAX_PROPERTIES_BYTES, NAVIGATE_TAG, type RecordedEvent, type RecorderHandle, type RecorderStatus, type ResolvedOptions, type SessionMeta, TRACK_TAG, type TrackPayload, Transport, createRecorder, hashToUnitInterval, isSampledIn, resolveIdentity, resolveOptions, safeStorage, uuidv4, validateTrack };
|
|
241
|
+
export { type AnyReplayOptions, ConfigError, DEFAULTS, EVENT_NAME_PATTERN, MAX_PROPERTIES_BYTES, NAVIGATE_TAG, type RecordedEvent, type RecorderHandle, type RecorderStatus, type ResolvedOptions, type SessionMeta, TRACK_TAG, type TrackPayload, Transport, createRecorder, forgetVisitor, hashToUnitInterval, isSampledIn, resolveIdentity, resolveOptions, safeStorage, uuidv4, validateTrack };
|
package/dist/index.d.ts
CHANGED
|
@@ -40,14 +40,15 @@ declare class ConfigError extends Error {
|
|
|
40
40
|
declare function resolveOptions(input: AnyReplayOptions): ResolvedOptions;
|
|
41
41
|
|
|
42
42
|
type RecorderStatus = 'idle' | 'awaiting-consent' | 'recording' | 'sampled-out' | 'stopped';
|
|
43
|
+
interface IdentifyTraits {
|
|
44
|
+
userId?: string;
|
|
45
|
+
email?: string;
|
|
46
|
+
}
|
|
43
47
|
interface RecorderHandle {
|
|
44
48
|
status: () => RecorderStatus;
|
|
45
49
|
sessionId: () => string | null;
|
|
46
50
|
consent: (granted: boolean) => void;
|
|
47
|
-
identify: (traits:
|
|
48
|
-
userId?: string;
|
|
49
|
-
email?: string;
|
|
50
|
-
}) => void;
|
|
51
|
+
identify: (traits: IdentifyTraits) => void;
|
|
51
52
|
/**
|
|
52
53
|
* Tags this moment of the recording so it shows on the player's timeline.
|
|
53
54
|
*
|
|
@@ -199,6 +200,18 @@ interface Identity {
|
|
|
199
200
|
* id resets after a period of inactivity.
|
|
200
201
|
*/
|
|
201
202
|
declare function resolveIdentity(local: Storage, session: Storage, now?: number): Identity;
|
|
203
|
+
/**
|
|
204
|
+
* Removes everything the recorder has stored in this browser.
|
|
205
|
+
*
|
|
206
|
+
* Called when consent is refused. A refusal has to undo an earlier acceptance,
|
|
207
|
+
* not merely stop the current page: the visitor id written last week is
|
|
208
|
+
* exactly what the visitor is now saying no to, and leaving it in place would
|
|
209
|
+
* make "we store nothing without consent" untrue for as long as it sat there.
|
|
210
|
+
* The cool-down goes too — it is a fact about a session that no longer exists.
|
|
211
|
+
* Removing a key that is not there creates nothing, so this is safe to call
|
|
212
|
+
* before anything was ever written.
|
|
213
|
+
*/
|
|
214
|
+
declare function forgetVisitor(local: Storage, session: Storage): void;
|
|
202
215
|
|
|
203
216
|
/**
|
|
204
217
|
* Custom events: the moments a page tags with `track()`.
|
|
@@ -225,4 +238,4 @@ interface TrackPayload {
|
|
|
225
238
|
*/
|
|
226
239
|
declare function validateTrack(name: unknown, properties: unknown): TrackPayload | string;
|
|
227
240
|
|
|
228
|
-
export { type AnyReplayOptions, ConfigError, DEFAULTS, EVENT_NAME_PATTERN, MAX_PROPERTIES_BYTES, NAVIGATE_TAG, type RecordedEvent, type RecorderHandle, type RecorderStatus, type ResolvedOptions, type SessionMeta, TRACK_TAG, type TrackPayload, Transport, createRecorder, hashToUnitInterval, isSampledIn, resolveIdentity, resolveOptions, safeStorage, uuidv4, validateTrack };
|
|
241
|
+
export { type AnyReplayOptions, ConfigError, DEFAULTS, EVENT_NAME_PATTERN, MAX_PROPERTIES_BYTES, NAVIGATE_TAG, type RecordedEvent, type RecorderHandle, type RecorderStatus, type ResolvedOptions, type SessionMeta, TRACK_TAG, type TrackPayload, Transport, createRecorder, forgetVisitor, hashToUnitInterval, isSampledIn, resolveIdentity, resolveOptions, safeStorage, uuidv4, validateTrack };
|
package/dist/index.js
CHANGED
|
@@ -4007,6 +4007,10 @@ function reserveSeq(session, next) {
|
|
|
4007
4007
|
function touchSession(session, now = Date.now()) {
|
|
4008
4008
|
session.set(SESSION_TS_KEY, String(now));
|
|
4009
4009
|
}
|
|
4010
|
+
function forgetVisitor(local, session) {
|
|
4011
|
+
local.remove(VISITOR_KEY);
|
|
4012
|
+
for (const key of [SESSION_KEY, SESSION_TS_KEY, SEQ_KEY, PAGE_KEY, COOLDOWN_KEY]) session.remove(key);
|
|
4013
|
+
}
|
|
4010
4014
|
|
|
4011
4015
|
// src/recorder.ts
|
|
4012
4016
|
var RAGE_CLICK_COUNT = 3;
|
|
@@ -4017,37 +4021,22 @@ function createRecorder(rawOptions) {
|
|
|
4017
4021
|
const options = resolveOptions(rawOptions);
|
|
4018
4022
|
const local = safeStorage(() => globalThis.localStorage);
|
|
4019
4023
|
const session = safeStorage(() => globalThis.sessionStorage);
|
|
4020
|
-
const identity = resolveIdentity(local, session);
|
|
4021
4024
|
let status = "idle";
|
|
4025
|
+
let identity;
|
|
4026
|
+
let transport;
|
|
4022
4027
|
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 = () => ({
|
|
4028
|
+
let pageCount = 0;
|
|
4029
|
+
let pendingTraits;
|
|
4030
|
+
const sendIdentity = (who, traits) => {
|
|
4031
|
+
void fetch(`${options.ingestUrl}/v1/ingest/identify`, {
|
|
4032
|
+
method: "POST",
|
|
4033
|
+
headers: { "Content-Type": "application/json" },
|
|
4034
|
+
credentials: "omit",
|
|
4035
|
+
body: JSON.stringify({ projectKey: options.projectKey, sessionId: who.sessionId, ...traits })
|
|
4036
|
+
}).catch(() => {
|
|
4037
|
+
});
|
|
4038
|
+
};
|
|
4039
|
+
const collectMeta = (who) => ({
|
|
4051
4040
|
startedAt: Date.now(),
|
|
4052
4041
|
url: safeLocation(),
|
|
4053
4042
|
lang: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
@@ -4057,10 +4046,10 @@ function createRecorder(rawOptions) {
|
|
|
4057
4046
|
referrer: typeof document !== "undefined" ? document.referrer || void 0 : void 0,
|
|
4058
4047
|
// The cap is a decision about starting a session, so it goes only where
|
|
4059
4048
|
// ingest makes that decision: chunk 0. A resumed page starts later.
|
|
4060
|
-
...options.maxSessionsPerMonth !== void 0 &&
|
|
4049
|
+
...options.maxSessionsPerMonth !== void 0 && who.nextSeq === 0 ? { sessionCap: options.maxSessionsPerMonth } : {}
|
|
4061
4050
|
});
|
|
4062
4051
|
const recentClicks = [];
|
|
4063
|
-
const noteInteraction = (event) => {
|
|
4052
|
+
const noteInteraction = (event, channel) => {
|
|
4064
4053
|
var _a;
|
|
4065
4054
|
const data = event.data;
|
|
4066
4055
|
if (event.type !== 3 || (data == null ? void 0 : data.source) !== 2 || typeof data.x !== "number") return;
|
|
@@ -4072,7 +4061,7 @@ function createRecorder(rawOptions) {
|
|
|
4072
4061
|
const clustered = recentClicks.every(
|
|
4073
4062
|
(c) => Math.abs(c.x - first.x) < RAGE_CLICK_RADIUS_PX && Math.abs(c.y - first.y) < RAGE_CLICK_RADIUS_PX
|
|
4074
4063
|
);
|
|
4075
|
-
if (clustered)
|
|
4064
|
+
if (clustered) channel.mergeFlags({ hasRageClick: true });
|
|
4076
4065
|
}
|
|
4077
4066
|
};
|
|
4078
4067
|
const pending = [];
|
|
@@ -4100,7 +4089,7 @@ function createRecorder(rawOptions) {
|
|
|
4100
4089
|
}
|
|
4101
4090
|
};
|
|
4102
4091
|
const emitCustom = (tag, payload) => {
|
|
4103
|
-
if (status === "stopped") return;
|
|
4092
|
+
if (status === "stopped" || status === "sampled-out") return;
|
|
4104
4093
|
if (status === "recording") {
|
|
4105
4094
|
try {
|
|
4106
4095
|
record.addCustomEvent(tag, payload);
|
|
@@ -4114,28 +4103,61 @@ function createRecorder(rawOptions) {
|
|
|
4114
4103
|
}
|
|
4115
4104
|
pending.push({ tag, payload });
|
|
4116
4105
|
};
|
|
4117
|
-
let lastUrl
|
|
4118
|
-
const noteNavigation = () => {
|
|
4106
|
+
let lastUrl;
|
|
4107
|
+
const noteNavigation = (channel) => {
|
|
4119
4108
|
const url = safeLocation();
|
|
4120
4109
|
if (!url || url === lastUrl) return;
|
|
4121
4110
|
lastUrl = url;
|
|
4122
4111
|
pageCount += 1;
|
|
4123
4112
|
notePageCount(session, pageCount);
|
|
4124
|
-
|
|
4113
|
+
channel.mergeFlags({ pageCount });
|
|
4125
4114
|
emitCustom(NAVIGATE_TAG, { url, title: typeof document !== "undefined" ? document.title : "" });
|
|
4126
4115
|
};
|
|
4127
4116
|
const begin = () => {
|
|
4128
|
-
if (status
|
|
4117
|
+
if (status !== "idle" && status !== "awaiting-consent") return;
|
|
4118
|
+
const who = resolveIdentity(local, session);
|
|
4119
|
+
identity = who;
|
|
4120
|
+
pageCount = who.pageCount;
|
|
4121
|
+
if (!isSampledIn(who.visitorId, options.sampleRate)) {
|
|
4122
|
+
status = "sampled-out";
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
if (inCooldown(session)) {
|
|
4126
|
+
status = "stopped";
|
|
4127
|
+
if (options.debug) console.warn("[anyreplay] not recording: ingest refused a new session recently");
|
|
4128
|
+
return;
|
|
4129
|
+
}
|
|
4130
|
+
const channel = new Transport(
|
|
4131
|
+
options,
|
|
4132
|
+
{
|
|
4133
|
+
projectKey: options.projectKey,
|
|
4134
|
+
sessionId: who.sessionId,
|
|
4135
|
+
visitorId: who.visitorId,
|
|
4136
|
+
initialSeq: who.nextSeq
|
|
4137
|
+
},
|
|
4138
|
+
{
|
|
4139
|
+
onSeqReserved: (next) => reserveSeq(session, next),
|
|
4140
|
+
onStopped: (reason) => {
|
|
4141
|
+
status = "stopped";
|
|
4142
|
+
stopRecording == null ? void 0 : stopRecording();
|
|
4143
|
+
stopRecording = void 0;
|
|
4144
|
+
if (reason === "rejected_402") startCooldown(session);
|
|
4145
|
+
if (options.debug) console.warn(`[anyreplay] recording stopped: ${reason}`);
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
);
|
|
4149
|
+
transport = channel;
|
|
4129
4150
|
status = "recording";
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4151
|
+
lastUrl = safeLocation();
|
|
4152
|
+
channel.setMeta(collectMeta(who));
|
|
4153
|
+
channel.mergeFlags({ pageCount });
|
|
4154
|
+
channel.start();
|
|
4133
4155
|
stopRecording = record({
|
|
4134
4156
|
emit: (event) => {
|
|
4135
4157
|
const typed = event;
|
|
4136
|
-
noteInteraction(typed);
|
|
4158
|
+
noteInteraction(typed, channel);
|
|
4137
4159
|
touchSession(session, typed.timestamp);
|
|
4138
|
-
|
|
4160
|
+
channel.push(typed);
|
|
4139
4161
|
drainPending();
|
|
4140
4162
|
},
|
|
4141
4163
|
maskAllInputs: options.maskAllInputs,
|
|
@@ -4148,29 +4170,33 @@ function createRecorder(rawOptions) {
|
|
|
4148
4170
|
collectFonts: false
|
|
4149
4171
|
});
|
|
4150
4172
|
drainPending();
|
|
4151
|
-
|
|
4173
|
+
if (pendingTraits) {
|
|
4174
|
+
sendIdentity(who, pendingTraits);
|
|
4175
|
+
pendingTraits = void 0;
|
|
4176
|
+
}
|
|
4177
|
+
attachLifecycleHooks(channel);
|
|
4152
4178
|
};
|
|
4153
|
-
const attachLifecycleHooks = () => {
|
|
4179
|
+
const attachLifecycleHooks = (channel) => {
|
|
4154
4180
|
if (typeof window === "undefined") return;
|
|
4155
4181
|
window.addEventListener("pagehide", () => {
|
|
4156
|
-
|
|
4182
|
+
channel.flushWithBeacon();
|
|
4157
4183
|
}, { capture: true });
|
|
4158
4184
|
document.addEventListener("visibilitychange", () => {
|
|
4159
|
-
if (document.visibilityState === "hidden")
|
|
4185
|
+
if (document.visibilityState === "hidden") channel.flushWithBeacon();
|
|
4160
4186
|
});
|
|
4161
|
-
window.addEventListener("error", () =>
|
|
4162
|
-
window.addEventListener("unhandledrejection", () =>
|
|
4187
|
+
window.addEventListener("error", () => channel.mergeFlags({ hasError: true }));
|
|
4188
|
+
window.addEventListener("unhandledrejection", () => channel.mergeFlags({ hasError: true }));
|
|
4163
4189
|
const patch2 = (method) => {
|
|
4164
4190
|
const original = history[method].bind(history);
|
|
4165
4191
|
history[method] = function patched(...args) {
|
|
4166
4192
|
const result = original(...args);
|
|
4167
|
-
noteNavigation();
|
|
4193
|
+
noteNavigation(channel);
|
|
4168
4194
|
return result;
|
|
4169
4195
|
};
|
|
4170
4196
|
};
|
|
4171
4197
|
patch2("pushState");
|
|
4172
4198
|
patch2("replaceState");
|
|
4173
|
-
window.addEventListener("popstate", noteNavigation);
|
|
4199
|
+
window.addEventListener("popstate", () => noteNavigation(channel));
|
|
4174
4200
|
};
|
|
4175
4201
|
if (options.requireConsent) {
|
|
4176
4202
|
status = "awaiting-consent";
|
|
@@ -4179,24 +4205,28 @@ function createRecorder(rawOptions) {
|
|
|
4179
4205
|
}
|
|
4180
4206
|
return {
|
|
4181
4207
|
status: () => status,
|
|
4182
|
-
sessionId: () =>
|
|
4208
|
+
sessionId: () => {
|
|
4209
|
+
var _a;
|
|
4210
|
+
return (_a = identity == null ? void 0 : identity.sessionId) != null ? _a : null;
|
|
4211
|
+
},
|
|
4183
4212
|
consent: (granted) => {
|
|
4184
|
-
if (granted)
|
|
4185
|
-
|
|
4213
|
+
if (granted) {
|
|
4214
|
+
begin();
|
|
4215
|
+
return;
|
|
4216
|
+
}
|
|
4217
|
+
transport == null ? void 0 : transport.stop("consent_withdrawn");
|
|
4218
|
+
if (status === "idle" || status === "awaiting-consent") status = "stopped";
|
|
4219
|
+
pendingTraits = void 0;
|
|
4220
|
+
forgetVisitor(local, session);
|
|
4186
4221
|
},
|
|
4187
4222
|
identify: (traits) => {
|
|
4188
4223
|
if (!traits.userId && !traits.email) return;
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
sessionId: identity.sessionId,
|
|
4196
|
-
...traits
|
|
4197
|
-
})
|
|
4198
|
-
}).catch(() => {
|
|
4199
|
-
});
|
|
4224
|
+
if (identity && transport) {
|
|
4225
|
+
sendIdentity(identity, traits);
|
|
4226
|
+
return;
|
|
4227
|
+
}
|
|
4228
|
+
if (status === "idle" || status === "awaiting-consent") pendingTraits = traits;
|
|
4229
|
+
else if (options.debug) console.warn("[anyreplay] identify() ignored: not recording");
|
|
4200
4230
|
},
|
|
4201
4231
|
track: (name, properties) => {
|
|
4202
4232
|
const payload = validateTrack(name, properties);
|
|
@@ -4206,8 +4236,15 @@ function createRecorder(rawOptions) {
|
|
|
4206
4236
|
}
|
|
4207
4237
|
emitCustom(TRACK_TAG, payload);
|
|
4208
4238
|
},
|
|
4209
|
-
stop: () =>
|
|
4210
|
-
|
|
4239
|
+
stop: () => {
|
|
4240
|
+
transport == null ? void 0 : transport.stop("stopped_by_page");
|
|
4241
|
+
if (status === "idle" || status === "awaiting-consent") status = "stopped";
|
|
4242
|
+
pendingTraits = void 0;
|
|
4243
|
+
},
|
|
4244
|
+
flush: () => {
|
|
4245
|
+
var _a;
|
|
4246
|
+
return (_a = transport == null ? void 0 : transport.flush()) != null ? _a : Promise.resolve();
|
|
4247
|
+
}
|
|
4211
4248
|
};
|
|
4212
4249
|
}
|
|
4213
4250
|
function safeLocation() {
|
|
@@ -4217,22 +4254,6 @@ function safeLocation() {
|
|
|
4217
4254
|
return void 0;
|
|
4218
4255
|
}
|
|
4219
4256
|
}
|
|
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
4257
|
export {
|
|
4237
4258
|
ConfigError,
|
|
4238
4259
|
DEFAULTS,
|
|
@@ -4242,6 +4263,7 @@ export {
|
|
|
4242
4263
|
TRACK_TAG,
|
|
4243
4264
|
Transport,
|
|
4244
4265
|
createRecorder,
|
|
4266
|
+
forgetVisitor,
|
|
4245
4267
|
hashToUnitInterval,
|
|
4246
4268
|
isSampledIn,
|
|
4247
4269
|
resolveIdentity,
|