@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/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# @antseed/web-sdk
|
|
2
|
+
|
|
3
|
+
Browser buyer SDK for AntSeed. Connects to unmodified sellers over WebRTC
|
|
4
|
+
DataChannels (signaled through an [@antseed/relay](../../apps/relay)) and pays
|
|
5
|
+
per request with in-browser EIP-712 signatures. No native modules; the only
|
|
6
|
+
runtime dependency is ethers.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { AntseedWebClient } from '@antseed/web-sdk';
|
|
10
|
+
|
|
11
|
+
const client = await AntseedWebClient.create({
|
|
12
|
+
relayUrl: 'https://relay.example.com',
|
|
13
|
+
privateKey: buyerHotWalletKey, // peerId = its EVM address
|
|
14
|
+
connection: {
|
|
15
|
+
iceServers: [
|
|
16
|
+
{ urls: 'stun:stun.example.com' },
|
|
17
|
+
{ urls: 'turns:turn.example.com', username, credential },
|
|
18
|
+
],
|
|
19
|
+
onConnectionInfo: ({ path }) => showPath(path), // direct | relay | unknown
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const sellers = await client.sellers();
|
|
24
|
+
const session = await client.connect(sellers[0].peerId);
|
|
25
|
+
|
|
26
|
+
const response = await session.request(
|
|
27
|
+
{
|
|
28
|
+
path: '/v1/chat/completions',
|
|
29
|
+
provider: 'openai',
|
|
30
|
+
headers: { accept: 'text/event-stream' },
|
|
31
|
+
body: JSON.stringify({ model: 'gpt-oss-120b', stream: true, messages: [...] }),
|
|
32
|
+
},
|
|
33
|
+
{ onChunk: (data, done) => render(new TextDecoder().decode(data)) },
|
|
34
|
+
);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Example page
|
|
38
|
+
|
|
39
|
+
A working single-page example (seller discovery, connect, streaming chat)
|
|
40
|
+
lives in [`examples/example.html`](examples/example.html):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm --filter @antseed/web-sdk run example
|
|
44
|
+
# open http://127.0.0.1:8974/example.html
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It expects a reachable relay (`node apps/relay/dist/index.js`, default port
|
|
48
|
+
8917 — use `RELAY_STATIC_SELLERS=<peerId>@<host>:<port>` to pin a local
|
|
49
|
+
seller) and works unfunded against sellers that accept free requests.
|
|
50
|
+
|
|
51
|
+
## Durable channel state and multiple tabs
|
|
52
|
+
|
|
53
|
+
By default the client uses an in-memory channel store: on reload the latest
|
|
54
|
+
signed SpendingAuth state is gone and reserved funds stay locked until the
|
|
55
|
+
session deadline passes. Production apps should inject a durable store:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const client = new AntseedWebClient({ relayUrl, wallet, channelStore: myStore });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`channelStore` takes any `BuyerChannelStore` (re-exported here). For
|
|
62
|
+
crash-safety implement the optional `flush()`: it is awaited after each newly
|
|
63
|
+
signed authorization is persisted and *before* it is transmitted, so it must
|
|
64
|
+
not resolve until the write is durably committed (e.g. IndexedDB behind a
|
|
65
|
+
synchronous in-memory cache). If several tabs can share the store, the
|
|
66
|
+
implementation must also serialize signing (e.g. `navigator.locks`) —
|
|
67
|
+
SpendingAuth amounts are cumulative per channel, and two tabs signing
|
|
68
|
+
concurrently corrupt the sequence. Without a shared store, each tab opens its
|
|
69
|
+
own channel and reserves its own budget.
|
|
70
|
+
|
|
71
|
+
## ICE / TURN configuration
|
|
72
|
+
|
|
73
|
+
`connection.iceServers` accepts bare URL strings or full `RTCIceServer`
|
|
74
|
+
entries (for TURN credentials); `connection.iceTransportPolicy: 'relay'`
|
|
75
|
+
forces TURN-only paths. `connection.onConnectionInfo` reports the selected
|
|
76
|
+
path (`'direct' | 'relay' | 'unknown'`) once connected, without exposing
|
|
77
|
+
addresses or raw candidates.
|
|
78
|
+
|
|
79
|
+
## Architecture
|
|
80
|
+
|
|
81
|
+
This package contains only the browser-specific pieces: relay signaling
|
|
82
|
+
(`hello` envelope, SDP offer, trickle ICE), the `RTCPeerConnection`
|
|
83
|
+
transport, keepalive, channel-store adapters, and cross-tab coordination.
|
|
84
|
+
Everything else is shared code, not a reimplementation. Production clients
|
|
85
|
+
use an IndexedDB store; the in-memory store remains available only through
|
|
86
|
+
the explicit `ephemeral()` API:
|
|
87
|
+
|
|
88
|
+
- **@antseed/protocol** — wire format: message types, frame/HTTP/payment
|
|
89
|
+
codecs, EIP-712 payment signatures, connection auth. Same modules
|
|
90
|
+
@antseed/node runs.
|
|
91
|
+
- **@antseed/buyer-core** — the buyer machinery: `BuyerRequestHandler`,
|
|
92
|
+
`BuyerPaymentManager`, `BuyerPaymentNegotiator`, `ProxyMux`/`PaymentMux`.
|
|
93
|
+
The exact classes behind the node/CLI buyer proxy, wired here to a WebRTC
|
|
94
|
+
DataChannel and the configured channel store (in-memory by default).
|
|
95
|
+
|
|
96
|
+
So 402 negotiation, ReserveAuth/AuthAck, NeedAuth cost validation, cumulative
|
|
97
|
+
SpendingAuth metadata, streaming, and chunked uploads behave identically to
|
|
98
|
+
the node buyer. One browser-specific override: uploads chunk above 192 KiB
|
|
99
|
+
(not 512 KiB) to stay under the ~256 KiB SCTP message ceiling browsers
|
|
100
|
+
negotiate with libdatachannel sellers. Defaults target Base mainnet; override
|
|
101
|
+
`payment.*` for other chains.
|
|
102
|
+
|
|
103
|
+
The relay bridge is closed as soon as the DataChannel opens — it is only
|
|
104
|
+
needed for signaling, and holding it would consume the relay's per-IP bridge
|
|
105
|
+
slots. There is no re-signaling path; a dropped DataChannel means a fresh
|
|
106
|
+
`connect()`.
|
|
107
|
+
|
|
108
|
+
## Persistence and multiple tabs
|
|
109
|
+
|
|
110
|
+
`AntseedWebClient.create()` is asynchronous because it acquires an exclusive
|
|
111
|
+
Web Lock for the buyer identity and hydrates IndexedDB before the payment
|
|
112
|
+
manager may sign. Only one tab can use a buyer on the same chain/channels
|
|
113
|
+
contract at a time. A second tab fails with `BuyerAlreadyActiveError` by
|
|
114
|
+
default; set `persistence.waitForLock` to wait instead. A crashed tab releases
|
|
115
|
+
its Web Lock automatically, and its successor recovers the last durably
|
|
116
|
+
committed channel state.
|
|
117
|
+
|
|
118
|
+
Every ReserveAuth and SpendingAuth is committed to IndexedDB before it is
|
|
119
|
+
sent. The database contains channel identifiers, signatures, cumulative
|
|
120
|
+
amounts, metadata, and recovery context, but never the buyer private key.
|
|
121
|
+
Use `onPersistenceError` to surface background failures from ordinary
|
|
122
|
+
status/accounting writes immediately; awaited authorization commits and
|
|
123
|
+
`close()` also reject when persistence is unhealthy.
|
|
124
|
+
Call `await client.close()` when the application is done to flush storage and
|
|
125
|
+
release the lock. Page-unload handlers are not a channel recovery mechanism.
|
|
126
|
+
|
|
127
|
+
`AntseedWebClient.ephemeral()` retains in-memory behavior for tests and free
|
|
128
|
+
interoperability experiments. It must not be used for paid traffic because a
|
|
129
|
+
reload loses its channel state and it provides no multi-tab exclusion.
|
|
130
|
+
|
|
131
|
+
`SellerSession.close()` closes only the WebRTC transport. Use
|
|
132
|
+
`await session.closeChannel()` to request cooperative settlement from a
|
|
133
|
+
supporting seller. `client.listActiveChannels()` exposes locally unresolved
|
|
134
|
+
channels for recovery UI. If the seller is unavailable, call
|
|
135
|
+
`session.requestOnChainClose(operatorSigner)` and, after the contract grace
|
|
136
|
+
period, `session.withdrawTimedOutChannel(operatorSigner)`. The signer must be
|
|
137
|
+
the operator configured for the buyer in AntseedDeposits; the SDK does not
|
|
138
|
+
assume that the browser wallet or any particular backend owns that role.
|
|
139
|
+
|
|
140
|
+
The relay verifies DHT metadata before caching it, and the SDK rejects a
|
|
141
|
+
non-empty seller snapshot older than `maxSellerSnapshotAgeMs` (15 minutes by
|
|
142
|
+
default) before normalizing it into the shared buyer pricing/capability view.
|
|
143
|
+
The relay remains a trusted discovery-availability boundary; connection auth,
|
|
144
|
+
seller response authentication, and payment signatures remain end to end.
|
|
145
|
+
|
|
146
|
+
## Known limitation: large non-streamed responses
|
|
147
|
+
|
|
148
|
+
Chunking currently protects the upload direction only. A seller sends a
|
|
149
|
+
non-streamed response body as a single DataChannel message, and browsers cap
|
|
150
|
+
received SCTP messages at ~256 KiB — a larger response (e.g. base64 image
|
|
151
|
+
output) will kill the DataChannel. Until seller-side response chunking
|
|
152
|
+
exists, prefer streaming (`accept: text/event-stream`) for potentially large
|
|
153
|
+
responses.
|
|
154
|
+
|
|
155
|
+
## Prerequisites for paying sellers
|
|
156
|
+
|
|
157
|
+
The buyer address must have USDC deposited in AntseedDeposits (directly or
|
|
158
|
+
via the gasless EIP-3009 sweep). Deposit UX is out of scope for this SDK.
|
|
159
|
+
|
|
160
|
+
## Testing
|
|
161
|
+
|
|
162
|
+
`vitest run` covers byte-for-byte parity with `@antseed/node` (hello
|
|
163
|
+
envelope, frame codec, EIP-712 signatures, metadata hashing) and an
|
|
164
|
+
end-to-end suite that drives the real `@antseed/node` seller listener through
|
|
165
|
+
a real relay instance, with `node-datachannel/polyfill` standing in for the
|
|
166
|
+
browser's `RTCPeerConnection`. `pnpm run test:browser` additionally launches
|
|
167
|
+
real Chromium and Firefox runtimes, connects their native WebRTC stacks to an
|
|
168
|
+
unchanged seller through the relay, and verifies multi-tab exclusion plus
|
|
169
|
+
crash takeover. The browser suite also completes paid 402 negotiation, verifies
|
|
170
|
+
the EIP-712 authorizations, reloads IndexedDB, simulates lost seller state, and
|
|
171
|
+
recovers the same on-chain channel without issuing a second reserve.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { BuyerChannelStore, ChannelKind, ChannelRole, ChannelStatus, StoredChannel } from '@antseed/buyer-core/channel-store-types';
|
|
2
|
+
import type { SpendingAuthMetadata, SpendingAuthServiceMetadata } from '@antseed/protocol/signatures';
|
|
3
|
+
export declare class MemoryChannelStore implements BuyerChannelStore {
|
|
4
|
+
protected channels: Map<string, StoredChannel>;
|
|
5
|
+
protected serviceTotals: Map<string, SpendingAuthServiceMetadata[]>;
|
|
6
|
+
upsertChannel(channel: StoredChannel): void;
|
|
7
|
+
getChannel(sessionId: string): StoredChannel | null;
|
|
8
|
+
getActiveChannelByPeerAndBuyer(peerId: string, role: ChannelRole, buyerEvmAddr: string, channelKind?: ChannelKind): StoredChannel | null;
|
|
9
|
+
getLatestChannelByPeerAndBuyer(peerId: string, role: ChannelRole, buyerEvmAddr: string, channelKind?: ChannelKind): StoredChannel | null;
|
|
10
|
+
getActiveChannelsByBuyer(role: ChannelRole, buyerEvmAddr: string, channelKind?: ChannelKind): StoredChannel[];
|
|
11
|
+
updateChannelStatus(sessionId: string, status: ChannelStatus, settledAmount?: string): void;
|
|
12
|
+
replaceMetadataServiceTotals(sessionId: string, services?: readonly SpendingAuthServiceMetadata[]): void;
|
|
13
|
+
getChannelMetadata(channel: StoredChannel): SpendingAuthMetadata;
|
|
14
|
+
commitAuthorization(channel: StoredChannel, services?: readonly SpendingAuthServiceMetadata[]): void;
|
|
15
|
+
flush(): Promise<void>;
|
|
16
|
+
protected latestMatching(predicate: (channel: StoredChannel) => boolean): StoredChannel | null;
|
|
17
|
+
}
|
|
18
|
+
export interface IndexedDbChannelStoreOptions {
|
|
19
|
+
databaseName: string;
|
|
20
|
+
indexedDB?: IDBFactory;
|
|
21
|
+
/** Called asynchronously as soon as a queued IndexedDB write fails. */
|
|
22
|
+
onWriteError?: (error: Error) => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* IndexedDB-backed store with a synchronous in-memory read model. Call
|
|
26
|
+
* `open()` before constructing BuyerPaymentManager so its hydration pass sees
|
|
27
|
+
* all committed channels. Writes are serialized, and authorization commits
|
|
28
|
+
* update the channel and service totals in one IndexedDB transaction.
|
|
29
|
+
*/
|
|
30
|
+
export declare class IndexedDbChannelStore extends MemoryChannelStore {
|
|
31
|
+
private readonly db;
|
|
32
|
+
private readonly onWriteError?;
|
|
33
|
+
private writeTail;
|
|
34
|
+
private fatalWriteError;
|
|
35
|
+
private closed;
|
|
36
|
+
private constructor();
|
|
37
|
+
static open(options: IndexedDbChannelStoreOptions): Promise<IndexedDbChannelStore>;
|
|
38
|
+
upsertChannel(channel: StoredChannel): void;
|
|
39
|
+
updateChannelStatus(sessionId: string, status: ChannelStatus, settledAmount?: string): void;
|
|
40
|
+
replaceMetadataServiceTotals(sessionId: string, services?: readonly SpendingAuthServiceMetadata[]): void;
|
|
41
|
+
commitAuthorization(channel: StoredChannel, services?: readonly SpendingAuthServiceMetadata[]): Promise<void>;
|
|
42
|
+
flush(): Promise<void>;
|
|
43
|
+
close(): Promise<void>;
|
|
44
|
+
private hydrate;
|
|
45
|
+
private replaceServiceRecords;
|
|
46
|
+
private enqueuePutChannel;
|
|
47
|
+
private enqueue;
|
|
48
|
+
private assertWritable;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=channel-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-store.d.ts","sourceRoot":"","sources":["../src/channel-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,EACd,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAEtG,qBAAa,kBAAmB,YAAW,iBAAiB;IAC1D,SAAS,CAAC,QAAQ,6BAAoC;IACtD,SAAS,CAAC,aAAa,6CAAoD;IAE3E,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAI3C,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAKnD,8BAA8B,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,GAAE,WAA+B,GAC3C,aAAa,GAAG,IAAI;IAWvB,8BAA8B,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,GAAE,WAA+B,GAC3C,aAAa,GAAG,IAAI;IAUvB,wBAAwB,CACtB,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,GAAE,WAA+B,GAC3C,aAAa,EAAE;IAYlB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI;IAY3F,4BAA4B,CAC1B,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,SAAS,2BAA2B,EAAO,GACpD,IAAI;IAIP,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,oBAAoB;IAgBhE,mBAAmB,CACjB,OAAO,EAAE,aAAa,EACtB,QAAQ,GAAE,SAAS,2BAA2B,EAAO,GACpD,IAAI;IAKD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAE5B,SAAS,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,GAAG,aAAa,GAAG,IAAI;CAQ/F;AAED,MAAM,WAAW,4BAA4B;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,uEAAuE;IACvE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACvC;AAkBD;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB;IAMzD,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IANhC,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO;WAOM,IAAI,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAS/E,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAO3C,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI;IAQ3F,4BAA4B,CACnC,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,SAAS,2BAA2B,EAAO,GACpD,IAAI;IAOQ,mBAAmB,CAChC,OAAO,EAAE,aAAa,EACtB,QAAQ,GAAE,SAAS,2BAA2B,EAAO,GACpD,OAAO,CAAC,IAAI,CAAC;IAiBD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAOd,OAAO;IAwBrB,OAAO,CAAC,qBAAqB;IAU7B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,cAAc;CAMvB"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { CHANNEL_KIND, CHANNEL_STATUS } from '@antseed/buyer-core/channel-store-types';
|
|
2
|
+
export class MemoryChannelStore {
|
|
3
|
+
channels = new Map();
|
|
4
|
+
serviceTotals = new Map();
|
|
5
|
+
upsertChannel(channel) {
|
|
6
|
+
this.channels.set(channel.sessionId, cloneChannel(channel));
|
|
7
|
+
}
|
|
8
|
+
getChannel(sessionId) {
|
|
9
|
+
const channel = this.channels.get(sessionId);
|
|
10
|
+
return channel ? cloneChannel(channel) : null;
|
|
11
|
+
}
|
|
12
|
+
getActiveChannelByPeerAndBuyer(peerId, role, buyerEvmAddr, channelKind = CHANNEL_KIND.PAID) {
|
|
13
|
+
return this.latestMatching((c) => c.peerId === peerId &&
|
|
14
|
+
c.role === role &&
|
|
15
|
+
sameAddress(c.buyerEvmAddr, buyerEvmAddr) &&
|
|
16
|
+
(c.channelKind ?? CHANNEL_KIND.PAID) === channelKind &&
|
|
17
|
+
c.status === CHANNEL_STATUS.ACTIVE);
|
|
18
|
+
}
|
|
19
|
+
getLatestChannelByPeerAndBuyer(peerId, role, buyerEvmAddr, channelKind = CHANNEL_KIND.PAID) {
|
|
20
|
+
return this.latestMatching((c) => c.peerId === peerId &&
|
|
21
|
+
c.role === role &&
|
|
22
|
+
sameAddress(c.buyerEvmAddr, buyerEvmAddr) &&
|
|
23
|
+
(c.channelKind ?? CHANNEL_KIND.PAID) === channelKind);
|
|
24
|
+
}
|
|
25
|
+
getActiveChannelsByBuyer(role, buyerEvmAddr, channelKind = CHANNEL_KIND.PAID) {
|
|
26
|
+
return [...this.channels.values()]
|
|
27
|
+
.filter((c) => c.role === role &&
|
|
28
|
+
sameAddress(c.buyerEvmAddr, buyerEvmAddr) &&
|
|
29
|
+
(c.channelKind ?? CHANNEL_KIND.PAID) === channelKind &&
|
|
30
|
+
c.status === CHANNEL_STATUS.ACTIVE)
|
|
31
|
+
.map(cloneChannel);
|
|
32
|
+
}
|
|
33
|
+
updateChannelStatus(sessionId, status, settledAmount) {
|
|
34
|
+
const channel = this.channels.get(sessionId);
|
|
35
|
+
if (!channel)
|
|
36
|
+
return;
|
|
37
|
+
this.channels.set(sessionId, {
|
|
38
|
+
...channel,
|
|
39
|
+
status,
|
|
40
|
+
settledAmount: settledAmount ?? channel.settledAmount,
|
|
41
|
+
settledAt: status === CHANNEL_STATUS.SETTLED ? Date.now() : channel.settledAt,
|
|
42
|
+
updatedAt: Date.now(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
replaceMetadataServiceTotals(sessionId, services = []) {
|
|
46
|
+
this.serviceTotals.set(sessionId, services.map(cloneService));
|
|
47
|
+
}
|
|
48
|
+
getChannelMetadata(channel) {
|
|
49
|
+
const services = (this.serviceTotals.get(channel.sessionId) ?? []).map(cloneService);
|
|
50
|
+
return {
|
|
51
|
+
cumulativeInputTokens: BigInt(channel.tokensDelivered),
|
|
52
|
+
cumulativeOutputTokens: BigInt(channel.previousConsumption),
|
|
53
|
+
cumulativeRequestCount: BigInt(channel.requestCount),
|
|
54
|
+
// StoredChannel has no top-level image column, so recover it from the
|
|
55
|
+
// per-service totals just like the SQLite channel store.
|
|
56
|
+
cumulativeOutputImages: services.reduce((sum, service) => sum + (service.cumulativeOutputImages ?? 0n), 0n),
|
|
57
|
+
services,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
commitAuthorization(channel, services = []) {
|
|
61
|
+
this.upsertChannel(channel);
|
|
62
|
+
this.replaceMetadataServiceTotals(channel.sessionId, services);
|
|
63
|
+
}
|
|
64
|
+
async flush() { }
|
|
65
|
+
latestMatching(predicate) {
|
|
66
|
+
let latest = null;
|
|
67
|
+
for (const channel of this.channels.values()) {
|
|
68
|
+
if (!predicate(channel))
|
|
69
|
+
continue;
|
|
70
|
+
if (!latest || channel.createdAt > latest.createdAt)
|
|
71
|
+
latest = channel;
|
|
72
|
+
}
|
|
73
|
+
return latest ? cloneChannel(latest) : null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const DATABASE_VERSION = 1;
|
|
77
|
+
const CHANNELS_STORE = 'channels';
|
|
78
|
+
const SERVICE_TOTALS_STORE = 'serviceTotals';
|
|
79
|
+
const SESSION_ID_INDEX = 'sessionId';
|
|
80
|
+
/**
|
|
81
|
+
* IndexedDB-backed store with a synchronous in-memory read model. Call
|
|
82
|
+
* `open()` before constructing BuyerPaymentManager so its hydration pass sees
|
|
83
|
+
* all committed channels. Writes are serialized, and authorization commits
|
|
84
|
+
* update the channel and service totals in one IndexedDB transaction.
|
|
85
|
+
*/
|
|
86
|
+
export class IndexedDbChannelStore extends MemoryChannelStore {
|
|
87
|
+
db;
|
|
88
|
+
onWriteError;
|
|
89
|
+
writeTail = Promise.resolve();
|
|
90
|
+
fatalWriteError = null;
|
|
91
|
+
closed = false;
|
|
92
|
+
constructor(db, onWriteError) {
|
|
93
|
+
super();
|
|
94
|
+
this.db = db;
|
|
95
|
+
this.onWriteError = onWriteError;
|
|
96
|
+
}
|
|
97
|
+
static async open(options) {
|
|
98
|
+
const factory = options.indexedDB ?? globalThis.indexedDB;
|
|
99
|
+
if (!factory)
|
|
100
|
+
throw new Error('IndexedDB unavailable');
|
|
101
|
+
const db = await openDatabase(factory, options.databaseName);
|
|
102
|
+
const store = new IndexedDbChannelStore(db, options.onWriteError);
|
|
103
|
+
await store.hydrate();
|
|
104
|
+
return store;
|
|
105
|
+
}
|
|
106
|
+
upsertChannel(channel) {
|
|
107
|
+
this.assertWritable();
|
|
108
|
+
const copy = cloneChannel(channel);
|
|
109
|
+
super.upsertChannel(copy);
|
|
110
|
+
this.enqueuePutChannel(copy);
|
|
111
|
+
}
|
|
112
|
+
updateChannelStatus(sessionId, status, settledAmount) {
|
|
113
|
+
this.assertWritable();
|
|
114
|
+
super.updateChannelStatus(sessionId, status, settledAmount);
|
|
115
|
+
const channel = this.channels.get(sessionId);
|
|
116
|
+
if (!channel)
|
|
117
|
+
return;
|
|
118
|
+
this.enqueuePutChannel(cloneChannel(channel));
|
|
119
|
+
}
|
|
120
|
+
replaceMetadataServiceTotals(sessionId, services = []) {
|
|
121
|
+
this.assertWritable();
|
|
122
|
+
const copies = services.map(cloneService);
|
|
123
|
+
super.replaceMetadataServiceTotals(sessionId, copies);
|
|
124
|
+
this.enqueue(() => this.replaceServiceRecords(sessionId, copies));
|
|
125
|
+
}
|
|
126
|
+
async commitAuthorization(channel, services = []) {
|
|
127
|
+
this.assertWritable();
|
|
128
|
+
const channelCopy = cloneChannel(channel);
|
|
129
|
+
const serviceCopies = services.map(cloneService);
|
|
130
|
+
super.upsertChannel(channelCopy);
|
|
131
|
+
super.replaceMetadataServiceTotals(channelCopy.sessionId, serviceCopies);
|
|
132
|
+
this.enqueue(async () => {
|
|
133
|
+
const tx = this.db.transaction([CHANNELS_STORE, SERVICE_TOTALS_STORE], 'readwrite');
|
|
134
|
+
const done = transactionDone(tx);
|
|
135
|
+
tx.objectStore(CHANNELS_STORE).put(channelCopy);
|
|
136
|
+
await replaceServiceRecordsInTransaction(tx, channelCopy.sessionId, serviceCopies);
|
|
137
|
+
await done;
|
|
138
|
+
});
|
|
139
|
+
await this.flush();
|
|
140
|
+
}
|
|
141
|
+
async flush() {
|
|
142
|
+
await this.writeTail;
|
|
143
|
+
if (this.fatalWriteError) {
|
|
144
|
+
throw new Error('IndexedDB channel persistence failed', { cause: this.fatalWriteError });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async close() {
|
|
148
|
+
if (this.closed)
|
|
149
|
+
return;
|
|
150
|
+
await this.flush();
|
|
151
|
+
this.closed = true;
|
|
152
|
+
this.db.close();
|
|
153
|
+
}
|
|
154
|
+
async hydrate() {
|
|
155
|
+
const tx = this.db.transaction([CHANNELS_STORE, SERVICE_TOTALS_STORE], 'readonly');
|
|
156
|
+
const done = transactionDone(tx);
|
|
157
|
+
const channelsRequest = requestResult(tx.objectStore(CHANNELS_STORE).getAll());
|
|
158
|
+
const totalsRequest = requestResult(tx.objectStore(SERVICE_TOTALS_STORE).getAll());
|
|
159
|
+
const [channels, totals] = await Promise.all([channelsRequest, totalsRequest]);
|
|
160
|
+
await done;
|
|
161
|
+
for (const channel of channels) {
|
|
162
|
+
super.upsertChannel(channel);
|
|
163
|
+
}
|
|
164
|
+
const grouped = new Map();
|
|
165
|
+
for (const record of totals) {
|
|
166
|
+
const services = grouped.get(record.sessionId) ?? [];
|
|
167
|
+
services.push(serviceFromRecord(record));
|
|
168
|
+
grouped.set(record.sessionId, services);
|
|
169
|
+
}
|
|
170
|
+
for (const [sessionId, services] of grouped) {
|
|
171
|
+
super.replaceMetadataServiceTotals(sessionId, services);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
replaceServiceRecords(sessionId, services) {
|
|
175
|
+
const tx = this.db.transaction(SERVICE_TOTALS_STORE, 'readwrite');
|
|
176
|
+
const done = transactionDone(tx);
|
|
177
|
+
return replaceServiceRecordsInTransaction(tx, sessionId, services)
|
|
178
|
+
.then(() => done);
|
|
179
|
+
}
|
|
180
|
+
enqueuePutChannel(copy) {
|
|
181
|
+
this.enqueue(async () => {
|
|
182
|
+
const tx = this.db.transaction(CHANNELS_STORE, 'readwrite');
|
|
183
|
+
const done = transactionDone(tx);
|
|
184
|
+
tx.objectStore(CHANNELS_STORE).put(copy);
|
|
185
|
+
await done;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
enqueue(operation) {
|
|
189
|
+
this.writeTail = this.writeTail
|
|
190
|
+
.then(() => {
|
|
191
|
+
if (this.fatalWriteError)
|
|
192
|
+
throw this.fatalWriteError;
|
|
193
|
+
return operation();
|
|
194
|
+
})
|
|
195
|
+
.catch((error) => {
|
|
196
|
+
if (this.fatalWriteError)
|
|
197
|
+
return;
|
|
198
|
+
this.fatalWriteError = error;
|
|
199
|
+
try {
|
|
200
|
+
this.onWriteError?.(error instanceof Error ? error : new Error(String(error)));
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Persistence observers are diagnostic only.
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
assertWritable() {
|
|
208
|
+
if (this.closed)
|
|
209
|
+
throw new Error('IndexedDB channel store is closed');
|
|
210
|
+
if (this.fatalWriteError) {
|
|
211
|
+
throw new Error('IndexedDB channel persistence failed', { cause: this.fatalWriteError });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function cloneChannel(channel) {
|
|
216
|
+
return { ...channel };
|
|
217
|
+
}
|
|
218
|
+
function cloneService(service) {
|
|
219
|
+
return { ...service };
|
|
220
|
+
}
|
|
221
|
+
function sameAddress(left, right) {
|
|
222
|
+
return left.toLowerCase() === right.toLowerCase();
|
|
223
|
+
}
|
|
224
|
+
function serviceToRecord(sessionId, service) {
|
|
225
|
+
return {
|
|
226
|
+
sessionId,
|
|
227
|
+
serviceId: service.serviceId,
|
|
228
|
+
cumulativeAmount: service.cumulativeAmount.toString(),
|
|
229
|
+
cumulativeInputTokens: service.cumulativeInputTokens.toString(),
|
|
230
|
+
cumulativeCachedInputTokens: service.cumulativeCachedInputTokens.toString(),
|
|
231
|
+
cumulativeOutputTokens: service.cumulativeOutputTokens.toString(),
|
|
232
|
+
cumulativeRequestCount: service.cumulativeRequestCount.toString(),
|
|
233
|
+
cumulativeOutputImages: (service.cumulativeOutputImages ?? 0n).toString(),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function serviceFromRecord(record) {
|
|
237
|
+
return {
|
|
238
|
+
serviceId: record.serviceId,
|
|
239
|
+
cumulativeAmount: BigInt(record.cumulativeAmount),
|
|
240
|
+
cumulativeInputTokens: BigInt(record.cumulativeInputTokens),
|
|
241
|
+
cumulativeCachedInputTokens: BigInt(record.cumulativeCachedInputTokens),
|
|
242
|
+
cumulativeOutputTokens: BigInt(record.cumulativeOutputTokens),
|
|
243
|
+
cumulativeRequestCount: BigInt(record.cumulativeRequestCount),
|
|
244
|
+
cumulativeOutputImages: BigInt(record.cumulativeOutputImages ?? '0'),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
async function replaceServiceRecordsInTransaction(tx, sessionId, services) {
|
|
248
|
+
const store = tx.objectStore(SERVICE_TOTALS_STORE);
|
|
249
|
+
const keys = await requestResult(store.index(SESSION_ID_INDEX).getAllKeys(sessionId));
|
|
250
|
+
for (const key of keys)
|
|
251
|
+
store.delete(key);
|
|
252
|
+
for (const service of services)
|
|
253
|
+
store.put(serviceToRecord(sessionId, service));
|
|
254
|
+
}
|
|
255
|
+
function openDatabase(factory, name) {
|
|
256
|
+
return new Promise((resolve, reject) => {
|
|
257
|
+
const request = factory.open(name, DATABASE_VERSION);
|
|
258
|
+
request.onupgradeneeded = () => {
|
|
259
|
+
const db = request.result;
|
|
260
|
+
if (!db.objectStoreNames.contains(CHANNELS_STORE)) {
|
|
261
|
+
db.createObjectStore(CHANNELS_STORE, { keyPath: 'sessionId' });
|
|
262
|
+
}
|
|
263
|
+
if (!db.objectStoreNames.contains(SERVICE_TOTALS_STORE)) {
|
|
264
|
+
const totals = db.createObjectStore(SERVICE_TOTALS_STORE, {
|
|
265
|
+
keyPath: ['sessionId', 'serviceId'],
|
|
266
|
+
});
|
|
267
|
+
totals.createIndex(SESSION_ID_INDEX, 'sessionId', { unique: false });
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
request.onsuccess = () => resolve(request.result);
|
|
271
|
+
request.onerror = () => reject(request.error ?? new Error('failed to open IndexedDB'));
|
|
272
|
+
request.onblocked = () => reject(new Error('IndexedDB upgrade blocked by another tab'));
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function requestResult(request) {
|
|
276
|
+
return new Promise((resolve, reject) => {
|
|
277
|
+
request.onsuccess = () => resolve(request.result);
|
|
278
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed'));
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function transactionDone(tx) {
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
tx.oncomplete = () => resolve();
|
|
284
|
+
tx.onerror = () => reject(tx.error ?? new Error('IndexedDB transaction failed'));
|
|
285
|
+
tx.onabort = () => reject(tx.error ?? new Error('IndexedDB transaction aborted'));
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=channel-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-store.js","sourceRoot":"","sources":["../src/channel-store.ts"],"names":[],"mappings":"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"}
|