@ianmenethil/zp-observer 6.0.0 → 6.1.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.
Potentially problematic release.
This version of @ianmenethil/zp-observer might be problematic. Click here for more details.
- package/README.md +19 -276
- package/dist/adapters/browser-lifecycle-adapter.cjs +51 -0
- package/dist/adapters/browser-lifecycle-adapter.js +48 -0
- package/dist/adapters/iframe-detector-adapter.cjs +108 -0
- package/dist/adapters/iframe-detector-adapter.js +106 -0
- package/dist/client/create-telemetry-client.cjs +136 -0
- package/dist/client/create-telemetry-client.js +134 -0
- package/dist/client/state-machine.cjs +20 -0
- package/dist/client/state-machine.js +18 -0
- package/dist/diagnostics/diagnostics-buffer.cjs +34 -0
- package/dist/diagnostics/diagnostics-buffer.js +32 -0
- package/dist/diagnostics/preflight.cjs +36 -0
- package/dist/diagnostics/preflight.js +34 -0
- package/dist/events/envelope.cjs +23 -0
- package/dist/events/envelope.js +21 -0
- package/dist/index.cjs +23 -0
- package/dist/index.d.ts +230 -0
- package/dist/index.js +10 -0
- package/dist/persistence/local-storage-outbox.cjs +56 -0
- package/dist/persistence/local-storage-outbox.js +54 -0
- package/dist/persistence/memory-outbox.cjs +23 -0
- package/dist/persistence/memory-outbox.js +21 -0
- package/dist/runtime/event-pipeline.cjs +64 -0
- package/dist/runtime/event-pipeline.js +62 -0
- package/dist/runtime/heartbeat-scheduler.cjs +46 -0
- package/dist/runtime/heartbeat-scheduler.js +44 -0
- package/dist/runtime/session-manager.cjs +47 -0
- package/dist/runtime/session-manager.js +45 -0
- package/dist/transport/beacon.cjs +14 -0
- package/dist/transport/beacon.js +12 -0
- package/dist/transport/callback-transport.cjs +19 -0
- package/dist/transport/callback-transport.js +17 -0
- package/dist/transport/http-transport.cjs +62 -0
- package/dist/transport/http-transport.js +60 -0
- package/dist/types/internal.cjs +3 -0
- package/dist/types/internal.js +0 -0
- package/dist/types/public.cjs +3 -0
- package/dist/types/public.js +0 -0
- package/dist/utils/ids.cjs +20 -0
- package/dist/utils/ids.js +17 -0
- package/dist/utils/safe-globals.cjs +11 -0
- package/dist/utils/safe-globals.js +9 -0
- package/dist/version.cjs +5 -0
- package/dist/version.js +2 -0
- package/package.json +29 -89
- package/PRIVACY.md +0 -67
- package/dist/auto-patch.cjs +0 -171
- package/dist/auto-patch.cjs.map +0 -7
- package/dist/auto-patch.mjs +0 -148
- package/dist/auto-patch.mjs.map +0 -7
- package/dist/session.cjs +0 -1186
- package/dist/session.cjs.map +0 -7
- package/dist/session.mjs +0 -1163
- package/dist/session.mjs.map +0 -7
- package/dist/types/auto-patch.d.ts +0 -9
- package/dist/types/auto-patch.d.ts.map +0 -1
- package/dist/types/core/beacon.d.ts +0 -6
- package/dist/types/core/beacon.d.ts.map +0 -1
- package/dist/types/core/detection.d.ts +0 -34
- package/dist/types/core/detection.d.ts.map +0 -1
- package/dist/types/core/event-bus.d.ts +0 -21
- package/dist/types/core/event-bus.d.ts.map +0 -1
- package/dist/types/core/experimental.d.ts +0 -35
- package/dist/types/core/experimental.d.ts.map +0 -1
- package/dist/types/core/heartbeat.d.ts +0 -32
- package/dist/types/core/heartbeat.d.ts.map +0 -1
- package/dist/types/core/lifecycle.d.ts +0 -31
- package/dist/types/core/lifecycle.d.ts.map +0 -1
- package/dist/types/core/observer.d.ts +0 -10
- package/dist/types/core/observer.d.ts.map +0 -1
- package/dist/types/core/outbox.d.ts +0 -20
- package/dist/types/core/outbox.d.ts.map +0 -1
- package/dist/types/core/random.d.ts +0 -8
- package/dist/types/core/random.d.ts.map +0 -1
- package/dist/types/core/shortcode.d.ts +0 -17
- package/dist/types/core/shortcode.d.ts.map +0 -1
- package/dist/types/core/types.d.ts +0 -292
- package/dist/types/core/types.d.ts.map +0 -1
- package/dist/types/diagnostics/preflight.d.ts +0 -17
- package/dist/types/diagnostics/preflight.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -41
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/integration/devicefp-bridge.d.ts +0 -31
- package/dist/types/integration/devicefp-bridge.d.ts.map +0 -1
- package/dist/types/integration/hpp-bridge.d.ts +0 -13
- package/dist/types/integration/hpp-bridge.d.ts.map +0 -1
- package/dist/types/integration/zenpay-auto-patch.d.ts +0 -28
- package/dist/types/integration/zenpay-auto-patch.d.ts.map +0 -1
- package/dist/types/outcome.d.ts +0 -20
- package/dist/types/outcome.d.ts.map +0 -1
- package/dist/types/session.d.ts +0 -54
- package/dist/types/session.d.ts.map +0 -1
- package/dist/types/transport/callback-transport.d.ts +0 -17
- package/dist/types/transport/callback-transport.d.ts.map +0 -1
- package/dist/types/transport/http-transport.d.ts +0 -30
- package/dist/types/transport/http-transport.d.ts.map +0 -1
- package/dist/types/umd.d.ts +0 -16
- package/dist/types/umd.d.ts.map +0 -1
- package/dist/zp-observer.cjs +0 -1375
- package/dist/zp-observer.cjs.map +0 -7
- package/dist/zp-observer.js +0 -1377
- package/dist/zp-observer.js.map +0 -7
- package/dist/zp-observer.min.js +0 -2
- package/dist/zp-observer.min.js.map +0 -7
- package/dist/zp-observer.min.obf.js +0 -1
- package/dist/zp-observer.mjs +0 -1352
- package/dist/zp-observer.mjs.map +0 -7
package/dist/zp-observer.cjs
DELETED
|
@@ -1,1375 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
VERSION: () => VERSION,
|
|
24
|
-
callbackTransport: () => callbackTransport,
|
|
25
|
-
createObserver: () => createObserver,
|
|
26
|
-
httpTransport: () => httpTransport,
|
|
27
|
-
installDeviceFPBridge: () => installDeviceFPBridge,
|
|
28
|
-
installHPPBridge: () => installHPPBridge,
|
|
29
|
-
installZenPayAutoPatch: () => installZenPayAutoPatch,
|
|
30
|
-
preflight: () => preflight,
|
|
31
|
-
reportOutcome: () => reportOutcome
|
|
32
|
-
});
|
|
33
|
-
module.exports = __toCommonJS(index_exports);
|
|
34
|
-
|
|
35
|
-
// src/core/detection.ts
|
|
36
|
-
var DEFAULT_IFRAME_SELECTORS = [
|
|
37
|
-
"iframe.zp-payment-frame",
|
|
38
|
-
'iframe[src*="zenithpayments"]',
|
|
39
|
-
'iframe[src*="travelpay"]',
|
|
40
|
-
".modal-payment iframe",
|
|
41
|
-
"div.modal.show iframe"
|
|
42
|
-
];
|
|
43
|
-
function startDetection(hooks, opts) {
|
|
44
|
-
if (typeof document === "undefined") {
|
|
45
|
-
return { stop() {
|
|
46
|
-
} };
|
|
47
|
-
}
|
|
48
|
-
let stopped = false;
|
|
49
|
-
let pollTimer = null;
|
|
50
|
-
let mutationObserver = null;
|
|
51
|
-
let bootstrapHandler = null;
|
|
52
|
-
const startedAt = Date.now();
|
|
53
|
-
const findIframe = () => {
|
|
54
|
-
for (const selector of opts.selectors) {
|
|
55
|
-
const el = document.querySelector(selector);
|
|
56
|
-
if (el instanceof HTMLIFrameElement) return el;
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
};
|
|
60
|
-
const stopPolling = () => {
|
|
61
|
-
if (pollTimer !== null) {
|
|
62
|
-
clearInterval(pollTimer);
|
|
63
|
-
pollTimer = null;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
const watchForRemoval = (iframe) => {
|
|
67
|
-
if (typeof MutationObserver === "undefined" || !document.body) return;
|
|
68
|
-
mutationObserver = new MutationObserver((mutations) => {
|
|
69
|
-
for (const m of mutations) {
|
|
70
|
-
for (const node of Array.from(m.removedNodes)) {
|
|
71
|
-
if (node === iframe || node instanceof Node && node.contains(iframe)) {
|
|
72
|
-
hooks.onRemoved();
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
mutationObserver.observe(document.body, { childList: true, subtree: true });
|
|
79
|
-
};
|
|
80
|
-
const watchBootstrapModal = () => {
|
|
81
|
-
if (typeof document.addEventListener !== "function") return;
|
|
82
|
-
bootstrapHandler = () => hooks.onBootstrapHidden();
|
|
83
|
-
document.addEventListener("hidden.bs.modal", bootstrapHandler, true);
|
|
84
|
-
};
|
|
85
|
-
const onFound = (iframe) => {
|
|
86
|
-
stopPolling();
|
|
87
|
-
watchForRemoval(iframe);
|
|
88
|
-
watchBootstrapModal();
|
|
89
|
-
hooks.onFound(iframe);
|
|
90
|
-
};
|
|
91
|
-
const existing = findIframe();
|
|
92
|
-
if (existing) {
|
|
93
|
-
queueMicrotask(() => {
|
|
94
|
-
if (!stopped) onFound(existing);
|
|
95
|
-
});
|
|
96
|
-
} else {
|
|
97
|
-
pollTimer = setInterval(() => {
|
|
98
|
-
if (stopped) return;
|
|
99
|
-
if (Date.now() - startedAt > opts.timeoutMs) {
|
|
100
|
-
stopPolling();
|
|
101
|
-
hooks.onTimeout();
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const found = findIframe();
|
|
105
|
-
if (found) onFound(found);
|
|
106
|
-
}, opts.intervalMs);
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
stop() {
|
|
110
|
-
stopped = true;
|
|
111
|
-
stopPolling();
|
|
112
|
-
if (mutationObserver) {
|
|
113
|
-
mutationObserver.disconnect();
|
|
114
|
-
mutationObserver = null;
|
|
115
|
-
}
|
|
116
|
-
if (bootstrapHandler) {
|
|
117
|
-
document.removeEventListener(
|
|
118
|
-
"hidden.bs.modal",
|
|
119
|
-
bootstrapHandler,
|
|
120
|
-
true
|
|
121
|
-
);
|
|
122
|
-
bootstrapHandler = null;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// src/core/beacon.ts
|
|
129
|
-
var JSON_TYPE = "application/json";
|
|
130
|
-
function postJsonBeacon(url, body) {
|
|
131
|
-
if (typeof navigator === "undefined" || typeof navigator.sendBeacon !== "function") {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
try {
|
|
135
|
-
const blob = new Blob([JSON.stringify(body)], { type: JSON_TYPE });
|
|
136
|
-
return navigator.sendBeacon(url, blob);
|
|
137
|
-
} catch {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// src/core/experimental.ts
|
|
143
|
-
function hasGlobalExperimentalFeatures(exp) {
|
|
144
|
-
if (!exp) return false;
|
|
145
|
-
return !!(exp.pageLifecycleBeaconUrl || exp.captureWasDiscarded || exp.capturePageShow || exp.captureWindowFocus || exp.captureVisibilityDetail || exp.captureFreezeResume || exp.capturePerformanceEntries);
|
|
146
|
-
}
|
|
147
|
-
function hasIframeExperimentalFeatures(exp) {
|
|
148
|
-
if (!exp) return false;
|
|
149
|
-
return !!(exp.captureIframeResize || exp.captureIframeSrcChanges);
|
|
150
|
-
}
|
|
151
|
-
function logDiag(debug, ...args) {
|
|
152
|
-
if (debug && typeof console !== "undefined") {
|
|
153
|
-
console.log("[ZPObserver/experimental]", ...args);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
function captureUserActivation() {
|
|
157
|
-
try {
|
|
158
|
-
const ua = typeof navigator !== "undefined" ? navigator.userActivation : void 0;
|
|
159
|
-
if (!ua) return void 0;
|
|
160
|
-
return {
|
|
161
|
-
hasBeenActive: ua.hasBeenActive ?? false,
|
|
162
|
-
isActive: ua.isActive ?? false
|
|
163
|
-
};
|
|
164
|
-
} catch {
|
|
165
|
-
return void 0;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function readNavigationDetail() {
|
|
169
|
-
try {
|
|
170
|
-
const entries = performance.getEntriesByType("navigation");
|
|
171
|
-
const nav = entries[0];
|
|
172
|
-
if (!nav) return null;
|
|
173
|
-
const wasDiscarded = "wasDiscarded" in nav ? Boolean(nav.wasDiscarded) : null;
|
|
174
|
-
return {
|
|
175
|
-
wasDiscarded,
|
|
176
|
-
redirectCount: nav.redirectCount ?? null,
|
|
177
|
-
transferSize: nav.transferSize ?? null,
|
|
178
|
-
type: nav.type ?? null
|
|
179
|
-
};
|
|
180
|
-
} catch {
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
function readOpenExperimentalFields(exp) {
|
|
185
|
-
if (!exp) return {};
|
|
186
|
-
const out = {};
|
|
187
|
-
if (exp.captureUserActivation) {
|
|
188
|
-
const ua = captureUserActivation();
|
|
189
|
-
if (ua) out.userActivation = ua;
|
|
190
|
-
}
|
|
191
|
-
if (exp.captureNavigationTimingDetail) {
|
|
192
|
-
const nav = readNavigationDetail();
|
|
193
|
-
if (nav) out.navigationDetail = nav;
|
|
194
|
-
}
|
|
195
|
-
return out;
|
|
196
|
-
}
|
|
197
|
-
function installGlobalExperimental(ctx) {
|
|
198
|
-
const { experimental: exp, onDiagnostic, debug, sessionId, getNavigationType: getNavigationType2 } = ctx;
|
|
199
|
-
const stops = [];
|
|
200
|
-
const beaconUrl = exp.pageLifecycleBeaconUrl;
|
|
201
|
-
const fireBeacon = (phase, extra) => {
|
|
202
|
-
if (!beaconUrl) return;
|
|
203
|
-
const payload = { phase, sessionId, timestamp: Date.now(), ...extra };
|
|
204
|
-
const ok = postJsonBeacon(beaconUrl, payload);
|
|
205
|
-
onDiagnostic({ kind: "exp.page_lifecycle_beacon", phase, ok, timestamp: Date.now() });
|
|
206
|
-
logDiag(debug, "pageLifecycleBeacon", phase, ok);
|
|
207
|
-
};
|
|
208
|
-
if (beaconUrl) {
|
|
209
|
-
fireBeacon("navigation", { navType: getNavigationType2() });
|
|
210
|
-
}
|
|
211
|
-
if (exp.captureWasDiscarded) {
|
|
212
|
-
const nav = readNavigationDetail();
|
|
213
|
-
const wasDiscarded = nav?.wasDiscarded ?? null;
|
|
214
|
-
onDiagnostic({ kind: "exp.was_discarded", wasDiscarded, timestamp: Date.now() });
|
|
215
|
-
logDiag(debug, "wasDiscarded", wasDiscarded);
|
|
216
|
-
}
|
|
217
|
-
if ((exp.capturePageShow || beaconUrl) && typeof window !== "undefined") {
|
|
218
|
-
const handler = (ev) => {
|
|
219
|
-
const persisted = ev.persisted === true;
|
|
220
|
-
if (exp.capturePageShow) {
|
|
221
|
-
onDiagnostic({
|
|
222
|
-
kind: "exp.pageshow",
|
|
223
|
-
persisted,
|
|
224
|
-
navigationType: getNavigationType2(),
|
|
225
|
-
timestamp: Date.now()
|
|
226
|
-
});
|
|
227
|
-
logDiag(debug, "pageshow", { persisted });
|
|
228
|
-
}
|
|
229
|
-
if (beaconUrl) fireBeacon("pageshow", { persisted });
|
|
230
|
-
};
|
|
231
|
-
window.addEventListener("pageshow", handler, { passive: true });
|
|
232
|
-
stops.push(() => window.removeEventListener("pageshow", handler));
|
|
233
|
-
}
|
|
234
|
-
if (beaconUrl && typeof window !== "undefined") {
|
|
235
|
-
const ph = (ev) => {
|
|
236
|
-
fireBeacon("pagehide", { persisted: ev.persisted === true });
|
|
237
|
-
};
|
|
238
|
-
window.addEventListener("pagehide", ph, { passive: true });
|
|
239
|
-
stops.push(() => window.removeEventListener("pagehide", ph));
|
|
240
|
-
}
|
|
241
|
-
if (exp.captureWindowFocus && typeof window !== "undefined") {
|
|
242
|
-
const onF = () => {
|
|
243
|
-
onDiagnostic({ kind: "exp.window_focus", timestamp: Date.now() });
|
|
244
|
-
logDiag(debug, "window focus");
|
|
245
|
-
};
|
|
246
|
-
const onB = () => {
|
|
247
|
-
onDiagnostic({ kind: "exp.window_blur", timestamp: Date.now() });
|
|
248
|
-
logDiag(debug, "window blur");
|
|
249
|
-
};
|
|
250
|
-
window.addEventListener("focus", onF, { passive: true });
|
|
251
|
-
window.addEventListener("blur", onB, { passive: true });
|
|
252
|
-
stops.push(() => {
|
|
253
|
-
window.removeEventListener("focus", onF);
|
|
254
|
-
window.removeEventListener("blur", onB);
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
if (exp.captureVisibilityDetail && typeof document !== "undefined") {
|
|
258
|
-
let lastState = document.visibilityState ?? "visible";
|
|
259
|
-
let lastChangeAt = Date.now();
|
|
260
|
-
const handler = () => {
|
|
261
|
-
const now = Date.now();
|
|
262
|
-
const curr = document.visibilityState ?? "visible";
|
|
263
|
-
const prev = lastState;
|
|
264
|
-
lastState = curr;
|
|
265
|
-
const deltaMs = now - lastChangeAt;
|
|
266
|
-
lastChangeAt = now;
|
|
267
|
-
const hasFocus = typeof document.hasFocus === "function" ? document.hasFocus() : null;
|
|
268
|
-
const onLine = typeof navigator !== "undefined" && typeof navigator.onLine === "boolean" ? navigator.onLine : null;
|
|
269
|
-
onDiagnostic({
|
|
270
|
-
kind: "exp.visibility_detail",
|
|
271
|
-
prevState: prev,
|
|
272
|
-
currState: curr,
|
|
273
|
-
prevStateDurationMs: deltaMs,
|
|
274
|
-
hasFocus,
|
|
275
|
-
onLine,
|
|
276
|
-
timestamp: now
|
|
277
|
-
});
|
|
278
|
-
logDiag(debug, "visibility_detail", { prev, curr, hasFocus, onLine });
|
|
279
|
-
};
|
|
280
|
-
document.addEventListener("visibilitychange", handler, { passive: true });
|
|
281
|
-
stops.push(() => document.removeEventListener("visibilitychange", handler));
|
|
282
|
-
}
|
|
283
|
-
if (exp.captureFreezeResume && typeof document !== "undefined") {
|
|
284
|
-
const onFreeze = () => {
|
|
285
|
-
onDiagnostic({ kind: "exp.freeze", timestamp: Date.now() });
|
|
286
|
-
logDiag(debug, "freeze");
|
|
287
|
-
};
|
|
288
|
-
const onResume = () => {
|
|
289
|
-
onDiagnostic({ kind: "exp.resume", timestamp: Date.now() });
|
|
290
|
-
logDiag(debug, "resume");
|
|
291
|
-
};
|
|
292
|
-
try {
|
|
293
|
-
document.addEventListener("freeze", onFreeze, { passive: true });
|
|
294
|
-
document.addEventListener("resume", onResume, { passive: true });
|
|
295
|
-
stops.push(() => {
|
|
296
|
-
document.removeEventListener("freeze", onFreeze);
|
|
297
|
-
document.removeEventListener("resume", onResume);
|
|
298
|
-
});
|
|
299
|
-
} catch {
|
|
300
|
-
logDiag(debug, "freeze/resume not supported");
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
if (exp.capturePerformanceEntries && typeof PerformanceObserver !== "undefined") {
|
|
304
|
-
const types = exp.perfEntryTypes ?? ["longtask", "largest-contentful-paint"];
|
|
305
|
-
const emitPerf = (entry) => {
|
|
306
|
-
if (entry.entryType === "longtask") {
|
|
307
|
-
onDiagnostic({
|
|
308
|
-
kind: "exp.perf_entry",
|
|
309
|
-
entryType: entry.entryType,
|
|
310
|
-
name: entry.name,
|
|
311
|
-
startTime: entry.startTime,
|
|
312
|
-
duration: entry.duration,
|
|
313
|
-
timestamp: Date.now()
|
|
314
|
-
});
|
|
315
|
-
} else if (entry.entryType === "largest-contentful-paint") {
|
|
316
|
-
const lcp = entry;
|
|
317
|
-
onDiagnostic({
|
|
318
|
-
kind: "exp.perf_entry",
|
|
319
|
-
entryType: entry.entryType,
|
|
320
|
-
name: entry.name,
|
|
321
|
-
startTime: entry.startTime,
|
|
322
|
-
...lcp.size !== void 0 && { size: lcp.size },
|
|
323
|
-
...lcp.url !== void 0 && { url: lcp.url },
|
|
324
|
-
timestamp: Date.now()
|
|
325
|
-
});
|
|
326
|
-
} else {
|
|
327
|
-
onDiagnostic({
|
|
328
|
-
kind: "exp.perf_entry",
|
|
329
|
-
entryType: entry.entryType,
|
|
330
|
-
name: entry.name,
|
|
331
|
-
startTime: entry.startTime,
|
|
332
|
-
timestamp: Date.now()
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
logDiag(debug, "perf", entry.entryType, entry.name);
|
|
336
|
-
};
|
|
337
|
-
for (const t of types) {
|
|
338
|
-
try {
|
|
339
|
-
const obs = new PerformanceObserver((list) => {
|
|
340
|
-
for (const entry of list.getEntries()) emitPerf(entry);
|
|
341
|
-
});
|
|
342
|
-
try {
|
|
343
|
-
obs.observe({ type: t, buffered: true });
|
|
344
|
-
} catch {
|
|
345
|
-
obs.observe({ entryTypes: [t] });
|
|
346
|
-
}
|
|
347
|
-
stops.push(() => obs.disconnect());
|
|
348
|
-
} catch (err) {
|
|
349
|
-
logDiag(debug, "PerformanceObserver failed for", t, err);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
return {
|
|
354
|
-
stop() {
|
|
355
|
-
for (const s of stops) s();
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
function installIframeExperimental(iframe, ctx) {
|
|
360
|
-
const { experimental: exp, onDiagnostic, debug } = ctx;
|
|
361
|
-
const stops = [];
|
|
362
|
-
if (exp.captureIframeResize && typeof ResizeObserver !== "undefined") {
|
|
363
|
-
const ro = new ResizeObserver((entries) => {
|
|
364
|
-
const e = entries[entries.length - 1];
|
|
365
|
-
if (!e) return;
|
|
366
|
-
const { width, height } = e.contentRect;
|
|
367
|
-
onDiagnostic({
|
|
368
|
-
kind: "exp.iframe_resize",
|
|
369
|
-
width: Math.round(width),
|
|
370
|
-
height: Math.round(height),
|
|
371
|
-
timestamp: Date.now()
|
|
372
|
-
});
|
|
373
|
-
logDiag(debug, "iframe_resize", width, height);
|
|
374
|
-
});
|
|
375
|
-
ro.observe(iframe);
|
|
376
|
-
stops.push(() => ro.disconnect());
|
|
377
|
-
}
|
|
378
|
-
if (exp.captureIframeSrcChanges && typeof MutationObserver !== "undefined") {
|
|
379
|
-
let prev = iframe.src || null;
|
|
380
|
-
const mo = new MutationObserver(() => {
|
|
381
|
-
const cur = iframe.src || "";
|
|
382
|
-
if (cur !== prev) {
|
|
383
|
-
onDiagnostic({
|
|
384
|
-
kind: "exp.iframe_src",
|
|
385
|
-
previousSrc: prev,
|
|
386
|
-
currentSrc: cur,
|
|
387
|
-
timestamp: Date.now()
|
|
388
|
-
});
|
|
389
|
-
logDiag(debug, "iframe_src", prev, cur);
|
|
390
|
-
prev = cur || null;
|
|
391
|
-
}
|
|
392
|
-
});
|
|
393
|
-
mo.observe(iframe, { attributes: true, attributeFilter: ["src"] });
|
|
394
|
-
stops.push(() => mo.disconnect());
|
|
395
|
-
}
|
|
396
|
-
return {
|
|
397
|
-
stop() {
|
|
398
|
-
for (const s of stops) s();
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// src/core/heartbeat.ts
|
|
404
|
-
function startHeartbeat(sessionId, correlationId, getMetadata, sender, hooks, opts) {
|
|
405
|
-
let sequence = 0;
|
|
406
|
-
let missed = 0;
|
|
407
|
-
let abandoned = false;
|
|
408
|
-
let timer = null;
|
|
409
|
-
const tick = () => {
|
|
410
|
-
if (abandoned) return;
|
|
411
|
-
sequence += 1;
|
|
412
|
-
const ev = {
|
|
413
|
-
kind: "heartbeat",
|
|
414
|
-
sessionId,
|
|
415
|
-
correlationId,
|
|
416
|
-
timestamp: Date.now(),
|
|
417
|
-
sequence,
|
|
418
|
-
missedBeats: missed,
|
|
419
|
-
metadata: getMetadata()
|
|
420
|
-
};
|
|
421
|
-
void sender.sendHeartbeat(ev).then((result) => {
|
|
422
|
-
if (result.ok) {
|
|
423
|
-
missed = 0;
|
|
424
|
-
} else {
|
|
425
|
-
missed += 1;
|
|
426
|
-
if (missed >= opts.missThreshold && !abandoned) {
|
|
427
|
-
abandoned = true;
|
|
428
|
-
hooks.onAbandoned(missed);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
hooks.onTick(sequence, missed);
|
|
432
|
-
}).catch(() => {
|
|
433
|
-
missed += 1;
|
|
434
|
-
if (missed >= opts.missThreshold && !abandoned) {
|
|
435
|
-
abandoned = true;
|
|
436
|
-
hooks.onAbandoned(missed);
|
|
437
|
-
}
|
|
438
|
-
hooks.onTick(sequence, missed);
|
|
439
|
-
});
|
|
440
|
-
};
|
|
441
|
-
timer = setInterval(tick, opts.intervalMs);
|
|
442
|
-
return {
|
|
443
|
-
stop() {
|
|
444
|
-
abandoned = true;
|
|
445
|
-
if (timer !== null) {
|
|
446
|
-
clearInterval(timer);
|
|
447
|
-
timer = null;
|
|
448
|
-
}
|
|
449
|
-
},
|
|
450
|
-
heartbeatCount: () => sequence,
|
|
451
|
-
missedBeats: () => missed
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// src/core/lifecycle.ts
|
|
456
|
-
function installPageLifecycle(hooks) {
|
|
457
|
-
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
458
|
-
return { stop() {
|
|
459
|
-
} };
|
|
460
|
-
}
|
|
461
|
-
const visibilityHandler = () => {
|
|
462
|
-
if (document.visibilityState === "hidden") hooks.onHidden();
|
|
463
|
-
else if (document.visibilityState === "visible") hooks.onVisible();
|
|
464
|
-
};
|
|
465
|
-
const pageHideHandler = (event) => {
|
|
466
|
-
const persisted = event.persisted === true;
|
|
467
|
-
hooks.onPageHide(persisted);
|
|
468
|
-
};
|
|
469
|
-
document.addEventListener("visibilitychange", visibilityHandler);
|
|
470
|
-
window.addEventListener("pagehide", pageHideHandler);
|
|
471
|
-
return {
|
|
472
|
-
stop() {
|
|
473
|
-
document.removeEventListener("visibilitychange", visibilityHandler);
|
|
474
|
-
window.removeEventListener("pagehide", pageHideHandler);
|
|
475
|
-
}
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
function observeIframeVisibility(iframe, onChange) {
|
|
479
|
-
if (typeof IntersectionObserver === "undefined") {
|
|
480
|
-
return { stop() {
|
|
481
|
-
} };
|
|
482
|
-
}
|
|
483
|
-
const io = new IntersectionObserver(
|
|
484
|
-
(entries) => {
|
|
485
|
-
const entry = entries[entries.length - 1];
|
|
486
|
-
if (entry) onChange(entry.isIntersecting);
|
|
487
|
-
},
|
|
488
|
-
{ threshold: 0.01 }
|
|
489
|
-
);
|
|
490
|
-
io.observe(iframe);
|
|
491
|
-
return {
|
|
492
|
-
stop() {
|
|
493
|
-
io.disconnect();
|
|
494
|
-
}
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// src/core/shortcode.ts
|
|
499
|
-
var PREFIX = "zpcb_";
|
|
500
|
-
function generateShortcodeName() {
|
|
501
|
-
const ts = Date.now().toString(36);
|
|
502
|
-
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
503
|
-
const buf = new Uint32Array(2);
|
|
504
|
-
crypto.getRandomValues(buf);
|
|
505
|
-
return `${PREFIX}${ts}_${buf[0].toString(36)}${buf[1].toString(36)}`;
|
|
506
|
-
}
|
|
507
|
-
const rand = Math.floor(Math.random() * 4294967295).toString(36);
|
|
508
|
-
return `${PREFIX}${ts}_${rand}`;
|
|
509
|
-
}
|
|
510
|
-
function registerShortcode(name, fn) {
|
|
511
|
-
if (typeof window !== "undefined") {
|
|
512
|
-
window[name] = fn;
|
|
513
|
-
}
|
|
514
|
-
return `window.${name}`;
|
|
515
|
-
}
|
|
516
|
-
function unregisterShortcode(name) {
|
|
517
|
-
if (typeof window !== "undefined") {
|
|
518
|
-
try {
|
|
519
|
-
delete window[name];
|
|
520
|
-
} catch {
|
|
521
|
-
window[name] = void 0;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// src/core/random.ts
|
|
527
|
-
function fallbackUUID() {
|
|
528
|
-
const hex = "0123456789abcdef";
|
|
529
|
-
const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
|
|
530
|
-
let result = "";
|
|
531
|
-
for (const ch of template) {
|
|
532
|
-
if (ch === "-") {
|
|
533
|
-
result += "-";
|
|
534
|
-
} else if (ch === "4") {
|
|
535
|
-
result += "4";
|
|
536
|
-
} else if (ch === "y") {
|
|
537
|
-
result += hex[Math.random() * 16 | 0];
|
|
538
|
-
} else {
|
|
539
|
-
result += hex[Math.random() * 16 | 0];
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return result;
|
|
543
|
-
}
|
|
544
|
-
function generateUUID() {
|
|
545
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
546
|
-
return crypto.randomUUID();
|
|
547
|
-
}
|
|
548
|
-
return fallbackUUID();
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// src/core/event-bus.ts
|
|
552
|
-
function createEventBus(transport, beforeSend, outbox) {
|
|
553
|
-
function applyBeforeSend(ev) {
|
|
554
|
-
if (!beforeSend) return ev;
|
|
555
|
-
try {
|
|
556
|
-
return beforeSend(ev);
|
|
557
|
-
} catch {
|
|
558
|
-
return ev;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
function sendViaTransport(ev) {
|
|
562
|
-
return transport.send(ev).catch(() => ({ ok: false }));
|
|
563
|
-
}
|
|
564
|
-
const bus = {
|
|
565
|
-
emit(ev) {
|
|
566
|
-
const filtered = applyBeforeSend(ev);
|
|
567
|
-
if (!filtered) return;
|
|
568
|
-
if (outbox && ev.kind !== "heartbeat") {
|
|
569
|
-
outbox.push(filtered);
|
|
570
|
-
}
|
|
571
|
-
void sendViaTransport(filtered).then((result) => {
|
|
572
|
-
if (result.ok && outbox && ev.kind !== "heartbeat") {
|
|
573
|
-
outbox.remove(filtered);
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
},
|
|
577
|
-
async sendHeartbeat(ev) {
|
|
578
|
-
const filtered = applyBeforeSend(ev);
|
|
579
|
-
if (!filtered) return { ok: true };
|
|
580
|
-
const result = await sendViaTransport(filtered);
|
|
581
|
-
return result;
|
|
582
|
-
},
|
|
583
|
-
drain(sessionId) {
|
|
584
|
-
if (!outbox) return;
|
|
585
|
-
const pending = outbox.drain(sessionId);
|
|
586
|
-
for (const ev of pending) {
|
|
587
|
-
void sendViaTransport(ev).then((result) => {
|
|
588
|
-
if (result.ok) outbox.remove(ev);
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
return bus;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// src/core/outbox.ts
|
|
597
|
-
var STORAGE_KEY = "__zpobs_outbox_v1";
|
|
598
|
-
var MAX_ENTRIES = 64;
|
|
599
|
-
function createOutbox(onUnavailable) {
|
|
600
|
-
if (typeof localStorage === "undefined") {
|
|
601
|
-
return null;
|
|
602
|
-
}
|
|
603
|
-
function readAll() {
|
|
604
|
-
try {
|
|
605
|
-
const raw = localStorage.getItem(STORAGE_KEY);
|
|
606
|
-
if (!raw) return [];
|
|
607
|
-
const parsed = JSON.parse(raw);
|
|
608
|
-
if (!Array.isArray(parsed)) return [];
|
|
609
|
-
return parsed;
|
|
610
|
-
} catch {
|
|
611
|
-
onUnavailable("Failed to read outbox from localStorage");
|
|
612
|
-
return [];
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
function writeAll(entries) {
|
|
616
|
-
try {
|
|
617
|
-
const trimmed = entries.slice(-MAX_ENTRIES);
|
|
618
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(trimmed));
|
|
619
|
-
return true;
|
|
620
|
-
} catch (err) {
|
|
621
|
-
onUnavailable(
|
|
622
|
-
err instanceof Error ? err.message : "localStorage write failed (quota or private mode)"
|
|
623
|
-
);
|
|
624
|
-
return false;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
return {
|
|
628
|
-
push(event) {
|
|
629
|
-
const entries = readAll();
|
|
630
|
-
entries.push({ sessionId: event.sessionId, correlationId: event.correlationId, event });
|
|
631
|
-
writeAll(entries);
|
|
632
|
-
},
|
|
633
|
-
remove(event) {
|
|
634
|
-
const entries = readAll();
|
|
635
|
-
const filtered = entries.filter(
|
|
636
|
-
(e) => !(e.event.kind === event.kind && e.event.timestamp === event.timestamp && e.event.sessionId === event.sessionId)
|
|
637
|
-
);
|
|
638
|
-
writeAll(filtered);
|
|
639
|
-
},
|
|
640
|
-
drain(targetSessionId) {
|
|
641
|
-
const entries = readAll();
|
|
642
|
-
const match = entries.filter((e) => e.sessionId === targetSessionId);
|
|
643
|
-
const rest = entries.filter((e) => e.sessionId !== targetSessionId);
|
|
644
|
-
writeAll(rest);
|
|
645
|
-
return match.map((e) => e.event);
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// src/core/observer.ts
|
|
651
|
-
function getNavigationType() {
|
|
652
|
-
try {
|
|
653
|
-
const entries = performance.getEntriesByType("navigation");
|
|
654
|
-
const entry = entries[0];
|
|
655
|
-
return entry?.type ?? null;
|
|
656
|
-
} catch {
|
|
657
|
-
return null;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
function logDebug(enabled, ...args) {
|
|
661
|
-
if (enabled && typeof console !== "undefined") {
|
|
662
|
-
console.log("[ZPObserver]", ...args);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
function createObserver(options) {
|
|
666
|
-
if (!options || typeof options !== "object") {
|
|
667
|
-
throw new Error("[ZPObserver] createObserver(options) requires an options object");
|
|
668
|
-
}
|
|
669
|
-
if (!options.sessionId || typeof options.sessionId !== "string") {
|
|
670
|
-
throw new Error("[ZPObserver] options.sessionId is required");
|
|
671
|
-
}
|
|
672
|
-
if (!options.transport) {
|
|
673
|
-
throw new Error("[ZPObserver] options.transport is required");
|
|
674
|
-
}
|
|
675
|
-
const sessionId = options.sessionId;
|
|
676
|
-
const correlationId = options.correlationId ?? generateUUID();
|
|
677
|
-
const transport = options.transport;
|
|
678
|
-
const metadata = options.metadata ?? {};
|
|
679
|
-
const beforeSend = options.beforeSend;
|
|
680
|
-
const heartbeatMs = options.heartbeatMs ?? 5e3;
|
|
681
|
-
const missThreshold = options.heartbeatMissThreshold ?? 3;
|
|
682
|
-
const idleTimeoutMs = options.idleTimeoutMs ?? 15 * 60 * 1e3;
|
|
683
|
-
const detectionIntervalMs = options.detectionIntervalMs ?? 100;
|
|
684
|
-
const detectionTimeoutMs = options.detectionTimeoutMs ?? 3e4;
|
|
685
|
-
const iframeSelectors = options.iframeSelectors ?? Array.from(DEFAULT_IFRAME_SELECTORS);
|
|
686
|
-
const debug = options.debug === true;
|
|
687
|
-
const experimental = options.experimental;
|
|
688
|
-
const diagMax = experimental?.diagnosticsMax ?? 50;
|
|
689
|
-
const diagnosticsBuffer = [];
|
|
690
|
-
const recentEvents = [];
|
|
691
|
-
const eventsMax = 50;
|
|
692
|
-
let uiState = "IDLE";
|
|
693
|
-
let transportState = "CONNECTED";
|
|
694
|
-
let isActive = false;
|
|
695
|
-
let startedAtMs = 0;
|
|
696
|
-
let iframeEl = null;
|
|
697
|
-
let closed = false;
|
|
698
|
-
let detectionHandle = null;
|
|
699
|
-
let lifecycleHandle = null;
|
|
700
|
-
let visibilityHandle = null;
|
|
701
|
-
let heartbeat = null;
|
|
702
|
-
let idleTimer = null;
|
|
703
|
-
let experimentalGlobalHandle = null;
|
|
704
|
-
let experimentalIframeHandle = null;
|
|
705
|
-
const shortcodeName = generateShortcodeName();
|
|
706
|
-
const shortcode = registerShortcode(shortcodeName, () => {
|
|
707
|
-
logDebug(debug, `shortcode invoked: ${shortcodeName}`);
|
|
708
|
-
stop("user.callback_invoked");
|
|
709
|
-
});
|
|
710
|
-
const emitDiagnostic = (e) => {
|
|
711
|
-
diagnosticsBuffer.push(e);
|
|
712
|
-
while (diagnosticsBuffer.length > diagMax) diagnosticsBuffer.shift();
|
|
713
|
-
options.onDiagnostic?.(e);
|
|
714
|
-
logDebug(debug, "diagnostic", e);
|
|
715
|
-
};
|
|
716
|
-
const outbox = options.persistence === "localStorage" ? createOutbox((error) => {
|
|
717
|
-
emitDiagnostic({ kind: "system.persistence_unavailable", error, timestamp: Date.now() });
|
|
718
|
-
}) : null;
|
|
719
|
-
const bus = createEventBus(transport, beforeSend, outbox);
|
|
720
|
-
const trackEvent = (ev) => {
|
|
721
|
-
recentEvents.push(ev);
|
|
722
|
-
while (recentEvents.length > eventsMax) recentEvents.shift();
|
|
723
|
-
};
|
|
724
|
-
const emit = (event) => {
|
|
725
|
-
trackEvent(event);
|
|
726
|
-
bus.emit(event);
|
|
727
|
-
};
|
|
728
|
-
const getMetadata = () => {
|
|
729
|
-
const base = { ...metadata };
|
|
730
|
-
if (experimental?.attachDiagnosticsToHeartbeat) {
|
|
731
|
-
base.zpObserverDiagnostics = diagnosticsBuffer.slice();
|
|
732
|
-
}
|
|
733
|
-
return base;
|
|
734
|
-
};
|
|
735
|
-
const resetIdleTimer = () => {
|
|
736
|
-
if (idleTimeoutMs <= 0) return;
|
|
737
|
-
if (idleTimer !== null) clearTimeout(idleTimer);
|
|
738
|
-
idleTimer = setTimeout(() => {
|
|
739
|
-
logDebug(debug, "idle timeout reached");
|
|
740
|
-
stop("system.idle_timeout");
|
|
741
|
-
}, idleTimeoutMs);
|
|
742
|
-
};
|
|
743
|
-
const sendOpen = (iframe) => {
|
|
744
|
-
const openExtras = readOpenExperimentalFields(experimental);
|
|
745
|
-
const ev = {
|
|
746
|
-
kind: "open",
|
|
747
|
-
sessionId,
|
|
748
|
-
correlationId,
|
|
749
|
-
timestamp: Date.now(),
|
|
750
|
-
iframeSrc: iframe.src || null,
|
|
751
|
-
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
752
|
-
screenWidth: typeof screen !== "undefined" ? screen.width : 0,
|
|
753
|
-
screenHeight: typeof screen !== "undefined" ? screen.height : 0,
|
|
754
|
-
navigationType: getNavigationType(),
|
|
755
|
-
metadata: getMetadata(),
|
|
756
|
-
...openExtras
|
|
757
|
-
};
|
|
758
|
-
emit(ev);
|
|
759
|
-
};
|
|
760
|
-
const sendClose = (reason, wasPersisted) => {
|
|
761
|
-
const hbCount = heartbeat?.heartbeatCount() ?? 0;
|
|
762
|
-
const missed = heartbeat?.missedBeats() ?? 0;
|
|
763
|
-
const ev = {
|
|
764
|
-
kind: "close",
|
|
765
|
-
sessionId,
|
|
766
|
-
correlationId,
|
|
767
|
-
timestamp: Date.now(),
|
|
768
|
-
reason,
|
|
769
|
-
elapsedMs: startedAtMs ? Date.now() - startedAtMs : 0,
|
|
770
|
-
heartbeatCount: hbCount,
|
|
771
|
-
missedBeats: missed,
|
|
772
|
-
wasPersisted,
|
|
773
|
-
metadata: getMetadata()
|
|
774
|
-
};
|
|
775
|
-
emit(ev);
|
|
776
|
-
};
|
|
777
|
-
const start = () => {
|
|
778
|
-
if (isActive) {
|
|
779
|
-
logDebug(debug, "start() called while already active \u2014 ignoring");
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
isActive = true;
|
|
783
|
-
closed = false;
|
|
784
|
-
startedAtMs = Date.now();
|
|
785
|
-
uiState = "DETECTING";
|
|
786
|
-
bus.drain(sessionId);
|
|
787
|
-
emit({
|
|
788
|
-
kind: "payment.session_init",
|
|
789
|
-
sessionId,
|
|
790
|
-
correlationId,
|
|
791
|
-
timestamp: Date.now(),
|
|
792
|
-
metadata: getMetadata()
|
|
793
|
-
});
|
|
794
|
-
if (hasGlobalExperimentalFeatures(experimental) && experimental) {
|
|
795
|
-
experimentalGlobalHandle = installGlobalExperimental({
|
|
796
|
-
sessionId,
|
|
797
|
-
experimental,
|
|
798
|
-
onDiagnostic: emitDiagnostic,
|
|
799
|
-
debug,
|
|
800
|
-
getNavigationType
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
lifecycleHandle = installPageLifecycle({
|
|
804
|
-
onHidden: () => {
|
|
805
|
-
if (uiState === "ACTIVE_MODAL_VISIBLE") uiState = "BACKGROUND_HIDDEN";
|
|
806
|
-
},
|
|
807
|
-
onVisible: () => {
|
|
808
|
-
if (uiState === "BACKGROUND_HIDDEN") uiState = "ACTIVE_MODAL_VISIBLE";
|
|
809
|
-
},
|
|
810
|
-
onPageHide: (persisted) => {
|
|
811
|
-
if (!closed) stopInternal("page.pagehide", persisted);
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
detectionHandle = startDetection(
|
|
815
|
-
{
|
|
816
|
-
onFound: (iframe) => {
|
|
817
|
-
iframeEl = iframe;
|
|
818
|
-
uiState = "ACTIVE_MODAL_VISIBLE";
|
|
819
|
-
logDebug(debug, "iframe detected:", iframe.src);
|
|
820
|
-
sendOpen(iframe);
|
|
821
|
-
heartbeat = startHeartbeat(
|
|
822
|
-
sessionId,
|
|
823
|
-
correlationId,
|
|
824
|
-
getMetadata,
|
|
825
|
-
bus,
|
|
826
|
-
{
|
|
827
|
-
onTick: (_seq, missed) => {
|
|
828
|
-
transportState = missed === 0 ? "CONNECTED" : missed < missThreshold ? "HEARTBEAT_LATE" : "DISCONNECTED";
|
|
829
|
-
if (missed === 0) resetIdleTimer();
|
|
830
|
-
},
|
|
831
|
-
onAbandoned: () => {
|
|
832
|
-
transportState = "DISCONNECTED";
|
|
833
|
-
if (!closed) stop("system.network_abandoned");
|
|
834
|
-
}
|
|
835
|
-
},
|
|
836
|
-
{ intervalMs: heartbeatMs, missThreshold }
|
|
837
|
-
);
|
|
838
|
-
visibilityHandle = observeIframeVisibility(iframe, (visible) => {
|
|
839
|
-
logDebug(debug, "iframe visibility:", visible);
|
|
840
|
-
});
|
|
841
|
-
if (hasIframeExperimentalFeatures(experimental) && experimental) {
|
|
842
|
-
experimentalIframeHandle = installIframeExperimental(iframe, {
|
|
843
|
-
sessionId,
|
|
844
|
-
experimental,
|
|
845
|
-
onDiagnostic: emitDiagnostic,
|
|
846
|
-
debug,
|
|
847
|
-
getNavigationType
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
resetIdleTimer();
|
|
851
|
-
},
|
|
852
|
-
onRemoved: () => {
|
|
853
|
-
if (!closed) stop("user.modal_closed");
|
|
854
|
-
},
|
|
855
|
-
onBootstrapHidden: () => {
|
|
856
|
-
if (!closed) stop("user.modal_closed");
|
|
857
|
-
},
|
|
858
|
-
onTimeout: () => {
|
|
859
|
-
logDebug(debug, "detection timeout \u2014 no iframe found");
|
|
860
|
-
}
|
|
861
|
-
},
|
|
862
|
-
{
|
|
863
|
-
selectors: iframeSelectors,
|
|
864
|
-
intervalMs: detectionIntervalMs,
|
|
865
|
-
timeoutMs: detectionTimeoutMs
|
|
866
|
-
}
|
|
867
|
-
);
|
|
868
|
-
logDebug(debug, `started session ${sessionId}, correlationId=${correlationId}, shortcode=${shortcode}`);
|
|
869
|
-
};
|
|
870
|
-
const stopInternal = (reason, wasPersisted) => {
|
|
871
|
-
if (closed) return;
|
|
872
|
-
closed = true;
|
|
873
|
-
isActive = false;
|
|
874
|
-
uiState = "DISMISSED";
|
|
875
|
-
logDebug(debug, `stopping: ${reason}`);
|
|
876
|
-
if (detectionHandle) {
|
|
877
|
-
detectionHandle.stop();
|
|
878
|
-
detectionHandle = null;
|
|
879
|
-
}
|
|
880
|
-
if (heartbeat) {
|
|
881
|
-
heartbeat.stop();
|
|
882
|
-
heartbeat = null;
|
|
883
|
-
}
|
|
884
|
-
if (visibilityHandle) {
|
|
885
|
-
visibilityHandle.stop();
|
|
886
|
-
visibilityHandle = null;
|
|
887
|
-
}
|
|
888
|
-
if (experimentalIframeHandle) {
|
|
889
|
-
experimentalIframeHandle.stop();
|
|
890
|
-
experimentalIframeHandle = null;
|
|
891
|
-
}
|
|
892
|
-
if (idleTimer !== null) {
|
|
893
|
-
clearTimeout(idleTimer);
|
|
894
|
-
idleTimer = null;
|
|
895
|
-
}
|
|
896
|
-
if (lifecycleHandle) {
|
|
897
|
-
lifecycleHandle.stop();
|
|
898
|
-
lifecycleHandle = null;
|
|
899
|
-
}
|
|
900
|
-
if (experimentalGlobalHandle) {
|
|
901
|
-
experimentalGlobalHandle.stop();
|
|
902
|
-
experimentalGlobalHandle = null;
|
|
903
|
-
}
|
|
904
|
-
sendClose(reason, wasPersisted);
|
|
905
|
-
unregisterShortcode(shortcodeName);
|
|
906
|
-
};
|
|
907
|
-
const stop = (reason = "user.manual_close") => {
|
|
908
|
-
stopInternal(reason, null);
|
|
909
|
-
};
|
|
910
|
-
const getState = () => ({
|
|
911
|
-
ui: uiState,
|
|
912
|
-
transport: transportState,
|
|
913
|
-
sessionId,
|
|
914
|
-
correlationId,
|
|
915
|
-
shortcode,
|
|
916
|
-
isActive,
|
|
917
|
-
iframeDetected: iframeEl !== null,
|
|
918
|
-
heartbeatCount: heartbeat?.heartbeatCount() ?? 0,
|
|
919
|
-
missedBeats: heartbeat?.missedBeats() ?? 0,
|
|
920
|
-
elapsedMs: startedAtMs ? Date.now() - startedAtMs : 0
|
|
921
|
-
});
|
|
922
|
-
const diagnostics = {
|
|
923
|
-
dump() {
|
|
924
|
-
return {
|
|
925
|
-
sessionId,
|
|
926
|
-
correlationId,
|
|
927
|
-
state: getState(),
|
|
928
|
-
recentEvents: recentEvents.slice(),
|
|
929
|
-
diagnostics: diagnosticsBuffer.slice(),
|
|
930
|
-
configuredTransport: transport.constructor?.name ?? "custom",
|
|
931
|
-
version: "6.0.0"
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
};
|
|
935
|
-
return { sessionId, correlationId, shortcode, start, stop, getState, emit, diagnostics };
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// src/transport/http-transport.ts
|
|
939
|
-
function resolveHeaders(h) {
|
|
940
|
-
if (!h) return {};
|
|
941
|
-
return typeof h === "function" ? h() : h;
|
|
942
|
-
}
|
|
943
|
-
function postKeepalive(fetchImpl, url, body, headers) {
|
|
944
|
-
try {
|
|
945
|
-
void fetchImpl(url, {
|
|
946
|
-
method: "POST",
|
|
947
|
-
headers: { "Content-Type": "application/json", ...headers },
|
|
948
|
-
body: JSON.stringify(body),
|
|
949
|
-
keepalive: true,
|
|
950
|
-
credentials: "include"
|
|
951
|
-
}).catch(() => {
|
|
952
|
-
});
|
|
953
|
-
} catch {
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
function urlForEvent(event, opts) {
|
|
957
|
-
switch (event.kind) {
|
|
958
|
-
case "open":
|
|
959
|
-
case "payment.modal_opened":
|
|
960
|
-
return opts.openUrl;
|
|
961
|
-
case "heartbeat":
|
|
962
|
-
return opts.aliveUrl;
|
|
963
|
-
case "close":
|
|
964
|
-
return opts.closeUrl;
|
|
965
|
-
default:
|
|
966
|
-
return opts.eventUrl ?? opts.aliveUrl;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
function httpTransport(opts) {
|
|
970
|
-
if (!opts || typeof opts !== "object") {
|
|
971
|
-
throw new Error("[ZPObserver] httpTransport requires options");
|
|
972
|
-
}
|
|
973
|
-
if (!opts.openUrl || !opts.aliveUrl || !opts.closeUrl) {
|
|
974
|
-
throw new Error(
|
|
975
|
-
"[ZPObserver] httpTransport requires openUrl, aliveUrl, and closeUrl"
|
|
976
|
-
);
|
|
977
|
-
}
|
|
978
|
-
const fetchImpl = opts.fetch ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : void 0);
|
|
979
|
-
const hbTimeoutMs = opts.heartbeatTimeoutMs ?? 8e3;
|
|
980
|
-
const transport = {
|
|
981
|
-
async send(event) {
|
|
982
|
-
const url = urlForEvent(event, opts);
|
|
983
|
-
if (event.kind === "heartbeat") {
|
|
984
|
-
if (!fetchImpl) return { ok: false };
|
|
985
|
-
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
986
|
-
const timer = controller ? setTimeout(() => controller.abort(), hbTimeoutMs) : null;
|
|
987
|
-
try {
|
|
988
|
-
const res = await fetchImpl(url, {
|
|
989
|
-
method: "POST",
|
|
990
|
-
headers: { "Content-Type": "application/json", ...resolveHeaders(opts.headers) },
|
|
991
|
-
body: JSON.stringify(event),
|
|
992
|
-
credentials: "include",
|
|
993
|
-
...controller ? { signal: controller.signal } : {}
|
|
994
|
-
});
|
|
995
|
-
return { ok: res.ok, status: res.status };
|
|
996
|
-
} catch {
|
|
997
|
-
return { ok: false };
|
|
998
|
-
} finally {
|
|
999
|
-
if (timer !== null) clearTimeout(timer);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
if (postJsonBeacon(url, event)) return { ok: true };
|
|
1003
|
-
if (fetchImpl) {
|
|
1004
|
-
postKeepalive(fetchImpl, url, event, resolveHeaders(opts.headers));
|
|
1005
|
-
}
|
|
1006
|
-
return { ok: true };
|
|
1007
|
-
},
|
|
1008
|
-
sendOpen(event) {
|
|
1009
|
-
void transport.send(event);
|
|
1010
|
-
},
|
|
1011
|
-
sendHeartbeat(event) {
|
|
1012
|
-
return transport.send(event);
|
|
1013
|
-
},
|
|
1014
|
-
sendClose(event) {
|
|
1015
|
-
void transport.send(event);
|
|
1016
|
-
}
|
|
1017
|
-
};
|
|
1018
|
-
return transport;
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// src/transport/callback-transport.ts
|
|
1022
|
-
function callbackTransport(opts = {}) {
|
|
1023
|
-
const transport = {
|
|
1024
|
-
async send(event) {
|
|
1025
|
-
switch (event.kind) {
|
|
1026
|
-
case "open":
|
|
1027
|
-
case "payment.modal_opened": {
|
|
1028
|
-
try {
|
|
1029
|
-
opts.onOpen?.(event);
|
|
1030
|
-
} catch {
|
|
1031
|
-
}
|
|
1032
|
-
return { ok: true };
|
|
1033
|
-
}
|
|
1034
|
-
case "heartbeat": {
|
|
1035
|
-
if (!opts.onHeartbeat) return { ok: true };
|
|
1036
|
-
try {
|
|
1037
|
-
const result = await opts.onHeartbeat(event);
|
|
1038
|
-
return result ?? { ok: true };
|
|
1039
|
-
} catch {
|
|
1040
|
-
return { ok: false };
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
case "close": {
|
|
1044
|
-
try {
|
|
1045
|
-
opts.onClose?.(event);
|
|
1046
|
-
} catch {
|
|
1047
|
-
}
|
|
1048
|
-
return { ok: true };
|
|
1049
|
-
}
|
|
1050
|
-
default: {
|
|
1051
|
-
if (!opts.onEvent) return { ok: true };
|
|
1052
|
-
try {
|
|
1053
|
-
const result = await opts.onEvent(event);
|
|
1054
|
-
return result ?? { ok: true };
|
|
1055
|
-
} catch {
|
|
1056
|
-
return { ok: false };
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
},
|
|
1061
|
-
sendOpen(event) {
|
|
1062
|
-
void transport.send(event);
|
|
1063
|
-
},
|
|
1064
|
-
sendHeartbeat(event) {
|
|
1065
|
-
return transport.send(event);
|
|
1066
|
-
},
|
|
1067
|
-
sendClose(event) {
|
|
1068
|
-
void transport.send(event);
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
return transport;
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
// src/integration/zenpay-auto-patch.ts
|
|
1075
|
-
function logDebug2(enabled, ...args) {
|
|
1076
|
-
if (enabled && typeof console !== "undefined") {
|
|
1077
|
-
console.log("[ZPObserver/auto-patch]", ...args);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
function resolveAndCallWindowPath(windowPath) {
|
|
1081
|
-
const path = windowPath.replace(/^window\./, "").split(".");
|
|
1082
|
-
let ref = window;
|
|
1083
|
-
for (const part of path) {
|
|
1084
|
-
if (ref && typeof ref === "object" && part in ref) {
|
|
1085
|
-
ref = ref[part];
|
|
1086
|
-
} else {
|
|
1087
|
-
ref = void 0;
|
|
1088
|
-
break;
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
if (typeof ref === "function") ref();
|
|
1092
|
-
}
|
|
1093
|
-
function wrapOptions(opts, shortcode, chainExisting) {
|
|
1094
|
-
const next = { ...opts ?? {} };
|
|
1095
|
-
const existing = next.onPluginClose;
|
|
1096
|
-
if (chainExisting && existing != null) {
|
|
1097
|
-
if (typeof window !== "undefined") {
|
|
1098
|
-
const wrapperName = `__zpobs_chain_${Date.now().toString(36)}`;
|
|
1099
|
-
window[wrapperName] = () => {
|
|
1100
|
-
try {
|
|
1101
|
-
if (typeof existing === "function") {
|
|
1102
|
-
existing();
|
|
1103
|
-
} else if (typeof existing === "string") {
|
|
1104
|
-
resolveAndCallWindowPath(existing);
|
|
1105
|
-
}
|
|
1106
|
-
} catch {
|
|
1107
|
-
}
|
|
1108
|
-
try {
|
|
1109
|
-
resolveAndCallWindowPath(shortcode);
|
|
1110
|
-
} catch {
|
|
1111
|
-
}
|
|
1112
|
-
};
|
|
1113
|
-
next.onPluginClose = `window.${wrapperName}`;
|
|
1114
|
-
return next;
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
next.onPluginClose = shortcode;
|
|
1118
|
-
return next;
|
|
1119
|
-
}
|
|
1120
|
-
function patchJQueryPlugin($, shortcode, chainExisting, debug, onInvoke) {
|
|
1121
|
-
const original = $.fn.zpPayment;
|
|
1122
|
-
if (typeof original !== "function" || original.__zpobsPatched) {
|
|
1123
|
-
return false;
|
|
1124
|
-
}
|
|
1125
|
-
const patched = function(opts) {
|
|
1126
|
-
onInvoke();
|
|
1127
|
-
return original.call(this, wrapOptions(opts, shortcode, chainExisting));
|
|
1128
|
-
};
|
|
1129
|
-
patched.__zpobsPatched = true;
|
|
1130
|
-
$.fn.zpPayment = patched;
|
|
1131
|
-
logDebug2(debug, "patched $.fn.zpPayment");
|
|
1132
|
-
return true;
|
|
1133
|
-
}
|
|
1134
|
-
function patchZenPayGlobal(shortcode, chainExisting, debug, onInvoke) {
|
|
1135
|
-
const w = window;
|
|
1136
|
-
const original = w.ZenPay;
|
|
1137
|
-
if (typeof original !== "function" || original.__zpobsPatched) {
|
|
1138
|
-
return false;
|
|
1139
|
-
}
|
|
1140
|
-
const handler = {
|
|
1141
|
-
construct(target, args) {
|
|
1142
|
-
onInvoke();
|
|
1143
|
-
const [first, ...rest] = args;
|
|
1144
|
-
const wrapped = wrapOptions(first, shortcode, chainExisting);
|
|
1145
|
-
return Reflect.construct(target, [wrapped, ...rest]);
|
|
1146
|
-
},
|
|
1147
|
-
apply(target, thisArg, args) {
|
|
1148
|
-
onInvoke();
|
|
1149
|
-
const [first, ...rest] = args;
|
|
1150
|
-
const wrapped = wrapOptions(first, shortcode, chainExisting);
|
|
1151
|
-
return Reflect.apply(target, thisArg, [
|
|
1152
|
-
wrapped,
|
|
1153
|
-
...rest
|
|
1154
|
-
]);
|
|
1155
|
-
}
|
|
1156
|
-
};
|
|
1157
|
-
const patched = new Proxy(original, handler);
|
|
1158
|
-
patched.__zpobsPatched = true;
|
|
1159
|
-
w.ZenPay = patched;
|
|
1160
|
-
logDebug2(debug, "patched window.ZenPay");
|
|
1161
|
-
return true;
|
|
1162
|
-
}
|
|
1163
|
-
function installZenPayAutoPatch(observer, options = {}) {
|
|
1164
|
-
if (typeof window === "undefined") {
|
|
1165
|
-
return { uninstall() {
|
|
1166
|
-
}, isPatched: () => false };
|
|
1167
|
-
}
|
|
1168
|
-
const pollIntervalMs = options.pollIntervalMs ?? 200;
|
|
1169
|
-
const pollTimeoutMs = options.pollTimeoutMs ?? 3e4;
|
|
1170
|
-
const chainExisting = options.chainExisting !== false;
|
|
1171
|
-
const debug = options.debug === true;
|
|
1172
|
-
let patched = false;
|
|
1173
|
-
const shortcode = observer.shortcode;
|
|
1174
|
-
const w = window;
|
|
1175
|
-
const emitHppInit = () => {
|
|
1176
|
-
observer.emit({
|
|
1177
|
-
kind: "payment.hpp_init",
|
|
1178
|
-
sessionId: observer.sessionId,
|
|
1179
|
-
correlationId: observer.correlationId,
|
|
1180
|
-
timestamp: Date.now(),
|
|
1181
|
-
metadata: {}
|
|
1182
|
-
});
|
|
1183
|
-
};
|
|
1184
|
-
const tryPatch = () => {
|
|
1185
|
-
let didSomething = false;
|
|
1186
|
-
const jq = w.jQuery ?? w.$;
|
|
1187
|
-
if (jq && typeof jq === "object" && "fn" in jq) {
|
|
1188
|
-
if (patchJQueryPlugin(jq, shortcode, chainExisting, debug, emitHppInit)) didSomething = true;
|
|
1189
|
-
}
|
|
1190
|
-
if ("ZenPay" in window) {
|
|
1191
|
-
if (patchZenPayGlobal(shortcode, chainExisting, debug, emitHppInit)) didSomething = true;
|
|
1192
|
-
}
|
|
1193
|
-
if (didSomething) patched = true;
|
|
1194
|
-
return didSomething;
|
|
1195
|
-
};
|
|
1196
|
-
tryPatch();
|
|
1197
|
-
const startedAt = Date.now();
|
|
1198
|
-
let pollTimer = setInterval(() => {
|
|
1199
|
-
if (Date.now() - startedAt > pollTimeoutMs) {
|
|
1200
|
-
if (pollTimer !== null) {
|
|
1201
|
-
clearInterval(pollTimer);
|
|
1202
|
-
pollTimer = null;
|
|
1203
|
-
}
|
|
1204
|
-
return;
|
|
1205
|
-
}
|
|
1206
|
-
tryPatch();
|
|
1207
|
-
}, pollIntervalMs);
|
|
1208
|
-
return {
|
|
1209
|
-
uninstall() {
|
|
1210
|
-
if (pollTimer !== null) {
|
|
1211
|
-
clearInterval(pollTimer);
|
|
1212
|
-
pollTimer = null;
|
|
1213
|
-
}
|
|
1214
|
-
},
|
|
1215
|
-
isPatched: () => patched
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
// src/integration/hpp-bridge.ts
|
|
1220
|
-
function installHPPBridge(observer) {
|
|
1221
|
-
if (typeof window === "undefined") {
|
|
1222
|
-
return { uninstall() {
|
|
1223
|
-
} };
|
|
1224
|
-
}
|
|
1225
|
-
const w = window;
|
|
1226
|
-
const original = w.onPaymentPluginLoaded;
|
|
1227
|
-
const wrapper = () => {
|
|
1228
|
-
try {
|
|
1229
|
-
observer.emit({
|
|
1230
|
-
kind: "payment.hpp_iframe_loaded",
|
|
1231
|
-
sessionId: observer.sessionId,
|
|
1232
|
-
correlationId: observer.correlationId,
|
|
1233
|
-
timestamp: Date.now(),
|
|
1234
|
-
metadata: {}
|
|
1235
|
-
});
|
|
1236
|
-
} catch {
|
|
1237
|
-
}
|
|
1238
|
-
if (typeof original === "function") {
|
|
1239
|
-
try {
|
|
1240
|
-
original();
|
|
1241
|
-
} catch {
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
};
|
|
1245
|
-
w.onPaymentPluginLoaded = wrapper;
|
|
1246
|
-
return {
|
|
1247
|
-
uninstall() {
|
|
1248
|
-
w.onPaymentPluginLoaded = original ?? null;
|
|
1249
|
-
}
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// src/integration/devicefp-bridge.ts
|
|
1254
|
-
function installDeviceFPBridge(observer, options = {}) {
|
|
1255
|
-
const emit = observer.emit.bind(observer);
|
|
1256
|
-
const { sessionId, correlationId } = observer;
|
|
1257
|
-
return {
|
|
1258
|
-
onFingerprintStarted(attempt) {
|
|
1259
|
-
emit({
|
|
1260
|
-
kind: "payment.fingerprint_started",
|
|
1261
|
-
sessionId,
|
|
1262
|
-
correlationId,
|
|
1263
|
-
timestamp: Date.now(),
|
|
1264
|
-
attempt,
|
|
1265
|
-
metadata: {}
|
|
1266
|
-
});
|
|
1267
|
-
options.onFingerprintStarted?.(attempt);
|
|
1268
|
-
},
|
|
1269
|
-
onFingerprintCacheHit(age) {
|
|
1270
|
-
emit({
|
|
1271
|
-
kind: "payment.fingerprint_cache_hit",
|
|
1272
|
-
sessionId,
|
|
1273
|
-
correlationId,
|
|
1274
|
-
timestamp: Date.now(),
|
|
1275
|
-
age,
|
|
1276
|
-
metadata: {}
|
|
1277
|
-
});
|
|
1278
|
-
options.onFingerprintCacheHit?.(age);
|
|
1279
|
-
},
|
|
1280
|
-
onFingerprintSucceeded() {
|
|
1281
|
-
emit({
|
|
1282
|
-
kind: "payment.fingerprint_succeeded",
|
|
1283
|
-
sessionId,
|
|
1284
|
-
correlationId,
|
|
1285
|
-
timestamp: Date.now(),
|
|
1286
|
-
metadata: {}
|
|
1287
|
-
});
|
|
1288
|
-
options.onFingerprintSucceeded?.();
|
|
1289
|
-
},
|
|
1290
|
-
onFingerprintFailed(error, attempt) {
|
|
1291
|
-
emit({
|
|
1292
|
-
kind: "payment.fingerprint_failed",
|
|
1293
|
-
sessionId,
|
|
1294
|
-
correlationId,
|
|
1295
|
-
timestamp: Date.now(),
|
|
1296
|
-
error,
|
|
1297
|
-
attempt,
|
|
1298
|
-
metadata: {}
|
|
1299
|
-
});
|
|
1300
|
-
options.onFingerprintFailed?.(error, attempt);
|
|
1301
|
-
}
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
// src/diagnostics/preflight.ts
|
|
1306
|
-
async function preflight(options) {
|
|
1307
|
-
const { transport, sample } = options;
|
|
1308
|
-
const sampleEvent = {
|
|
1309
|
-
kind: "payment.preflight",
|
|
1310
|
-
sessionId: sample?.sessionId ?? "__preflight__",
|
|
1311
|
-
correlationId: sample?.correlationId ?? "__preflight_cid__",
|
|
1312
|
-
timestamp: Date.now(),
|
|
1313
|
-
metadata: sample?.metadata ?? {}
|
|
1314
|
-
};
|
|
1315
|
-
const startedAt = performance.now();
|
|
1316
|
-
let status;
|
|
1317
|
-
let corsError = false;
|
|
1318
|
-
let networkError = null;
|
|
1319
|
-
const beaconSupported = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function";
|
|
1320
|
-
try {
|
|
1321
|
-
const result = await transport.send(sampleEvent);
|
|
1322
|
-
status = result.status;
|
|
1323
|
-
if (!result.ok && result.status === void 0) {
|
|
1324
|
-
networkError = "Transport returned ok:false with no status (possible CORS or network block)";
|
|
1325
|
-
}
|
|
1326
|
-
} catch (err) {
|
|
1327
|
-
networkError = err instanceof Error ? err.message : "Unknown transport error";
|
|
1328
|
-
if (networkError.toLowerCase().includes("cors") || networkError.toLowerCase().includes("networkerror")) {
|
|
1329
|
-
corsError = true;
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
const latencyMs = Math.round(performance.now() - startedAt);
|
|
1333
|
-
return {
|
|
1334
|
-
ok: status !== void 0 && status >= 200 && status < 300 && networkError === null,
|
|
1335
|
-
endpoint: "transport.send",
|
|
1336
|
-
status,
|
|
1337
|
-
latencyMs,
|
|
1338
|
-
corsError,
|
|
1339
|
-
networkError,
|
|
1340
|
-
beaconSupported
|
|
1341
|
-
};
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
// src/outcome.ts
|
|
1345
|
-
function reportOutcome(options) {
|
|
1346
|
-
const { correlationId, sessionId, outcome, transport, metadata } = options;
|
|
1347
|
-
const event = {
|
|
1348
|
-
kind: "payment.outcome",
|
|
1349
|
-
sessionId,
|
|
1350
|
-
correlationId,
|
|
1351
|
-
timestamp: Date.now(),
|
|
1352
|
-
outcome,
|
|
1353
|
-
metadata: metadata ?? {}
|
|
1354
|
-
};
|
|
1355
|
-
void transport.send(event);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
// src/core/types.ts
|
|
1359
|
-
var VERSION = "6.0.0";
|
|
1360
|
-
|
|
1361
|
-
// src/index.ts
|
|
1362
|
-
if (typeof window !== "undefined") {
|
|
1363
|
-
window.ZPObserver = {
|
|
1364
|
-
createObserver,
|
|
1365
|
-
httpTransport,
|
|
1366
|
-
callbackTransport,
|
|
1367
|
-
installZenPayAutoPatch,
|
|
1368
|
-
installHPPBridge,
|
|
1369
|
-
installDeviceFPBridge,
|
|
1370
|
-
preflight,
|
|
1371
|
-
reportOutcome,
|
|
1372
|
-
VERSION
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
//# sourceMappingURL=zp-observer.cjs.map
|