@agent-cards/checkout 0.1.0 → 0.2.1
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 +174 -6
- package/dist/cdp.d.ts +87 -2
- package/dist/cdp.js +445 -26
- package/dist/client.d.ts +268 -3
- package/dist/client.js +360 -12
- package/dist/hosted-form.d.ts +44 -0
- package/dist/hosted-form.js +78 -0
- package/dist/index.d.ts +10 -6
- package/dist/index.js +5 -3
- package/dist/registry.d.ts +50 -1
- package/dist/registry.js +436 -4
- package/dist/substitute.d.ts +42 -0
- package/dist/substitute.js +87 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,15 +42,40 @@ const vault = new VaultClient({
|
|
|
42
42
|
clientSecret: process.env.AGENTCARD_CLIENT_SECRET!,
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
// Pull the current processor list. attachToCdp arms the browser from it, so
|
|
46
|
+
// without this you only intercept the processors built into your installed
|
|
47
|
+
// version. Safe to call on every run: a failed fetch keeps the built-ins.
|
|
48
|
+
await vault.syncRegistry();
|
|
49
|
+
|
|
45
50
|
await attachToCdp(cdp, pageSessionId, {
|
|
46
51
|
vault,
|
|
47
52
|
user: 'usr_123', // whose card should pay
|
|
48
53
|
merchant: 'vanman.shop',
|
|
49
|
-
|
|
54
|
+
amountCents: 583, // what the user approves, smallest currency unit
|
|
55
|
+
currency: 'usd', // "$5.83" is derived for the approval screen
|
|
50
56
|
onApprovalUrl: (url) => sendToUser(url), // iMessage, SMS, push, your call
|
|
51
57
|
});
|
|
52
58
|
```
|
|
53
59
|
|
|
60
|
+
`amount: '$5.83'` (a display string) still works on its own. Pass
|
|
61
|
+
`amountCents` + `currency` when you want the amount enforced: on a Stripe
|
|
62
|
+
PaymentIntent confirm, Agentcard reads the intent back from Stripe at create
|
|
63
|
+
and again right before the cardholder's device replays, a different amount is
|
|
64
|
+
refused with nothing charged, and after the replay the charge is reconciled
|
|
65
|
+
against the approval (`ReplayResponse.amountVerified`, with
|
|
66
|
+
`chargedAmountCents` and `chargedKind`: `captured` for a succeeded intent's
|
|
67
|
+
`amount_received`, `authorized` for a manual-capture intent's
|
|
68
|
+
`amount_capturable`, `none` when nothing is collected yet; plus the
|
|
69
|
+
`checkout_authorization.amount_mismatch` webhook to your server when it
|
|
70
|
+
disagrees). Amounts are Stripe minor units up to 2^53-1. `onEvent` payloads
|
|
71
|
+
name URLs by origin and path only, so a client secret in a paused request's
|
|
72
|
+
query string never reaches your telemetry. When you pass the pair, any
|
|
73
|
+
`amount` string you also pass is
|
|
74
|
+
ignored: Agentcard derives the display string from the number, following
|
|
75
|
+
Stripe's minor units, so the approval screen, the notifications and every
|
|
76
|
+
read show one amount. Tokenization requests carry no amount, so there the
|
|
77
|
+
pair is shown and reported (`amountAuthority: 'display_only'`), not enforced.
|
|
78
|
+
|
|
54
79
|
Then let your agent click "Pay" like it always does. `attachToCdp` holds the
|
|
55
80
|
request open until the cardholder approves, so the checkout simply continues.
|
|
56
81
|
|
|
@@ -101,18 +126,161 @@ Merchants inherit their processor, so one entry covers every store on it.
|
|
|
101
126
|
| Stripe | supported, verified end to end |
|
|
102
127
|
| Braintree / PayPal | supported, verified end to end |
|
|
103
128
|
| Checkout.com | supported |
|
|
104
|
-
|
|
|
129
|
+
| VGS Collect (Very Good Security; Wolt) | not supported: VGS's proxy aliases only submissions from its own iframe, so a replay from the cardholder's device is refused by the merchant (verified on Wolt, 2026-09-03). Not recognized, so the agent's browser is not paused there |
|
|
130
|
+
| Adyen | supported (mode `cse`): the vault encrypts the card for Adyen on the cardholder's device and your browser sends it |
|
|
131
|
+
| Tranzila | supported (mode `hosted_form`): the cardholder finishes on Tranzila's own page; the paused form navigation resolves to a synthetic page, and you poll the merchant's order state |
|
|
105
132
|
|
|
106
133
|
The recognizer list is fetched from the API at runtime (`vault.syncRegistry()`),
|
|
107
|
-
so new processors work without you shipping a release.
|
|
134
|
+
so new processors work without you shipping a release. `attachToCdp` derives the
|
|
135
|
+
`Fetch.enable` url patterns from that same list rather than a constant, which is
|
|
136
|
+
why the sync call belongs before the attach. `vault.cardUrlPatterns()` returns
|
|
137
|
+
those patterns if you arm a CDP connection yourself. Call
|
|
138
|
+
`GET /v2/checkout/recognizers?modes=token,cse,hosted_form` for the list that is
|
|
139
|
+
live right now.
|
|
140
|
+
|
|
141
|
+
## Modes
|
|
142
|
+
|
|
143
|
+
Each recognizer carries a `mode` (absent means `token`), and every authorization
|
|
144
|
+
carries the mode it was handled in. The adapters do the right thing for all
|
|
145
|
+
three; the difference matters if you drive `authorize()` yourself.
|
|
146
|
+
`ReplayResponse` is a union, so branch on `mode`.
|
|
147
|
+
|
|
148
|
+
- **`token`** (every processor but Adyen). The cardholder's device calls the
|
|
149
|
+
processor and reports its answer; `authorize()` resolves with `status`,
|
|
150
|
+
`headers` and `body` to fulfill the paused request with. The browser checks
|
|
151
|
+
a fulfilled answer exactly as it checks a real one, so when the page called
|
|
152
|
+
the processor cross-origin (Stripe always does: Checkout on
|
|
153
|
+
`checkout.stripe.com` and Elements in the `js.stripe.com` frame both fetch
|
|
154
|
+
`api.stripe.com`) the answer must carry `access-control-allow-origin` for
|
|
155
|
+
the request's own `Origin`, or the page's fetch rejects and the checkout
|
|
156
|
+
reports a connection error even though the cardholder approved. The
|
|
157
|
+
adapters add those headers (`corsHeadersFor` + `withCorsHeaders`, exported
|
|
158
|
+
for a runtime that fulfills by hand, and `corsDecision` when you also want
|
|
159
|
+
the reason) and report the decision on the `authorized` event as `cors`:
|
|
160
|
+
`echoed`, `same_origin`, or `none` (no usable Origin on the request, so
|
|
161
|
+
the page could not read the answer). Only what a browser serializes is
|
|
162
|
+
echoed: one canonical http(s) origin, or the opaque `null`. Shopify's
|
|
163
|
+
card iframe posts to its own origin, so it never needed them.
|
|
164
|
+
- **`cse`** (Adyen). Adyen's own page SDK encrypts the card before the request
|
|
165
|
+
leaves the browser, so the paused body carries ciphertext. The cardholder's
|
|
166
|
+
device produces the same ciphertext under the merchant's Adyen public key
|
|
167
|
+
(fetched by Agentcard from Adyen's host when the request is parked) and
|
|
168
|
+
`authorize()` resolves with `substitutions: { encoding: 'json', at, fields,
|
|
169
|
+
remove }`. Write them into the paused body with
|
|
170
|
+
`substituteEncryptedFields(body, substitutions)` and CONTINUE the request
|
|
171
|
+
from the same browser (`Fetch.continueRequest` with the rewritten
|
|
172
|
+
`postData`, or Playwright's `route.continue({ postData })`): its session
|
|
173
|
+
data, risk data and cookies must stay its own. Only the four encrypted
|
|
174
|
+
fields change, and the siblings named in `remove` are dropped (Adyen's
|
|
175
|
+
`brand`, which adyen-web derived from the dummy digits the agent typed:
|
|
176
|
+
left in place it names the wrong card and Adyen refuses the mismatch;
|
|
177
|
+
absent, Adyen reads the brand off the card it decrypts). A body that lacks
|
|
178
|
+
the fields throws `SubstitutionError`, which is not terminal. Adyen answers the browser,
|
|
179
|
+
so `charged_kind` is null on the approval and the merchant's order state is
|
|
180
|
+
the outcome to poll.
|
|
181
|
+
- **`hosted_form`** (Tranzila). The processor's hosted card form submits the
|
|
182
|
+
card as a TOP-LEVEL form post, so the paused request is a page navigation
|
|
183
|
+
(the adapters arm `Fetch.enable` with no resource-type filter and attach the
|
|
184
|
+
processor's iframe, which is how a Document request on `direct.tranzila.com`
|
|
185
|
+
gets paused at all). The cardholder's device rebuilds that form with the
|
|
186
|
+
real card and submits it itself; the processor answers the device, and
|
|
187
|
+
`authorize()` resolves with `{ mode: 'hosted_form', kind:
|
|
188
|
+
'submitted_on_device', outcome: 'unverified', submittedAt }` once the
|
|
189
|
+
device reports the form left. **This is not an approved payment.** The
|
|
190
|
+
stamp is the cardholder's device attesting that the form left it;
|
|
191
|
+
Agentcard holds no processor evidence on this mode and cannot obtain any,
|
|
192
|
+
so the API finishes the authorization as `submitted_on_device` (never
|
|
193
|
+
`approved`) and sends your server `checkout_authorization.submitted`
|
|
194
|
+
(never `.approved`). Treat it as "the person paid, or tried to, on their
|
|
195
|
+
own device" and confirm the order with the merchant before you count it.
|
|
196
|
+
There is no response to replay: FULFIL the paused navigation with
|
|
197
|
+
`hostedFormSubmittedPage({ authorizationId, merchant, submittedAt })` (200,
|
|
198
|
+
`text/html`, `x-agentcard-checkout: submitted_on_device`, a `<meta
|
|
199
|
+
name="agentcard-checkout">` and an inert JSON block saying "submitted on
|
|
200
|
+
the cardholder's device, payment unverified, do not resubmit"), the way
|
|
201
|
+
the adapters do. Do not abort it: an aborted navigation renders nothing,
|
|
202
|
+
the iframe silently keeps its dummy-card form, and the agent's next move is
|
|
203
|
+
to click Pay again. Do not fake the processor's result page either: this
|
|
204
|
+
SDK does not know the outcome. The adapters emit `submitted_on_device` (not
|
|
205
|
+
`authorized`), refuse a byte-identical re-post of the same form for 15
|
|
206
|
+
minutes (`hostedFormRepeatQuietMs`), and the API answers a regenerated one
|
|
207
|
+
with `409 duplicate_submission`, which the adapters quiet the way they quiet
|
|
208
|
+
a decline (`approvalCooldownMs`): the page's immediate re-posts are refused
|
|
209
|
+
without a round trip, and once the prior authorization is declined or
|
|
210
|
+
expired the same form is a new question. Confirm the order with the
|
|
211
|
+
merchant, which learns the outcome from the processor.
|
|
212
|
+
|
|
213
|
+
`syncRegistry()` asks the API for `SUPPORTED_MODES` only
|
|
214
|
+
(`token,cse,hosted_form`), so a processor whose flow this build cannot finish
|
|
215
|
+
is never paused; the API serves `hosted_form` entries only to callers that ask.
|
|
216
|
+
An approval in some other mode throws `UnsupportedModeError`, which is
|
|
217
|
+
terminal for that page (upgrade the SDK). The `authorized` event's detail names
|
|
218
|
+
the `mode`, the `authorizationId` and, for `cse`, the `fields` that were
|
|
219
|
+
substituted; it never carries ciphertext. The `submitted_on_device` event's
|
|
220
|
+
detail names the `authorizationId`, `submittedAt` and `outcome:
|
|
221
|
+
'unverified'`; it is not an `authorized` event and must not be counted as
|
|
222
|
+
one. `amountAuthority` on every replay is `stripe_payment_intent`,
|
|
223
|
+
`hosted_form_sum` (the form's own amount) or `display_only`.
|
|
108
224
|
|
|
109
225
|
## Errors worth handling
|
|
110
226
|
|
|
111
227
|
- `ApprovalTimeoutError` — the user never approved. Default window is 15 minutes;
|
|
112
228
|
we have completed checkouts after a 5.5 minute approval delay.
|
|
113
229
|
- `ApprovalDeclinedError` — the user said no.
|
|
114
|
-
- `
|
|
115
|
-
|
|
230
|
+
- `AmountMismatchError`: the processor's amount did not match the amount the
|
|
231
|
+
user was (or would have been) asked to approve. Nothing was charged. An
|
|
232
|
+
`ApprovalDeclinedError` with `expectedCents`, `actualCents`, `currency`,
|
|
233
|
+
`code: 'amount_mismatch'` and `stage`: `'pre_replay'` (checked right before
|
|
234
|
+
the device would have sent the card; `authorizationId` names the declined
|
|
235
|
+
authorization) or `'create'` (the intent already disagreed when the request
|
|
236
|
+
was parked; no authorization exists, `authorizationId` is null). Per
|
|
237
|
+
request, not per page: a merchant can still update an intent's amount
|
|
238
|
+
until it is confirmed, so the adapters quiet the page's immediate retry
|
|
239
|
+
and judge the next request afresh instead of latching.
|
|
240
|
+
- `IntentNotConfirmableError`: the PaymentIntent was already charged, is
|
|
241
|
+
processing, or is authorized and on hold (or canceled), so Agentcard
|
|
242
|
+
refused to replay a confirm at it. An `ApprovalDeclinedError` with
|
|
243
|
+
`code: 'intent_not_confirmable'`. Deliberately not "nothing was charged":
|
|
244
|
+
check the intent at Stripe before retrying.
|
|
245
|
+
- `ProcessorRefusedError`: the cardholder's device sent the card and the
|
|
246
|
+
processor refused it outright (`pspErrorCode` is the processor's own code,
|
|
247
|
+
such as Stripe's `card_declined`). Nothing was charged. An
|
|
248
|
+
`ApprovalDeclinedError` with `code: 'processor_refused'`; the page's next
|
|
249
|
+
attempt raises a fresh approval where the person can pick another card.
|
|
250
|
+
- `CheckoutApiError` with `code === 'amount_unverifiable'`: Stripe could not
|
|
251
|
+
be asked (502; the SDK retries twice, 500ms then 1500ms, before throwing)
|
|
252
|
+
or the paused request lacked its client secret or publishable key (400).
|
|
253
|
+
`code === 'intent_not_confirmable'` at create (409) means the intent was
|
|
254
|
+
already used; the adapters stop intercepting for that page.
|
|
255
|
+
`code === 'cse_key_unavailable'` (502) means Adyen did not answer the
|
|
256
|
+
public-key fetch and is retried the same way; `cse_client_key_unknown`
|
|
257
|
+
(400) means Adyen does not know the merchant's `clientKey`, and
|
|
258
|
+
`cse_template_unsupported` (400) means the paused body carries no
|
|
259
|
+
encrypted card fields to fill (a stored card, a wallet, a single-blob
|
|
260
|
+
`encryptedCard`); both are terminal for that page.
|
|
261
|
+
`hosted_form_template_incomplete` (400) means the paused form lacks a
|
|
262
|
+
field the device fills or the processor requires, `hosted_form_gated`
|
|
263
|
+
(400) means it carries a live captcha token the device could never
|
|
264
|
+
re-submit, and `hosted_form_field_refused` (400, with `field` and a
|
|
265
|
+
`reason` of `stored_credential`, `not_a_sale` or `callback_host`) means
|
|
266
|
+
the form asks the processor for something other than one plain sale
|
|
267
|
+
reporting to the merchant you named (a reusable token in or out, a sale
|
|
268
|
+
mode that is not a sale, a callback URL off the merchant's host: name the
|
|
269
|
+
merchant by its hostname when the form carries callback URLs); all three
|
|
270
|
+
are terminal for that page. `duplicate_submission` (409,
|
|
271
|
+
with `prior_authorization_id` and `prior_status`) means this exact
|
|
272
|
+
submission already has, or already had, its prompt: the adapters quiet the
|
|
273
|
+
page's re-posts for `approvalCooldownMs` (no second notification for one
|
|
274
|
+
payment) and judge the next request afresh, since the prior authorization
|
|
275
|
+
declines or expires and the same form is then a new question.
|
|
276
|
+
- `CardEncryptedError`: this processor encrypts the card in-page and its
|
|
277
|
+
registry entry does not (yet) say the vault can produce that ciphertext;
|
|
278
|
+
route the purchase to an Agentcard-issued card instead.
|
|
279
|
+
- `UnsupportedModeError`: the approval came back in a mode this build of the
|
|
280
|
+
SDK cannot finish. Terminal for the page; upgrade.
|
|
281
|
+
- `SubstitutionError`: a `cse` approval could not be written into the paused
|
|
282
|
+
body (the four encrypted fields were not there). The request is failed and
|
|
283
|
+
the next one is judged afresh.
|
|
116
284
|
|
|
117
285
|
## Building this package
|
|
118
286
|
|
|
@@ -122,5 +290,5 @@ be installed here without regenerating the lockfile, and a full regen drifts
|
|
|
122
290
|
unrelated transitive versions). Build it with the workspace TypeScript:
|
|
123
291
|
|
|
124
292
|
```bash
|
|
125
|
-
|
|
293
|
+
cd packages/checkout && pnpm build && pnpm test
|
|
126
294
|
```
|
package/dist/cdp.d.ts
CHANGED
|
@@ -1,4 +1,67 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type VaultClient } from './client.js';
|
|
2
|
+
/**
|
|
3
|
+
* The CORS headers a fulfilled CROSS-ORIGIN request needs, or null when the
|
|
4
|
+
* request is same-origin (or carries no Origin, so no CORS check applies).
|
|
5
|
+
*
|
|
6
|
+
* The browser checks a fulfilled response exactly as it checks a real one. A
|
|
7
|
+
* page that fetches a processor on another origin therefore needs
|
|
8
|
+
* `access-control-allow-origin` on the synthetic answer, or its fetch rejects
|
|
9
|
+
* with "Failed to fetch" and the page never sees the processor's reply, even
|
|
10
|
+
* though the cardholder approved and the processor answered the vault.
|
|
11
|
+
* Shopify never hit this on its current host: the checkout.pci.shopifyinc.com
|
|
12
|
+
* card iframe posts to its own origin (its older deposit.<region>.shopifycs.com
|
|
13
|
+
* host is called from the checkout.shopifycs.com frame, cross-origin, and gets
|
|
14
|
+
* the answer like everyone else). Stripe hits it on every surface (Checkout on
|
|
15
|
+
* checkout.stripe.com or a merchant domain, and Elements in the js.stripe.com
|
|
16
|
+
* frame, all call api.stripe.com), and so does every other processor whose
|
|
17
|
+
* card frame calls a separate API host. Observed live on
|
|
18
|
+
* 2026-09-03: the vault replayed a Stripe PaymentMethod into a raw-CDP
|
|
19
|
+
* runtime, the browser refused the answer for want of this header, and
|
|
20
|
+
* Stripe Checkout showed "We are experiencing connection issues".
|
|
21
|
+
*
|
|
22
|
+
* The exact Origin is echoed rather than `*`: a credentialed request refuses
|
|
23
|
+
* `*`, the echo satisfies both. The processor's own value can never reach
|
|
24
|
+
* this adapter (a browser does not expose that header to the page that
|
|
25
|
+
* replayed the call), so whatever the replay carries under these names is
|
|
26
|
+
* replaced by the one value that is right for THIS request. Playwright adds
|
|
27
|
+
* the same headers inside route.fulfill when a cross-origin fulfill carries
|
|
28
|
+
* none (microsoft/playwright#12929), which is why attachToPlaywright never
|
|
29
|
+
* needed this; it writes them itself anyway, replacing a stale value, so both
|
|
30
|
+
* adapters answer the vault's replays identically.
|
|
31
|
+
*
|
|
32
|
+
* This widens nothing. A tokenization endpoint is built for anonymous
|
|
33
|
+
* browsers and answers every origin (`access-control-allow-origin: *` on
|
|
34
|
+
* Stripe's and Shopify's own replies), so the page that made the request
|
|
35
|
+
* could always read the processor's answer to it; the replay is made exactly
|
|
36
|
+
* as visible, to exactly that page. Whether a card goes anywhere at all is
|
|
37
|
+
* decided by the cardholder on the approval screen, never by this header.
|
|
38
|
+
*
|
|
39
|
+
* Only what a browser serializes is ever echoed: one canonical http(s)
|
|
40
|
+
* origin (`new URL(origin).origin === origin`), or the opaque `null` a
|
|
41
|
+
* sandboxed or data: document sends, which Chrome matches against
|
|
42
|
+
* `access-control-allow-origin: null` and which Playwright echoes too. That
|
|
43
|
+
* refuses userinfo, a path, an explicit default port, several origins in one
|
|
44
|
+
* value, or a control character that would break the fulfill after the
|
|
45
|
+
* cardholder already approved. Anything refused simply gets no CORS answer,
|
|
46
|
+
* which is what every fulfill got before this existed.
|
|
47
|
+
*/
|
|
48
|
+
export declare function corsHeadersFor(url: string, requestHeaders: Record<string, string> | undefined): Record<string, string> | null;
|
|
49
|
+
/** How a fulfill was answered, reported on the `authorized` event so a silent CORS failure is diagnosable from events alone. */
|
|
50
|
+
export type CorsOutcome = 'echoed' | 'same_origin' | 'none';
|
|
51
|
+
/** The CORS answer and its reason: `none` when no usable Origin was sent (or the url is not http(s)), `same_origin` when no check applies. */
|
|
52
|
+
export declare function corsDecision(url: string, requestHeaders: Record<string, string> | undefined): {
|
|
53
|
+
headers: Record<string, string> | null;
|
|
54
|
+
outcome: CorsOutcome;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* `headers` with the CORS answer for this request written in; the object
|
|
58
|
+
* itself when none is needed. Any header the answer names is replaced
|
|
59
|
+
* whatever its case, so a name is never sent twice. The answer varies by
|
|
60
|
+
* Origin, and a `vary` the replay already carries is extended rather than
|
|
61
|
+
* replaced (or left alone when it already covers Origin or is `*`); a `vary`
|
|
62
|
+
* the answer itself names is taken as given.
|
|
63
|
+
*/
|
|
64
|
+
export declare function withCorsHeaders(headers: Record<string, string>, cors: Record<string, string> | null): Record<string, string>;
|
|
2
65
|
/**
|
|
3
66
|
* Minimal shape of a CDP connection. Works with a raw websocket client, a
|
|
4
67
|
* Puppeteer CDPSession, or Playwright's CDPSession.
|
|
@@ -11,12 +74,25 @@ export interface AttachOptions {
|
|
|
11
74
|
vault: VaultClient;
|
|
12
75
|
user: string;
|
|
13
76
|
merchant: string;
|
|
14
|
-
|
|
77
|
+
/** Display string for the approval screen. Optional when amountCents + currency are given. */
|
|
78
|
+
amount?: string;
|
|
79
|
+
/**
|
|
80
|
+
* The amount as a number (smallest currency unit) with its ISO 4217 code.
|
|
81
|
+
* See AuthorizeInput.amountCents: on a Stripe PaymentIntent confirm this
|
|
82
|
+
* binds the approval to the intent's amount, checked at create and again
|
|
83
|
+
* right before replay, so a larger charge is refused with nothing charged.
|
|
84
|
+
*/
|
|
85
|
+
amountCents?: number;
|
|
86
|
+
currency?: string;
|
|
15
87
|
onApprovalUrl?: (url: string) => void;
|
|
16
88
|
onEvent?: (e: {
|
|
17
89
|
type: string;
|
|
18
90
|
detail?: unknown;
|
|
19
91
|
}) => void;
|
|
92
|
+
/** Silence after a decline or timeout. Defaults to APPROVAL_COOLDOWN_MS. */
|
|
93
|
+
approvalCooldownMs?: number;
|
|
94
|
+
/** How long a hosted form the device already submitted stays refused on a re-post. Defaults to HOSTED_FORM_REPEAT_QUIET_MS. */
|
|
95
|
+
hostedFormRepeatQuietMs?: number;
|
|
20
96
|
}
|
|
21
97
|
/**
|
|
22
98
|
* Take over card tokenization for a page.
|
|
@@ -25,6 +101,15 @@ export interface AttachOptions {
|
|
|
25
101
|
* targets. Enabling Fetch on the page session alone will never see the
|
|
26
102
|
* tokenization request. This attaches recursively so every nested target is
|
|
27
103
|
* armed, which is the whole reason this adapter exists.
|
|
104
|
+
*
|
|
105
|
+
* The patterns armed here come from the REGISTRY, not from a constant, so a PSP
|
|
106
|
+
* the API knows about is paused without an SDK release — call
|
|
107
|
+
* `vault.syncRegistry()` before attaching and every recognizer the server
|
|
108
|
+
* serves is covered. They are a coarse pre-filter and are deliberately wider
|
|
109
|
+
* than the recognizers (see cardUrlPatterns): each paused request is re-checked
|
|
110
|
+
* with `isCardRequest` below and continued untouched unless it is an exact
|
|
111
|
+
* match. Patterns are resolved once, at attach, so every nested target ends up
|
|
112
|
+
* armed identically.
|
|
28
113
|
*/
|
|
29
114
|
export declare function attachToCdp(cdp: CdpLike, pageSessionId: string, opts: AttachOptions): Promise<void>;
|
|
30
115
|
/**
|