@drip-apex/sdk 0.1.0 → 0.3.0
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 +5 -0
- package/dist/bundle-report.json +584 -226
- package/dist/define.cjs +123 -0
- package/dist/define.cjs.map +7 -0
- package/dist/define.mjs +102 -0
- package/dist/define.mjs.map +7 -0
- package/dist/drip.cjs +19283 -0
- package/dist/drip.cjs.map +7 -0
- package/dist/drip.debug.js +1 -1
- package/dist/drip.debug.js.map +1 -1
- package/dist/drip.heatmap.js +1 -1
- package/dist/drip.heatmap.js.map +1 -1
- package/dist/drip.js +1 -1
- package/dist/drip.js.map +1 -1
- package/dist/drip.live.js +1 -1
- package/dist/drip.live.js.map +1 -1
- package/dist/drip.mjs +19267 -0
- package/dist/drip.mjs.map +7 -0
- package/dist/drip.replay-recorder.js +2 -0
- package/dist/drip.replay-recorder.js.map +1 -0
- package/dist/editor.js +147 -142
- package/dist/editor.js.map +4 -4
- package/dist/flags.cjs +1788 -0
- package/dist/flags.cjs.map +7 -0
- package/dist/flags.mjs +1765 -0
- package/dist/flags.mjs.map +7 -0
- package/dist/index.mjs +12 -0
- package/dist/index.mjs.map +2 -2
- package/dist/metafiles/define.cjs.meta.json +28 -0
- package/dist/metafiles/define.mjs.meta.json +32 -0
- package/dist/metafiles/drip.cjs.meta.json +3193 -0
- package/dist/metafiles/drip.debug.meta.json +2859 -574
- package/dist/metafiles/drip.heatmap.meta.json +2732 -460
- package/dist/metafiles/drip.live.meta.json +2732 -460
- package/dist/metafiles/drip.meta.json +2859 -574
- package/dist/metafiles/drip.mjs.meta.json +3235 -0
- package/dist/metafiles/drip.replay-recorder.meta.json +82 -0
- package/dist/metafiles/editor.meta.json +67 -27
- package/dist/metafiles/flags.cjs.meta.json +220 -0
- package/dist/metafiles/flags.mjs.meta.json +225 -0
- package/dist/metafiles/index.meta.json +2717 -491
- package/dist/metafiles/index.mjs.meta.json +14 -4
- package/dist/react/index.js +16636 -13533
- package/dist/react/index.js.map +4 -4
- package/dist/sri.json +20 -15
- package/dist/types/activation-runtime.d.ts +91 -0
- package/dist/types/analytics-runtime.d.ts +166 -0
- package/dist/types/antiflicker.d.ts +9 -3
- package/dist/types/assignment-cookie-sync.d.ts +31 -0
- package/dist/types/assignment-runtime.d.ts +257 -0
- package/dist/types/assignment-store.d.ts +4 -8
- package/dist/types/bucket.d.ts +1 -2
- package/dist/types/compact-store.d.ts +1 -0
- package/dist/types/config-runtime.d.ts +313 -0
- package/dist/types/consent-evaluation.d.ts +28 -0
- package/dist/types/css.d.ts +5 -3
- package/dist/types/debug-loader.d.ts +2 -0
- package/dist/types/decide-assignments.d.ts +31 -0
- package/dist/types/delivery-timing.d.ts +8 -0
- package/dist/types/early-interaction-capture.d.ts +79 -0
- package/dist/types/early-reassert.d.ts +32 -0
- package/dist/types/edge-runtime.d.ts +11 -0
- package/dist/types/editor/api.d.ts +3 -0
- package/dist/types/editor/controller.d.ts +3 -1
- package/dist/types/editor/publication-result.d.ts +11 -0
- package/dist/types/editor/types.d.ts +2 -1
- package/dist/types/editor/view-kit.d.ts +1 -1
- package/dist/types/endpoint.d.ts +8 -0
- package/dist/types/exclusion-groups.d.ts +4 -7
- package/dist/types/flags.d.ts +2 -0
- package/dist/types/goal-eligibility.d.ts +4 -0
- package/dist/types/goals.d.ts +10 -1
- package/dist/types/guards.d.ts +7 -7
- package/dist/types/handover.d.ts +12 -1
- package/dist/types/hash.d.ts +8 -4
- package/dist/types/hydration.d.ts +2 -1
- package/dist/types/index.d.ts +24 -128
- package/dist/types/init-test-helpers.d.ts +48 -0
- package/dist/types/inline-bootstrap.d.ts +26 -0
- package/dist/types/js.d.ts +3 -1
- package/dist/types/money.d.ts +12 -0
- package/dist/types/mutate.d.ts +45 -6
- package/dist/types/performance.d.ts +2 -0
- package/dist/types/persistence-decoders.d.ts +12 -0
- package/dist/types/public-api.d.ts +209 -0
- package/dist/types/qa-assistant-state.d.ts +34 -0
- package/dist/types/react/index.d.ts +28 -3
- package/dist/types/reassert-sentinel.d.ts +6 -0
- package/dist/types/redirect.d.ts +48 -0
- package/dist/types/replay-recorder-entry.d.ts +1 -0
- package/dist/types/replay-signal-state.d.ts +6 -0
- package/dist/types/replay-signals.d.ts +13 -0
- package/dist/types/replay.d.ts +110 -0
- package/dist/types/router.d.ts +12 -2
- package/dist/types/runtime-state.d.ts +166 -0
- package/dist/types/sanitize.d.ts +1 -0
- package/dist/types/shadow.d.ts +3 -0
- package/dist/types/shopify-cart.d.ts +25 -1
- package/dist/types/storefront-vitals.d.ts +24 -0
- package/dist/types/targeting-diagnostics.d.ts +1 -0
- package/dist/types/targeting.d.ts +16 -2
- package/dist/types/track.d.ts +15 -1
- package/dist/types/types.d.ts +115 -9
- package/dist/types/wait.d.ts +14 -1
- package/dist/types/weekday-schedule.d.ts +3 -0
- package/package.json +17 -8
- package/src/activation-runtime.ts +2737 -0
- package/src/analytics-runtime.ts +2175 -0
- package/src/antiflicker.test.ts +216 -44
- package/src/antiflicker.ts +103 -28
- package/src/artifact-concurrency.test.ts +1091 -0
- package/src/assignment-cookie-sync.test.ts +202 -0
- package/src/assignment-cookie-sync.ts +189 -0
- package/src/assignment-runtime.ts +3204 -0
- package/src/assignment-store.test.ts +56 -0
- package/src/assignment-store.ts +7 -38
- package/src/bucket.ts +3 -18
- package/src/commerce-analytics-lifecycle.test.ts +75 -15
- package/src/commerce-config-lifecycle.test.ts +81 -1
- package/src/commerce-price-preserve.test.ts +133 -0
- package/src/commerce-price.test.ts +97 -0
- package/src/commerce-price.ts +74 -20
- package/src/commerce-shipping.test.ts +53 -0
- package/src/commerce-shipping.ts +5 -11
- package/src/compact-store.ts +15 -0
- package/src/config-fetch.test.ts +480 -9
- package/src/config-runtime.ts +2071 -0
- package/src/consent-evaluation-borlabs.test.ts +41 -0
- package/src/consent-evaluation-cookiescript.test.ts +50 -0
- package/src/consent-evaluation.ts +151 -0
- package/src/css.test.ts +30 -0
- package/src/css.ts +120 -6
- package/src/cwv.test.ts +60 -77
- package/src/debug-loader.test.ts +214 -0
- package/src/debug-loader.ts +41 -0
- package/src/decide-assignments.test.ts +196 -0
- package/src/decide-assignments.ts +204 -0
- package/src/delivery-timing.test.ts +104 -0
- package/src/delivery-timing.ts +89 -0
- package/src/early-interaction-capture.test.ts +451 -0
- package/src/early-interaction-capture.ts +230 -0
- package/src/early-reassert.test.ts +207 -0
- package/src/early-reassert.ts +152 -0
- package/src/edge-adoption.test.ts +435 -0
- package/src/edge-runtime.ts +30 -0
- package/src/editor/api.test.ts +109 -0
- package/src/editor/api.ts +34 -3
- package/src/editor/controller.test.ts +297 -14
- package/src/editor/controller.ts +69 -5
- package/src/editor/index.test.ts +2 -0
- package/src/editor/index.ts +1 -0
- package/src/editor/publication-result.ts +58 -0
- package/src/editor/types.ts +10 -1
- package/src/editor/view-kit.ts +2 -1
- package/src/editor/view.test.ts +108 -0
- package/src/editor/view.ts +49 -7
- package/src/endpoint.test.ts +15 -0
- package/src/endpoint.ts +10 -0
- package/src/exclusion-groups.ts +8 -18
- package/src/flags.test.ts +88 -1
- package/src/flags.ts +29 -29
- package/src/global-runtime-reconciler.test.ts +271 -0
- package/src/goal-eligibility.test.ts +29 -0
- package/src/goal-eligibility.ts +17 -0
- package/src/goals.test.ts +144 -1
- package/src/goals.ts +97 -18
- package/src/guards.test.ts +99 -1
- package/src/guards.ts +34 -15
- package/src/handover-cache-order.test.ts +6 -3
- package/src/handover.test.ts +154 -8
- package/src/handover.ts +122 -12
- package/src/hash.ts +29 -54
- package/src/holdout.test.ts +26 -0
- package/src/hydration.test.ts +73 -2
- package/src/hydration.ts +18 -9
- package/src/index.ts +130 -7287
- package/src/init-analytics.test.ts +1694 -0
- package/src/init-anti-flicker.test.ts +1211 -0
- package/src/init-cart-attributes.test.ts +2003 -0
- package/src/init-config-resolution.test.ts +999 -0
- package/src/init-consent.test.ts +971 -0
- package/src/init-empty.test.ts +11 -5406
- package/src/init-integrations.test.ts +846 -0
- package/src/init-page-activation.test.ts +5064 -0
- package/src/init-qa-loading.test.ts +740 -0
- package/src/init-redirects.test.ts +2156 -0
- package/src/init-test-helpers.ts +287 -0
- package/src/inline-bootstrap.test.ts +184 -0
- package/src/inline-bootstrap.ts +73 -0
- package/src/js.test.ts +51 -1
- package/src/js.ts +86 -2
- package/src/money.test.ts +55 -1
- package/src/money.ts +104 -2
- package/src/money.web-parity.contract.test.ts +85 -0
- package/src/mutate.test.ts +474 -10
- package/src/mutate.ts +739 -230
- package/src/page-trigger.test.ts +218 -8
- package/src/performance.ts +10 -0
- package/src/persistence-decoders.ts +18 -0
- package/src/public-api.ts +1828 -0
- package/src/qa-assistant-state.test.ts +249 -3
- package/src/qa-assistant-state.ts +146 -6
- package/src/qa-assistant-view.test.ts +220 -2
- package/src/qa-assistant-view.ts +102 -5
- package/src/react/index.test.tsx +37 -0
- package/src/react/index.tsx +19 -3
- package/src/reassert-sentinel.test.ts +155 -1
- package/src/reassert-sentinel.ts +168 -8
- package/src/redirect.test.ts +541 -0
- package/src/redirect.ts +572 -0
- package/src/replay-recorder-entry.ts +115 -0
- package/src/replay-signal-state.ts +100 -0
- package/src/replay-signals.test.ts +249 -0
- package/src/replay-signals.ts +151 -0
- package/src/replay.test.ts +764 -0
- package/src/replay.ts +514 -0
- package/src/router.test.ts +61 -2
- package/src/router.ts +50 -12
- package/src/runtime-state.ts +507 -0
- package/src/runtime.test.ts +174 -7
- package/src/runtime.ts +88 -14
- package/src/sanitize.test.ts +34 -0
- package/src/sanitize.ts +23 -12
- package/src/shadow.test.ts +187 -0
- package/src/shadow.ts +99 -0
- package/src/shopify-cart.test.ts +733 -23
- package/src/shopify-cart.ts +402 -49
- package/src/storefront-vitals.test.ts +217 -0
- package/src/storefront-vitals.ts +206 -0
- package/src/surface-signals.ts +6 -0
- package/src/targeting-conformance.test.ts +9 -0
- package/src/targeting-diagnostics.ts +38 -8
- package/src/targeting-parity-corpus.json +559 -0
- package/src/targeting-parity-corpus.test.ts +18 -0
- package/src/targeting.detailed.test.ts +105 -0
- package/src/targeting.test.ts +211 -0
- package/src/targeting.ts +88 -207
- package/src/track.test.ts +147 -0
- package/src/track.ts +125 -26
- package/src/types.ts +120 -13
- package/src/wait.test.ts +23 -0
- package/src/wait.ts +129 -13
- package/src/weekday-delivery.test.ts +56 -0
- package/src/weekday-schedule-parity-corpus.json +92 -0
- package/src/weekday-schedule.test.ts +69 -0
- package/src/weekday-schedule.ts +20 -0
package/src/redirect.ts
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import type { Assignment, Experiment } from "./types";
|
|
2
|
+
import { persistAssignmentForExperiment } from "./assignment-store";
|
|
3
|
+
import {
|
|
4
|
+
evalUrlRule,
|
|
5
|
+
matchUrlRules,
|
|
6
|
+
normalizeRuntimeUrlRules,
|
|
7
|
+
} from "./targeting";
|
|
8
|
+
import {
|
|
9
|
+
bucketingHandoverExpiresAtMs,
|
|
10
|
+
createBucketingHandoverPayload,
|
|
11
|
+
verifyBucketingHandoverToken,
|
|
12
|
+
} from "./handover";
|
|
13
|
+
import type { ExternalTrackingSettings } from "./types";
|
|
14
|
+
|
|
15
|
+
export const REDIRECT_ASSIGNMENT_PARAM = "_apex_assignment";
|
|
16
|
+
export const REDIRECT_ASSIGNMENT_PENDING_KEY = "drip_redirect_assignment_pending_v1";
|
|
17
|
+
const MAX_PENDING_REDIRECT_CARRY_TTL_MS = 10 * 60 * 1000;
|
|
18
|
+
type RedirectCarry = {
|
|
19
|
+
e: string;
|
|
20
|
+
v: string;
|
|
21
|
+
epoch: number;
|
|
22
|
+
handover: string;
|
|
23
|
+
exposureSent: boolean;
|
|
24
|
+
destinationPath?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ParsedRedirectCarry = {
|
|
28
|
+
experimentId: string;
|
|
29
|
+
variationId: string;
|
|
30
|
+
assignmentEpoch: number;
|
|
31
|
+
handoverToken: string;
|
|
32
|
+
expiresAt: number;
|
|
33
|
+
exposureSent: boolean;
|
|
34
|
+
destinationPath?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type VerifiedRedirectCarry = ParsedRedirectCarry & {
|
|
38
|
+
visitorId: string;
|
|
39
|
+
attributionToken?: string;
|
|
40
|
+
attributionDestinationUrl?: string;
|
|
41
|
+
attributionIssuedAt?: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type ImportedRedirectCarry = {
|
|
45
|
+
experiment: Experiment;
|
|
46
|
+
variationId: string;
|
|
47
|
+
visitorId: string;
|
|
48
|
+
exposureSent: boolean;
|
|
49
|
+
attributionToken?: string;
|
|
50
|
+
attributionDestinationUrl?: string;
|
|
51
|
+
attributionIssuedAt?: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const REDIRECT_ATTRIBUTION_COOKIE = "drip_ra";
|
|
55
|
+
const REDIRECT_ATTRIBUTION_TTL_MS = 30_000;
|
|
56
|
+
|
|
57
|
+
function canonicalRedirectAttributionDestination(value: string): string {
|
|
58
|
+
const parsed = new URL(value);
|
|
59
|
+
parsed.searchParams.delete(REDIRECT_ASSIGNMENT_PARAM);
|
|
60
|
+
const normalizedPathname = parsed.pathname.replace(/\/$/, "") || "/";
|
|
61
|
+
const pairs: Array<[string, string]> = [];
|
|
62
|
+
parsed.searchParams.forEach((queryValue, key) => pairs.push([key, queryValue]));
|
|
63
|
+
const compareCodePoints = (left: string, right: string): number =>
|
|
64
|
+
left < right ? -1 : left > right ? 1 : 0;
|
|
65
|
+
pairs.sort(([leftKey, leftValue], [rightKey, rightValue]) =>
|
|
66
|
+
compareCodePoints(leftKey, rightKey) || compareCodePoints(leftValue, rightValue));
|
|
67
|
+
const normalized = new URL(parsed.origin + normalizedPathname);
|
|
68
|
+
for (const [key, queryValue] of pairs) normalized.searchParams.append(key, queryValue);
|
|
69
|
+
return normalized.toString();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizedEpoch(value: number | undefined): number {
|
|
73
|
+
return Math.max(1, Number.isSafeInteger(value) ? value! : 1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const generatedRedirectCarryValues = new Set<string>();
|
|
77
|
+
|
|
78
|
+
function normalizeRedirectPath(pathname: string): string {
|
|
79
|
+
return pathname.replace(/\/+$/, "") || "/";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function hasGeneratedRedirectAssignmentCarry(destination: URL): boolean {
|
|
83
|
+
const carry = destination.searchParams.get(REDIRECT_ASSIGNMENT_PARAM);
|
|
84
|
+
return carry !== null && generatedRedirectCarryValues.has(carry);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function buildRedirectDestination(currentHref: string, redirectUrl: string,
|
|
88
|
+
assignment: Pick<Assignment, "experimentId" | "variationId" | "assignmentEpoch">,
|
|
89
|
+
visitorId = "",
|
|
90
|
+
handoverSettings?: ExternalTrackingSettings,
|
|
91
|
+
includeAssignmentCarry = true,
|
|
92
|
+
exposureSent = false): URL | null {
|
|
93
|
+
try {
|
|
94
|
+
const current = new URL(currentHref);
|
|
95
|
+
const destination = new URL(redirectUrl, current);
|
|
96
|
+
if (!/https?:/.test(destination.protocol)) return null;
|
|
97
|
+
const destinationKeys = new Set<string>();
|
|
98
|
+
destination.searchParams.forEach((_value, key) => destinationKeys.add(key));
|
|
99
|
+
current.searchParams.forEach((value, key) => {
|
|
100
|
+
if (!destinationKeys.has(key) && key !== REDIRECT_ASSIGNMENT_PARAM) destination.searchParams.append(key, value);
|
|
101
|
+
});
|
|
102
|
+
if (!destination.hash && current.hash) destination.hash = current.hash;
|
|
103
|
+
// Browser storage is origin-scoped, including protocol and port.
|
|
104
|
+
if (current.origin !== destination.origin && includeAssignmentCarry) {
|
|
105
|
+
if (destination.searchParams.has(REDIRECT_ASSIGNMENT_PARAM)) {
|
|
106
|
+
console.warn(
|
|
107
|
+
`[drip] Redirect destination already uses reserved ${REDIRECT_ASSIGNMENT_PARAM}; preserving the merchant-authored value and omitting assignment carry.`,
|
|
108
|
+
);
|
|
109
|
+
return destination;
|
|
110
|
+
}
|
|
111
|
+
const carry: RedirectCarry = {
|
|
112
|
+
e: assignment.experimentId,
|
|
113
|
+
v: assignment.variationId,
|
|
114
|
+
epoch: normalizedEpoch(assignment.assignmentEpoch),
|
|
115
|
+
handover: createBucketingHandoverPayload(visitorId, [assignment], handoverSettings),
|
|
116
|
+
exposureSent,
|
|
117
|
+
destinationPath: destination.pathname,
|
|
118
|
+
};
|
|
119
|
+
destination.searchParams.set(REDIRECT_ASSIGNMENT_PARAM, JSON.stringify(carry));
|
|
120
|
+
generatedRedirectCarryValues.add(JSON.stringify(carry));
|
|
121
|
+
}
|
|
122
|
+
return destination;
|
|
123
|
+
} catch { return null; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function markRedirectAssignmentCarryExposed(destination: URL): void {
|
|
127
|
+
try {
|
|
128
|
+
if (!hasGeneratedRedirectAssignmentCarry(destination)) return;
|
|
129
|
+
const value = destination.searchParams.get(REDIRECT_ASSIGNMENT_PARAM);
|
|
130
|
+
if (!value) return;
|
|
131
|
+
const carry = JSON.parse(value) as Partial<RedirectCarry>;
|
|
132
|
+
if (
|
|
133
|
+
typeof carry.e !== "string" ||
|
|
134
|
+
typeof carry.v !== "string" ||
|
|
135
|
+
!Number.isSafeInteger(carry.epoch) ||
|
|
136
|
+
typeof carry.handover !== "string"
|
|
137
|
+
) return;
|
|
138
|
+
const nextValue = JSON.stringify({ ...carry, exposureSent: true });
|
|
139
|
+
generatedRedirectCarryValues.delete(value);
|
|
140
|
+
generatedRedirectCarryValues.add(nextValue);
|
|
141
|
+
destination.searchParams.set(REDIRECT_ASSIGNMENT_PARAM, nextValue);
|
|
142
|
+
} catch {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function finalizeRedirectAssignmentCarryHandover(
|
|
146
|
+
destination: URL,
|
|
147
|
+
verifiedToken: string | null,
|
|
148
|
+
): void {
|
|
149
|
+
try {
|
|
150
|
+
if (!hasGeneratedRedirectAssignmentCarry(destination)) return;
|
|
151
|
+
const value = destination.searchParams.get(REDIRECT_ASSIGNMENT_PARAM);
|
|
152
|
+
if (!value || !verifiedToken) {
|
|
153
|
+
if (value) generatedRedirectCarryValues.delete(value);
|
|
154
|
+
destination.searchParams.delete(REDIRECT_ASSIGNMENT_PARAM);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const carry = JSON.parse(value) as Partial<RedirectCarry>;
|
|
158
|
+
if (
|
|
159
|
+
typeof carry.e !== "string" ||
|
|
160
|
+
typeof carry.v !== "string" ||
|
|
161
|
+
!Number.isSafeInteger(carry.epoch) ||
|
|
162
|
+
typeof carry.handover !== "string"
|
|
163
|
+
) {
|
|
164
|
+
generatedRedirectCarryValues.delete(value);
|
|
165
|
+
destination.searchParams.delete(REDIRECT_ASSIGNMENT_PARAM);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const nextValue = JSON.stringify({ ...carry, handover: verifiedToken });
|
|
169
|
+
generatedRedirectCarryValues.delete(value);
|
|
170
|
+
generatedRedirectCarryValues.add(nextValue);
|
|
171
|
+
destination.searchParams.set(REDIRECT_ASSIGNMENT_PARAM, nextValue);
|
|
172
|
+
} catch {
|
|
173
|
+
const value = destination.searchParams.get(REDIRECT_ASSIGNMENT_PARAM);
|
|
174
|
+
if (value) generatedRedirectCarryValues.delete(value);
|
|
175
|
+
destination.searchParams.delete(REDIRECT_ASSIGNMENT_PARAM);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function isRedirectLoop(currentHref: string, destination: URL): boolean {
|
|
180
|
+
try {
|
|
181
|
+
const current = new URL(currentHref);
|
|
182
|
+
current.searchParams.delete(REDIRECT_ASSIGNMENT_PARAM);
|
|
183
|
+
const compared = new URL(destination.href);
|
|
184
|
+
compared.searchParams.delete(REDIRECT_ASSIGNMENT_PARAM);
|
|
185
|
+
return current.origin === compared.origin && normalizeRedirectPath(current.pathname) === normalizeRedirectPath(compared.pathname) &&
|
|
186
|
+
current.search === compared.search && current.hash === compared.hash;
|
|
187
|
+
} catch { return true; }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function destinationMatchesSourceRules(
|
|
191
|
+
sourceHref: string,
|
|
192
|
+
destination: URL,
|
|
193
|
+
sourceRules: ReturnType<typeof normalizeRuntimeUrlRules>,
|
|
194
|
+
storeOrigin?: string | string[],
|
|
195
|
+
): boolean {
|
|
196
|
+
try {
|
|
197
|
+
if (storeOrigin) {
|
|
198
|
+
return matchUrlRules(destination.href, sourceRules, storeOrigin);
|
|
199
|
+
}
|
|
200
|
+
const source = new URL(sourceHref);
|
|
201
|
+
if (source.origin === destination.origin) {
|
|
202
|
+
return matchUrlRules(destination.href, sourceRules);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const probe = new URL(destination.href);
|
|
206
|
+
probe.hostname = "apex-host-probe.invalid";
|
|
207
|
+
const matchingHostIncludes = sourceRules.filter(
|
|
208
|
+
(rule) =>
|
|
209
|
+
rule.include !== false &&
|
|
210
|
+
evalUrlRule(destination.href, rule) &&
|
|
211
|
+
!evalUrlRule(probe.href, rule),
|
|
212
|
+
);
|
|
213
|
+
const destinationRules = [
|
|
214
|
+
...matchingHostIncludes,
|
|
215
|
+
...sourceRules.filter((rule) => rule.include === false),
|
|
216
|
+
];
|
|
217
|
+
return (
|
|
218
|
+
matchingHostIncludes.length > 0 &&
|
|
219
|
+
matchUrlRules(destination.href, destinationRules)
|
|
220
|
+
);
|
|
221
|
+
} catch {
|
|
222
|
+
return matchUrlRules(destination.href, sourceRules);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function redirectPlanForCandidate(
|
|
227
|
+
experiment: Experiment | undefined,
|
|
228
|
+
assignment: Assignment,
|
|
229
|
+
currentHref: string,
|
|
230
|
+
visitorId: string,
|
|
231
|
+
handoverSettings?: ExternalTrackingSettings,
|
|
232
|
+
includeAssignmentCarry = true,
|
|
233
|
+
storeOrigin?: string | string[],
|
|
234
|
+
): { destination: URL; assignment: Assignment } | null {
|
|
235
|
+
if (
|
|
236
|
+
experiment?.experimentType !== "redirect" ||
|
|
237
|
+
assignment.isControl ||
|
|
238
|
+
!assignment.redirectUrl
|
|
239
|
+
) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const sourceRules = normalizeRuntimeUrlRules(experiment.targeting);
|
|
244
|
+
if (!sourceRules.some((rule) => rule.include !== false)) return null;
|
|
245
|
+
if (!matchUrlRules(currentHref, sourceRules, storeOrigin)) return null;
|
|
246
|
+
|
|
247
|
+
const destination = buildRedirectDestination(
|
|
248
|
+
currentHref,
|
|
249
|
+
assignment.redirectUrl,
|
|
250
|
+
assignment,
|
|
251
|
+
visitorId,
|
|
252
|
+
handoverSettings,
|
|
253
|
+
includeAssignmentCarry,
|
|
254
|
+
false,
|
|
255
|
+
);
|
|
256
|
+
if (
|
|
257
|
+
!destination ||
|
|
258
|
+
isRedirectLoop(currentHref, destination) ||
|
|
259
|
+
destinationMatchesSourceRules(
|
|
260
|
+
currentHref,
|
|
261
|
+
destination,
|
|
262
|
+
sourceRules,
|
|
263
|
+
storeOrigin,
|
|
264
|
+
)
|
|
265
|
+
) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { destination, assignment };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function filterUnsafeRedirectAssignments(
|
|
273
|
+
experiments: Experiment[],
|
|
274
|
+
assignments: Assignment[],
|
|
275
|
+
currentHref: string,
|
|
276
|
+
visitorId = "",
|
|
277
|
+
handoverSettings?: ExternalTrackingSettings,
|
|
278
|
+
preserveAssignment?: (assignment: Assignment) => boolean,
|
|
279
|
+
storeOrigin?: string | string[],
|
|
280
|
+
): Assignment[] {
|
|
281
|
+
const experimentsById = new Map(
|
|
282
|
+
experiments.map((experiment) => [experiment.id, experiment]),
|
|
283
|
+
);
|
|
284
|
+
return assignments.filter((assignment) => {
|
|
285
|
+
if (preserveAssignment?.(assignment)) return true;
|
|
286
|
+
const experiment = experimentsById.get(assignment.experimentId);
|
|
287
|
+
if (experiment?.experimentType !== "redirect" || assignment.isControl) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
if (assignment.redirectUrl) {
|
|
291
|
+
const destination = buildRedirectDestination(
|
|
292
|
+
currentHref,
|
|
293
|
+
assignment.redirectUrl,
|
|
294
|
+
assignment,
|
|
295
|
+
visitorId,
|
|
296
|
+
handoverSettings,
|
|
297
|
+
);
|
|
298
|
+
// A sticky redirect treatment remains an active, exposable assignment
|
|
299
|
+
// after arrival. This classification must survive reloads, when the
|
|
300
|
+
// one-init carry-import marker no longer exists.
|
|
301
|
+
if (destination && isRedirectLoop(currentHref, destination)) return true;
|
|
302
|
+
}
|
|
303
|
+
return Boolean(
|
|
304
|
+
redirectPlanForCandidate(
|
|
305
|
+
experiment,
|
|
306
|
+
assignment,
|
|
307
|
+
currentHref,
|
|
308
|
+
visitorId,
|
|
309
|
+
handoverSettings,
|
|
310
|
+
true,
|
|
311
|
+
storeOrigin,
|
|
312
|
+
),
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function redirectPlanForAssignment(experiments: Experiment[], assignments: Assignment[], currentHref: string,
|
|
318
|
+
visitorId = "", handoverSettings?: ExternalTrackingSettings,
|
|
319
|
+
includeAssignmentCarry = true,
|
|
320
|
+
storeOrigin?: string | string[]): { destination: URL; assignment: Assignment } | null {
|
|
321
|
+
for (const assignment of assignments) {
|
|
322
|
+
const experiment = experiments.find((candidate) => candidate.id === assignment.experimentId);
|
|
323
|
+
const plan = redirectPlanForCandidate(
|
|
324
|
+
experiment,
|
|
325
|
+
assignment,
|
|
326
|
+
currentHref,
|
|
327
|
+
visitorId,
|
|
328
|
+
handoverSettings,
|
|
329
|
+
includeAssignmentCarry,
|
|
330
|
+
storeOrigin,
|
|
331
|
+
);
|
|
332
|
+
if (plan) return plan;
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function redirectForAssignment(experiments: Experiment[], assignments: Assignment[], currentHref: string,
|
|
338
|
+
visitorId = "", handoverSettings?: ExternalTrackingSettings,
|
|
339
|
+
includeAssignmentCarry = true,
|
|
340
|
+
storeOrigin?: string | string[]): URL | null {
|
|
341
|
+
return redirectPlanForAssignment(
|
|
342
|
+
experiments,
|
|
343
|
+
assignments,
|
|
344
|
+
currentHref,
|
|
345
|
+
visitorId,
|
|
346
|
+
handoverSettings,
|
|
347
|
+
includeAssignmentCarry,
|
|
348
|
+
storeOrigin,
|
|
349
|
+
)?.destination ?? null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function parseRedirectAssignmentCarry(href: string): ParsedRedirectCarry | null {
|
|
353
|
+
try {
|
|
354
|
+
const current = new URL(href);
|
|
355
|
+
const value = current.searchParams.get(REDIRECT_ASSIGNMENT_PARAM);
|
|
356
|
+
if (!value) return null;
|
|
357
|
+
const carry = JSON.parse(value) as Partial<RedirectCarry>;
|
|
358
|
+
if (typeof carry.e !== "string" || typeof carry.v !== "string" || !Number.isSafeInteger(carry.epoch) || typeof carry.handover !== "string") return null;
|
|
359
|
+
if (carry.destinationPath !== undefined && (
|
|
360
|
+
typeof carry.destinationPath !== "string" ||
|
|
361
|
+
normalizeRedirectPath(carry.destinationPath) !== normalizeRedirectPath(current.pathname)
|
|
362
|
+
)) return null;
|
|
363
|
+
const expiresAt = bucketingHandoverExpiresAtMs(carry.handover);
|
|
364
|
+
if (
|
|
365
|
+
expiresAt === null ||
|
|
366
|
+
expiresAt <= Date.now()
|
|
367
|
+
) return null;
|
|
368
|
+
return {
|
|
369
|
+
experimentId: carry.e,
|
|
370
|
+
variationId: carry.v,
|
|
371
|
+
assignmentEpoch: normalizedEpoch(carry.epoch),
|
|
372
|
+
handoverToken: carry.handover,
|
|
373
|
+
expiresAt,
|
|
374
|
+
// Carries emitted by pre-fix SDKs always implied that the source had
|
|
375
|
+
// exposed the assignment. Explicit false is the new gated-source case.
|
|
376
|
+
exposureSent: carry.exposureSent !== false,
|
|
377
|
+
destinationPath: carry.destinationPath,
|
|
378
|
+
};
|
|
379
|
+
} catch { return null; }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function persistPendingRedirectAssignmentCarry(
|
|
383
|
+
carry: ParsedRedirectCarry | null,
|
|
384
|
+
): void {
|
|
385
|
+
if (!carry) return;
|
|
386
|
+
try {
|
|
387
|
+
const expiresAt = Math.min(
|
|
388
|
+
carry.expiresAt,
|
|
389
|
+
Date.now() + MAX_PENDING_REDIRECT_CARRY_TTL_MS,
|
|
390
|
+
);
|
|
391
|
+
if (expiresAt <= Date.now()) {
|
|
392
|
+
sessionStorage.removeItem(REDIRECT_ASSIGNMENT_PENDING_KEY);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
sessionStorage.setItem(
|
|
396
|
+
REDIRECT_ASSIGNMENT_PENDING_KEY,
|
|
397
|
+
JSON.stringify({ ...carry, expiresAt }),
|
|
398
|
+
);
|
|
399
|
+
} catch {}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function readPendingRedirectAssignmentCarry(): ParsedRedirectCarry | null {
|
|
403
|
+
try {
|
|
404
|
+
const raw = sessionStorage.getItem(REDIRECT_ASSIGNMENT_PENDING_KEY);
|
|
405
|
+
if (!raw) return null;
|
|
406
|
+
const parsed = JSON.parse(raw) as Partial<ParsedRedirectCarry>;
|
|
407
|
+
const now = Date.now();
|
|
408
|
+
if (
|
|
409
|
+
typeof parsed.experimentId !== "string" ||
|
|
410
|
+
typeof parsed.variationId !== "string" ||
|
|
411
|
+
!Number.isSafeInteger(parsed.assignmentEpoch) ||
|
|
412
|
+
typeof parsed.handoverToken !== "string" ||
|
|
413
|
+
!parsed.handoverToken ||
|
|
414
|
+
(parsed.exposureSent !== undefined && typeof parsed.exposureSent !== "boolean") ||
|
|
415
|
+
typeof parsed.expiresAt !== "number" ||
|
|
416
|
+
(parsed.destinationPath !== undefined && typeof parsed.destinationPath !== "string") ||
|
|
417
|
+
!Number.isFinite(parsed.expiresAt) ||
|
|
418
|
+
parsed.expiresAt <= now ||
|
|
419
|
+
parsed.expiresAt > now + MAX_PENDING_REDIRECT_CARRY_TTL_MS
|
|
420
|
+
) {
|
|
421
|
+
sessionStorage.removeItem(REDIRECT_ASSIGNMENT_PENDING_KEY);
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
experimentId: parsed.experimentId,
|
|
426
|
+
variationId: parsed.variationId,
|
|
427
|
+
assignmentEpoch: normalizedEpoch(parsed.assignmentEpoch),
|
|
428
|
+
handoverToken: parsed.handoverToken,
|
|
429
|
+
expiresAt: parsed.expiresAt,
|
|
430
|
+
exposureSent: parsed.exposureSent !== false,
|
|
431
|
+
destinationPath: parsed.destinationPath,
|
|
432
|
+
};
|
|
433
|
+
} catch {
|
|
434
|
+
try { sessionStorage.removeItem(REDIRECT_ASSIGNMENT_PENDING_KEY); } catch {}
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function clearPendingRedirectAssignmentCarry(): void {
|
|
440
|
+
try {
|
|
441
|
+
sessionStorage.removeItem(REDIRECT_ASSIGNMENT_PENDING_KEY);
|
|
442
|
+
} catch {}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export async function verifyRedirectAssignmentCarry(
|
|
446
|
+
carry: ParsedRedirectCarry | null,
|
|
447
|
+
shopId: string,
|
|
448
|
+
verifyToken: (token: string) => Promise<unknown>,
|
|
449
|
+
): Promise<VerifiedRedirectCarry | null> {
|
|
450
|
+
if (!carry || carry.expiresAt <= Date.now()) return null;
|
|
451
|
+
const handover = await verifyBucketingHandoverToken(
|
|
452
|
+
carry.handoverToken,
|
|
453
|
+
shopId,
|
|
454
|
+
verifyToken,
|
|
455
|
+
);
|
|
456
|
+
if (
|
|
457
|
+
!handover?.visitorId ||
|
|
458
|
+
handover.forceMap?.[carry.experimentId] !== carry.variationId
|
|
459
|
+
) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
const attribution = handover.redirectAttribution;
|
|
463
|
+
const matchingAttribution = attribution
|
|
464
|
+
&& attribution.experimentId === carry.experimentId
|
|
465
|
+
&& attribution.variationId === carry.variationId
|
|
466
|
+
? attribution
|
|
467
|
+
: undefined;
|
|
468
|
+
return {
|
|
469
|
+
...carry,
|
|
470
|
+
visitorId: handover.visitorId,
|
|
471
|
+
attributionToken: matchingAttribution?.token,
|
|
472
|
+
attributionDestinationUrl: matchingAttribution?.destinationUrl,
|
|
473
|
+
attributionIssuedAt: matchingAttribution?.issuedAt,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export function validateRedirectAssignmentCarry(
|
|
478
|
+
experiments: Experiment[],
|
|
479
|
+
carry: VerifiedRedirectCarry | null,
|
|
480
|
+
href?: string,
|
|
481
|
+
): ImportedRedirectCarry | null {
|
|
482
|
+
if (!carry) return null;
|
|
483
|
+
let currentUrl: URL | undefined;
|
|
484
|
+
if (href) {
|
|
485
|
+
try {
|
|
486
|
+
currentUrl = new URL(href);
|
|
487
|
+
if (carry.destinationPath !== undefined && (
|
|
488
|
+
normalizeRedirectPath(currentUrl.pathname) !== normalizeRedirectPath(carry.destinationPath)
|
|
489
|
+
)) return null;
|
|
490
|
+
} catch {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const experiment = experiments.find((candidate) =>
|
|
495
|
+
candidate.id === carry.experimentId &&
|
|
496
|
+
candidate.experimentType === "redirect" &&
|
|
497
|
+
candidate.variations.some((variation) => variation.id === carry.variationId));
|
|
498
|
+
if (!experiment || normalizedEpoch(experiment.assignmentEpoch) !== carry.assignmentEpoch) return null;
|
|
499
|
+
if (currentUrl) {
|
|
500
|
+
const variation = experiment.variations.find((candidate) => candidate.id === carry.variationId);
|
|
501
|
+
if (!variation?.redirectUrl) return null;
|
|
502
|
+
try {
|
|
503
|
+
const destination = new URL(variation.redirectUrl, currentUrl);
|
|
504
|
+
const redirectIsAbsolute = (() => {
|
|
505
|
+
try {
|
|
506
|
+
new URL(variation.redirectUrl!);
|
|
507
|
+
return true;
|
|
508
|
+
} catch {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
})();
|
|
512
|
+
if (
|
|
513
|
+
normalizeRedirectPath(destination.pathname) !== normalizeRedirectPath(currentUrl.pathname) ||
|
|
514
|
+
(redirectIsAbsolute && destination.host !== currentUrl.host)
|
|
515
|
+
) return null;
|
|
516
|
+
} catch {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
experiment,
|
|
522
|
+
variationId: carry.variationId,
|
|
523
|
+
visitorId: carry.visitorId,
|
|
524
|
+
exposureSent: carry.exposureSent,
|
|
525
|
+
attributionToken: carry.attributionToken,
|
|
526
|
+
attributionDestinationUrl: carry.attributionDestinationUrl,
|
|
527
|
+
attributionIssuedAt: carry.attributionIssuedAt,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export async function readRedirectAssignmentCarry(
|
|
532
|
+
experiments: Experiment[],
|
|
533
|
+
href: string,
|
|
534
|
+
shopId: string,
|
|
535
|
+
verifyToken: (token: string) => Promise<unknown>,
|
|
536
|
+
): Promise<ImportedRedirectCarry | null> {
|
|
537
|
+
const verifiedCarry = await verifyRedirectAssignmentCarry(
|
|
538
|
+
parseRedirectAssignmentCarry(href),
|
|
539
|
+
shopId,
|
|
540
|
+
verifyToken,
|
|
541
|
+
);
|
|
542
|
+
return validateRedirectAssignmentCarry(experiments, verifiedCarry, href);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export function importRedirectAssignmentCarry(
|
|
546
|
+
carry: ImportedRedirectCarry,
|
|
547
|
+
href: string,
|
|
548
|
+
persistAttribution = true,
|
|
549
|
+
): void {
|
|
550
|
+
persistAssignmentForExperiment(carry.experiment, carry.variationId);
|
|
551
|
+
try {
|
|
552
|
+
if (!persistAttribution) return;
|
|
553
|
+
if (!carry.attributionToken || !carry.attributionDestinationUrl || !carry.attributionIssuedAt) return;
|
|
554
|
+
if (
|
|
555
|
+
canonicalRedirectAttributionDestination(href)
|
|
556
|
+
!== canonicalRedirectAttributionDestination(carry.attributionDestinationUrl)
|
|
557
|
+
) return;
|
|
558
|
+
const remainingSeconds = Math.floor(
|
|
559
|
+
(carry.attributionIssuedAt + REDIRECT_ATTRIBUTION_TTL_MS - Date.now()) / 1000,
|
|
560
|
+
);
|
|
561
|
+
if (remainingSeconds <= 0) return;
|
|
562
|
+
globalThis.document.cookie = `${REDIRECT_ATTRIBUTION_COOKIE}=${encodeURIComponent(carry.attributionToken)}; Max-Age=${remainingSeconds}; Path=/; SameSite=Lax`;
|
|
563
|
+
} catch {}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function stripRedirectAssignmentCarry(href: string): void {
|
|
567
|
+
try {
|
|
568
|
+
const cleaned = new URL(href);
|
|
569
|
+
cleaned.searchParams.delete(REDIRECT_ASSIGNMENT_PARAM);
|
|
570
|
+
try { globalThis.history?.replaceState(globalThis.history.state, "", cleaned.href); } catch {}
|
|
571
|
+
} catch {}
|
|
572
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { record } from "rrweb";
|
|
2
|
+
import { deflate, deflateSync } from "fflate";
|
|
3
|
+
import type { ReplayRecorderApi, ReplayRecorderEmission } from "./replay";
|
|
4
|
+
import { createReplaySignalDeriver } from "./replay-signals";
|
|
5
|
+
|
|
6
|
+
const RRWEB_VERSION = "2.1.1";
|
|
7
|
+
const BLOCK_SELECTOR = [
|
|
8
|
+
"[data-drip-replay-block]",
|
|
9
|
+
"iframe[src*='payment' i]",
|
|
10
|
+
"iframe[name*='payment' i]",
|
|
11
|
+
"iframe[src*='checkout' i]",
|
|
12
|
+
".StripeElement",
|
|
13
|
+
"[data-stripe]",
|
|
14
|
+
].join(",");
|
|
15
|
+
const MASK_TEXT_SELECTOR = [
|
|
16
|
+
"[data-drip-replay-mask]",
|
|
17
|
+
"[data-email]",
|
|
18
|
+
"[data-phone]",
|
|
19
|
+
"[data-address]",
|
|
20
|
+
".customer-email",
|
|
21
|
+
".customer-phone",
|
|
22
|
+
".customer-address",
|
|
23
|
+
"[class*='personal-info' i]",
|
|
24
|
+
].join(",");
|
|
25
|
+
|
|
26
|
+
function targetForEvent(event: Record<string, unknown>): Element | null {
|
|
27
|
+
const data = event.data;
|
|
28
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
29
|
+
const id = (data as Record<string, unknown>).id;
|
|
30
|
+
if (typeof id !== "number") return null;
|
|
31
|
+
const node = record.mirror.getNode(id);
|
|
32
|
+
return node instanceof Element ? node : node?.parentElement ?? null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function errorMessage(value: unknown): string {
|
|
36
|
+
if (value instanceof Error) return value.message || value.name;
|
|
37
|
+
if (typeof value === "string") return value;
|
|
38
|
+
try {
|
|
39
|
+
return JSON.stringify(value) || "Unhandled rejection";
|
|
40
|
+
} catch {
|
|
41
|
+
return "Unhandled rejection";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let replaySignals = createReplaySignalDeriver();
|
|
46
|
+
|
|
47
|
+
const api: ReplayRecorderApi = {
|
|
48
|
+
version: RRWEB_VERSION,
|
|
49
|
+
takeSignals() {
|
|
50
|
+
return replaySignals.snapshot();
|
|
51
|
+
},
|
|
52
|
+
compressSync(bytes) {
|
|
53
|
+
return deflateSync(bytes);
|
|
54
|
+
},
|
|
55
|
+
async compress(bytes) {
|
|
56
|
+
try {
|
|
57
|
+
return await new Promise<Uint8Array>((resolve, reject) => {
|
|
58
|
+
try {
|
|
59
|
+
deflate(bytes, (error, compressed) => {
|
|
60
|
+
if (error) reject(error);
|
|
61
|
+
else resolve(compressed);
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
reject(error);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
} catch {
|
|
68
|
+
return deflateSync(bytes);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
start({ emit, signalState }) {
|
|
72
|
+
replaySignals = createReplaySignalDeriver(signalState);
|
|
73
|
+
const stop = record({
|
|
74
|
+
emit(event) {
|
|
75
|
+
const rawEvent = event as unknown as Record<string, unknown>;
|
|
76
|
+
replaySignals.accept(rawEvent, targetForEvent(rawEvent));
|
|
77
|
+
const emission: ReplayRecorderEmission = { event: rawEvent };
|
|
78
|
+
emit(emission);
|
|
79
|
+
},
|
|
80
|
+
maskAllInputs: true,
|
|
81
|
+
slimDOMOptions: "all",
|
|
82
|
+
blockSelector: BLOCK_SELECTOR,
|
|
83
|
+
maskTextSelector: MASK_TEXT_SELECTOR,
|
|
84
|
+
sampling: {
|
|
85
|
+
mousemove: 50,
|
|
86
|
+
scroll: 150,
|
|
87
|
+
media: 800,
|
|
88
|
+
input: "last",
|
|
89
|
+
},
|
|
90
|
+
recordCanvas: false,
|
|
91
|
+
collectFonts: false,
|
|
92
|
+
inlineStylesheet: true,
|
|
93
|
+
});
|
|
94
|
+
const onError = (event: ErrorEvent) => {
|
|
95
|
+
replaySignals.error(event.message || "Window error");
|
|
96
|
+
};
|
|
97
|
+
const onUnhandledRejection = (event: PromiseRejectionEvent) => {
|
|
98
|
+
replaySignals.error(errorMessage(event.reason));
|
|
99
|
+
};
|
|
100
|
+
const onSubmit = (event: SubmitEvent) => {
|
|
101
|
+
replaySignals.submit(event.target instanceof Element ? event.target : null);
|
|
102
|
+
};
|
|
103
|
+
window.addEventListener("error", onError);
|
|
104
|
+
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
105
|
+
window.addEventListener("submit", onSubmit, { capture: true, passive: true });
|
|
106
|
+
return () => {
|
|
107
|
+
window.removeEventListener("error", onError);
|
|
108
|
+
window.removeEventListener("unhandledrejection", onUnhandledRejection);
|
|
109
|
+
window.removeEventListener("submit", onSubmit, { capture: true });
|
|
110
|
+
stop?.();
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
(globalThis as typeof globalThis & { __dripReplayRecorder?: ReplayRecorderApi }).__dripReplayRecorder = api;
|