@antseed/web-sdk 0.1.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/LICENSE +674 -0
- package/README.md +171 -0
- package/dist/channel-store.d.ts +50 -0
- package/dist/channel-store.d.ts.map +1 -0
- package/dist/channel-store.js +288 -0
- package/dist/channel-store.js.map +1 -0
- package/dist/client.d.ts +174 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +470 -0
- package/dist/client.js.map +1 -0
- package/dist/connection.d.ts +66 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +280 -0
- package/dist/connection.js.map +1 -0
- package/dist/identity.d.ts +11 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +23 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/signaling.d.ts +46 -0
- package/dist/signaling.d.ts.map +1 -0
- package/dist/signaling.js +87 -0
- package/dist/signaling.js.map +1 -0
- package/dist/tab-lock.d.ts +27 -0
- package/dist/tab-lock.d.ts.map +1 -0
- package/dist/tab-lock.js +54 -0
- package/dist/tab-lock.js.map +1 -0
- package/package.json +38 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level browser buyer built on the SHARED buyer machinery from
|
|
3
|
+
* @antseed/buyer-core — the same BuyerRequestHandler / BuyerPaymentManager /
|
|
4
|
+
* BuyerPaymentNegotiator stack the node buyer proxy runs, wired to a WebRTC
|
|
5
|
+
* transport and an injectable channel store. `create()` supplies durable
|
|
6
|
+
* browser persistence; the public constructor retains the in-memory default.
|
|
7
|
+
*/
|
|
8
|
+
import { type AbstractSigner } from 'ethers';
|
|
9
|
+
import { BuyerPaymentNegotiator, ChannelsClient, DepositsClient, ProxyMux, VerificationMux, type BuyerChannelStore, type BuyerPeerView, type BuyerSigner, type StoredChannel } from '@antseed/buyer-core';
|
|
10
|
+
import { type CloseChannelResultPayload, type PeerMetadata, type PeerId, type SerializedHttpRequest, type SerializedHttpResponse } from '@antseed/protocol';
|
|
11
|
+
import { SellerConnection, type ConnectionOptions, type RtcEnvironment } from './connection.js';
|
|
12
|
+
import { BuyerTabLock, type WebLockManagerLike } from './tab-lock.js';
|
|
13
|
+
export interface SellerSummary extends BuyerPeerView {
|
|
14
|
+
displayName?: string;
|
|
15
|
+
publicAddress?: string;
|
|
16
|
+
sellerContract?: string;
|
|
17
|
+
metadata?: PeerMetadata;
|
|
18
|
+
}
|
|
19
|
+
export interface WebPaymentConfig {
|
|
20
|
+
chainId: number;
|
|
21
|
+
channelsContractAddress: string;
|
|
22
|
+
depositsContractAddress: string;
|
|
23
|
+
usdcAddress: string;
|
|
24
|
+
/** RPC endpoint for read-only deposit/channel checks. Empty disables them. */
|
|
25
|
+
rpcUrl: string;
|
|
26
|
+
fallbackRpcUrls?: string[];
|
|
27
|
+
maxPerRequestUsdc?: bigint;
|
|
28
|
+
maxReserveAmountUsdc?: bigint;
|
|
29
|
+
defaultAuthDurationSecs?: number;
|
|
30
|
+
costToleranceMultiplier?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface ClientOptions {
|
|
33
|
+
/** Relay base URL, e.g. https://relay.antseed.com */
|
|
34
|
+
relayUrl: string;
|
|
35
|
+
/**
|
|
36
|
+
* Buyer hot-wallet key, or any ethers AbstractSigner exposing its address
|
|
37
|
+
* as a property (Wallet, JsonRpcSigner, custom non-extractable-key
|
|
38
|
+
* signers). The peerId is its EVM address. Note: the payment stack signs a
|
|
39
|
+
* SpendingAuth per request, so signers that prompt per signature (browser
|
|
40
|
+
* extension wallets) are impractical for chat-style traffic.
|
|
41
|
+
*/
|
|
42
|
+
privateKey?: string;
|
|
43
|
+
wallet?: BuyerSigner;
|
|
44
|
+
/** Defaults to Base mainnet (chain 8453). */
|
|
45
|
+
payment?: Partial<WebPaymentConfig>;
|
|
46
|
+
connection?: ConnectionOptions;
|
|
47
|
+
requestTimeoutMs?: number;
|
|
48
|
+
/** Receives asynchronous transport/protocol failures before the session closes. */
|
|
49
|
+
onError?: (error: Error, context: {
|
|
50
|
+
sellerPeerId: PeerId;
|
|
51
|
+
}) => void;
|
|
52
|
+
/** Receives background IndexedDB failures as soon as a queued write fails. */
|
|
53
|
+
onPersistenceError?: (error: Error) => void;
|
|
54
|
+
/** Reject non-empty DHT snapshots older than this. Defaults to 15 minutes. */
|
|
55
|
+
maxSellerSnapshotAgeMs?: number;
|
|
56
|
+
/** Override RTCPeerConnection/WebSocket (browser globals by default). */
|
|
57
|
+
env?: Partial<RtcEnvironment>;
|
|
58
|
+
/**
|
|
59
|
+
* Pre-opened custom channel store. The public constructor defaults to a
|
|
60
|
+
* fresh in-memory store; production browser apps should prefer `create()`
|
|
61
|
+
* so signed authorizations are durably committed before transmission and
|
|
62
|
+
* signing is serialized across tabs.
|
|
63
|
+
*/
|
|
64
|
+
channelStore?: BuyerChannelStore;
|
|
65
|
+
/** Durable browser storage and cross-tab coordination options. */
|
|
66
|
+
persistence?: {
|
|
67
|
+
databaseName?: string;
|
|
68
|
+
indexedDB?: IDBFactory;
|
|
69
|
+
locks?: WebLockManagerLike;
|
|
70
|
+
/** Wait for the other tab to release the buyer instead of failing fast. */
|
|
71
|
+
waitForLock?: boolean;
|
|
72
|
+
};
|
|
73
|
+
/** Override the on-chain read clients (tests, custom RPC stacks). */
|
|
74
|
+
depositsClient?: DepositsClient | null;
|
|
75
|
+
channelsClient?: ChannelsClient | null;
|
|
76
|
+
}
|
|
77
|
+
export interface RequestInput {
|
|
78
|
+
method?: string;
|
|
79
|
+
path: string;
|
|
80
|
+
headers?: Record<string, string>;
|
|
81
|
+
body?: Uint8Array | string;
|
|
82
|
+
/** Selects the provider on the seller (x-antseed-provider). */
|
|
83
|
+
provider?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface StreamCallbacks {
|
|
86
|
+
onChunk?: (data: Uint8Array, done: boolean) => void;
|
|
87
|
+
}
|
|
88
|
+
interface SessionWiring {
|
|
89
|
+
conn: SellerConnection;
|
|
90
|
+
proxyMux: ProxyMux;
|
|
91
|
+
verificationMux: VerificationMux;
|
|
92
|
+
}
|
|
93
|
+
export declare class AntseedWebClient {
|
|
94
|
+
private readonly options;
|
|
95
|
+
private readonly tabLock?;
|
|
96
|
+
private readonly identity;
|
|
97
|
+
private readonly env;
|
|
98
|
+
private readonly payment;
|
|
99
|
+
private readonly store;
|
|
100
|
+
private readonly channelsClient;
|
|
101
|
+
private readonly handler;
|
|
102
|
+
readonly negotiator: BuyerPaymentNegotiator;
|
|
103
|
+
private readonly sessions;
|
|
104
|
+
private readonly sessionPromises;
|
|
105
|
+
private closed;
|
|
106
|
+
constructor(options: ClientOptions, tabLock?: BuyerTabLock | undefined);
|
|
107
|
+
/**
|
|
108
|
+
* Open a production browser client. This acquires the identity-wide tab lock
|
|
109
|
+
* and hydrates IndexedDB before BuyerPaymentManager is constructed.
|
|
110
|
+
*/
|
|
111
|
+
static create(options: ClientOptions): Promise<AntseedWebClient>;
|
|
112
|
+
/**
|
|
113
|
+
* In-memory client for tests and free interoperability experiments. Paid
|
|
114
|
+
* applications must use `create()` so reload recovery and tab exclusion are
|
|
115
|
+
* active.
|
|
116
|
+
*/
|
|
117
|
+
static ephemeral(options: ClientOptions): AntseedWebClient;
|
|
118
|
+
get peerId(): PeerId;
|
|
119
|
+
sellers(): Promise<SellerSummary[]>;
|
|
120
|
+
/** Connect to a seller. `peer` extras (pricing, metadata) improve cost validation. */
|
|
121
|
+
connect(seller: string | SellerSummary, peer?: Partial<BuyerPeerView>): Promise<SellerSession>;
|
|
122
|
+
/** @internal */
|
|
123
|
+
requireSession(peerId: PeerId): Promise<SessionWiring>;
|
|
124
|
+
private openSession;
|
|
125
|
+
/** @internal */
|
|
126
|
+
sendRequest(peer: BuyerPeerView, req: SerializedHttpRequest, callbacks?: {
|
|
127
|
+
onChunk?: (data: Uint8Array, done: boolean) => void;
|
|
128
|
+
}): Promise<SerializedHttpResponse>;
|
|
129
|
+
disconnect(sellerPeerId: string): void;
|
|
130
|
+
closeAll(): void;
|
|
131
|
+
listActiveChannels(): StoredChannel[];
|
|
132
|
+
/** @internal */
|
|
133
|
+
getActiveChannelForPeer(sellerPeerId: string): StoredChannel | null;
|
|
134
|
+
/** Start the contract grace period using the buyer's configured operator signer. */
|
|
135
|
+
requestOnChainClose(channelId: string, operatorSigner: AbstractSigner): Promise<string>;
|
|
136
|
+
/** Release the remaining reserve after the contract grace period has elapsed. */
|
|
137
|
+
withdrawTimedOutChannel(channelId: string, operatorSigner: AbstractSigner): Promise<string>;
|
|
138
|
+
/** Close all transports, flush storage, and release this buyer for another tab. */
|
|
139
|
+
close(): Promise<void>;
|
|
140
|
+
/** @internal */
|
|
141
|
+
requestChannelClose(peer: BuyerPeerView, options?: {
|
|
142
|
+
includeAuth?: boolean;
|
|
143
|
+
timeoutMs?: number;
|
|
144
|
+
}): Promise<CloseChannelResultPayload>;
|
|
145
|
+
private dropSession;
|
|
146
|
+
private failSession;
|
|
147
|
+
private assertOpen;
|
|
148
|
+
private assertOwnedActiveChannel;
|
|
149
|
+
}
|
|
150
|
+
export declare function sellerSummaryFromMetadata(metadata: PeerMetadata): SellerSummary;
|
|
151
|
+
export declare class SellerSession {
|
|
152
|
+
private readonly client;
|
|
153
|
+
readonly peer: BuyerPeerView;
|
|
154
|
+
constructor(client: AntseedWebClient, peer: BuyerPeerView);
|
|
155
|
+
/**
|
|
156
|
+
* Send one request through the shared buyer stack: 402 negotiation,
|
|
157
|
+
* NeedAuth signing, retries, and streaming all behave exactly like the
|
|
158
|
+
* node buyer proxy.
|
|
159
|
+
*/
|
|
160
|
+
request(input: RequestInput, callbacks?: StreamCallbacks): Promise<SerializedHttpResponse>;
|
|
161
|
+
close(): void;
|
|
162
|
+
/** Ask the seller to settle and release this payment channel. */
|
|
163
|
+
closeChannel(options?: {
|
|
164
|
+
includeAuth?: boolean;
|
|
165
|
+
timeoutMs?: number;
|
|
166
|
+
}): Promise<CloseChannelResultPayload>;
|
|
167
|
+
/** Start the on-chain timeout path when cooperative close is unavailable. */
|
|
168
|
+
requestOnChainClose(operatorSigner: AbstractSigner): Promise<string>;
|
|
169
|
+
/** Withdraw after the on-chain close grace period. */
|
|
170
|
+
withdrawTimedOutChannel(operatorSigner: AbstractSigner): Promise<string>;
|
|
171
|
+
private requireActiveChannel;
|
|
172
|
+
}
|
|
173
|
+
export {};
|
|
174
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level browser buyer built on the SHARED buyer machinery from
|
|
3
|
+
* @antseed/buyer-core — the same BuyerRequestHandler / BuyerPaymentManager /
|
|
4
|
+
* BuyerPaymentNegotiator stack the node buyer proxy runs, wired to a WebRTC
|
|
5
|
+
* transport and an injectable channel store. `create()` supplies durable
|
|
6
|
+
* browser persistence; the public constructor retains the in-memory default.
|
|
7
|
+
*/
|
|
8
|
+
import { Contract } from 'ethers';
|
|
9
|
+
import { BuyerPaymentManager, BuyerPaymentNegotiator, BuyerRequestHandler, CHANNEL_ROLE, CHANNEL_STATUS, ChannelsClient, DepositsClient, ProxyMux, SellerAddressResolver, VerificationMux, } from '@antseed/buyer-core';
|
|
10
|
+
import { MessageType, toPeerId, } from '@antseed/protocol';
|
|
11
|
+
import { SellerConnection } from './connection.js';
|
|
12
|
+
import { IndexedDbChannelStore, MemoryChannelStore } from './channel-store.js';
|
|
13
|
+
import { signerFromPrivateKey } from './identity.js';
|
|
14
|
+
import { BuyerTabLock } from './tab-lock.js';
|
|
15
|
+
// Base mainnet defaults (see packages/node/src/payments/chain-config.ts).
|
|
16
|
+
const BASE_MAINNET = {
|
|
17
|
+
chainId: 8453,
|
|
18
|
+
channelsContractAddress: '0xBA66d3b4fbCf472F6F11D6F9F96aaCE96516F09d',
|
|
19
|
+
depositsContractAddress: '0x0F7a3a8f4Da01637d1202bb5443fcF7F88F99fD2',
|
|
20
|
+
usdcAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
21
|
+
rpcUrl: 'https://base.publicnode.com',
|
|
22
|
+
};
|
|
23
|
+
// Stay under the ~256 KiB SCTP message ceiling browsers negotiate with
|
|
24
|
+
// libdatachannel sellers; the shared mux default (512 KiB) assumes node peers.
|
|
25
|
+
const BROWSER_UPLOAD_THRESHOLD_BYTES = 192 * 1024;
|
|
26
|
+
export class AntseedWebClient {
|
|
27
|
+
options;
|
|
28
|
+
tabLock;
|
|
29
|
+
identity;
|
|
30
|
+
env;
|
|
31
|
+
payment;
|
|
32
|
+
store;
|
|
33
|
+
channelsClient;
|
|
34
|
+
handler;
|
|
35
|
+
negotiator;
|
|
36
|
+
sessions = new Map();
|
|
37
|
+
sessionPromises = new Map();
|
|
38
|
+
closed = false;
|
|
39
|
+
constructor(options, tabLock) {
|
|
40
|
+
this.options = options;
|
|
41
|
+
this.tabLock = tabLock;
|
|
42
|
+
const wallet = resolveWallet(options);
|
|
43
|
+
this.identity = { peerId: toPeerId(wallet.address.slice(2).toLowerCase()), wallet };
|
|
44
|
+
this.store = options.channelStore ?? new MemoryChannelStore();
|
|
45
|
+
const globalRtc = globalThis.RTCPeerConnection;
|
|
46
|
+
const globalWs = globalThis.WebSocket;
|
|
47
|
+
const RTCPeerConnectionImpl = options.env?.RTCPeerConnection ?? globalRtc;
|
|
48
|
+
const WebSocketImpl = options.env?.WebSocket ?? globalWs;
|
|
49
|
+
if (!RTCPeerConnectionImpl || !WebSocketImpl) {
|
|
50
|
+
throw new Error('RTCPeerConnection/WebSocket unavailable — pass them via options.env');
|
|
51
|
+
}
|
|
52
|
+
this.env = { RTCPeerConnection: RTCPeerConnectionImpl, WebSocket: WebSocketImpl };
|
|
53
|
+
this.payment = { ...BASE_MAINNET, ...options.payment };
|
|
54
|
+
let depositsClient = null;
|
|
55
|
+
if (options.depositsClient !== undefined) {
|
|
56
|
+
depositsClient = options.depositsClient;
|
|
57
|
+
}
|
|
58
|
+
else if (this.payment.rpcUrl) {
|
|
59
|
+
depositsClient = new DepositsClient({
|
|
60
|
+
rpcUrl: this.payment.rpcUrl,
|
|
61
|
+
...(this.payment.fallbackRpcUrls ? { fallbackRpcUrls: this.payment.fallbackRpcUrls } : {}),
|
|
62
|
+
contractAddress: this.payment.depositsContractAddress,
|
|
63
|
+
usdcAddress: this.payment.usdcAddress,
|
|
64
|
+
evmChainId: this.payment.chainId,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
let channelsClient = null;
|
|
68
|
+
if (options.channelsClient !== undefined) {
|
|
69
|
+
channelsClient = options.channelsClient;
|
|
70
|
+
}
|
|
71
|
+
else if (this.payment.rpcUrl) {
|
|
72
|
+
channelsClient = new ChannelsClient({
|
|
73
|
+
rpcUrl: this.payment.rpcUrl,
|
|
74
|
+
...(this.payment.fallbackRpcUrls ? { fallbackRpcUrls: this.payment.fallbackRpcUrls } : {}),
|
|
75
|
+
contractAddress: this.payment.channelsContractAddress,
|
|
76
|
+
evmChainId: this.payment.chainId,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
this.channelsClient = channelsClient;
|
|
80
|
+
const sellerAddressResolver = channelsClient
|
|
81
|
+
? new SellerAddressResolver({
|
|
82
|
+
isOperator: async (sellerContract, peerAddress) => {
|
|
83
|
+
const contract = new Contract(sellerContract, ['function isOperator(address) view returns (bool)'], channelsClient.provider);
|
|
84
|
+
return contract.getFunction('isOperator')(peerAddress);
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
: undefined;
|
|
88
|
+
const bpm = new BuyerPaymentManager(this.identity, {
|
|
89
|
+
rpcUrl: this.payment.rpcUrl,
|
|
90
|
+
...(this.payment.fallbackRpcUrls ? { fallbackRpcUrls: this.payment.fallbackRpcUrls } : {}),
|
|
91
|
+
depositsContractAddress: this.payment.depositsContractAddress,
|
|
92
|
+
channelsContractAddress: this.payment.channelsContractAddress,
|
|
93
|
+
usdcAddress: this.payment.usdcAddress,
|
|
94
|
+
identityRegistryAddress: '0x0000000000000000000000000000000000000000',
|
|
95
|
+
chainId: this.payment.chainId,
|
|
96
|
+
defaultAuthDurationSecs: this.payment.defaultAuthDurationSecs ?? 900,
|
|
97
|
+
maxPerRequestUsdc: this.payment.maxPerRequestUsdc ?? 500000n,
|
|
98
|
+
maxReserveAmountUsdc: this.payment.maxReserveAmountUsdc ?? 1000000n,
|
|
99
|
+
...(this.payment.costToleranceMultiplier !== undefined
|
|
100
|
+
? { costToleranceMultiplier: this.payment.costToleranceMultiplier }
|
|
101
|
+
: {}),
|
|
102
|
+
dataDir: '',
|
|
103
|
+
}, this.store, sellerAddressResolver);
|
|
104
|
+
this.negotiator = new BuyerPaymentNegotiator(this.identity, bpm, depositsClient, channelsClient, this.store, {}, { emit: () => true }, sellerAddressResolver);
|
|
105
|
+
this.handler = new BuyerRequestHandler({ ...(options.requestTimeoutMs !== undefined ? { requestTimeoutMs: options.requestTimeoutMs } : {}) }, {
|
|
106
|
+
localPeerId: this.identity.peerId,
|
|
107
|
+
negotiator: this.negotiator,
|
|
108
|
+
verificationStorage: null,
|
|
109
|
+
verificationSampler: null,
|
|
110
|
+
getConnection: (peer) => this.requireSession(peer.peerId).then((s) => s.conn),
|
|
111
|
+
getMux: (peerId) => this.sessions.get(peerId).proxyMux,
|
|
112
|
+
getVerificationMux: (peerId) => this.sessions.get(peerId).verificationMux,
|
|
113
|
+
registerPaymentMux: () => { },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Open a production browser client. This acquires the identity-wide tab lock
|
|
118
|
+
* and hydrates IndexedDB before BuyerPaymentManager is constructed.
|
|
119
|
+
*/
|
|
120
|
+
static async create(options) {
|
|
121
|
+
const wallet = resolveWallet(options);
|
|
122
|
+
const payment = { ...BASE_MAINNET, ...options.payment };
|
|
123
|
+
const locks = options.persistence?.locks
|
|
124
|
+
?? globalThis.navigator?.locks;
|
|
125
|
+
if (!locks) {
|
|
126
|
+
throw new Error('Web Locks API unavailable — paid browser operation is disabled');
|
|
127
|
+
}
|
|
128
|
+
const scope = [
|
|
129
|
+
payment.chainId,
|
|
130
|
+
payment.channelsContractAddress.toLowerCase(),
|
|
131
|
+
wallet.address.toLowerCase(),
|
|
132
|
+
].join(':');
|
|
133
|
+
const tabLock = await BuyerTabLock.acquire(`antseed:buyer:${scope}`, locks, {
|
|
134
|
+
wait: options.persistence?.waitForLock ?? false,
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
const store = options.channelStore ?? await IndexedDbChannelStore.open({
|
|
138
|
+
databaseName: options.persistence?.databaseName ?? `antseed-web-sdk:${scope}`,
|
|
139
|
+
...(options.persistence?.indexedDB ? { indexedDB: options.persistence.indexedDB } : {}),
|
|
140
|
+
...(options.onPersistenceError ? { onWriteError: options.onPersistenceError } : {}),
|
|
141
|
+
});
|
|
142
|
+
const { privateKey: _privateKey, ...safeOptions } = options;
|
|
143
|
+
return new AntseedWebClient({ ...safeOptions, wallet, channelStore: store }, tabLock);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
await tabLock.release();
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* In-memory client for tests and free interoperability experiments. Paid
|
|
152
|
+
* applications must use `create()` so reload recovery and tab exclusion are
|
|
153
|
+
* active.
|
|
154
|
+
*/
|
|
155
|
+
static ephemeral(options) {
|
|
156
|
+
return new AntseedWebClient({ ...options, channelStore: options.channelStore ?? new MemoryChannelStore() });
|
|
157
|
+
}
|
|
158
|
+
get peerId() {
|
|
159
|
+
return this.identity.peerId;
|
|
160
|
+
}
|
|
161
|
+
async sellers() {
|
|
162
|
+
const res = await fetch(`${this.options.relayUrl.replace(/\/$/, '')}/sellers`);
|
|
163
|
+
if (!res.ok)
|
|
164
|
+
throw new Error(`relay /sellers failed: ${res.status}`);
|
|
165
|
+
const body = (await res.json());
|
|
166
|
+
let useDiscoveredPeers = body.peers.length > 0;
|
|
167
|
+
if (useDiscoveredPeers) {
|
|
168
|
+
const updatedAt = Date.parse(body.updatedAt);
|
|
169
|
+
const maxAge = this.options.maxSellerSnapshotAgeMs ?? 15 * 60_000;
|
|
170
|
+
if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > maxAge) {
|
|
171
|
+
if (body.static.length === 0)
|
|
172
|
+
throw new Error('relay seller snapshot is stale');
|
|
173
|
+
useDiscoveredPeers = false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const byId = new Map();
|
|
177
|
+
for (const entry of body.static) {
|
|
178
|
+
const peerId = toPeerId(entry.peerId.toLowerCase());
|
|
179
|
+
byId.set(peerId, { peerId, providers: [], publicAddress: `${entry.host}:${entry.port}` });
|
|
180
|
+
}
|
|
181
|
+
if (useDiscoveredPeers) {
|
|
182
|
+
for (const metadata of body.peers) {
|
|
183
|
+
const peer = sellerSummaryFromMetadata(metadata);
|
|
184
|
+
byId.set(peer.peerId, peer);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return [...byId.values()];
|
|
188
|
+
}
|
|
189
|
+
/** Connect to a seller. `peer` extras (pricing, metadata) improve cost validation. */
|
|
190
|
+
async connect(seller, peer = {}) {
|
|
191
|
+
this.assertOpen();
|
|
192
|
+
const sellerPeerId = typeof seller === 'string' ? seller : seller.peerId;
|
|
193
|
+
const peerId = toPeerId(sellerPeerId.toLowerCase());
|
|
194
|
+
const sellerView = typeof seller === 'string'
|
|
195
|
+
? {}
|
|
196
|
+
: seller;
|
|
197
|
+
const peerView = { ...sellerView, ...peer, peerId };
|
|
198
|
+
await this.requireSession(peerId);
|
|
199
|
+
return new SellerSession(this, peerView);
|
|
200
|
+
}
|
|
201
|
+
/** @internal */
|
|
202
|
+
async requireSession(peerId) {
|
|
203
|
+
this.assertOpen();
|
|
204
|
+
const existing = this.sessions.get(peerId);
|
|
205
|
+
if (existing) {
|
|
206
|
+
if (existing.conn.isOpen)
|
|
207
|
+
return existing;
|
|
208
|
+
// Close a lingering (e.g. mid-connect) session before overwriting it.
|
|
209
|
+
existing.conn.close();
|
|
210
|
+
this.dropSession(peerId);
|
|
211
|
+
}
|
|
212
|
+
const pending = this.sessionPromises.get(peerId);
|
|
213
|
+
if (pending)
|
|
214
|
+
return pending;
|
|
215
|
+
const opening = this.openSession(peerId);
|
|
216
|
+
this.sessionPromises.set(peerId, opening);
|
|
217
|
+
try {
|
|
218
|
+
return await opening;
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
if (this.sessionPromises.get(peerId) === opening) {
|
|
222
|
+
this.sessionPromises.delete(peerId);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async openSession(peerId) {
|
|
227
|
+
const bridgeUrl = `${this.options.relayUrl.replace(/\/$/, '').replace(/^http/, 'ws')}/bridge/${peerId}`;
|
|
228
|
+
const conn = await SellerConnection.connect(bridgeUrl, this.identity.wallet, this.env, this.options.connection);
|
|
229
|
+
if (this.closed) {
|
|
230
|
+
conn.close();
|
|
231
|
+
throw new Error('AntseedWebClient is closed');
|
|
232
|
+
}
|
|
233
|
+
const proxyMux = new ProxyMux(conn, { uploadThresholdBytes: BROWSER_UPLOAD_THRESHOLD_BYTES });
|
|
234
|
+
const verificationMux = new VerificationMux(conn);
|
|
235
|
+
const paymentMux = this.negotiator.getOrCreatePaymentMux(peerId, conn);
|
|
236
|
+
conn.onFrame = (frame) => {
|
|
237
|
+
if (frame.type >= 0x50 && frame.type <= 0x5f) {
|
|
238
|
+
void paymentMux.handleFrame(frame).catch((error) => this.failSession(peerId, error));
|
|
239
|
+
}
|
|
240
|
+
else if (frame.type >= 0x80 && frame.type <= 0x8f) {
|
|
241
|
+
void verificationMux.handleFrame(frame).catch((error) => this.failSession(peerId, error));
|
|
242
|
+
}
|
|
243
|
+
else if (frame.type !== MessageType.Ping && frame.type !== MessageType.Pong) {
|
|
244
|
+
void proxyMux.handleFrame(frame).catch((error) => this.failSession(peerId, error));
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
conn.onClose = () => this.dropSession(peerId);
|
|
248
|
+
const wiring = { conn, proxyMux, verificationMux };
|
|
249
|
+
this.sessions.set(peerId, wiring);
|
|
250
|
+
return wiring;
|
|
251
|
+
}
|
|
252
|
+
/** @internal */
|
|
253
|
+
async sendRequest(peer, req, callbacks) {
|
|
254
|
+
const session = await this.requireSession(peer.peerId);
|
|
255
|
+
await this.negotiator.recoverPendingReserveBeforeRequest(peer, session.conn);
|
|
256
|
+
const streamCallbacks = callbacks?.onChunk
|
|
257
|
+
? {
|
|
258
|
+
onResponseChunk: (chunk) => callbacks.onChunk(chunk.data, chunk.done),
|
|
259
|
+
}
|
|
260
|
+
: undefined;
|
|
261
|
+
return this.handler.sendRequest(peer, req, streamCallbacks);
|
|
262
|
+
}
|
|
263
|
+
disconnect(sellerPeerId) {
|
|
264
|
+
const peerId = toPeerId(sellerPeerId.toLowerCase());
|
|
265
|
+
const session = this.sessions.get(peerId);
|
|
266
|
+
session?.conn.close();
|
|
267
|
+
this.dropSession(peerId);
|
|
268
|
+
}
|
|
269
|
+
closeAll() {
|
|
270
|
+
for (const [peerId, session] of [...this.sessions]) {
|
|
271
|
+
session.conn.close();
|
|
272
|
+
this.dropSession(peerId);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
listActiveChannels() {
|
|
276
|
+
return this.store.getActiveChannelsByBuyer(CHANNEL_ROLE.BUYER, this.identity.wallet.address);
|
|
277
|
+
}
|
|
278
|
+
/** @internal */
|
|
279
|
+
getActiveChannelForPeer(sellerPeerId) {
|
|
280
|
+
return this.store.getActiveChannelByPeerAndBuyer(toPeerId(sellerPeerId.toLowerCase()), CHANNEL_ROLE.BUYER, this.identity.wallet.address);
|
|
281
|
+
}
|
|
282
|
+
/** Start the contract grace period using the buyer's configured operator signer. */
|
|
283
|
+
async requestOnChainClose(channelId, operatorSigner) {
|
|
284
|
+
this.assertOwnedActiveChannel(channelId);
|
|
285
|
+
if (!this.channelsClient)
|
|
286
|
+
throw new Error('channels client unavailable');
|
|
287
|
+
return this.channelsClient.requestClose(operatorSigner, channelId);
|
|
288
|
+
}
|
|
289
|
+
/** Release the remaining reserve after the contract grace period has elapsed. */
|
|
290
|
+
async withdrawTimedOutChannel(channelId, operatorSigner) {
|
|
291
|
+
this.assertOwnedActiveChannel(channelId);
|
|
292
|
+
if (!this.channelsClient)
|
|
293
|
+
throw new Error('channels client unavailable');
|
|
294
|
+
const txHash = await this.channelsClient.withdraw(operatorSigner, channelId);
|
|
295
|
+
this.store.updateChannelStatus(channelId, CHANNEL_STATUS.TIMEOUT);
|
|
296
|
+
await this.store.flush?.();
|
|
297
|
+
return txHash;
|
|
298
|
+
}
|
|
299
|
+
/** Close all transports, flush storage, and release this buyer for another tab. */
|
|
300
|
+
async close() {
|
|
301
|
+
if (this.closed)
|
|
302
|
+
return;
|
|
303
|
+
this.closed = true;
|
|
304
|
+
await this.negotiator.drainPendingNeedAuth();
|
|
305
|
+
this.closeAll();
|
|
306
|
+
this.negotiator.cleanup();
|
|
307
|
+
try {
|
|
308
|
+
await this.store.flush?.();
|
|
309
|
+
await this.store.close?.();
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
await this.tabLock?.release();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** @internal */
|
|
316
|
+
async requestChannelClose(peer, options) {
|
|
317
|
+
const session = await this.requireSession(peer.peerId);
|
|
318
|
+
const result = await this.negotiator.requestChannelClose(peer.peerId, session.conn, options);
|
|
319
|
+
await this.store.flush?.();
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
dropSession(peerId) {
|
|
323
|
+
const session = this.sessions.get(peerId);
|
|
324
|
+
session?.verificationMux.close();
|
|
325
|
+
this.negotiator.onPeerDisconnect(peerId);
|
|
326
|
+
this.sessions.delete(peerId);
|
|
327
|
+
}
|
|
328
|
+
failSession(peerId, cause) {
|
|
329
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
330
|
+
try {
|
|
331
|
+
this.options.onError?.(error, { sellerPeerId: peerId });
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
// Error observers are diagnostic only and must not create a second
|
|
335
|
+
// unhandled rejection while the failed session is being torn down.
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
this.disconnect(peerId);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
assertOpen() {
|
|
342
|
+
if (this.closed)
|
|
343
|
+
throw new Error('AntseedWebClient is closed');
|
|
344
|
+
}
|
|
345
|
+
assertOwnedActiveChannel(channelId) {
|
|
346
|
+
this.assertOpen();
|
|
347
|
+
const channel = this.store.getChannel(channelId);
|
|
348
|
+
if (!channel
|
|
349
|
+
|| channel.status !== CHANNEL_STATUS.ACTIVE
|
|
350
|
+
|| channel.role !== CHANNEL_ROLE.BUYER
|
|
351
|
+
|| channel.buyerEvmAddr.toLowerCase() !== this.identity.wallet.address.toLowerCase()) {
|
|
352
|
+
throw new Error('active channel is not owned by this buyer');
|
|
353
|
+
}
|
|
354
|
+
return channel;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
export function sellerSummaryFromMetadata(metadata) {
|
|
358
|
+
const providers = metadata.providers.map((provider) => provider.provider);
|
|
359
|
+
const firstProvider = metadata.providers[0];
|
|
360
|
+
const providerPricing = {};
|
|
361
|
+
const providerServiceCategories = {};
|
|
362
|
+
const providerServiceApiProtocols = {};
|
|
363
|
+
const providerServiceUnitBillingModels = {};
|
|
364
|
+
const providerServiceCapabilities = {};
|
|
365
|
+
for (const announcement of metadata.providers) {
|
|
366
|
+
const provider = announcement.provider;
|
|
367
|
+
const services = Object.fromEntries(announcement.services.map((service) => [
|
|
368
|
+
service,
|
|
369
|
+
announcement.servicePricing?.[service] ?? announcement.defaultPricing,
|
|
370
|
+
]));
|
|
371
|
+
const pricing = providerPricing[provider];
|
|
372
|
+
if (pricing) {
|
|
373
|
+
pricing.services ??= {};
|
|
374
|
+
Object.assign(pricing.services, services);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
providerPricing[provider] = {
|
|
378
|
+
defaults: { ...announcement.defaultPricing },
|
|
379
|
+
...(Object.keys(services).length > 0 ? { services } : {}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
mergeServiceMatrix(providerServiceCategories, provider, announcement.serviceCategories);
|
|
383
|
+
mergeServiceMatrix(providerServiceApiProtocols, provider, announcement.serviceApiProtocols);
|
|
384
|
+
mergeServiceMatrix(providerServiceUnitBillingModels, provider, announcement.serviceUnitBillingModels);
|
|
385
|
+
mergeServiceMatrix(providerServiceCapabilities, provider, announcement.serviceCapabilities);
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
peerId: toPeerId(metadata.peerId.toLowerCase()),
|
|
389
|
+
...(metadata.displayName ? { displayName: metadata.displayName } : {}),
|
|
390
|
+
...(metadata.publicAddress ? { publicAddress: metadata.publicAddress } : {}),
|
|
391
|
+
...(metadata.sellerContract ? { sellerContract: metadata.sellerContract } : {}),
|
|
392
|
+
metadata,
|
|
393
|
+
providers,
|
|
394
|
+
...(metadata.capabilities?.length ? { capabilities: [...metadata.capabilities] } : {}),
|
|
395
|
+
...(Object.keys(providerPricing).length ? { providerPricing } : {}),
|
|
396
|
+
...(Object.keys(providerServiceCategories).length ? { providerServiceCategories } : {}),
|
|
397
|
+
...(Object.keys(providerServiceApiProtocols).length ? { providerServiceApiProtocols } : {}),
|
|
398
|
+
...(Object.keys(providerServiceUnitBillingModels).length
|
|
399
|
+
? { providerServiceUnitBillingModels }
|
|
400
|
+
: {}),
|
|
401
|
+
...(Object.keys(providerServiceCapabilities).length ? { providerServiceCapabilities } : {}),
|
|
402
|
+
defaultInputUsdPerMillion: firstProvider?.defaultPricing.inputUsdPerMillion,
|
|
403
|
+
defaultOutputUsdPerMillion: firstProvider?.defaultPricing.outputUsdPerMillion,
|
|
404
|
+
defaultCachedInputUsdPerMillion: firstProvider?.defaultPricing.cachedInputUsdPerMillion,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function mergeServiceMatrix(target, provider, services) {
|
|
408
|
+
if (!services || Object.keys(services).length === 0)
|
|
409
|
+
return;
|
|
410
|
+
const existing = target[provider];
|
|
411
|
+
if (existing)
|
|
412
|
+
Object.assign(existing.services, services);
|
|
413
|
+
else
|
|
414
|
+
target[provider] = { services: { ...services } };
|
|
415
|
+
}
|
|
416
|
+
export class SellerSession {
|
|
417
|
+
client;
|
|
418
|
+
peer;
|
|
419
|
+
constructor(client, peer) {
|
|
420
|
+
this.client = client;
|
|
421
|
+
this.peer = peer;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Send one request through the shared buyer stack: 402 negotiation,
|
|
425
|
+
* NeedAuth signing, retries, and streaming all behave exactly like the
|
|
426
|
+
* node buyer proxy.
|
|
427
|
+
*/
|
|
428
|
+
async request(input, callbacks = {}) {
|
|
429
|
+
const req = {
|
|
430
|
+
requestId: crypto.randomUUID(),
|
|
431
|
+
method: input.method ?? 'POST',
|
|
432
|
+
path: input.path,
|
|
433
|
+
headers: {
|
|
434
|
+
...(input.provider ? { 'x-antseed-provider': input.provider } : {}),
|
|
435
|
+
...input.headers,
|
|
436
|
+
},
|
|
437
|
+
body: typeof input.body === 'string' ? new TextEncoder().encode(input.body) : input.body ?? new Uint8Array(0),
|
|
438
|
+
};
|
|
439
|
+
return this.client.sendRequest(this.peer, req, callbacks);
|
|
440
|
+
}
|
|
441
|
+
close() {
|
|
442
|
+
this.client.disconnect(this.peer.peerId);
|
|
443
|
+
}
|
|
444
|
+
/** Ask the seller to settle and release this payment channel. */
|
|
445
|
+
closeChannel(options) {
|
|
446
|
+
return this.client.requestChannelClose(this.peer, options);
|
|
447
|
+
}
|
|
448
|
+
/** Start the on-chain timeout path when cooperative close is unavailable. */
|
|
449
|
+
requestOnChainClose(operatorSigner) {
|
|
450
|
+
return this.client.requestOnChainClose(this.requireActiveChannel().sessionId, operatorSigner);
|
|
451
|
+
}
|
|
452
|
+
/** Withdraw after the on-chain close grace period. */
|
|
453
|
+
withdrawTimedOutChannel(operatorSigner) {
|
|
454
|
+
return this.client.withdrawTimedOutChannel(this.requireActiveChannel().sessionId, operatorSigner);
|
|
455
|
+
}
|
|
456
|
+
requireActiveChannel() {
|
|
457
|
+
const channel = this.client.getActiveChannelForPeer(this.peer.peerId);
|
|
458
|
+
if (!channel)
|
|
459
|
+
throw new Error(`No active payment channel with seller ${this.peer.peerId.slice(0, 12)}...`);
|
|
460
|
+
return channel;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function resolveWallet(options) {
|
|
464
|
+
const wallet = options.wallet
|
|
465
|
+
?? (options.privateKey ? signerFromPrivateKey(options.privateKey) : null);
|
|
466
|
+
if (!wallet)
|
|
467
|
+
throw new Error('provide privateKey or wallet');
|
|
468
|
+
return wallet;
|
|
469
|
+
}
|
|
470
|
+
//# sourceMappingURL=client.js.map
|