@agent-cards/checkout 0.19.0 → 0.22.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 +6 -753
- package/cdp.d.ts +1 -0
- package/cdp.js +2 -0
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/package.json +33 -33
- package/playwright.d.ts +1 -0
- package/playwright.js +2 -0
- package/preflight.d.ts +1 -0
- package/preflight.js +2 -0
- package/CHANGELOG.md +0 -132
- package/PREFLIGHT.md +0 -312
- package/dist/adyen-merchant-hosted.generated.d.ts +0 -277
- package/dist/adyen-merchant-hosted.generated.js +0 -1902
- package/dist/adyen.generated.d.ts +0 -24
- package/dist/adyen.generated.js +0 -64
- package/dist/attachment.d.ts +0 -11
- package/dist/attachment.js +0 -50
- package/dist/braintree.d.ts +0 -2
- package/dist/braintree.generated.d.ts +0 -10
- package/dist/braintree.generated.js +0 -302
- package/dist/braintree.js +0 -2
- package/dist/builtin-registry.generated.d.ts +0 -2
- package/dist/builtin-registry.generated.js +0 -1
- package/dist/card-fields.generated.d.ts +0 -3
- package/dist/card-fields.generated.js +0 -46
- package/dist/cdp.d.ts +0 -192
- package/dist/cdp.js +0 -2393
- package/dist/checkout-com.generated.d.ts +0 -4
- package/dist/checkout-com.generated.js +0 -183
- package/dist/client.d.ts +0 -849
- package/dist/client.js +0 -1754
- package/dist/cse-body.d.ts +0 -25
- package/dist/cse-body.js +0 -41
- package/dist/fiserv.d.ts +0 -65
- package/dist/fiserv.generated.d.ts +0 -73
- package/dist/fiserv.generated.js +0 -830
- package/dist/fiserv.js +0 -104
- package/dist/hosted-form.d.ts +0 -44
- package/dist/hosted-form.js +0 -78
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -10
- package/dist/lifecycle.d.ts +0 -179
- package/dist/lifecycle.js +0 -395
- package/dist/mercado-checkout.d.ts +0 -20
- package/dist/mercado-checkout.generated.d.ts +0 -52
- package/dist/mercado-checkout.generated.js +0 -198
- package/dist/mercado-checkout.js +0 -108
- package/dist/merchant-handoff.d.ts +0 -54
- package/dist/merchant-handoff.js +0 -100
- package/dist/merchant-hosted.d.ts +0 -140
- package/dist/merchant-hosted.js +0 -170
- package/dist/merchant-total-watch.d.ts +0 -115
- package/dist/merchant-total-watch.js +0 -268
- package/dist/merchant-total.d.ts +0 -257
- package/dist/merchant-total.js +0 -383
- package/dist/owned-shop.generated.d.ts +0 -24
- package/dist/owned-shop.generated.js +0 -108
- package/dist/paysafe.generated.d.ts +0 -12
- package/dist/paysafe.generated.js +0 -87
- package/dist/playwright.d.ts +0 -3
- package/dist/playwright.js +0 -3
- package/dist/pre-claim.d.ts +0 -123
- package/dist/pre-claim.js +0 -386
- package/dist/preflight-capabilities.generated.d.ts +0 -1253
- package/dist/preflight-capabilities.generated.js +0 -1929
- package/dist/preflight-catalog.json +0 -4727
- package/dist/preflight-playwright.d.ts +0 -34
- package/dist/preflight-playwright.js +0 -355
- package/dist/preflight-schemas.json +0 -1122
- package/dist/preflight.d.ts +0 -1
- package/dist/preflight.generated.d.ts +0 -1965
- package/dist/preflight.generated.js +0 -570
- package/dist/preflight.js +0 -2
- package/dist/preparation.d.ts +0 -38
- package/dist/preparation.js +0 -191
- package/dist/prepared-processor.d.ts +0 -43
- package/dist/prepared-processor.js +0 -172
- package/dist/recurly.generated.d.ts +0 -1
- package/dist/recurly.generated.js +0 -87
- package/dist/registry.d.ts +0 -121
- package/dist/registry.js +0 -310
- package/dist/spreedly.generated.d.ts +0 -10
- package/dist/spreedly.generated.js +0 -332
- package/dist/stripe-checkout.d.ts +0 -81
- package/dist/stripe-checkout.generated.d.ts +0 -82
- package/dist/stripe-checkout.generated.js +0 -1093
- package/dist/stripe-checkout.js +0 -140
- package/dist/substitute.d.ts +0 -38
- package/dist/substitute.js +0 -23
- package/dist/substitutions.generated.d.ts +0 -11
- package/dist/substitutions.generated.js +0 -818
- package/examples/existing-browser.mjs +0 -63
- package/examples/preflight/classify-direct.mjs +0 -21
- package/examples/preflight/classify-kernel.mjs +0 -30
- package/examples/preflight/inspect-browser.mjs +0 -44
- package/examples/preflight/kernel-native/README.md +0 -112
- package/examples/preflight/kernel-native/documented-adapters.json +0 -113
- package/examples/preflight/kernel-native/inventory.json +0 -233
- package/examples/preflight/kernel-native/qualification.mjs +0 -182
- package/examples/preflight/kernel-profile.empty.json +0 -11
- package/examples/preflight/mollie-hosted.observations.json +0 -23
- package/examples/preflight/mollie-hosted.result.json +0 -103
- package/examples/preflight/stripe-script.direct.result.json +0 -92
- package/examples/preflight/stripe-script.observations.json +0 -16
- package/examples/preflight/stripe-script.result.json +0 -87
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { type CheckoutSignalSnapshot, type CheckoutPreflightOptions, type CheckoutSupportResult } from './preflight.js';
|
|
2
|
-
/** The public Playwright methods used by inspection; no Playwright dependency is required. */
|
|
3
|
-
export interface CheckoutInspectionFrame {
|
|
4
|
-
url(): string;
|
|
5
|
-
parentFrame(): CheckoutInspectionFrame | null;
|
|
6
|
-
isDetached(): boolean;
|
|
7
|
-
evaluate<Result, Argument>(fn: (argument: Argument) => Result | Promise<Result>, argument: Argument): Promise<Result>;
|
|
8
|
-
frameElement(): Promise<{
|
|
9
|
-
evaluate<Result>(fn: (element: Element) => Result | Promise<Result>): Promise<Result>;
|
|
10
|
-
dispose(): Promise<void>;
|
|
11
|
-
}>;
|
|
12
|
-
}
|
|
13
|
-
export interface CheckoutInspectionPage {
|
|
14
|
-
frames(): CheckoutInspectionFrame[];
|
|
15
|
-
on(event: 'framenavigated' | 'frameattached' | 'framedetached', listener: (frame: CheckoutInspectionFrame) => void): unknown;
|
|
16
|
-
off(event: 'framenavigated' | 'frameattached' | 'framedetached', listener: (frame: CheckoutInspectionFrame) => void): unknown;
|
|
17
|
-
}
|
|
18
|
-
export interface CheckoutCollectionOptions {
|
|
19
|
-
/** Total inspection deadline, including all frames. Default 1,500 ms; maximum 10,000 ms. */
|
|
20
|
-
timeoutMs?: number;
|
|
21
|
-
/** Maximum frame count. Default and maximum 64. */
|
|
22
|
-
maxFrames?: number;
|
|
23
|
-
/** Maximum document and asset observations. Default and maximum 2,048. */
|
|
24
|
-
maxSignals?: number;
|
|
25
|
-
/** Maximum transient observation and returned snapshot size. Default and maximum 262,144 bytes; minimum 512 bytes. */
|
|
26
|
-
maxSnapshotBytes?: number;
|
|
27
|
-
}
|
|
28
|
-
export type InspectCheckoutOptions = CheckoutPreflightOptions & {
|
|
29
|
-
collection?: CheckoutCollectionOptions;
|
|
30
|
-
};
|
|
31
|
-
/** Read only frame, external asset and form locations; return reviewed rule IDs, never raw URLs. */
|
|
32
|
-
export declare function collectCheckoutSignals(page: CheckoutInspectionPage, options?: CheckoutCollectionOptions): Promise<CheckoutSignalSnapshot>;
|
|
33
|
-
/** Inspect once without preparing or submitting a checkout. */
|
|
34
|
-
export declare function inspectCheckout(page: CheckoutInspectionPage, options?: InspectCheckoutOptions): Promise<CheckoutSupportResult>;
|
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import { normalizeCheckoutSignals, assessCheckoutSupport, cardEntryAttributable, } from './preflight.js';
|
|
2
|
-
const EVENTS = ['framenavigated', 'frameattached', 'framedetached'];
|
|
3
|
-
const DEADLINE = Symbol('checkout inspection deadline');
|
|
4
|
-
const DEFAULTS = { timeoutMs: 1_500, maxFrames: 64, maxSignals: 2_048, maxSnapshotBytes: 262_144 };
|
|
5
|
-
/** Frames at one nesting depth are read together. A remote CDP connection pays per round trip, not per frame. */
|
|
6
|
-
const CONCURRENCY = 8;
|
|
7
|
-
const encoder = new TextEncoder();
|
|
8
|
-
function bounded(value, fallback, maximum, minimum = 1) {
|
|
9
|
-
if (value === undefined)
|
|
10
|
-
return fallback;
|
|
11
|
-
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
12
|
-
throw new TypeError('Checkout inspection limits must be positive integers within the documented bounds.');
|
|
13
|
-
}
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
async function beforeDeadline(operation, deadline) {
|
|
17
|
-
const remaining = deadline - performance.now();
|
|
18
|
-
if (remaining <= 0)
|
|
19
|
-
throw DEADLINE;
|
|
20
|
-
let timer;
|
|
21
|
-
try {
|
|
22
|
-
return await Promise.race([
|
|
23
|
-
Promise.resolve().then(operation),
|
|
24
|
-
new Promise((_resolve, reject) => { timer = setTimeout(() => reject(DEADLINE), remaining); }),
|
|
25
|
-
]);
|
|
26
|
-
}
|
|
27
|
-
finally {
|
|
28
|
-
if (timer !== undefined)
|
|
29
|
-
clearTimeout(timer);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function httpOrigin(url) {
|
|
33
|
-
if (!url || !/^https?:/i.test(url))
|
|
34
|
-
return null;
|
|
35
|
-
try {
|
|
36
|
-
return new URL(url).origin;
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/** Run `worker` over `items` with bounded concurrency; results keep the input order. */
|
|
43
|
-
async function inBatches(items, size, worker) {
|
|
44
|
-
const results = new Array(items.length);
|
|
45
|
-
let next = 0;
|
|
46
|
-
await Promise.all(Array.from({ length: Math.min(size, items.length) }, async () => {
|
|
47
|
-
while (next < items.length) {
|
|
48
|
-
const index = next++;
|
|
49
|
-
results[index] = await worker(items[index]);
|
|
50
|
-
}
|
|
51
|
-
}));
|
|
52
|
-
return results;
|
|
53
|
-
}
|
|
54
|
-
/** Read only frame, external asset and form locations; return reviewed rule IDs, never raw URLs. */
|
|
55
|
-
export async function collectCheckoutSignals(page, options = {}) {
|
|
56
|
-
const limits = {
|
|
57
|
-
timeoutMs: bounded(options.timeoutMs, DEFAULTS.timeoutMs, 10_000),
|
|
58
|
-
maxFrames: bounded(options.maxFrames, DEFAULTS.maxFrames, DEFAULTS.maxFrames),
|
|
59
|
-
maxSignals: bounded(options.maxSignals, DEFAULTS.maxSignals, DEFAULTS.maxSignals),
|
|
60
|
-
maxSnapshotBytes: bounded(options.maxSnapshotBytes, DEFAULTS.maxSnapshotBytes, DEFAULTS.maxSnapshotBytes, 512),
|
|
61
|
-
};
|
|
62
|
-
const deadline = performance.now() + limits.timeoutMs;
|
|
63
|
-
const reasons = new Set();
|
|
64
|
-
const signals = [];
|
|
65
|
-
const registered = [];
|
|
66
|
-
let transientBytes = 0;
|
|
67
|
-
let truncated = false;
|
|
68
|
-
let stopped = false;
|
|
69
|
-
const changed = () => { reasons.add('navigation_changed'); };
|
|
70
|
-
const limit = (reason) => { reasons.add(reason); truncated = true; };
|
|
71
|
-
const append = (signal) => {
|
|
72
|
-
if (signals.length >= limits.maxSignals) {
|
|
73
|
-
limit('signal_limit');
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
// Raw merchant URLs exist only during this call and never enter errors or the return value.
|
|
77
|
-
const bytes = encoder.encode(JSON.stringify(signal)).length;
|
|
78
|
-
if (transientBytes + bytes > limits.maxSnapshotBytes) {
|
|
79
|
-
limit('snapshot_limit');
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
transientBytes += bytes;
|
|
83
|
-
signals.push(signal);
|
|
84
|
-
return true;
|
|
85
|
-
};
|
|
86
|
-
const stop = () => {
|
|
87
|
-
if (stopped)
|
|
88
|
-
return true;
|
|
89
|
-
if (performance.now() >= deadline) {
|
|
90
|
-
reasons.add('collection_timeout');
|
|
91
|
-
stopped = true;
|
|
92
|
-
}
|
|
93
|
-
else if (signals.length >= limits.maxSignals) {
|
|
94
|
-
limit('signal_limit');
|
|
95
|
-
stopped = true;
|
|
96
|
-
}
|
|
97
|
-
else if (truncated && reasons.has('snapshot_limit'))
|
|
98
|
-
stopped = true;
|
|
99
|
-
return stopped;
|
|
100
|
-
};
|
|
101
|
-
// Hostnames of visible nested card-entry frames outside the top document's origin. They stay
|
|
102
|
-
// local to this call; only the attribution verdict reaches the observation.
|
|
103
|
-
const cardEntryHosts = new Set();
|
|
104
|
-
try {
|
|
105
|
-
for (const event of EVENTS) {
|
|
106
|
-
page.on(event, changed);
|
|
107
|
-
registered.push(event);
|
|
108
|
-
}
|
|
109
|
-
const observedFrames = page.frames();
|
|
110
|
-
const roots = observedFrames.filter(frame => frame.parentFrame() === null);
|
|
111
|
-
if (roots.length !== 1)
|
|
112
|
-
reasons.add('frame_unavailable');
|
|
113
|
-
// Frame zero always denotes the main document, regardless of adapter enumeration order.
|
|
114
|
-
const frames = roots.length === 1 ? [roots[0], ...observedFrames.filter(frame => frame !== roots[0])] : observedFrames;
|
|
115
|
-
const initialUrls = new Map(frames.map(frame => [frame, frame.url()]));
|
|
116
|
-
const ids = new Map(frames.map((frame, index) => [frame, index]));
|
|
117
|
-
if (frames.length > limits.maxFrames)
|
|
118
|
-
limit('frame_limit');
|
|
119
|
-
const topOrigin = frames.length ? httpOrigin(initialUrls.get(frames[0])) : null;
|
|
120
|
-
const visibility = new Map();
|
|
121
|
-
const depth = (frame) => {
|
|
122
|
-
const visited = new Set();
|
|
123
|
-
while (frame.parentFrame() && !visited.has(frame)) {
|
|
124
|
-
visited.add(frame);
|
|
125
|
-
frame = frame.parentFrame();
|
|
126
|
-
}
|
|
127
|
-
return visited.size;
|
|
128
|
-
};
|
|
129
|
-
// Parents settle before their children so visibility can propagate; siblings share round trips.
|
|
130
|
-
const levels = new Map();
|
|
131
|
-
for (const frame of frames.slice(0, limits.maxFrames)) {
|
|
132
|
-
const level = depth(frame);
|
|
133
|
-
levels.set(level, [...(levels.get(level) ?? []), frame]);
|
|
134
|
-
}
|
|
135
|
-
const readFrame = async (frame) => {
|
|
136
|
-
if (stop())
|
|
137
|
-
return null;
|
|
138
|
-
const parent = frame.parentFrame();
|
|
139
|
-
const frame_id = ids.get(frame);
|
|
140
|
-
const parent_frame_id = parent ? ids.get(parent) ?? null : null;
|
|
141
|
-
let visible = parent ? visibility.get(parent) ?? null : true;
|
|
142
|
-
try {
|
|
143
|
-
if (frame.isDetached()) {
|
|
144
|
-
reasons.add('frame_unavailable');
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
let released;
|
|
148
|
-
if (parent) {
|
|
149
|
-
if (!ids.has(parent))
|
|
150
|
-
reasons.add('frame_unavailable');
|
|
151
|
-
let acquisitionTimedOut = false;
|
|
152
|
-
const acquisition = Promise.resolve().then(() => frame.frameElement());
|
|
153
|
-
void acquisition.then(element => {
|
|
154
|
-
if (acquisitionTimedOut)
|
|
155
|
-
void Promise.resolve().then(() => element.dispose()).catch(() => { });
|
|
156
|
-
}, () => { });
|
|
157
|
-
let element;
|
|
158
|
-
try {
|
|
159
|
-
element = await beforeDeadline(() => acquisition, deadline);
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
acquisitionTimedOut = true;
|
|
163
|
-
throw error;
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
const ownVisibility = await beforeDeadline(() => element.evaluate(node => {
|
|
167
|
-
if (!node.isConnected)
|
|
168
|
-
return null;
|
|
169
|
-
for (let current = node; current;) {
|
|
170
|
-
const style = getComputedStyle(current);
|
|
171
|
-
if (style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse' || style.opacity === '0')
|
|
172
|
-
return false;
|
|
173
|
-
const root = current.getRootNode();
|
|
174
|
-
current = current.parentElement ?? (root instanceof ShadowRoot ? root.host : null);
|
|
175
|
-
}
|
|
176
|
-
const rect = node.getBoundingClientRect();
|
|
177
|
-
return rect.width > 0 && rect.height > 0;
|
|
178
|
-
}), deadline);
|
|
179
|
-
visible = visible === false || ownVisibility === false ? false : visible === null || ownVisibility === null ? null : true;
|
|
180
|
-
}
|
|
181
|
-
finally {
|
|
182
|
-
// Disposal only releases our automation handle and overlaps the document read below.
|
|
183
|
-
// A stalled disposal still cannot extend the inspection deadline.
|
|
184
|
-
released = beforeDeadline(() => element.dispose(), deadline).then(() => undefined, error => error);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
visibility.set(frame, visible);
|
|
188
|
-
const data = await beforeDeadline(() => frame.evaluate(({ remaining, byteBudget }) => {
|
|
189
|
-
const collected = [];
|
|
190
|
-
let bytes = 0;
|
|
191
|
-
let signalLimit = false;
|
|
192
|
-
let byteLimit = false;
|
|
193
|
-
const shown = (element) => {
|
|
194
|
-
if (!element.isConnected)
|
|
195
|
-
return false;
|
|
196
|
-
for (let current = element; current;) {
|
|
197
|
-
const style = getComputedStyle(current);
|
|
198
|
-
if (style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse' || style.opacity === '0')
|
|
199
|
-
return false;
|
|
200
|
-
const root = current.getRootNode();
|
|
201
|
-
current = current.parentElement ?? (root instanceof ShadowRoot ? root.host : null);
|
|
202
|
-
}
|
|
203
|
-
const rect = element.getBoundingClientRect();
|
|
204
|
-
return rect.width > 0 && rect.height > 0;
|
|
205
|
-
};
|
|
206
|
-
const elements = document.querySelectorAll('script[src], iframe[src], form[action]');
|
|
207
|
-
for (let index = 0; index < elements.length; index++) {
|
|
208
|
-
if (index >= remaining) {
|
|
209
|
-
signalLimit = true;
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
const element = elements[index];
|
|
213
|
-
const kind = element.tagName === 'SCRIPT' ? 'script' : element.tagName === 'IFRAME' ? 'iframe' : 'form';
|
|
214
|
-
const source = element.getAttribute(kind === 'form' ? 'action' : 'src');
|
|
215
|
-
if (!source)
|
|
216
|
-
continue;
|
|
217
|
-
if (source.length > byteBudget - bytes) {
|
|
218
|
-
byteLimit = true;
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
let url;
|
|
222
|
-
try {
|
|
223
|
-
url = new URL(source, document.baseURI).href;
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
bytes += url.length * 3 + 128;
|
|
229
|
-
if (bytes > byteBudget) {
|
|
230
|
-
byteLimit = true;
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
collected.push({ kind, url, visible: kind === 'script' ? null : shown(element) });
|
|
234
|
-
}
|
|
235
|
-
// Card-entry detection reads field metadata only: the autocomplete token or the
|
|
236
|
-
// control's name and id. Values, labels and page text are never read.
|
|
237
|
-
let cardEntry = false;
|
|
238
|
-
const inputs = document.querySelectorAll('input');
|
|
239
|
-
for (let index = 0; index < inputs.length && index < 256 && !cardEntry; index++) {
|
|
240
|
-
const input = inputs[index];
|
|
241
|
-
const type = (input.getAttribute('type') || 'text').toLowerCase();
|
|
242
|
-
if (['hidden', 'submit', 'button', 'checkbox', 'radio', 'image', 'reset', 'file', 'range', 'color'].includes(type))
|
|
243
|
-
continue;
|
|
244
|
-
const autocomplete = (input.getAttribute('autocomplete') || '').trim().toLowerCase();
|
|
245
|
-
const label = `${input.getAttribute('name') || ''} ${input.getAttribute('id') || ''}`.toLowerCase().replace(/[^a-z0-9]+/g, '');
|
|
246
|
-
const cardField = /^cc-(?:number|csc|exp|exp-month|exp-year)$/.test(autocomplete)
|
|
247
|
-
|| !label.includes('gift') && /cardnumber|cardnum|cardno|ccnumber|ccnum|ccno|cvv|cvc|csc|securitycode|cardcode/.test(label);
|
|
248
|
-
if (cardField && shown(input))
|
|
249
|
-
cardEntry = true;
|
|
250
|
-
}
|
|
251
|
-
const documentUrl = document.URL;
|
|
252
|
-
if (documentUrl.length * 3 > byteBudget)
|
|
253
|
-
byteLimit = true;
|
|
254
|
-
return { documentUrl: byteLimit && documentUrl.length * 3 > byteBudget ? '' : documentUrl, collected, signalLimit, byteLimit, cardEntry };
|
|
255
|
-
}, { remaining: Math.max(0, limits.maxSignals - signals.length - 1), byteBudget: Math.max(0, limits.maxSnapshotBytes - transientBytes) }), deadline);
|
|
256
|
-
const releaseError = released ? await released : undefined;
|
|
257
|
-
if (releaseError !== undefined)
|
|
258
|
-
throw releaseError;
|
|
259
|
-
if (frame.isDetached())
|
|
260
|
-
reasons.add('frame_unavailable');
|
|
261
|
-
// Committed URLs are compared with committed URLs. The document URL is compared only for a
|
|
262
|
-
// frame that committed an http(s) document: a script-created frame written through
|
|
263
|
-
// document.open() inherits the merchant URL without navigating anywhere.
|
|
264
|
-
const initial = initialUrls.get(frame) ?? '';
|
|
265
|
-
if (frame.url() !== initial || (/^https?:/i.test(initial) && data.documentUrl !== '' && data.documentUrl !== initial))
|
|
266
|
-
reasons.add('navigation_changed');
|
|
267
|
-
return { frame_id, parent_frame_id, nested: parent !== null, visible, data };
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
if (error === DEADLINE) {
|
|
271
|
-
reasons.add('collection_timeout');
|
|
272
|
-
stopped = true;
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
// Browser errors can include complete merchant URLs. Return a bounded reason instead.
|
|
276
|
-
reasons.add('frame_unavailable');
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
for (const level of [...levels.keys()].sort((a, b) => a - b)) {
|
|
281
|
-
if (stop())
|
|
282
|
-
break;
|
|
283
|
-
const outcomes = await inBatches(levels.get(level), CONCURRENCY, readFrame);
|
|
284
|
-
// Append in frame order so identical pages produce identical observations.
|
|
285
|
-
for (const outcome of outcomes) {
|
|
286
|
-
if (!outcome)
|
|
287
|
-
continue;
|
|
288
|
-
const { data, frame_id, parent_frame_id, nested, visible } = outcome;
|
|
289
|
-
append({ kind: nested ? 'frame' : 'document', url: data.documentUrl, frame_id, parent_frame_id, visible });
|
|
290
|
-
for (const signal of data.collected) {
|
|
291
|
-
if (!append({ ...signal, frame_id, parent_frame_id, visible: visible === false ? false : signal.kind === 'script' ? visible : visible === null ? null : signal.visible }))
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
|
-
if (data.signalLimit)
|
|
295
|
-
limit('signal_limit');
|
|
296
|
-
if (data.byteLimit)
|
|
297
|
-
limit('snapshot_limit');
|
|
298
|
-
if (nested && visible === true && data.cardEntry === true) {
|
|
299
|
-
const origin = httpOrigin(data.documentUrl);
|
|
300
|
-
if (origin && origin !== topOrigin)
|
|
301
|
-
cardEntryHosts.add(new URL(origin).hostname);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
if (performance.now() >= deadline)
|
|
306
|
-
reasons.add('collection_timeout');
|
|
307
|
-
const finalFrames = page.frames();
|
|
308
|
-
if (frames.length !== finalFrames.length || finalFrames.some(frame => !ids.has(frame) || frame.url() !== initialUrls.get(frame))) {
|
|
309
|
-
reasons.add('navigation_changed');
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
reasons.add('frame_unavailable');
|
|
314
|
-
}
|
|
315
|
-
finally {
|
|
316
|
-
for (const event of registered) {
|
|
317
|
-
try {
|
|
318
|
-
page.off(event, changed);
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
reasons.add('collection_incomplete');
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
const observation = () => ({ complete: reasons.size === 0, truncated, reasons: [...reasons] });
|
|
326
|
-
let snapshot = normalizeCheckoutSignals({ signals, observation: observation() });
|
|
327
|
-
// Visible card controls on a site no evidenced processor owns mean the card form belongs to
|
|
328
|
-
// something the catalog has not reviewed. The observation is then incomplete, so a co-loaded
|
|
329
|
-
// SDK script stays a candidate instead of becoming the selected processor. A snapshot past the
|
|
330
|
-
// byte budget is already incomplete and is bounded below instead.
|
|
331
|
-
if (encoder.encode(JSON.stringify(snapshot)).length <= limits.maxSnapshotBytes
|
|
332
|
-
&& [...cardEntryHosts].some(host => !cardEntryAttributable(snapshot, host))) {
|
|
333
|
-
reasons.add('card_entry_unrecognized');
|
|
334
|
-
snapshot = normalizeCheckoutSignals({ signals, observation: observation() });
|
|
335
|
-
}
|
|
336
|
-
if (encoder.encode(JSON.stringify(snapshot)).length > limits.maxSnapshotBytes) {
|
|
337
|
-
limit('snapshot_limit');
|
|
338
|
-
const boundedSnapshot = normalizeCheckoutSignals({ signals: [], observation: { complete: false, truncated, reasons: [...reasons] } });
|
|
339
|
-
let bytes = encoder.encode(JSON.stringify(boundedSnapshot)).length;
|
|
340
|
-
for (const signal of snapshot.signals) {
|
|
341
|
-
const addedBytes = encoder.encode(JSON.stringify(signal)).length + (boundedSnapshot.signals.length ? 1 : 0);
|
|
342
|
-
if (bytes + addedBytes > limits.maxSnapshotBytes)
|
|
343
|
-
break;
|
|
344
|
-
boundedSnapshot.signals.push(signal);
|
|
345
|
-
bytes += addedBytes;
|
|
346
|
-
}
|
|
347
|
-
snapshot = boundedSnapshot;
|
|
348
|
-
}
|
|
349
|
-
return snapshot;
|
|
350
|
-
}
|
|
351
|
-
/** Inspect once without preparing or submitting a checkout. */
|
|
352
|
-
export async function inspectCheckout(page, options = {}) {
|
|
353
|
-
const { collection, ...assessment } = options;
|
|
354
|
-
return assessCheckoutSupport(await collectCheckoutSignals(page, collection), assessment);
|
|
355
|
-
}
|