@agent-cards/sdk 0.20.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/CHANGELOG.md +198 -0
- package/PREFLIGHT.md +314 -0
- package/README.md +864 -0
- package/dist/adyen-merchant-hosted.generated.d.ts +277 -0
- package/dist/adyen-merchant-hosted.generated.js +1902 -0
- package/dist/adyen.generated.d.ts +24 -0
- package/dist/adyen.generated.js +64 -0
- package/dist/agentcard.d.ts +72 -0
- package/dist/agentcard.js +71 -0
- package/dist/api-module.d.ts +54 -0
- package/dist/api-module.js +85 -0
- package/dist/attachment.d.ts +11 -0
- package/dist/attachment.js +50 -0
- package/dist/braintree.d.ts +2 -0
- package/dist/braintree.generated.d.ts +10 -0
- package/dist/braintree.generated.js +302 -0
- package/dist/braintree.js +2 -0
- package/dist/builtin-registry.generated.d.ts +2 -0
- package/dist/builtin-registry.generated.js +1 -0
- package/dist/buy.d.ts +374 -0
- package/dist/buy.js +314 -0
- package/dist/card-fields.generated.d.ts +3 -0
- package/dist/card-fields.generated.js +88 -0
- package/dist/cdp.d.ts +202 -0
- package/dist/cdp.js +2540 -0
- package/dist/checkout-com.generated.d.ts +4 -0
- package/dist/checkout-com.generated.js +183 -0
- package/dist/client.d.ts +929 -0
- package/dist/client.js +1896 -0
- package/dist/cse-body.d.ts +25 -0
- package/dist/cse-body.js +41 -0
- package/dist/fiserv.d.ts +65 -0
- package/dist/fiserv.generated.d.ts +73 -0
- package/dist/fiserv.generated.js +830 -0
- package/dist/fiserv.js +104 -0
- package/dist/hosted-form.d.ts +44 -0
- package/dist/hosted-form.js +78 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +26 -0
- package/dist/languages.d.ts +23 -0
- package/dist/languages.js +76 -0
- package/dist/lifecycle.d.ts +235 -0
- package/dist/lifecycle.js +405 -0
- package/dist/mercado-checkout.d.ts +20 -0
- package/dist/mercado-checkout.generated.d.ts +52 -0
- package/dist/mercado-checkout.generated.js +198 -0
- package/dist/mercado-checkout.js +108 -0
- package/dist/merchant-handoff.d.ts +54 -0
- package/dist/merchant-handoff.js +100 -0
- package/dist/merchant-hosted.d.ts +140 -0
- package/dist/merchant-hosted.js +170 -0
- package/dist/merchant-total-watch.d.ts +115 -0
- package/dist/merchant-total-watch.js +268 -0
- package/dist/merchant-total.d.ts +257 -0
- package/dist/merchant-total.js +383 -0
- package/dist/owned-shop.generated.d.ts +24 -0
- package/dist/owned-shop.generated.js +108 -0
- package/dist/paysafe.generated.d.ts +12 -0
- package/dist/paysafe.generated.js +87 -0
- package/dist/playwright.d.ts +3 -0
- package/dist/playwright.js +3 -0
- package/dist/pre-claim.d.ts +123 -0
- package/dist/pre-claim.js +386 -0
- package/dist/preflight-capabilities.generated.d.ts +1311 -0
- package/dist/preflight-capabilities.generated.js +2007 -0
- package/dist/preflight-catalog.json +5031 -0
- package/dist/preflight-playwright.d.ts +34 -0
- package/dist/preflight-playwright.js +355 -0
- package/dist/preflight-schemas.json +1144 -0
- package/dist/preflight.d.ts +1 -0
- package/dist/preflight.generated.d.ts +2023 -0
- package/dist/preflight.generated.js +604 -0
- package/dist/preflight.js +2 -0
- package/dist/preparation.d.ts +38 -0
- package/dist/preparation.js +191 -0
- package/dist/prepared-processor.d.ts +43 -0
- package/dist/prepared-processor.js +172 -0
- package/dist/recurly.generated.d.ts +1 -0
- package/dist/recurly.generated.js +87 -0
- package/dist/registry.d.ts +145 -0
- package/dist/registry.js +338 -0
- package/dist/requests.d.ts +66 -0
- package/dist/requests.js +76 -0
- package/dist/spreedly.generated.d.ts +10 -0
- package/dist/spreedly.generated.js +332 -0
- package/dist/stripe-checkout.d.ts +81 -0
- package/dist/stripe-checkout.generated.d.ts +82 -0
- package/dist/stripe-checkout.generated.js +1153 -0
- package/dist/stripe-checkout.js +140 -0
- package/dist/substitute.d.ts +38 -0
- package/dist/substitute.js +23 -0
- package/dist/substitutions.generated.d.ts +11 -0
- package/dist/substitutions.generated.js +818 -0
- package/dist/voice-pkce.d.ts +49 -0
- package/dist/voice-pkce.js +147 -0
- package/dist/voice-retry.d.ts +6 -0
- package/dist/voice-retry.js +27 -0
- package/dist/voice-stream.generated.d.ts +118 -0
- package/dist/voice-stream.generated.js +170 -0
- package/dist/voice-types.d.ts +708 -0
- package/dist/voice-types.js +16 -0
- package/dist/voice.d.ts +365 -0
- package/dist/voice.js +807 -0
- package/dist/webhooks.d.ts +87 -0
- package/dist/webhooks.js +136 -0
- package/examples/existing-browser.mjs +62 -0
- package/examples/preflight/classify-direct.mjs +21 -0
- package/examples/preflight/classify-kernel.mjs +30 -0
- package/examples/preflight/inspect-browser.mjs +44 -0
- package/examples/preflight/kernel-native/README.md +112 -0
- package/examples/preflight/kernel-native/documented-adapters.json +113 -0
- package/examples/preflight/kernel-native/inventory.json +242 -0
- package/examples/preflight/kernel-native/qualification.mjs +182 -0
- package/examples/preflight/kernel-profile.empty.json +11 -0
- package/examples/preflight/mollie-hosted.observations.json +23 -0
- package/examples/preflight/mollie-hosted.result.json +103 -0
- package/examples/preflight/stripe-script.direct.result.json +92 -0
- package/examples/preflight/stripe-script.observations.json +16 -0
- package/examples/preflight/stripe-script.result.json +87 -0
- package/package.json +53 -0
- package/readme.test.mjs +53 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Adyen Web Sessions payments request, before it consumes an approval.
|
|
2
|
+
* adyen-web (Drop-in and the Card component in the Sessions flow) posts one
|
|
3
|
+
* JSON body to /checkoutshopper/v1/sessions/{id}/payments on the merchant's
|
|
4
|
+
* Adyen host, with `clientKey` as its only query parameter, and carries the
|
|
5
|
+
* card as four JWE blobs under paymentMethod. Those four blobs are what the
|
|
6
|
+
* cardholder's device replaces; nothing else in the body is rewritten. Test
|
|
7
|
+
* and live are separate host families AND separate client-key prefixes, so a
|
|
8
|
+
* preparation names one of them and the request must agree on both. A body
|
|
9
|
+
* that pays with a stored card, carries a single encrypted blob, or asks
|
|
10
|
+
* Adyen to store the card is not the purchase the person approved and never
|
|
11
|
+
* consumes readiness. This module never rewrites a body.
|
|
12
|
+
*/
|
|
13
|
+
export type AdyenPreparationEnvironment = 'production' | 'sandbox';
|
|
14
|
+
export declare const ADYEN_ENCRYPTED_CARD_FIELDS: readonly ["encryptedCardNumber", "encryptedExpiryMonth", "encryptedExpiryYear", "encryptedSecurityCode"];
|
|
15
|
+
/** Which preparation environment a hostname belongs to: Adyen's test host, one of its live host families, or neither. */
|
|
16
|
+
export declare function adyenPreparationEnvironment(hostname: string): AdyenPreparationEnvironment | null;
|
|
17
|
+
/** The URL a preparation advertises for the approved host family: every registry recognizes it, and it is never a real session. */
|
|
18
|
+
export declare function adyenPreparationUrl(environment: AdyenPreparationEnvironment): string;
|
|
19
|
+
/** A Sessions payments URL on the approved host family whose only query is a client key of that same environment. */
|
|
20
|
+
export declare function isPreparedAdyenUrl(rawUrl: string, environment: AdyenPreparationEnvironment): boolean;
|
|
21
|
+
/** A fresh scheme card carrying the four encrypted fields the device replaces; stored cards, single-blob encryption and store requests never consume consent. */
|
|
22
|
+
export declare function isPreparedAdyenBody(body: string | null | undefined): boolean;
|
|
23
|
+
/** Processor identity, environment and request shape are part of the device's prior consent. */
|
|
24
|
+
export declare function isPreparedAdyenRequest(rawUrl: string, method: string, body: string | null | undefined, environment: AdyenPreparationEnvironment): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const ADYEN_ENCRYPTED_CARD_FIELDS = ['encryptedCardNumber', 'encryptedExpiryMonth', 'encryptedExpiryYear', 'encryptedSecurityCode'];
|
|
2
|
+
const SESSIONS_PAYMENTS = /^\/checkoutshopper\/v1\/sessions\/[A-Za-z0-9_-]{1,256}\/payments$/;
|
|
3
|
+
const TEST_HOST = /^checkoutshopper-test\.adyen\.com$/;
|
|
4
|
+
const LIVE_HOST = /^(?:checkoutshopper-live(?:-[a-z]+)?\.adyen\.com|(?:[a-z0-9-]+\.)*adyenpayments\.com)$/;
|
|
5
|
+
const CLIENT_KEY = /^(test|live)_[A-Za-z0-9]{32}$/;
|
|
6
|
+
const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
const text = (value, max = 4096) => typeof value === 'string' && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
|
|
8
|
+
/** Which preparation environment a hostname belongs to: Adyen's test host, one of its live host families, or neither. */
|
|
9
|
+
export function adyenPreparationEnvironment(hostname) {
|
|
10
|
+
const host = hostname.toLowerCase();
|
|
11
|
+
if (TEST_HOST.test(host))
|
|
12
|
+
return 'sandbox';
|
|
13
|
+
if (LIVE_HOST.test(host))
|
|
14
|
+
return 'production';
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
/** The URL a preparation advertises for the approved host family: every registry recognizes it, and it is never a real session. */
|
|
18
|
+
export function adyenPreparationUrl(environment) {
|
|
19
|
+
return `https://checkoutshopper-${environment === 'production' ? 'live' : 'test'}.adyen.com/checkoutshopper/v1/sessions/prepared/payments`;
|
|
20
|
+
}
|
|
21
|
+
/** A Sessions payments URL on the approved host family whose only query is a client key of that same environment. */
|
|
22
|
+
export function isPreparedAdyenUrl(rawUrl, environment) {
|
|
23
|
+
let url;
|
|
24
|
+
try {
|
|
25
|
+
url = new URL(rawUrl);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if (url.protocol !== 'https:' || url.port || url.username || url.password || url.hash)
|
|
31
|
+
return false;
|
|
32
|
+
if (adyenPreparationEnvironment(url.hostname) !== environment || !SESSIONS_PAYMENTS.test(url.pathname))
|
|
33
|
+
return false;
|
|
34
|
+
const names = [...url.searchParams.keys()];
|
|
35
|
+
if (names.length !== 1 || names[0] !== 'clientKey')
|
|
36
|
+
return false;
|
|
37
|
+
const key = url.searchParams.get('clientKey') ?? '';
|
|
38
|
+
const match = CLIENT_KEY.exec(key);
|
|
39
|
+
return !!match && match[0] === key && (match[1] === 'live') === (environment === 'production');
|
|
40
|
+
}
|
|
41
|
+
/** A fresh scheme card carrying the four encrypted fields the device replaces; stored cards, single-blob encryption and store requests never consume consent. */
|
|
42
|
+
export function isPreparedAdyenBody(body) {
|
|
43
|
+
if (typeof body !== 'string' || !body || body.length > 65536)
|
|
44
|
+
return false;
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(body);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (!record(parsed) || parsed.storePaymentMethod === true)
|
|
53
|
+
return false;
|
|
54
|
+
const method = parsed.paymentMethod;
|
|
55
|
+
if (!record(method) || method.type !== 'scheme')
|
|
56
|
+
return false;
|
|
57
|
+
if ('storedPaymentMethodId' in method || 'encryptedCard' in method || 'recurringDetailReference' in method)
|
|
58
|
+
return false;
|
|
59
|
+
return ADYEN_ENCRYPTED_CARD_FIELDS.every(field => text(method[field]));
|
|
60
|
+
}
|
|
61
|
+
/** Processor identity, environment and request shape are part of the device's prior consent. */
|
|
62
|
+
export function isPreparedAdyenRequest(rawUrl, method, body, environment) {
|
|
63
|
+
return method.toUpperCase() === 'POST' && isPreparedAdyenUrl(rawUrl, environment) && isPreparedAdyenBody(body);
|
|
64
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one client: `new Agentcard({ clientId, clientSecret, voice?, sandbox? })`,
|
|
3
|
+
* with two modules. `checkout` pauses a browser purchase or creates one
|
|
4
|
+
* directly and replays the processor's answer; `voice` connects a person to
|
|
5
|
+
* your product once and approves a purchase by voice. One pair of
|
|
6
|
+
* credentials, one token, one base URL; sandbox and live are decided by the
|
|
7
|
+
* credentials.
|
|
8
|
+
*/
|
|
9
|
+
import { VaultClient, type AuthorizeInput, type PrepareCheckoutInput, type PreparedCheckout, type ReplayResponse, type RuntimeCancelReason, type VaultClientOptions } from './client.js';
|
|
10
|
+
import { type AttachOptions, type CdpLike } from './cdp.js';
|
|
11
|
+
import type { CheckoutController } from './lifecycle.js';
|
|
12
|
+
import type { CheckoutMode, Recognizer } from './registry.js';
|
|
13
|
+
import type { RequestRegistry } from './requests.js';
|
|
14
|
+
/** `attachToCdp`'s options without the client, which the module supplies. */
|
|
15
|
+
export type CheckoutAttachOptions = Omit<AttachOptions, 'vault'>;
|
|
16
|
+
/**
|
|
17
|
+
* `agentcard.checkout`: a browser purchase paused and replayed, a purchase
|
|
18
|
+
* created directly, the recognisers that decide what pauses and the request
|
|
19
|
+
* builders that shape a direct create. Every verb here is the client's own
|
|
20
|
+
* under its module name; the client's top-level spellings stay for one release.
|
|
21
|
+
*/
|
|
22
|
+
export declare class CheckoutModule {
|
|
23
|
+
private readonly client;
|
|
24
|
+
constructor(client: Agentcard);
|
|
25
|
+
/**
|
|
26
|
+
* Arms a CDP session so the page's card request pauses for the cardholder's
|
|
27
|
+
* approval and resumes with the processor's answer. `syncRegistry()` first,
|
|
28
|
+
* so the recognisers are the API's current list.
|
|
29
|
+
*/
|
|
30
|
+
attachToCdp(cdp: CdpLike, pageSessionId: string, opts: CheckoutAttachOptions): Promise<CheckoutController>;
|
|
31
|
+
/** The same over a Playwright page. */
|
|
32
|
+
attachToPlaywright(page: unknown, opts: CheckoutAttachOptions): Promise<CheckoutController>;
|
|
33
|
+
/** Refreshes the recognisers from the API so a new processor works without a redeploy; a failed read keeps the current list. */
|
|
34
|
+
syncRegistry(): Promise<void>;
|
|
35
|
+
/** Creates the purchase for a paused request, waits for the approval and returns what to replay into the page. */
|
|
36
|
+
authorize(input: AuthorizeInput): Promise<ReplayResponse>;
|
|
37
|
+
/** Approves a purchase before the page's own card request, for processors with a short request deadline. */
|
|
38
|
+
prepareCheckout(input: PrepareCheckoutInput): Promise<PreparedCheckout>;
|
|
39
|
+
cancelPreparation(id: string): Promise<void>;
|
|
40
|
+
/** Withdraws an approval the page will not use, with the runtime's reason. */
|
|
41
|
+
cancelAuthorization(authorizationId: string, reason?: RuntimeCancelReason): ReturnType<VaultClient['cancelAuthorization']>;
|
|
42
|
+
/** Asks the API whether a Stripe confirmation that pays with an approved token may continue. */
|
|
43
|
+
checkStripeContinuation(authorizationId: string, request: {
|
|
44
|
+
url: string;
|
|
45
|
+
method: string;
|
|
46
|
+
headers: Record<string, string>;
|
|
47
|
+
body: string;
|
|
48
|
+
}): ReturnType<VaultClient['checkStripeContinuation']>;
|
|
49
|
+
/** True when this request is a card request a recogniser claims. */
|
|
50
|
+
isCardRequest(url: string, method?: string): boolean;
|
|
51
|
+
/** The recogniser that claims a card request, or null. */
|
|
52
|
+
recognizerOf(url: string, method?: string): Recognizer | null;
|
|
53
|
+
/** How the card would reach the processor on this request, or null when nothing recognises it. */
|
|
54
|
+
checkoutModeOf(url: string, method?: string): CheckoutMode | null;
|
|
55
|
+
/** What happens to a recognised request whose body carries no card. */
|
|
56
|
+
withoutCard(url: string, body: string | null | undefined): 'continue' | 'refuse' | null;
|
|
57
|
+
/** The URL patterns a raw CDP connection hands `Fetch.enable`, for the current recognisers. */
|
|
58
|
+
cardUrlPatterns(): string[];
|
|
59
|
+
/** The recognisers: the served or built-in list plus yours (`register`, `list`, `find`). */
|
|
60
|
+
get recognizers(): Agentcard['recognizers'];
|
|
61
|
+
/** The request builders: the shipped ones plus yours (`register`, `build`, `list`). */
|
|
62
|
+
get requests(): RequestRegistry;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The Agentcard client. Construct one per server with your OAuth client
|
|
66
|
+
* credentials; every module shares its token.
|
|
67
|
+
*/
|
|
68
|
+
export declare class Agentcard extends VaultClient {
|
|
69
|
+
/** A browser purchase paused and replayed, a purchase created directly, the recognisers and the request builders. */
|
|
70
|
+
readonly checkout: CheckoutModule;
|
|
71
|
+
constructor(opts: VaultClientOptions);
|
|
72
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one client: `new Agentcard({ clientId, clientSecret, voice?, sandbox? })`,
|
|
3
|
+
* with two modules. `checkout` pauses a browser purchase or creates one
|
|
4
|
+
* directly and replays the processor's answer; `voice` connects a person to
|
|
5
|
+
* your product once and approves a purchase by voice. One pair of
|
|
6
|
+
* credentials, one token, one base URL; sandbox and live are decided by the
|
|
7
|
+
* credentials.
|
|
8
|
+
*/
|
|
9
|
+
import { VaultClient } from './client.js';
|
|
10
|
+
import { attachToCdp, attachToPlaywright } from './cdp.js';
|
|
11
|
+
/**
|
|
12
|
+
* `agentcard.checkout`: a browser purchase paused and replayed, a purchase
|
|
13
|
+
* created directly, the recognisers that decide what pauses and the request
|
|
14
|
+
* builders that shape a direct create. Every verb here is the client's own
|
|
15
|
+
* under its module name; the client's top-level spellings stay for one release.
|
|
16
|
+
*/
|
|
17
|
+
export class CheckoutModule {
|
|
18
|
+
client;
|
|
19
|
+
constructor(client) {
|
|
20
|
+
this.client = client;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Arms a CDP session so the page's card request pauses for the cardholder's
|
|
24
|
+
* approval and resumes with the processor's answer. `syncRegistry()` first,
|
|
25
|
+
* so the recognisers are the API's current list.
|
|
26
|
+
*/
|
|
27
|
+
attachToCdp(cdp, pageSessionId, opts) {
|
|
28
|
+
return attachToCdp(cdp, pageSessionId, { ...opts, vault: this.client });
|
|
29
|
+
}
|
|
30
|
+
/** The same over a Playwright page. */
|
|
31
|
+
attachToPlaywright(page, opts) {
|
|
32
|
+
return attachToPlaywright(page, { ...opts, vault: this.client });
|
|
33
|
+
}
|
|
34
|
+
/** Refreshes the recognisers from the API so a new processor works without a redeploy; a failed read keeps the current list. */
|
|
35
|
+
syncRegistry() { return this.client.syncRegistry(); }
|
|
36
|
+
/** Creates the purchase for a paused request, waits for the approval and returns what to replay into the page. */
|
|
37
|
+
authorize(input) { return this.client.authorize(input); }
|
|
38
|
+
/** Approves a purchase before the page's own card request, for processors with a short request deadline. */
|
|
39
|
+
prepareCheckout(input) { return this.client.prepareCheckout(input); }
|
|
40
|
+
cancelPreparation(id) { return this.client.cancelPreparation(id); }
|
|
41
|
+
/** Withdraws an approval the page will not use, with the runtime's reason. */
|
|
42
|
+
cancelAuthorization(authorizationId, reason) { return this.client.cancelAuthorization(authorizationId, reason); }
|
|
43
|
+
/** Asks the API whether a Stripe confirmation that pays with an approved token may continue. */
|
|
44
|
+
checkStripeContinuation(authorizationId, request) { return this.client.checkStripeContinuation(authorizationId, request); }
|
|
45
|
+
/** True when this request is a card request a recogniser claims. */
|
|
46
|
+
isCardRequest(url, method = 'POST') { return this.client.isCardRequest(url, method); }
|
|
47
|
+
/** The recogniser that claims a card request, or null. */
|
|
48
|
+
recognizerOf(url, method = 'POST') { return this.client.recognizerOf(url, method); }
|
|
49
|
+
/** How the card would reach the processor on this request, or null when nothing recognises it. */
|
|
50
|
+
checkoutModeOf(url, method = 'POST') { return this.client.checkoutModeOf(url, method); }
|
|
51
|
+
/** What happens to a recognised request whose body carries no card. */
|
|
52
|
+
withoutCard(url, body) { return this.client.withoutCard(url, body); }
|
|
53
|
+
/** The URL patterns a raw CDP connection hands `Fetch.enable`, for the current recognisers. */
|
|
54
|
+
cardUrlPatterns() { return this.client.cardUrlPatterns(); }
|
|
55
|
+
/** The recognisers: the served or built-in list plus yours (`register`, `list`, `find`). */
|
|
56
|
+
get recognizers() { return this.client.recognizers; }
|
|
57
|
+
/** The request builders: the shipped ones plus yours (`register`, `build`, `list`). */
|
|
58
|
+
get requests() { return this.client.requests; }
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The Agentcard client. Construct one per server with your OAuth client
|
|
62
|
+
* credentials; every module shares its token.
|
|
63
|
+
*/
|
|
64
|
+
export class Agentcard extends VaultClient {
|
|
65
|
+
/** A browser purchase paused and replayed, a purchase created directly, the recognisers and the request builders. */
|
|
66
|
+
checkout;
|
|
67
|
+
constructor(opts) {
|
|
68
|
+
super(opts);
|
|
69
|
+
this.checkout = new CheckoutModule(this);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Languages } from './languages.js';
|
|
2
|
+
/** What the client lends its modules: the shared token, the shared HTTP path, and its settings. */
|
|
3
|
+
export interface ApiTransport {
|
|
4
|
+
/** The client-credentials bearer, cached and refreshed by the client; an `AgentcardError('oauth_failed')` when the exchange is refused. */
|
|
5
|
+
bearer(): Promise<string>;
|
|
6
|
+
/** One authenticated call on the client's path (one 401 refresh, the client's timeout, or `timeoutMs` for a call that waits on a turn); an `AgentcardError` carrying the API's code on refusal. */
|
|
7
|
+
request<T>(path: string, init?: {
|
|
8
|
+
method?: string;
|
|
9
|
+
body?: unknown;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
}): Promise<T>;
|
|
13
|
+
pollIntervalMs: number;
|
|
14
|
+
/** Deadline for the await calls. Default 60 s. */
|
|
15
|
+
pollTimeoutMs?: number;
|
|
16
|
+
/** Test seam: the retry pauses. */
|
|
17
|
+
backoffMs?: readonly number[];
|
|
18
|
+
/** The pause between two polls; an app may pass one that also wakes when it returns to the foreground. */
|
|
19
|
+
wait?: (ms: number) => Promise<void>;
|
|
20
|
+
/** The languages Agentcard speaks, shared by every module that names one. */
|
|
21
|
+
languages: Languages;
|
|
22
|
+
}
|
|
23
|
+
export declare abstract class ApiModule {
|
|
24
|
+
protected readonly t: ApiTransport;
|
|
25
|
+
protected readonly backoff: readonly number[];
|
|
26
|
+
protected readonly pollTimeoutMs: number;
|
|
27
|
+
protected readonly wait: (ms: number) => Promise<void>;
|
|
28
|
+
protected constructor(t: ApiTransport);
|
|
29
|
+
/** One call, tried again through a transient failure (no response, 502, 503, 504). */
|
|
30
|
+
protected request<T>(path: string, init?: {
|
|
31
|
+
method?: string;
|
|
32
|
+
body?: unknown;
|
|
33
|
+
headers?: Record<string, string>;
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
}): Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* One read that waits out `slow_down` instead of throwing it: the API names the interval it wants
|
|
38
|
+
* (`interval`, seconds), the client sleeps that long (never under its own `pollIntervalMs`, never over
|
|
39
|
+
* thirty seconds) and reads again, for at most two minutes in all, or up to `deadline` when the caller
|
|
40
|
+
* has one; past either bound the refusal is thrown as it came. Every single read the modules expose goes
|
|
41
|
+
* through here, so a customer never has to add sleeps around a read.
|
|
42
|
+
*/
|
|
43
|
+
protected read<T>(path: string, opts?: {
|
|
44
|
+
deadline?: number;
|
|
45
|
+
onInterval?: (intervalMs: number) => void;
|
|
46
|
+
}): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Polls `read` until it returns a value or the deadline; a poll that cannot
|
|
49
|
+
* reach Agentcard is not an answer. `spacingMs` names the least the request
|
|
50
|
+
* asks between two reads; the client never polls faster than the larger of
|
|
51
|
+
* it and its own `pollIntervalMs`.
|
|
52
|
+
*/
|
|
53
|
+
protected poll<T>(read: () => Promise<T | undefined>, timeoutMs: number | undefined, onDeadline: () => T, spacingMs?: () => number | undefined): Promise<T>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every module of the client shares: the client's token, the client's
|
|
3
|
+
* HTTP path, the retry through a transient failure, the read that waits out
|
|
4
|
+
* `slow_down`, and the poll that stops at a deadline. Constructed by the
|
|
5
|
+
* client, never on their own.
|
|
6
|
+
*/
|
|
7
|
+
import { isTransient, RETRY_BACKOFF_MS, sleep, withRetries } from './voice-retry.js';
|
|
8
|
+
import { AgentcardError } from './voice-types.js';
|
|
9
|
+
/** The longest one `slow_down` wait, whatever interval the API names. */
|
|
10
|
+
const SLOW_DOWN_WAIT_CAP_MS = 30_000;
|
|
11
|
+
/** The longest a single read waits out `slow_down` in all before the refusal is thrown to the caller. */
|
|
12
|
+
const SLOW_DOWN_TOTAL_CAP_MS = 120_000;
|
|
13
|
+
export class ApiModule {
|
|
14
|
+
t;
|
|
15
|
+
backoff;
|
|
16
|
+
pollTimeoutMs;
|
|
17
|
+
wait;
|
|
18
|
+
constructor(t) {
|
|
19
|
+
this.t = t;
|
|
20
|
+
this.backoff = t.backoffMs ?? RETRY_BACKOFF_MS;
|
|
21
|
+
this.pollTimeoutMs = t.pollTimeoutMs ?? 60_000;
|
|
22
|
+
this.wait = t.wait ?? sleep;
|
|
23
|
+
}
|
|
24
|
+
/** One call, tried again through a transient failure (no response, 502, 503, 504). */
|
|
25
|
+
request(path, init = {}) {
|
|
26
|
+
return withRetries(() => this.t.request(path, init), this.backoff);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* One read that waits out `slow_down` instead of throwing it: the API names the interval it wants
|
|
30
|
+
* (`interval`, seconds), the client sleeps that long (never under its own `pollIntervalMs`, never over
|
|
31
|
+
* thirty seconds) and reads again, for at most two minutes in all, or up to `deadline` when the caller
|
|
32
|
+
* has one; past either bound the refusal is thrown as it came. Every single read the modules expose goes
|
|
33
|
+
* through here, so a customer never has to add sleeps around a read.
|
|
34
|
+
*/
|
|
35
|
+
async read(path, opts = {}) {
|
|
36
|
+
const started = Date.now();
|
|
37
|
+
for (;;) {
|
|
38
|
+
try {
|
|
39
|
+
return await this.request(path);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (!(err instanceof AgentcardError && err.code === 'slow_down'))
|
|
43
|
+
throw err;
|
|
44
|
+
const named = typeof err.details.interval === 'number' && err.details.interval > 0 ? err.details.interval * 1000 : this.t.pollIntervalMs;
|
|
45
|
+
opts.onInterval?.(named);
|
|
46
|
+
const waitMs = Math.min(Math.max(named, this.t.pollIntervalMs), SLOW_DOWN_WAIT_CAP_MS);
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
if (now - started + waitMs > SLOW_DOWN_TOTAL_CAP_MS)
|
|
49
|
+
throw err;
|
|
50
|
+
if (opts.deadline !== undefined && now + waitMs > opts.deadline)
|
|
51
|
+
throw err;
|
|
52
|
+
await this.wait(waitMs);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Polls `read` until it returns a value or the deadline; a poll that cannot
|
|
58
|
+
* reach Agentcard is not an answer. `spacingMs` names the least the request
|
|
59
|
+
* asks between two reads; the client never polls faster than the larger of
|
|
60
|
+
* it and its own `pollIntervalMs`.
|
|
61
|
+
*/
|
|
62
|
+
async poll(read, timeoutMs, onDeadline, spacingMs = () => undefined) {
|
|
63
|
+
const deadline = Date.now() + (timeoutMs ?? this.pollTimeoutMs);
|
|
64
|
+
let lastFailure = null;
|
|
65
|
+
for (;;) {
|
|
66
|
+
try {
|
|
67
|
+
const v = await read();
|
|
68
|
+
lastFailure = null;
|
|
69
|
+
if (v !== undefined)
|
|
70
|
+
return v;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (!isTransient(err))
|
|
74
|
+
throw err;
|
|
75
|
+
lastFailure = err;
|
|
76
|
+
}
|
|
77
|
+
if (Date.now() >= deadline) {
|
|
78
|
+
if (lastFailure)
|
|
79
|
+
throw new AgentcardError('unreachable', 'Agentcard did not answer.');
|
|
80
|
+
return onDeadline();
|
|
81
|
+
}
|
|
82
|
+
await this.wait(Math.max(this.t.pollIntervalMs, spacingMs() ?? 0));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** An attachment failure has no payment or browser credentials in its message. */
|
|
2
|
+
export declare class CheckoutAttachmentError extends Error {
|
|
3
|
+
readonly reason: 'timeout' | 'closed' | 'unavailable';
|
|
4
|
+
readonly code = "checkout_attachment_failed";
|
|
5
|
+
constructor(reason: 'timeout' | 'closed' | 'unavailable');
|
|
6
|
+
}
|
|
7
|
+
/** Preserve a known setup cause without exposing raw transport errors. */
|
|
8
|
+
export declare function attachmentFailure(error: unknown): CheckoutAttachmentError;
|
|
9
|
+
export declare function attachmentDeadline(value: number | undefined): number;
|
|
10
|
+
/** Stop waiting, then reject every late continuation before its next command. */
|
|
11
|
+
export declare function withinAttachmentDeadline(arm: (assertActive: () => void) => Promise<void>, timeoutMs: number, signal: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** An attachment failure has no payment or browser credentials in its message. */
|
|
2
|
+
export class CheckoutAttachmentError extends Error {
|
|
3
|
+
reason;
|
|
4
|
+
code = 'checkout_attachment_failed';
|
|
5
|
+
constructor(reason) {
|
|
6
|
+
super('Could not attach checkout interception. Close this checkout page and retry in a fresh browser context.');
|
|
7
|
+
this.reason = reason;
|
|
8
|
+
this.name = 'CheckoutAttachmentError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** Preserve a known setup cause without exposing raw transport errors. */
|
|
12
|
+
export function attachmentFailure(error) {
|
|
13
|
+
return error instanceof CheckoutAttachmentError ? error : new CheckoutAttachmentError('unavailable');
|
|
14
|
+
}
|
|
15
|
+
export function attachmentDeadline(value) {
|
|
16
|
+
if (value === undefined)
|
|
17
|
+
return 30_000;
|
|
18
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > 300_000) {
|
|
19
|
+
throw new Error('attachmentTimeoutMs must be an integer between 1 and 300000.');
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
/** Stop waiting, then reject every late continuation before its next command. */
|
|
24
|
+
export async function withinAttachmentDeadline(arm, timeoutMs, signal) {
|
|
25
|
+
let stopped;
|
|
26
|
+
let timer;
|
|
27
|
+
let onAbort = () => { };
|
|
28
|
+
const assertActive = () => { if (stopped)
|
|
29
|
+
throw stopped; };
|
|
30
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
31
|
+
const stop = (failure) => { stopped ??= failure; reject(stopped); };
|
|
32
|
+
onAbort = () => stop(attachmentFailure(signal.reason));
|
|
33
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
34
|
+
if (signal.aborted)
|
|
35
|
+
onAbort();
|
|
36
|
+
else
|
|
37
|
+
timer = setTimeout(() => stop(new CheckoutAttachmentError('timeout')), timeoutMs);
|
|
38
|
+
});
|
|
39
|
+
try {
|
|
40
|
+
await Promise.race([deadline, Promise.resolve().then(() => { assertActive(); return arm(assertActive); })]);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
stopped ??= attachmentFailure(error);
|
|
44
|
+
throw stopped;
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
signal.removeEventListener('abort', onAbort);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Classify native Braintree GraphQL requests without changing their bytes. */
|
|
2
|
+
export type BraintreeEnvironment = 'production' | 'sandbox';
|
|
3
|
+
export type BraintreeRequestKind = 'configuration' | 'tokenization' | 'invalid';
|
|
4
|
+
export declare function braintreeEnvironment(url: string): BraintreeEnvironment | undefined;
|
|
5
|
+
/** Bounded duplicate-aware JSON for fresh-card preparation bodies. */
|
|
6
|
+
export declare function readTokenizationJson(body: string | null): Record<string, unknown> | null;
|
|
7
|
+
/** Undefined belongs to another processor; invalid Braintree requests stay blocked. */
|
|
8
|
+
export declare function classifyBraintreeRequest(url: string, method: string, body: string | null): BraintreeRequestKind | undefined;
|
|
9
|
+
/** Preparation accepts guest tokenization only; ordinary interception stays broader. */
|
|
10
|
+
export declare function isPreparedBraintreeRequest(body: string | null): boolean;
|