@agent-cards/sdk 0.23.27 → 0.24.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 +66 -0
- package/PREFLIGHT.md +7 -1
- package/README.md +137 -99
- package/dist/aci.d.ts +52 -22
- package/dist/aci.js +79 -15
- package/dist/adyen-merchant-hosted.generated.d.ts +1 -1
- package/dist/adyen-merchant-hosted.generated.js +459 -21
- package/dist/agent-amount.generated.js +1 -1
- package/dist/agentcard.d.ts +4 -5
- package/dist/agentcard.js +4 -5
- package/dist/amadeus.d.ts +9 -3
- package/dist/amadeus.js +33 -4
- package/dist/api-module.d.ts +1 -7
- package/dist/api-module.js +3 -5
- package/dist/{voice-retry.js → api-retry.js} +1 -1
- package/dist/api-types.d.ts +53 -0
- package/dist/{voice-types.js → api-types.js} +7 -2
- package/dist/approvals.d.ts +166 -0
- package/dist/approvals.js +213 -0
- package/dist/buy.d.ts +20 -101
- package/dist/buy.js +19 -74
- package/dist/capabilities.generated.js +1 -1
- package/dist/card-fields.generated.js +1398 -106
- package/dist/cdp.d.ts +3 -13
- package/dist/cdp.js +231 -175
- package/dist/client.d.ts +104 -22
- package/dist/client.js +215 -40
- package/dist/creditguard.generated.d.ts +4 -4
- package/dist/datatrans.generated.js +22 -1
- package/dist/elavon-converge.generated.js +5 -3
- package/dist/hold.d.ts +8 -0
- package/dist/hold.js +43 -2
- package/dist/hosted-entry.js +2 -0
- package/dist/index.d.ts +9 -15
- package/dist/index.js +6 -10
- package/dist/juspay-gate.js +2 -2
- package/dist/juspay.generated.d.ts +2 -2
- package/dist/juspay.generated.js +174 -4
- package/dist/lifecycle.d.ts +9 -61
- package/dist/lifecycle.js +24 -10
- package/dist/merchant-card-endpoints.generated.d.ts +6 -1
- package/dist/merchant-card-endpoints.generated.js +344 -19
- package/dist/merchant-hosted.d.ts +8 -1
- package/dist/nike-order.d.ts +51 -0
- package/dist/nike-order.js +81 -0
- package/dist/oceanpayment.d.ts +38 -0
- package/dist/oceanpayment.js +70 -0
- package/dist/paddle-tokenex.generated.js +105 -10
- package/dist/payplug.d.ts +41 -0
- package/dist/payplug.js +71 -0
- package/dist/payrails.generated.js +27 -7
- package/dist/payrexx.generated.js +176 -2
- package/dist/pre-claim.d.ts +9 -0
- package/dist/pre-claim.js +86 -7
- package/dist/preflight-capabilities.generated.d.ts +0 -4
- package/dist/preflight-capabilities.generated.js +0 -5
- package/dist/preflight-catalog.json +46 -5
- package/dist/preflight-playwright.d.ts +2 -2
- package/dist/preflight-playwright.js +63 -14
- package/dist/preflight-schemas.json +4 -0
- package/dist/preflight.generated.d.ts +0 -4
- package/dist/preflight.generated.js +24 -1
- package/dist/prepared-processor.d.ts +18 -5
- package/dist/prepared-processor.js +48 -8
- package/dist/primer.generated.js +1418 -120
- package/dist/processout.generated.d.ts +61 -0
- package/dist/processout.generated.js +577 -0
- package/dist/requests.js +1 -1
- package/dist/shiji-sts.generated.js +105 -10
- package/dist/stripe-checkout.generated.js +1405 -234
- package/dist/substitutions.generated.js +460 -26
- package/dist/tap-payments.generated.js +105 -10
- package/dist/twoc2p.generated.js +105 -10
- package/dist/url-masks/bluesnap.d.ts +2 -0
- package/dist/url-masks/bluesnap.js +15 -0
- package/dist/url-masks/oceanpayment.d.ts +2 -0
- package/dist/url-masks/oceanpayment.js +13 -0
- package/dist/url-masks/payplug.d.ts +2 -0
- package/dist/url-masks/payplug.js +13 -0
- package/dist/url-masks/vtex_payments.d.ts +2 -0
- package/dist/url-masks/vtex_payments.js +12 -0
- package/dist/url-masks.generated.d.ts +1 -1
- package/dist/url-masks.generated.js +6 -2
- package/dist/verifone.generated.js +42 -2
- package/dist/vtex-payments.generated.d.ts +127 -0
- package/dist/vtex-payments.generated.js +544 -0
- package/dist/webhooks.d.ts +2 -2
- package/dist/webhooks.js +2 -2
- package/dist/wspay.generated.d.ts +1 -1
- package/dist/wspay.generated.js +175 -3
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +6 -8
- package/dist/languages.d.ts +0 -23
- package/dist/languages.js +0 -76
- package/dist/voice-pkce.d.ts +0 -49
- package/dist/voice-pkce.js +0 -147
- package/dist/voice-stream.generated.d.ts +0 -118
- package/dist/voice-stream.generated.js +0 -170
- package/dist/voice-types.d.ts +0 -735
- package/dist/voice.d.ts +0 -373
- package/dist/voice.js +0 -815
- package/readme.test.mjs +0 -53
- /package/dist/{voice-retry.d.ts → api-retry.d.ts} +0 -0
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The streaming door, from either side: the person's
|
|
3
|
-
* answer goes to the verifier as it is spoken, over one WebSocket, and the
|
|
4
|
-
* verifier says `hearing` the moment the key word is out and `verdict` when it
|
|
5
|
-
* has closed the span. One call serves a live microphone and a finished
|
|
6
|
-
* recording alike: a buffer pushed in one go and ended is a short stream.
|
|
7
|
-
*
|
|
8
|
-
* Uses the platform's WebSocket (Node 22, React Native, the browser). The
|
|
9
|
-
* credential rides in the first frame, never in a header, so every platform
|
|
10
|
-
* behaves the same.
|
|
11
|
-
*/
|
|
12
|
-
import { AgentcardError, type Lang, type Verdict } from './voice-types.js';
|
|
13
|
-
/**
|
|
14
|
-
* How the bytes are encoded. `pcm16-16k`: little-endian 16-bit mono at
|
|
15
|
-
* 16 kHz, judged live. `mulaw-8k`: a telephone band, judged on the words
|
|
16
|
-
* only. `opus`: an Ogg or WebM Opus stream, judged live. A media type such
|
|
17
|
-
* as `audio/m4a` or `audio/wav`: a finished recording pushed in one go, held
|
|
18
|
-
* until `end`, then judged exactly as an upload of the same bytes.
|
|
19
|
-
*/
|
|
20
|
-
export type StreamFormat = 'pcm16-16k' | 'mulaw-8k' | 'opus' | `audio/${string}`;
|
|
21
|
-
/**
|
|
22
|
-
* How the take is heard: the phone's own microphone, a wired headset, a Bluetooth one, or
|
|
23
|
-
* unknown when the platform says nothing. The verifier scores the voice on that route's cohort when it has one
|
|
24
|
-
* and records a change from the enrolment's route as evidence; only the kind travels, never a device name.
|
|
25
|
-
*/
|
|
26
|
-
export type ChannelKind = 'builtin' | 'wired' | 'bluetooth' | 'unknown';
|
|
27
|
-
export declare const CHANNEL_KINDS: readonly ChannelKind[];
|
|
28
|
-
/** The key word (and, on a phone's stream, the price) is out: a bot may start its next line now. `heard_count` is how many words landed; the words themselves never cross (WP115). */
|
|
29
|
-
export interface Hearing {
|
|
30
|
-
heard_count: number;
|
|
31
|
-
}
|
|
32
|
-
/** The verifier's verdict on the stream: the upload verdict plus what the live door knows. */
|
|
33
|
-
/**
|
|
34
|
-
* The verifier's signed word on a take (WP182): `payload` is the exact JSON string the signature covers (the challenge
|
|
35
|
-
* id, `accepted`, the template hash it matched, `z`, `cos`, `spoof_bits`, `issued_at`), `signature` the raw ECDSA
|
|
36
|
-
* `r || s` bytes base64url under the receipt key the Vault's page stamped on the listen, `kid` that key's name. The
|
|
37
|
-
* host relays it to the frame as `host-judged` once the proof landed; the frame checks the signature itself.
|
|
38
|
-
*/
|
|
39
|
-
export interface VerdictReceipt {
|
|
40
|
-
payload: string;
|
|
41
|
-
signature: string;
|
|
42
|
-
alg: string;
|
|
43
|
-
kid: string;
|
|
44
|
-
}
|
|
45
|
-
export interface StreamVerdict extends Verdict {
|
|
46
|
-
reason: string;
|
|
47
|
-
bits: {
|
|
48
|
-
words: number;
|
|
49
|
-
voice: number;
|
|
50
|
-
total: number;
|
|
51
|
-
};
|
|
52
|
-
cos: number | null;
|
|
53
|
-
z: number | null;
|
|
54
|
-
speech_s: number;
|
|
55
|
-
/** From the first byte to the verdict. */
|
|
56
|
-
ms: number;
|
|
57
|
-
/** From the caller's last byte (or `end`) to the verdict: what streaming saves. */
|
|
58
|
-
since_end_ms: number;
|
|
59
|
-
/** Why the verifier closed the span: `all_words`, `quiet`, `ended`, `stalled`, `span_cap`, `wall_cap`. */
|
|
60
|
-
closed_by: string;
|
|
61
|
-
/** The verifier's signed receipt for this take (WP182), when it sent one. */
|
|
62
|
-
receipt?: VerdictReceipt;
|
|
63
|
-
}
|
|
64
|
-
export interface StreamInput {
|
|
65
|
-
/** The verifier's streaming address, `wss://…/v1/stream` (`ws://` against a local verifier). */
|
|
66
|
-
stream_url: string;
|
|
67
|
-
authorization_id: string;
|
|
68
|
-
challenge_id: string;
|
|
69
|
-
lang: Lang;
|
|
70
|
-
format: StreamFormat;
|
|
71
|
-
/** The credential for this answer: the challenge's `answer_token` on the app side, the business's bearer on the server side. */
|
|
72
|
-
token: string;
|
|
73
|
-
/** The channel the take is heard on, when known at the open; `handle.channel(kind)` names it later. */
|
|
74
|
-
channel?: ChannelKind;
|
|
75
|
-
/** Test seam: the WebSocket constructor; defaults to the platform's. */
|
|
76
|
-
WebSocket?: typeof WebSocket;
|
|
77
|
-
}
|
|
78
|
-
type Listener<T> = (value: T) => void;
|
|
79
|
-
export interface StreamHandle {
|
|
80
|
-
readonly format: StreamFormat;
|
|
81
|
-
/** Queue bytes; they go the moment the socket is open. */
|
|
82
|
-
push(chunk: ArrayBuffer | Uint8Array): void;
|
|
83
|
-
/** Nothing more is coming: the verifier closes the span with what it has. */
|
|
84
|
-
end(): void;
|
|
85
|
-
/** How the take is heard, once the microphone is open and the device is known; before the verdict. */
|
|
86
|
-
channel(kind: ChannelKind): void;
|
|
87
|
-
/**
|
|
88
|
-
* The audio that follows is sealed (WP115): the take's ephemeral key goes ahead of it as the stream's `sealed`
|
|
89
|
-
* frame, once, before the first push. The verifier opens every later chunk with it; this side reads none of them.
|
|
90
|
-
* `kid` names the verifier key the take was sealed to (the first sixteen hex characters of SHA-256 over its SPKI,
|
|
91
|
-
* as the frame computes it): after a key rotation the verifier opens the take under the key it names, inside the
|
|
92
|
-
* previous key's grace window, instead of assuming the current one and refusing. A head with no `kid` means the
|
|
93
|
-
* current key, as a frame from before the field sealed to it.
|
|
94
|
-
*/
|
|
95
|
-
seal(head: {
|
|
96
|
-
alg: string;
|
|
97
|
-
eph: string;
|
|
98
|
-
kid?: string;
|
|
99
|
-
}): void;
|
|
100
|
-
on(event: 'hearing', fn: Listener<Hearing>): StreamHandle;
|
|
101
|
-
on(event: 'verdict', fn: Listener<StreamVerdict>): StreamHandle;
|
|
102
|
-
on(event: 'error', fn: Listener<AgentcardError>): StreamHandle;
|
|
103
|
-
/** Resolves with the verdict; rejects with an `AgentcardError` carrying the verifier's code. */
|
|
104
|
-
verdict(): Promise<StreamVerdict>;
|
|
105
|
-
/** Drops the socket without a verdict (the person walked away). */
|
|
106
|
-
close(): void;
|
|
107
|
-
}
|
|
108
|
-
/** Opens the door and hands back the handle. The first frame names the challenge; bytes follow. */
|
|
109
|
-
export declare function openStream(input: StreamInput): StreamHandle;
|
|
110
|
-
/**
|
|
111
|
-
* A finished recording through the same door. For `pcm16-16k` with `chunkMs`
|
|
112
|
-
* the bytes go in timed chunks, as a microphone would send them; otherwise
|
|
113
|
-
* the whole buffer goes at once. Ends the stream and resolves the verdict.
|
|
114
|
-
*/
|
|
115
|
-
export declare function streamBuffer(handle: StreamHandle, bytes: ArrayBuffer | Uint8Array, chunkMs?: number): Promise<StreamVerdict>;
|
|
116
|
-
/** The verifier's streaming address from its origin (`/v1/stream` appended) or a full address, as given. */
|
|
117
|
-
export declare function streamAddress(verifierUrl: string): string;
|
|
118
|
-
export {};
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
// GENERATED by scripts/generate-voice-stream.mjs from packages/voice/src/core/stream.ts. Do not edit; edit the source and rebuild.
|
|
2
|
-
/**
|
|
3
|
-
* The streaming door, from either side: the person's
|
|
4
|
-
* answer goes to the verifier as it is spoken, over one WebSocket, and the
|
|
5
|
-
* verifier says `hearing` the moment the key word is out and `verdict` when it
|
|
6
|
-
* has closed the span. One call serves a live microphone and a finished
|
|
7
|
-
* recording alike: a buffer pushed in one go and ended is a short stream.
|
|
8
|
-
*
|
|
9
|
-
* Uses the platform's WebSocket (Node 22, React Native, the browser). The
|
|
10
|
-
* credential rides in the first frame, never in a header, so every platform
|
|
11
|
-
* behaves the same.
|
|
12
|
-
*/
|
|
13
|
-
import { AgentcardError } from './voice-types.js';
|
|
14
|
-
export const CHANNEL_KINDS = ['builtin', 'wired', 'bluetooth', 'unknown'];
|
|
15
|
-
/** The receipt as the verifier sends it, every field a string, or null for anything else: a receipt with a piece missing cannot be checked, so it is not carried. */
|
|
16
|
-
function receiptOf(r) {
|
|
17
|
-
if (!r || typeof r !== 'object')
|
|
18
|
-
return null;
|
|
19
|
-
const { payload, signature, alg, kid } = r;
|
|
20
|
-
return typeof payload === 'string' && typeof signature === 'string' && typeof alg === 'string' && typeof kid === 'string' ? { payload, signature, alg, kid } : null;
|
|
21
|
-
}
|
|
22
|
-
function toArrayBuffer(chunk) {
|
|
23
|
-
if (chunk instanceof ArrayBuffer)
|
|
24
|
-
return chunk;
|
|
25
|
-
return chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
|
|
26
|
-
}
|
|
27
|
-
/** Opens the door and hands back the handle. The first frame names the challenge; bytes follow. */
|
|
28
|
-
export function openStream(input) {
|
|
29
|
-
const WS = input.WebSocket ?? globalThis.WebSocket;
|
|
30
|
-
if (!WS)
|
|
31
|
-
throw new AgentcardError('no_websocket', 'This runtime has no WebSocket; the streaming door needs one.');
|
|
32
|
-
const ws = new WS(input.stream_url);
|
|
33
|
-
ws.binaryType = 'arraybuffer';
|
|
34
|
-
const listeners = { hearing: [], verdict: [], error: [] };
|
|
35
|
-
// Audio chunks and, ahead of the first sealed chunk, the `sealed` text frame: sent in the order they were queued.
|
|
36
|
-
const queue = [];
|
|
37
|
-
let open = false, ended = false, settled = false, sealed = false;
|
|
38
|
-
let channelKind = input.channel && CHANNEL_KINDS.includes(input.channel) ? input.channel : undefined;
|
|
39
|
-
// A channel named after the open goes as its own text frame; one named before rides the first frame.
|
|
40
|
-
let channelPending = null;
|
|
41
|
-
let resolveVerdict, rejectVerdict;
|
|
42
|
-
const verdictPromise = new Promise((resolve, reject) => { resolveVerdict = resolve; rejectVerdict = reject; });
|
|
43
|
-
verdictPromise.catch(() => { });
|
|
44
|
-
const fail = (err) => {
|
|
45
|
-
if (settled)
|
|
46
|
-
return;
|
|
47
|
-
settled = true;
|
|
48
|
-
for (const fn of listeners.error)
|
|
49
|
-
fn(err);
|
|
50
|
-
rejectVerdict(err);
|
|
51
|
-
};
|
|
52
|
-
const flush = () => { while (open && queue.length)
|
|
53
|
-
ws.send(queue.shift()); };
|
|
54
|
-
ws.onopen = () => {
|
|
55
|
-
// No token on the frame when the caller has none: a fronting door may carry the credential on the upgrade instead.
|
|
56
|
-
ws.send(JSON.stringify({ authorization_id: input.authorization_id, challenge_id: input.challenge_id, lang: input.lang, format: input.format, ...(input.token ? { token: input.token } : {}), ...(channelKind ? { channel: channelKind } : {}) }));
|
|
57
|
-
open = true;
|
|
58
|
-
if (channelPending) {
|
|
59
|
-
ws.send(JSON.stringify({ type: 'channel', kind: channelPending }));
|
|
60
|
-
channelPending = null;
|
|
61
|
-
}
|
|
62
|
-
flush();
|
|
63
|
-
if (ended)
|
|
64
|
-
ws.send(JSON.stringify({ type: 'end' }));
|
|
65
|
-
};
|
|
66
|
-
ws.onmessage = (ev) => {
|
|
67
|
-
if (typeof ev.data !== 'string')
|
|
68
|
-
return;
|
|
69
|
-
let msg;
|
|
70
|
-
try {
|
|
71
|
-
msg = JSON.parse(ev.data);
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (msg.type === 'hearing') {
|
|
77
|
-
const h = { heard_count: Math.max(0, Math.floor(Number(msg.heard_count)) || 0) };
|
|
78
|
-
for (const fn of listeners.hearing)
|
|
79
|
-
fn(h);
|
|
80
|
-
}
|
|
81
|
-
else if (msg.type === 'verdict') {
|
|
82
|
-
if (settled)
|
|
83
|
-
return;
|
|
84
|
-
settled = true;
|
|
85
|
-
const receipt = receiptOf(msg.receipt);
|
|
86
|
-
const v = {
|
|
87
|
-
accepted: msg.accepted === true, spent: true,
|
|
88
|
-
approved_with: typeof msg.approved_with === 'string' ? msg.approved_with : null,
|
|
89
|
-
reason: String(msg.reason ?? ''), bits: msg.bits ?? { words: 0, voice: 0, total: 0 },
|
|
90
|
-
cos: typeof msg.cos === 'number' ? msg.cos : null, z: typeof msg.z === 'number' ? msg.z : null,
|
|
91
|
-
speech_s: Number(msg.speech_s ?? 0), ms: Number(msg.ms ?? 0), since_end_ms: Number(msg.since_end_ms ?? 0), closed_by: String(msg.closed_by ?? ''),
|
|
92
|
-
...(receipt ? { receipt } : {}),
|
|
93
|
-
};
|
|
94
|
-
for (const fn of listeners.verdict)
|
|
95
|
-
fn(v);
|
|
96
|
-
resolveVerdict(v);
|
|
97
|
-
}
|
|
98
|
-
else if (msg.type === 'error') {
|
|
99
|
-
fail(new AgentcardError(String(msg.code ?? 'verifier_error'), String(msg.message ?? 'The verifier refused the stream.')));
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
ws.onerror = () => fail(new AgentcardError('unreachable', 'The verifier could not be reached.'));
|
|
103
|
-
ws.onclose = () => fail(new AgentcardError('stream_closed', 'The verifier closed the stream without a verdict.'));
|
|
104
|
-
const handle = {
|
|
105
|
-
format: input.format,
|
|
106
|
-
push(chunk) { if (ended || settled)
|
|
107
|
-
return; queue.push(toArrayBuffer(chunk)); flush(); },
|
|
108
|
-
end() { if (ended)
|
|
109
|
-
return; ended = true; if (open) {
|
|
110
|
-
flush();
|
|
111
|
-
ws.send(JSON.stringify({ type: 'end' }));
|
|
112
|
-
} },
|
|
113
|
-
channel(kind) {
|
|
114
|
-
if (settled || ended || !CHANNEL_KINDS.includes(kind))
|
|
115
|
-
return;
|
|
116
|
-
if (!open) {
|
|
117
|
-
channelKind = kind;
|
|
118
|
-
channelPending = null;
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
channelKind = kind;
|
|
122
|
-
ws.send(JSON.stringify({ type: 'channel', kind }));
|
|
123
|
-
},
|
|
124
|
-
seal(head) {
|
|
125
|
-
if (settled || ended || sealed || typeof head.eph !== 'string' || !head.eph)
|
|
126
|
-
return;
|
|
127
|
-
sealed = true;
|
|
128
|
-
const kid = typeof head.kid === 'string' && head.kid ? head.kid : null;
|
|
129
|
-
queue.push(JSON.stringify({ type: 'sealed', alg: head.alg, eph: head.eph, ...(kid ? { kid } : {}) }));
|
|
130
|
-
flush();
|
|
131
|
-
},
|
|
132
|
-
on(event, fn) { listeners[event].push(fn); return handle; },
|
|
133
|
-
verdict: () => verdictPromise,
|
|
134
|
-
close() { if (!settled) {
|
|
135
|
-
settled = true;
|
|
136
|
-
rejectVerdict(new AgentcardError('stream_closed', 'The stream was closed before a verdict.'));
|
|
137
|
-
} try {
|
|
138
|
-
ws.close();
|
|
139
|
-
}
|
|
140
|
-
catch { /* already closed */ } },
|
|
141
|
-
};
|
|
142
|
-
return handle;
|
|
143
|
-
}
|
|
144
|
-
const PCM16_16K_BYTES_PER_MS = 32;
|
|
145
|
-
/**
|
|
146
|
-
* A finished recording through the same door. For `pcm16-16k` with `chunkMs`
|
|
147
|
-
* the bytes go in timed chunks, as a microphone would send them; otherwise
|
|
148
|
-
* the whole buffer goes at once. Ends the stream and resolves the verdict.
|
|
149
|
-
*/
|
|
150
|
-
export async function streamBuffer(handle, bytes, chunkMs) {
|
|
151
|
-
const u8 = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
152
|
-
if (chunkMs && chunkMs > 0 && handle.format === 'pcm16-16k') {
|
|
153
|
-
const step = PCM16_16K_BYTES_PER_MS * chunkMs;
|
|
154
|
-
for (let i = 0; i < u8.length; i += step) {
|
|
155
|
-
handle.push(u8.subarray(i, Math.min(u8.length, i + step)));
|
|
156
|
-
if (i + step < u8.length)
|
|
157
|
-
await new Promise((r) => setTimeout(r, chunkMs));
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
handle.push(u8);
|
|
162
|
-
}
|
|
163
|
-
handle.end();
|
|
164
|
-
return handle.verdict();
|
|
165
|
-
}
|
|
166
|
-
/** The verifier's streaming address from its origin (`/v1/stream` appended) or a full address, as given. */
|
|
167
|
-
export function streamAddress(verifierUrl) {
|
|
168
|
-
const trimmed = verifierUrl.replace(/\/+$/, '');
|
|
169
|
-
return /\/v1\/stream$/.test(trimmed) ? trimmed : trimmed + '/v1/stream';
|
|
170
|
-
}
|