@agent-cards/checkout 0.18.0 → 0.21.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.
Files changed (88) hide show
  1. package/README.md +6 -669
  2. package/cdp.d.ts +1 -0
  3. package/cdp.js +2 -0
  4. package/index.d.ts +1 -0
  5. package/index.js +2 -0
  6. package/package.json +33 -33
  7. package/playwright.d.ts +1 -0
  8. package/playwright.js +2 -0
  9. package/preflight.d.ts +1 -0
  10. package/preflight.js +2 -0
  11. package/CHANGELOG.md +0 -124
  12. package/PREFLIGHT.md +0 -308
  13. package/dist/adyen.generated.d.ts +0 -24
  14. package/dist/adyen.generated.js +0 -64
  15. package/dist/attachment.d.ts +0 -11
  16. package/dist/attachment.js +0 -50
  17. package/dist/braintree.d.ts +0 -2
  18. package/dist/braintree.generated.d.ts +0 -10
  19. package/dist/braintree.generated.js +0 -302
  20. package/dist/braintree.js +0 -2
  21. package/dist/builtin-registry.generated.d.ts +0 -2
  22. package/dist/builtin-registry.generated.js +0 -1
  23. package/dist/card-fields.generated.d.ts +0 -3
  24. package/dist/card-fields.generated.js +0 -46
  25. package/dist/cdp.d.ts +0 -189
  26. package/dist/cdp.js +0 -2194
  27. package/dist/checkout-com.generated.d.ts +0 -4
  28. package/dist/checkout-com.generated.js +0 -183
  29. package/dist/client.d.ts +0 -618
  30. package/dist/client.js +0 -1251
  31. package/dist/hosted-form.d.ts +0 -44
  32. package/dist/hosted-form.js +0 -78
  33. package/dist/index.d.ts +0 -13
  34. package/dist/index.js +0 -6
  35. package/dist/lifecycle.d.ts +0 -165
  36. package/dist/lifecycle.js +0 -370
  37. package/dist/mercado-checkout.d.ts +0 -20
  38. package/dist/mercado-checkout.generated.d.ts +0 -52
  39. package/dist/mercado-checkout.generated.js +0 -198
  40. package/dist/mercado-checkout.js +0 -108
  41. package/dist/owned-shop.generated.d.ts +0 -24
  42. package/dist/owned-shop.generated.js +0 -108
  43. package/dist/paysafe.generated.d.ts +0 -12
  44. package/dist/paysafe.generated.js +0 -87
  45. package/dist/playwright.d.ts +0 -3
  46. package/dist/playwright.js +0 -3
  47. package/dist/preflight-capabilities.generated.d.ts +0 -1253
  48. package/dist/preflight-capabilities.generated.js +0 -1929
  49. package/dist/preflight-catalog.json +0 -4595
  50. package/dist/preflight-playwright.d.ts +0 -34
  51. package/dist/preflight-playwright.js +0 -355
  52. package/dist/preflight-schemas.json +0 -1110
  53. package/dist/preflight.d.ts +0 -1
  54. package/dist/preflight.generated.d.ts +0 -1965
  55. package/dist/preflight.generated.js +0 -556
  56. package/dist/preflight.js +0 -2
  57. package/dist/preparation.d.ts +0 -31
  58. package/dist/preparation.js +0 -164
  59. package/dist/prepared-processor.d.ts +0 -10
  60. package/dist/prepared-processor.js +0 -122
  61. package/dist/recurly.generated.d.ts +0 -1
  62. package/dist/recurly.generated.js +0 -87
  63. package/dist/registry.d.ts +0 -76
  64. package/dist/registry.js +0 -296
  65. package/dist/spreedly.generated.d.ts +0 -10
  66. package/dist/spreedly.generated.js +0 -332
  67. package/dist/stripe-checkout.d.ts +0 -81
  68. package/dist/stripe-checkout.generated.d.ts +0 -82
  69. package/dist/stripe-checkout.generated.js +0 -1004
  70. package/dist/stripe-checkout.js +0 -140
  71. package/dist/substitute.d.ts +0 -38
  72. package/dist/substitute.js +0 -23
  73. package/dist/substitutions.generated.d.ts +0 -10
  74. package/dist/substitutions.generated.js +0 -66
  75. package/examples/existing-browser.mjs +0 -63
  76. package/examples/preflight/classify-direct.mjs +0 -21
  77. package/examples/preflight/classify-kernel.mjs +0 -30
  78. package/examples/preflight/inspect-browser.mjs +0 -44
  79. package/examples/preflight/kernel-native/README.md +0 -112
  80. package/examples/preflight/kernel-native/documented-adapters.json +0 -113
  81. package/examples/preflight/kernel-native/inventory.json +0 -233
  82. package/examples/preflight/kernel-native/qualification.mjs +0 -182
  83. package/examples/preflight/kernel-profile.empty.json +0 -11
  84. package/examples/preflight/mollie-hosted.observations.json +0 -23
  85. package/examples/preflight/mollie-hosted.result.json +0 -103
  86. package/examples/preflight/stripe-script.direct.result.json +0 -92
  87. package/examples/preflight/stripe-script.observations.json +0 -16
  88. 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
- }