@drakkar.software/octobot-client 0.4.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/README.md +140 -0
- package/dist/client/adapters/accounts.d.ts +66 -0
- package/dist/client/adapters/accounts.d.ts.map +1 -0
- package/dist/client/adapters/accounts.js +214 -0
- package/dist/client/adapters/accounts.js.map +1 -0
- package/dist/client/adapters/actionHandle.d.ts +29 -0
- package/dist/client/adapters/actionHandle.d.ts.map +1 -0
- package/dist/client/adapters/actionHandle.js +34 -0
- package/dist/client/adapters/actionHandle.js.map +1 -0
- package/dist/client/adapters/automations.d.ts +31 -0
- package/dist/client/adapters/automations.d.ts.map +1 -0
- package/dist/client/adapters/automations.js +79 -0
- package/dist/client/adapters/automations.js.map +1 -0
- package/dist/client/adapters/documents.d.ts +55 -0
- package/dist/client/adapters/documents.d.ts.map +1 -0
- package/dist/client/adapters/documents.js +66 -0
- package/dist/client/adapters/documents.js.map +1 -0
- package/dist/client/adapters/nodeApi.d.ts +25 -0
- package/dist/client/adapters/nodeApi.d.ts.map +1 -0
- package/dist/client/adapters/nodeApi.js +81 -0
- package/dist/client/adapters/nodeApi.js.map +1 -0
- package/dist/client/adapters/settings.d.ts +16 -0
- package/dist/client/adapters/settings.d.ts.map +1 -0
- package/dist/client/adapters/settings.js +43 -0
- package/dist/client/adapters/settings.js.map +1 -0
- package/dist/client/adapters/strategies.d.ts +17 -0
- package/dist/client/adapters/strategies.d.ts.map +1 -0
- package/dist/client/adapters/strategies.js +75 -0
- package/dist/client/adapters/strategies.js.map +1 -0
- package/dist/client/connect/connect.d.ts +40 -0
- package/dist/client/connect/connect.d.ts.map +1 -0
- package/dist/client/connect/connect.js +133 -0
- package/dist/client/connect/connect.js.map +1 -0
- package/dist/client/connect/readOnly.d.ts +97 -0
- package/dist/client/connect/readOnly.d.ts.map +1 -0
- package/dist/client/connect/readOnly.js +167 -0
- package/dist/client/connect/readOnly.js.map +1 -0
- package/dist/client/core/errors.d.ts +80 -0
- package/dist/client/core/errors.d.ts.map +1 -0
- package/dist/client/core/errors.js +154 -0
- package/dist/client/core/errors.js.map +1 -0
- package/dist/client/core/options.d.ts +44 -0
- package/dist/client/core/options.d.ts.map +1 -0
- package/dist/client/core/options.js +2 -0
- package/dist/client/core/options.js.map +1 -0
- package/dist/client/core/session.d.ts +90 -0
- package/dist/client/core/session.d.ts.map +1 -0
- package/dist/client/core/session.js +72 -0
- package/dist/client/core/session.js.map +1 -0
- package/dist/client/core/views.d.ts +40 -0
- package/dist/client/core/views.d.ts.map +1 -0
- package/dist/client/core/views.js +32 -0
- package/dist/client/core/views.js.map +1 -0
- package/dist/client/index.d.ts +16 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/mirror/collections.d.ts +125 -0
- package/dist/client/mirror/collections.d.ts.map +1 -0
- package/dist/client/mirror/collections.js +118 -0
- package/dist/client/mirror/collections.js.map +1 -0
- package/dist/client/mirror/index.d.ts +4 -0
- package/dist/client/mirror/index.d.ts.map +1 -0
- package/dist/client/mirror/index.js +4 -0
- package/dist/client/mirror/index.js.map +1 -0
- package/dist/client/mirror/writer.d.ts +39 -0
- package/dist/client/mirror/writer.d.ts.map +1 -0
- package/dist/client/mirror/writer.js +50 -0
- package/dist/client/mirror/writer.js.map +1 -0
- package/dist/client/pairing/mirrorGrant.d.ts +91 -0
- package/dist/client/pairing/mirrorGrant.d.ts.map +1 -0
- package/dist/client/pairing/mirrorGrant.js +99 -0
- package/dist/client/pairing/mirrorGrant.js.map +1 -0
- package/dist/client/pairing/mirrorReader.d.ts +99 -0
- package/dist/client/pairing/mirrorReader.d.ts.map +1 -0
- package/dist/client/pairing/mirrorReader.js +117 -0
- package/dist/client/pairing/mirrorReader.js.map +1 -0
- package/dist/client/pairing/pairingGrantExchange.d.ts +135 -0
- package/dist/client/pairing/pairingGrantExchange.d.ts.map +1 -0
- package/dist/client/pairing/pairingGrantExchange.js +186 -0
- package/dist/client/pairing/pairingGrantExchange.js.map +1 -0
- package/dist/client/pairing/pairingRequest.d.ts +82 -0
- package/dist/client/pairing/pairingRequest.d.ts.map +1 -0
- package/dist/client/pairing/pairingRequest.js +120 -0
- package/dist/client/pairing/pairingRequest.js.map +1 -0
- package/dist/client/strategy.d.ts +36 -0
- package/dist/client/strategy.d.ts.map +1 -0
- package/dist/client/strategy.js +26 -0
- package/dist/client/strategy.js.map +1 -0
- package/dist/collections/index.d.ts +3 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +3 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/collections/nodeCollections.d.ts +57 -0
- package/dist/collections/nodeCollections.d.ts.map +1 -0
- package/dist/collections/nodeCollections.js +40 -0
- package/dist/collections/nodeCollections.js.map +1 -0
- package/dist/collections/paths.d.ts +7 -0
- package/dist/collections/paths.d.ts.map +1 -0
- package/dist/collections/paths.js +13 -0
- package/dist/collections/paths.js.map +1 -0
- package/dist/crypto/collectionKeys.d.ts +28 -0
- package/dist/crypto/collectionKeys.d.ts.map +1 -0
- package/dist/crypto/collectionKeys.js +53 -0
- package/dist/crypto/collectionKeys.js.map +1 -0
- package/dist/crypto/hkdf.d.ts +14 -0
- package/dist/crypto/hkdf.d.ts.map +1 -0
- package/dist/crypto/hkdf.js +19 -0
- package/dist/crypto/hkdf.js.map +1 -0
- package/dist/crypto/index.d.ts +5 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +5 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/secretEncryptor.d.ts +20 -0
- package/dist/crypto/secretEncryptor.d.ts.map +1 -0
- package/dist/crypto/secretEncryptor.js +82 -0
- package/dist/crypto/secretEncryptor.js.map +1 -0
- package/dist/crypto/wireConstants.d.ts +14 -0
- package/dist/crypto/wireConstants.d.ts.map +1 -0
- package/dist/crypto/wireConstants.js +17 -0
- package/dist/crypto/wireConstants.js.map +1 -0
- package/dist/identity/address.d.ts +3 -0
- package/dist/identity/address.d.ts.map +1 -0
- package/dist/identity/address.js +17 -0
- package/dist/identity/address.js.map +1 -0
- package/dist/identity/bip39-wordlist.d.ts +2 -0
- package/dist/identity/bip39-wordlist.d.ts.map +1 -0
- package/dist/identity/bip39-wordlist.js +2051 -0
- package/dist/identity/bip39-wordlist.js.map +1 -0
- package/dist/identity/capProvider.d.ts +45 -0
- package/dist/identity/capProvider.d.ts.map +1 -0
- package/dist/identity/capProvider.js +80 -0
- package/dist/identity/capProvider.js.map +1 -0
- package/dist/identity/derivationSchemes.d.ts +25 -0
- package/dist/identity/derivationSchemes.d.ts.map +1 -0
- package/dist/identity/derivationSchemes.js +34 -0
- package/dist/identity/derivationSchemes.js.map +1 -0
- package/dist/identity/evm.d.ts +8 -0
- package/dist/identity/evm.d.ts.map +1 -0
- package/dist/identity/evm.js +78 -0
- package/dist/identity/evm.js.map +1 -0
- package/dist/identity/index.d.ts +10 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +10 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/keys.d.ts +17 -0
- package/dist/identity/keys.d.ts.map +1 -0
- package/dist/identity/keys.js +36 -0
- package/dist/identity/keys.js.map +1 -0
- package/dist/identity/mnemonic.d.ts +4 -0
- package/dist/identity/mnemonic.d.ts.map +1 -0
- package/dist/identity/mnemonic.js +93 -0
- package/dist/identity/mnemonic.js.map +1 -0
- package/dist/identity/pairing.d.ts +45 -0
- package/dist/identity/pairing.d.ts.map +1 -0
- package/dist/identity/pairing.js +91 -0
- package/dist/identity/pairing.js.map +1 -0
- package/dist/identity/pairingRequest.d.ts +99 -0
- package/dist/identity/pairingRequest.d.ts.map +1 -0
- package/dist/identity/pairingRequest.js +217 -0
- package/dist/identity/pairingRequest.js.map +1 -0
- package/dist/identity/secret.d.ts +32 -0
- package/dist/identity/secret.d.ts.map +1 -0
- package/dist/identity/secret.js +54 -0
- package/dist/identity/secret.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/bytes.d.ts +11 -0
- package/dist/internal/bytes.d.ts.map +1 -0
- package/dist/internal/bytes.js +38 -0
- package/dist/internal/bytes.js.map +1 -0
- package/dist/node-api/dexPair.d.ts +24 -0
- package/dist/node-api/dexPair.d.ts.map +1 -0
- package/dist/node-api/dexPair.js +39 -0
- package/dist/node-api/dexPair.js.map +1 -0
- package/dist/node-api/dsl.d.ts +10 -0
- package/dist/node-api/dsl.d.ts.map +1 -0
- package/dist/node-api/dsl.js +7 -0
- package/dist/node-api/dsl.js.map +1 -0
- package/dist/node-api/exchanges.d.ts +49 -0
- package/dist/node-api/exchanges.d.ts.map +1 -0
- package/dist/node-api/exchanges.js +32 -0
- package/dist/node-api/exchanges.js.map +1 -0
- package/dist/node-api/index.d.ts +12 -0
- package/dist/node-api/index.d.ts.map +1 -0
- package/dist/node-api/index.js +12 -0
- package/dist/node-api/index.js.map +1 -0
- package/dist/node-api/marketMaking.d.ts +39 -0
- package/dist/node-api/marketMaking.d.ts.map +1 -0
- package/dist/node-api/marketMaking.js +44 -0
- package/dist/node-api/marketMaking.js.map +1 -0
- package/dist/node-api/octobots.d.ts +14 -0
- package/dist/node-api/octobots.d.ts.map +1 -0
- package/dist/node-api/octobots.js +16 -0
- package/dist/node-api/octobots.js.map +1 -0
- package/dist/node-api/orderBookPreview.d.ts +11 -0
- package/dist/node-api/orderBookPreview.d.ts.map +1 -0
- package/dist/node-api/orderBookPreview.js +7 -0
- package/dist/node-api/orderBookPreview.js.map +1 -0
- package/dist/node-api/pairing.d.ts +39 -0
- package/dist/node-api/pairing.d.ts.map +1 -0
- package/dist/node-api/pairing.js +61 -0
- package/dist/node-api/pairing.js.map +1 -0
- package/dist/node-api/pairs.d.ts +32 -0
- package/dist/node-api/pairs.d.ts.map +1 -0
- package/dist/node-api/pairs.js +72 -0
- package/dist/node-api/pairs.js.map +1 -0
- package/dist/node-api/requiredFunds.d.ts +11 -0
- package/dist/node-api/requiredFunds.d.ts.map +1 -0
- package/dist/node-api/requiredFunds.js +7 -0
- package/dist/node-api/requiredFunds.js.map +1 -0
- package/dist/node-api/setup.d.ts +25 -0
- package/dist/node-api/setup.d.ts.map +1 -0
- package/dist/node-api/setup.js +13 -0
- package/dist/node-api/setup.js.map +1 -0
- package/dist/node-api/wallet.d.ts +55 -0
- package/dist/node-api/wallet.d.ts.map +1 -0
- package/dist/node-api/wallet.js +87 -0
- package/dist/node-api/wallet.js.map +1 -0
- package/dist/protocol/actions.d.ts +40 -0
- package/dist/protocol/actions.d.ts.map +1 -0
- package/dist/protocol/actions.js +118 -0
- package/dist/protocol/actions.js.map +1 -0
- package/dist/protocol/documents.d.ts +25 -0
- package/dist/protocol/documents.d.ts.map +1 -0
- package/dist/protocol/documents.js +2 -0
- package/dist/protocol/documents.js.map +1 -0
- package/dist/protocol/index.d.ts +10 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +12 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/orchestration/createAutomation.d.ts +61 -0
- package/dist/protocol/orchestration/createAutomation.d.ts.map +1 -0
- package/dist/protocol/orchestration/createAutomation.js +71 -0
- package/dist/protocol/orchestration/createAutomation.js.map +1 -0
- package/dist/protocol/poll.d.ts +4 -0
- package/dist/protocol/poll.d.ts.map +1 -0
- package/dist/protocol/poll.js +4 -0
- package/dist/protocol/poll.js.map +1 -0
- package/dist/protocol/proposal.d.ts +36 -0
- package/dist/protocol/proposal.d.ts.map +1 -0
- package/dist/protocol/proposal.js +39 -0
- package/dist/protocol/proposal.js.map +1 -0
- package/dist/protocol/state.d.ts +87 -0
- package/dist/protocol/state.d.ts.map +1 -0
- package/dist/protocol/state.js +303 -0
- package/dist/protocol/state.js.map +1 -0
- package/dist/protocol/strategy/build.d.ts +44 -0
- package/dist/protocol/strategy/build.d.ts.map +1 -0
- package/dist/protocol/strategy/build.js +50 -0
- package/dist/protocol/strategy/build.js.map +1 -0
- package/dist/protocol/strategy/builders.d.ts +252 -0
- package/dist/protocol/strategy/builders.d.ts.map +1 -0
- package/dist/protocol/strategy/builders.js +382 -0
- package/dist/protocol/strategy/builders.js.map +1 -0
- package/dist/protocol/strategy/index.d.ts +5 -0
- package/dist/protocol/strategy/index.d.ts.map +1 -0
- package/dist/protocol/strategy/index.js +4 -0
- package/dist/protocol/strategy/index.js.map +1 -0
- package/dist/protocol/strategy/kinds.d.ts +2 -0
- package/dist/protocol/strategy/kinds.d.ts.map +1 -0
- package/dist/protocol/strategy/kinds.js +2 -0
- package/dist/protocol/strategy/kinds.js.map +1 -0
- package/dist/protocol/strategy/patch.d.ts +59 -0
- package/dist/protocol/strategy/patch.d.ts.map +1 -0
- package/dist/protocol/strategy/patch.js +253 -0
- package/dist/protocol/strategy/patch.js.map +1 -0
- package/dist/protocol/symbols.d.ts +8 -0
- package/dist/protocol/symbols.d.ts.map +1 -0
- package/dist/protocol/symbols.js +11 -0
- package/dist/protocol/symbols.js.map +1 -0
- package/dist/transport/constants.d.ts +8 -0
- package/dist/transport/constants.d.ts.map +1 -0
- package/dist/transport/constants.js +8 -0
- package/dist/transport/constants.js.map +1 -0
- package/dist/transport/documents.d.ts +27 -0
- package/dist/transport/documents.d.ts.map +1 -0
- package/dist/transport/documents.js +34 -0
- package/dist/transport/documents.js.map +1 -0
- package/dist/transport/index.d.ts +8 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +8 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/probe.d.ts +44 -0
- package/dist/transport/probe.d.ts.map +1 -0
- package/dist/transport/probe.js +121 -0
- package/dist/transport/probe.js.map +1 -0
- package/dist/transport/rendezvous.d.ts +116 -0
- package/dist/transport/rendezvous.d.ts.map +1 -0
- package/dist/transport/rendezvous.js +139 -0
- package/dist/transport/rendezvous.js.map +1 -0
- package/dist/transport/rest.d.ts +35 -0
- package/dist/transport/rest.d.ts.map +1 -0
- package/dist/transport/rest.js +58 -0
- package/dist/transport/rest.js.map +1 -0
- package/dist/transport/syncClient.d.ts +14 -0
- package/dist/transport/syncClient.d.ts.map +1 -0
- package/dist/transport/syncClient.js +30 -0
- package/dist/transport/syncClient.js.map +1 -0
- package/dist/transport/urls.d.ts +35 -0
- package/dist/transport/urls.d.ts.map +1 -0
- package/dist/transport/urls.js +126 -0
- package/dist/transport/urls.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { StarfishClient } from '@drakkar.software/starfish-client';
|
|
2
|
+
/** Storage path for a device-code pairing exchange — must match the
|
|
3
|
+
* `joinsessions` collection's `storagePath` template
|
|
4
|
+
* (`_pairing/session/{code}`, defined in
|
|
5
|
+
* `Infra/sync/server/drakkar_sync/apps/dk_spaces/collections.py`). ONE
|
|
6
|
+
* address serves BOTH phases of the exchange: the website's discovery
|
|
7
|
+
* "request" doc, and the phone's delivery "grant" doc that later overwrites
|
|
8
|
+
* it — keyed throughout by the same short human-typeable `code` (see
|
|
9
|
+
* `client/pairing/pairingRequest.ts` and `pairingGrantExchange.ts` for how
|
|
10
|
+
* the two phases are told apart on the wire). Replaces the retired
|
|
11
|
+
* two-collection, two-address design (`pairingrequests`/`pairingsnapshots`,
|
|
12
|
+
* a low-entropy code address plus a separate high-entropy session
|
|
13
|
+
* address) — that split bought no real confidentiality the grant's own
|
|
14
|
+
* sealing didn't already provide, only lifecycle bookkeeping this merge no
|
|
15
|
+
* longer needs. */
|
|
16
|
+
export declare function joinSessionPath(code: string): string;
|
|
17
|
+
export interface RendezvousDoc {
|
|
18
|
+
data: Record<string, unknown>;
|
|
19
|
+
hash: string;
|
|
20
|
+
}
|
|
21
|
+
/** A cap-less `StarfishClient` against a public rendezvous collection — a
|
|
22
|
+
* different namespace/host from the node client `createSyncClient` builds
|
|
23
|
+
* (the rendezvous lives on the shared Drakkar sync server, not the user's
|
|
24
|
+
* own node), and deliberately carries no `capProvider`: every request is
|
|
25
|
+
* anonymous, matching the deployed `joinsessions` collection's `public`
|
|
26
|
+
* read/write roles. */
|
|
27
|
+
export declare function createRendezvousClient(opts: {
|
|
28
|
+
baseUrl: string;
|
|
29
|
+
namespace: string;
|
|
30
|
+
fetch?: typeof fetch;
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
}): StarfishClient;
|
|
33
|
+
/** Pull a rendezvous document. Returns `null` when nothing has been
|
|
34
|
+
* published to this slot yet — an unwritten collection document pulls with
|
|
35
|
+
* `data` as the STRING `"null"` (an observed real node's behavior for the
|
|
36
|
+
* standard collection pull path; this rendezvous server is a different,
|
|
37
|
+
* not-yet-deployed deployment, so a raw `null` or an empty `{}` are handled
|
|
38
|
+
* too rather than assumed away), not a 404 — so a nullish/empty `data` is
|
|
39
|
+
* the actual not-found signal here, not a thrown error. */
|
|
40
|
+
export declare function pullRendezvousDoc(client: StarfishClient, path: string): Promise<RendezvousDoc | null>;
|
|
41
|
+
/** Push a rendezvous document against an EXPLICIT `baseHash` — the caller's
|
|
42
|
+
* OWN remembered hash from its last successful write to this path, or
|
|
43
|
+
* `null` for a genuinely fresh slot nothing has been published to yet.
|
|
44
|
+
* Reimplements what `starfish-identities`' `pushPairingBundle` does
|
|
45
|
+
* internally, because that helper is hard-typed to a `PairingBundle` shape
|
|
46
|
+
* (`{capCert, rootEdPub, wrappedCEKs}`) this package's rendezvous documents
|
|
47
|
+
* don't have, and its sibling `fetchPairingBundle` returns `null` for
|
|
48
|
+
* anything without a top-level `capCert` — a document shaped like ours
|
|
49
|
+
* would never be recognized by it.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately does NOT re-pull and adopt whatever's currently at `path` on
|
|
52
|
+
* a conflict (the previous version of this function did exactly that,
|
|
53
|
+
* retrying against the server's current hash) — that behavior is what let
|
|
54
|
+
* a hostile overwrite of this slot go undetected forever, including by the
|
|
55
|
+
* original publisher's own next write, which would silently treat the
|
|
56
|
+
* attacker's content as the new legitimate baseline. A `ConflictError` here
|
|
57
|
+
* means the document changed since THIS CALLER last wrote it, which the
|
|
58
|
+
* caller should treat as "this slot may have been tampered with", not
|
|
59
|
+
* retry past. Returns the resulting hash so the caller can remember it for
|
|
60
|
+
* its next write.
|
|
61
|
+
*
|
|
62
|
+
* This is still inherently last-write-wins at the slot level, not
|
|
63
|
+
* first-writer-wins — a concurrent anonymous writer who wins the race for
|
|
64
|
+
* a GENUINELY fresh slot (`baseHash: null`) is indistinguishable from the
|
|
65
|
+
* legitimate first writer. The defense against that is the human-typeable
|
|
66
|
+
* `code`'s entropy plus the collection's per-IP rate limit, which bound how
|
|
67
|
+
* fast an unknown code can be guessed — NOT address secrecy (this
|
|
68
|
+
* collection is public-read, so anyone who knows or guesses a code can
|
|
69
|
+
* read whatever is currently published there regardless of write history).
|
|
70
|
+
* What THIS change adds is detecting tampering on every write AFTER the
|
|
71
|
+
* first, which the previous blind-retry behavior could never do because it
|
|
72
|
+
* always treated "conflict" as "let me just adopt whatever's there now".
|
|
73
|
+
* It does NOT stop a writer who first PULLS the slot (learning its current
|
|
74
|
+
* hash, which is public information) and then pushes with that hash as
|
|
75
|
+
* `baseHash` — that is a legitimate-looking, non-blind overwrite as far as
|
|
76
|
+
* this primitive is concerned; the real protection against that shape of
|
|
77
|
+
* attack is `code`-bound `popSig` verification (see
|
|
78
|
+
* `identity/pairingRequest.ts`) rejecting any replacement request that
|
|
79
|
+
* isn't signed by keys the ORIGINAL requester controls, and the grant
|
|
80
|
+
* phase's own `baseHash` CAS against the exact request hash it read. */
|
|
81
|
+
export declare function pushRendezvousDoc(client: StarfishClient, path: string, data: Record<string, unknown>, baseHash: string | null): Promise<{
|
|
82
|
+
hash: string;
|
|
83
|
+
}>;
|
|
84
|
+
/** Overwrite a slot with an empty document, regardless of its current
|
|
85
|
+
* content — the one place blind-overwrite-and-retry semantics are still
|
|
86
|
+
* correct: unpair/cleanup must succeed even without a remembered hash
|
|
87
|
+
* (e.g. in-memory state was lost across a page reload), and "did someone
|
|
88
|
+
* else already overwrite this" isn't a meaningful question when the
|
|
89
|
+
* caller's whole intent is "nothing should be published here anymore".
|
|
90
|
+
* The deployed `joinsessions` collection's `ttl_ms` is an outer backstop,
|
|
91
|
+
* not a substitute for actually clearing on unpair — see the Infra
|
|
92
|
+
* collection's doc comment. Because request and grant now share one
|
|
93
|
+
* address, clearing this slot clears BOTH the original request and any
|
|
94
|
+
* published grant together — there's no way to clear one but keep the
|
|
95
|
+
* other.
|
|
96
|
+
*
|
|
97
|
+
* Deliberately pulls the RAW `client.pull()` result here, not
|
|
98
|
+
* `pullRendezvousDoc` — that function collapses an existing-but-empty doc
|
|
99
|
+
* (`{}`, which is exactly what THIS function itself writes) down to `null`,
|
|
100
|
+
* discarding its real hash. Deriving `baseHash` from that collapsed `null`
|
|
101
|
+
* would push `baseHash: null` against a slot that DOES have a hash — a
|
|
102
|
+
* guaranteed conflict, identically, on every one of the retries below.
|
|
103
|
+
* That made clearing an already-cleared slot (a benign double-unpair, or
|
|
104
|
+
* the very recovery path this package documents: unpair then re-approve)
|
|
105
|
+
* permanently fail. The raw hash exists whether or not the data is
|
|
106
|
+
* "empty", so blind-overwrite-and-retry works correctly in every case.
|
|
107
|
+
*
|
|
108
|
+
* Returns the resulting hash — same shape as `pushRendezvousDoc` — so a
|
|
109
|
+
* caller that legitimately needs to publish again right after clearing
|
|
110
|
+
* (e.g. re-approving the same session) can use it as its next `baseHash`
|
|
111
|
+
* instead of `null`, which would otherwise conflict against the very
|
|
112
|
+
* document this call just wrote. */
|
|
113
|
+
export declare function clearRendezvousDoc(client: StarfishClient, path: string): Promise<{
|
|
114
|
+
hash: string;
|
|
115
|
+
}>;
|
|
116
|
+
//# sourceMappingURL=rendezvous.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rendezvous.d.ts","sourceRoot":"","sources":["../../src/transport/rendezvous.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAiB,MAAM,mCAAmC,CAAA;AAOjF;;;;;;;;;;;;;oBAaoB;AACpB,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;wBAKwB;AACxB,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,cAAc,CAMjB;AAED;;;;;;4DAM4D;AAC5D,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAM3G;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEAuCyE;AACzE,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAE3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;qCA4BqC;AACrC,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAYxG"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { StarfishClient, ConflictError } from '@drakkar.software/starfish-client';
|
|
2
|
+
import { createTimeoutFetch } from './syncClient.js';
|
|
3
|
+
import { pullPath, pushPath } from '../collections/paths.js';
|
|
4
|
+
import { SYNC_FETCH_TIMEOUT_MS } from '../crypto/wireConstants.js';
|
|
5
|
+
const MAX_PUSH_RETRIES = 3;
|
|
6
|
+
/** Storage path for a device-code pairing exchange — must match the
|
|
7
|
+
* `joinsessions` collection's `storagePath` template
|
|
8
|
+
* (`_pairing/session/{code}`, defined in
|
|
9
|
+
* `Infra/sync/server/drakkar_sync/apps/dk_spaces/collections.py`). ONE
|
|
10
|
+
* address serves BOTH phases of the exchange: the website's discovery
|
|
11
|
+
* "request" doc, and the phone's delivery "grant" doc that later overwrites
|
|
12
|
+
* it — keyed throughout by the same short human-typeable `code` (see
|
|
13
|
+
* `client/pairing/pairingRequest.ts` and `pairingGrantExchange.ts` for how
|
|
14
|
+
* the two phases are told apart on the wire). Replaces the retired
|
|
15
|
+
* two-collection, two-address design (`pairingrequests`/`pairingsnapshots`,
|
|
16
|
+
* a low-entropy code address plus a separate high-entropy session
|
|
17
|
+
* address) — that split bought no real confidentiality the grant's own
|
|
18
|
+
* sealing didn't already provide, only lifecycle bookkeeping this merge no
|
|
19
|
+
* longer needs. */
|
|
20
|
+
export function joinSessionPath(code) {
|
|
21
|
+
return `_pairing/session/${encodeURIComponent(code)}`;
|
|
22
|
+
}
|
|
23
|
+
/** A cap-less `StarfishClient` against a public rendezvous collection — a
|
|
24
|
+
* different namespace/host from the node client `createSyncClient` builds
|
|
25
|
+
* (the rendezvous lives on the shared Drakkar sync server, not the user's
|
|
26
|
+
* own node), and deliberately carries no `capProvider`: every request is
|
|
27
|
+
* anonymous, matching the deployed `joinsessions` collection's `public`
|
|
28
|
+
* read/write roles. */
|
|
29
|
+
export function createRendezvousClient(opts) {
|
|
30
|
+
return new StarfishClient({
|
|
31
|
+
baseUrl: opts.baseUrl,
|
|
32
|
+
namespace: opts.namespace,
|
|
33
|
+
fetch: createTimeoutFetch(opts.timeoutMs ?? SYNC_FETCH_TIMEOUT_MS, opts.fetch ?? globalThis.fetch),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** Pull a rendezvous document. Returns `null` when nothing has been
|
|
37
|
+
* published to this slot yet — an unwritten collection document pulls with
|
|
38
|
+
* `data` as the STRING `"null"` (an observed real node's behavior for the
|
|
39
|
+
* standard collection pull path; this rendezvous server is a different,
|
|
40
|
+
* not-yet-deployed deployment, so a raw `null` or an empty `{}` are handled
|
|
41
|
+
* too rather than assumed away), not a 404 — so a nullish/empty `data` is
|
|
42
|
+
* the actual not-found signal here, not a thrown error. */
|
|
43
|
+
export async function pullRendezvousDoc(client, path) {
|
|
44
|
+
const result = await client.pull(pullPath(path, {}));
|
|
45
|
+
const parsed = typeof result.data === 'string' ? JSON.parse(result.data) : result.data;
|
|
46
|
+
if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
47
|
+
return null;
|
|
48
|
+
if (Object.keys(parsed).length === 0)
|
|
49
|
+
return null;
|
|
50
|
+
return { data: parsed, hash: result.hash };
|
|
51
|
+
}
|
|
52
|
+
/** Push a rendezvous document against an EXPLICIT `baseHash` — the caller's
|
|
53
|
+
* OWN remembered hash from its last successful write to this path, or
|
|
54
|
+
* `null` for a genuinely fresh slot nothing has been published to yet.
|
|
55
|
+
* Reimplements what `starfish-identities`' `pushPairingBundle` does
|
|
56
|
+
* internally, because that helper is hard-typed to a `PairingBundle` shape
|
|
57
|
+
* (`{capCert, rootEdPub, wrappedCEKs}`) this package's rendezvous documents
|
|
58
|
+
* don't have, and its sibling `fetchPairingBundle` returns `null` for
|
|
59
|
+
* anything without a top-level `capCert` — a document shaped like ours
|
|
60
|
+
* would never be recognized by it.
|
|
61
|
+
*
|
|
62
|
+
* Deliberately does NOT re-pull and adopt whatever's currently at `path` on
|
|
63
|
+
* a conflict (the previous version of this function did exactly that,
|
|
64
|
+
* retrying against the server's current hash) — that behavior is what let
|
|
65
|
+
* a hostile overwrite of this slot go undetected forever, including by the
|
|
66
|
+
* original publisher's own next write, which would silently treat the
|
|
67
|
+
* attacker's content as the new legitimate baseline. A `ConflictError` here
|
|
68
|
+
* means the document changed since THIS CALLER last wrote it, which the
|
|
69
|
+
* caller should treat as "this slot may have been tampered with", not
|
|
70
|
+
* retry past. Returns the resulting hash so the caller can remember it for
|
|
71
|
+
* its next write.
|
|
72
|
+
*
|
|
73
|
+
* This is still inherently last-write-wins at the slot level, not
|
|
74
|
+
* first-writer-wins — a concurrent anonymous writer who wins the race for
|
|
75
|
+
* a GENUINELY fresh slot (`baseHash: null`) is indistinguishable from the
|
|
76
|
+
* legitimate first writer. The defense against that is the human-typeable
|
|
77
|
+
* `code`'s entropy plus the collection's per-IP rate limit, which bound how
|
|
78
|
+
* fast an unknown code can be guessed — NOT address secrecy (this
|
|
79
|
+
* collection is public-read, so anyone who knows or guesses a code can
|
|
80
|
+
* read whatever is currently published there regardless of write history).
|
|
81
|
+
* What THIS change adds is detecting tampering on every write AFTER the
|
|
82
|
+
* first, which the previous blind-retry behavior could never do because it
|
|
83
|
+
* always treated "conflict" as "let me just adopt whatever's there now".
|
|
84
|
+
* It does NOT stop a writer who first PULLS the slot (learning its current
|
|
85
|
+
* hash, which is public information) and then pushes with that hash as
|
|
86
|
+
* `baseHash` — that is a legitimate-looking, non-blind overwrite as far as
|
|
87
|
+
* this primitive is concerned; the real protection against that shape of
|
|
88
|
+
* attack is `code`-bound `popSig` verification (see
|
|
89
|
+
* `identity/pairingRequest.ts`) rejecting any replacement request that
|
|
90
|
+
* isn't signed by keys the ORIGINAL requester controls, and the grant
|
|
91
|
+
* phase's own `baseHash` CAS against the exact request hash it read. */
|
|
92
|
+
export async function pushRendezvousDoc(client, path, data, baseHash) {
|
|
93
|
+
return client.push(pushPath(path, {}), data, baseHash);
|
|
94
|
+
}
|
|
95
|
+
/** Overwrite a slot with an empty document, regardless of its current
|
|
96
|
+
* content — the one place blind-overwrite-and-retry semantics are still
|
|
97
|
+
* correct: unpair/cleanup must succeed even without a remembered hash
|
|
98
|
+
* (e.g. in-memory state was lost across a page reload), and "did someone
|
|
99
|
+
* else already overwrite this" isn't a meaningful question when the
|
|
100
|
+
* caller's whole intent is "nothing should be published here anymore".
|
|
101
|
+
* The deployed `joinsessions` collection's `ttl_ms` is an outer backstop,
|
|
102
|
+
* not a substitute for actually clearing on unpair — see the Infra
|
|
103
|
+
* collection's doc comment. Because request and grant now share one
|
|
104
|
+
* address, clearing this slot clears BOTH the original request and any
|
|
105
|
+
* published grant together — there's no way to clear one but keep the
|
|
106
|
+
* other.
|
|
107
|
+
*
|
|
108
|
+
* Deliberately pulls the RAW `client.pull()` result here, not
|
|
109
|
+
* `pullRendezvousDoc` — that function collapses an existing-but-empty doc
|
|
110
|
+
* (`{}`, which is exactly what THIS function itself writes) down to `null`,
|
|
111
|
+
* discarding its real hash. Deriving `baseHash` from that collapsed `null`
|
|
112
|
+
* would push `baseHash: null` against a slot that DOES have a hash — a
|
|
113
|
+
* guaranteed conflict, identically, on every one of the retries below.
|
|
114
|
+
* That made clearing an already-cleared slot (a benign double-unpair, or
|
|
115
|
+
* the very recovery path this package documents: unpair then re-approve)
|
|
116
|
+
* permanently fail. The raw hash exists whether or not the data is
|
|
117
|
+
* "empty", so blind-overwrite-and-retry works correctly in every case.
|
|
118
|
+
*
|
|
119
|
+
* Returns the resulting hash — same shape as `pushRendezvousDoc` — so a
|
|
120
|
+
* caller that legitimately needs to publish again right after clearing
|
|
121
|
+
* (e.g. re-approving the same session) can use it as its next `baseHash`
|
|
122
|
+
* instead of `null`, which would otherwise conflict against the very
|
|
123
|
+
* document this call just wrote. */
|
|
124
|
+
export async function clearRendezvousDoc(client, path) {
|
|
125
|
+
let lastConflict;
|
|
126
|
+
for (let attempt = 0; attempt < MAX_PUSH_RETRIES; attempt++) {
|
|
127
|
+
const current = await client.pull(pullPath(path, {}));
|
|
128
|
+
try {
|
|
129
|
+
return await client.push(pushPath(path, {}), {}, current.hash);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
if (!(err instanceof ConflictError))
|
|
133
|
+
throw err;
|
|
134
|
+
lastConflict = err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
throw new Error(`clearRendezvousDoc: too many baseHash conflicts at ${path}`, { cause: lastConflict });
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=rendezvous.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rendezvous.js","sourceRoot":"","sources":["../../src/transport/rendezvous.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type NodeEndpoint } from './urls.js';
|
|
2
|
+
/** A node answered, but with a non-2xx status. Carries the status so callers
|
|
3
|
+
* can branch on it (e.g. 501 = the node can't fetch volumes for this
|
|
4
|
+
* exchange) instead of parsing the message. */
|
|
5
|
+
export declare class NodeHttpError extends Error {
|
|
6
|
+
readonly status: number;
|
|
7
|
+
constructor(status: number);
|
|
8
|
+
}
|
|
9
|
+
/** HTTP Basic credentials for a node's REST API: the EVM address is the
|
|
10
|
+
* username (see the node's `CurrentUser` dependency). Only a node paired by an
|
|
11
|
+
* older QR has these — a current pairing code carries the wallet instead of a
|
|
12
|
+
* password, so nothing authenticates on its behalf. */
|
|
13
|
+
export type NodeCredentials = {
|
|
14
|
+
address: string;
|
|
15
|
+
password: string;
|
|
16
|
+
};
|
|
17
|
+
/** Unauthenticated `GET` against a node endpoint, combining an internal
|
|
18
|
+
* timeout with an optional caller `AbortSignal`. Throws `NodeHttpError` on a
|
|
19
|
+
* non-2xx answer. */
|
|
20
|
+
export declare function nodeRequest<T>(node: NodeEndpoint, path: string, options?: {
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
fetch?: typeof fetch;
|
|
24
|
+
}): Promise<T>;
|
|
25
|
+
/** Authenticated call against a node endpoint (`path` is relative to
|
|
26
|
+
* `/api/v1`). Same internal timeout + caller-abort plumbing as `nodeRequest`,
|
|
27
|
+
* and the same `NodeHttpError` on a non-2xx answer. */
|
|
28
|
+
export declare function nodeAuthRequest<T>(node: NodeEndpoint, credentials: NodeCredentials, path: string, options?: {
|
|
29
|
+
method?: string;
|
|
30
|
+
body?: unknown;
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
fetch?: typeof fetch;
|
|
34
|
+
}): Promise<T>;
|
|
35
|
+
//# sourceMappingURL=rest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../../src/transport/rest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,WAAW,CAAA;AAG1D;;gDAEgD;AAChD,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;gBACX,MAAM,EAAE,MAAM;CAK3B;AAED;;;wDAGwD;AACxD,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AA8BnE;;sBAEsB;AACtB,wBAAsB,WAAW,CAAC,CAAC,EACjC,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CAAO,GAC/E,OAAO,CAAC,CAAC,CAAC,CAEZ;AAED;;wDAEwD;AACxD,wBAAsB,eAAe,CAAC,CAAC,EACrC,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,eAAe,EAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CAAO,GAChH,OAAO,CAAC,CAAC,CAAC,CAcZ"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { nodeBaseUrl } from './urls.js';
|
|
2
|
+
import { EXCHANGES_TIMEOUT_MS } from './constants.js';
|
|
3
|
+
/** A node answered, but with a non-2xx status. Carries the status so callers
|
|
4
|
+
* can branch on it (e.g. 501 = the node can't fetch volumes for this
|
|
5
|
+
* exchange) instead of parsing the message. */
|
|
6
|
+
export class NodeHttpError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
constructor(status) {
|
|
9
|
+
super(`Node request failed: HTTP ${status}`);
|
|
10
|
+
this.name = 'NodeHttpError';
|
|
11
|
+
this.status = status;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function basicAuthHeader({ address, password }) {
|
|
15
|
+
return `Basic ${btoa(`${address}:${password}`)}`;
|
|
16
|
+
}
|
|
17
|
+
/** Shared timeout/abort plumbing for `nodeRequest`/`nodeAuthRequest`: combines
|
|
18
|
+
* an internal timeout with an optional caller `AbortSignal` into one signal,
|
|
19
|
+
* fetches, and throws `NodeHttpError` on a non-2xx answer. */
|
|
20
|
+
async function fetchNodeJson(node, path, init, options) {
|
|
21
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
22
|
+
const internalController = new AbortController();
|
|
23
|
+
const timer = setTimeout(() => internalController.abort(), options.timeoutMs ?? EXCHANGES_TIMEOUT_MS);
|
|
24
|
+
const onCallerAbort = () => internalController.abort();
|
|
25
|
+
if (options.signal)
|
|
26
|
+
options.signal.addEventListener('abort', onCallerAbort);
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetchImpl(`${nodeBaseUrl(node)}${path}`, { ...init, signal: internalController.signal });
|
|
29
|
+
if (!res.ok)
|
|
30
|
+
throw new NodeHttpError(res.status);
|
|
31
|
+
return (await res.json());
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
if (options.signal)
|
|
36
|
+
options.signal.removeEventListener('abort', onCallerAbort);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Unauthenticated `GET` against a node endpoint, combining an internal
|
|
40
|
+
* timeout with an optional caller `AbortSignal`. Throws `NodeHttpError` on a
|
|
41
|
+
* non-2xx answer. */
|
|
42
|
+
export async function nodeRequest(node, path, options = {}) {
|
|
43
|
+
return fetchNodeJson(node, path, { method: 'GET' }, options);
|
|
44
|
+
}
|
|
45
|
+
/** Authenticated call against a node endpoint (`path` is relative to
|
|
46
|
+
* `/api/v1`). Same internal timeout + caller-abort plumbing as `nodeRequest`,
|
|
47
|
+
* and the same `NodeHttpError` on a non-2xx answer. */
|
|
48
|
+
export async function nodeAuthRequest(node, credentials, path, options = {}) {
|
|
49
|
+
return fetchNodeJson(node, path, {
|
|
50
|
+
method: options.method ?? 'GET',
|
|
51
|
+
headers: {
|
|
52
|
+
Authorization: basicAuthHeader(credentials),
|
|
53
|
+
...(options.body === undefined ? {} : { 'Content-Type': 'application/json' }),
|
|
54
|
+
},
|
|
55
|
+
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
56
|
+
}, options);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=rest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest.js","sourceRoot":"","sources":["../../src/transport/rest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAqB,MAAM,WAAW,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAErD;;gDAEgD;AAChD,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,MAAM,CAAQ;IACvB,YAAY,MAAc;QACxB,KAAK,CAAC,6BAA6B,MAAM,EAAE,CAAC,CAAA;QAC5C,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;CACF;AAQD,SAAS,eAAe,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAmB;IAC7D,OAAO,SAAS,IAAI,CAAC,GAAG,OAAO,IAAI,QAAQ,EAAE,CAAC,EAAE,CAAA;AAClD,CAAC;AAED;;+DAE+D;AAC/D,KAAK,UAAU,aAAa,CAC1B,IAAkB,EAClB,IAAY,EACZ,IAAiB,EACjB,OAA2E;IAE3E,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAA;IACnD,MAAM,kBAAkB,GAAG,IAAI,eAAe,EAAE,CAAA;IAChD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,SAAS,IAAI,oBAAoB,CAAC,CAAA;IACrG,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAA;IACtD,IAAI,OAAO,CAAC,MAAM;QAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IAC3E,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAA;QAC1G,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAChD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAA;IAChC,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAA;QACnB,IAAI,OAAO,CAAC,MAAM;YAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IAChF,CAAC;AACH,CAAC;AAED;;sBAEsB;AACtB,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAkB,EAClB,IAAY,EACZ,UAA8E,EAAE;IAEhF,OAAO,aAAa,CAAI,IAAI,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,CAAC,CAAA;AACjE,CAAC;AAED;;wDAEwD;AACxD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAkB,EAClB,WAA4B,EAC5B,IAAY,EACZ,UAA+G,EAAE;IAEjH,OAAO,aAAa,CAClB,IAAI,EACJ,IAAI,EACJ;QACE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;QAC/B,OAAO,EAAE;YACP,aAAa,EAAE,eAAe,CAAC,WAAW,CAAC;YAC3C,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;SAC9E;QACD,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;KAC5E,EACD,OAAO,CACR,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StarfishClient, type StarfishCapProvider } from '@drakkar.software/starfish-client';
|
|
2
|
+
/** Bound every sync request with a timeout: without one, a pull to an
|
|
3
|
+
* offline node hangs for the OS TCP timeout (60s+). */
|
|
4
|
+
export declare function createTimeoutFetch(timeoutMs: number, baseFetch?: typeof fetch): typeof fetch;
|
|
5
|
+
/** A bare `StarfishClient` factory — no cache, no lifecycle. Callers that
|
|
6
|
+
* need per-node client caching (e.g. an offline sync engine juggling
|
|
7
|
+
* several nodes) own that themselves; this just builds one client. */
|
|
8
|
+
export declare function createSyncClient(opts: {
|
|
9
|
+
origin: string;
|
|
10
|
+
capProvider: StarfishCapProvider;
|
|
11
|
+
fetch?: typeof fetch;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}): StarfishClient;
|
|
14
|
+
//# sourceMappingURL=syncClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncClient.d.ts","sourceRoot":"","sources":["../../src/transport/syncClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;AAO5F;wDACwD;AACxD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,GAAE,OAAO,KAAwB,GAAG,OAAO,KAAK,CAU9G;AAED;;uEAEuE;AACvE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,mBAAmB,CAAA;IAChC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,cAAc,CAQjB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { StarfishClient } from '@drakkar.software/starfish-client';
|
|
2
|
+
import { SYNC_MOUNT_PATH, SYNC_NAMESPACE, SYNC_FETCH_TIMEOUT_MS } from '../crypto/wireConstants.js';
|
|
3
|
+
function buildSyncBaseUrl(origin) {
|
|
4
|
+
return `${origin.replace(/\/$/, '')}/${SYNC_MOUNT_PATH}`;
|
|
5
|
+
}
|
|
6
|
+
/** Bound every sync request with a timeout: without one, a pull to an
|
|
7
|
+
* offline node hangs for the OS TCP timeout (60s+). */
|
|
8
|
+
export function createTimeoutFetch(timeoutMs, baseFetch = globalThis.fetch) {
|
|
9
|
+
return (input, init) => {
|
|
10
|
+
const controller = new AbortController();
|
|
11
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
12
|
+
const callerSignal = init?.signal;
|
|
13
|
+
if (callerSignal)
|
|
14
|
+
callerSignal.addEventListener('abort', () => controller.abort());
|
|
15
|
+
return baseFetch(input, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer));
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** A bare `StarfishClient` factory — no cache, no lifecycle. Callers that
|
|
19
|
+
* need per-node client caching (e.g. an offline sync engine juggling
|
|
20
|
+
* several nodes) own that themselves; this just builds one client. */
|
|
21
|
+
export function createSyncClient(opts) {
|
|
22
|
+
const baseFetch = opts.fetch ?? globalThis.fetch;
|
|
23
|
+
return new StarfishClient({
|
|
24
|
+
baseUrl: buildSyncBaseUrl(opts.origin),
|
|
25
|
+
namespace: SYNC_NAMESPACE,
|
|
26
|
+
capProvider: opts.capProvider,
|
|
27
|
+
fetch: createTimeoutFetch(opts.timeoutMs ?? SYNC_FETCH_TIMEOUT_MS, baseFetch),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=syncClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncClient.js","sourceRoot":"","sources":["../../src/transport/syncClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA4B,MAAM,mCAAmC,CAAA;AAC5F,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAEnG,SAAS,gBAAgB,CAAC,MAAc;IACtC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,eAAe,EAAE,CAAA;AAC1D,CAAC;AAED;wDACwD;AACxD,MAAM,UAAU,kBAAkB,CAAC,SAAiB,EAAE,YAA0B,UAAU,CAAC,KAAK;IAC9F,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;QACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAA;QAC7D,MAAM,YAAY,GAAI,IAAgC,EAAE,MAAM,CAAA;QAC9D,IAAI,YAAY;YAAE,YAAY,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAA;QAClF,OAAO,SAAS,CAAC,KAAK,EAAE,EAAE,GAAI,IAAoB,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAC5F,YAAY,CAAC,KAAK,CAAC,CACpB,CAAA;IACH,CAAC,CAAA;AACH,CAAC;AAED;;uEAEuE;AACvE,MAAM,UAAU,gBAAgB,CAAC,IAKhC;IACC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAA;IAChD,OAAO,IAAI,cAAc,CAAC;QACxB,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC;QACtC,SAAS,EAAE,cAAc;QACzB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,KAAK,EAAE,kBAAkB,CAAC,IAAI,CAAC,SAAS,IAAI,qBAAqB,EAAE,SAAS,CAAC;KAC9E,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** The minimal address triple every node-facing call needs. Deliberately
|
|
2
|
+
* structural (not the caller's own "list of paired nodes" document type) —
|
|
3
|
+
* any `{ host, port, secure? }` works. */
|
|
4
|
+
export type NodeEndpoint = {
|
|
5
|
+
host: string;
|
|
6
|
+
port: number;
|
|
7
|
+
secure?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function nodeBaseUrl(node: NodeEndpoint): string;
|
|
10
|
+
export type NodeAddressSpace = 'loopback' | 'local' | 'public';
|
|
11
|
+
/** Parse a user-typed host string into a canonical { host, port, secure } triple.
|
|
12
|
+
* Accepts: `host`, `host:port`, `http://host:port`, `https://host`, `[::1]:5001`.
|
|
13
|
+
* A scheme is kept (drives `secure` and the scheme-derived default port below);
|
|
14
|
+
* no scheme falls back to `defaultPort` (plain HTTP), preserving legacy local-node
|
|
15
|
+
* behavior. Returns null on empty input or an out-of-range port. */
|
|
16
|
+
export declare function parseHostInput(raw: string, defaultPort?: number): {
|
|
17
|
+
host: string;
|
|
18
|
+
port: number;
|
|
19
|
+
secure: boolean;
|
|
20
|
+
} | null;
|
|
21
|
+
/** Inverse of {@link parseHostInput}: rebuild the editable host/port strings from a stored
|
|
22
|
+
* { host, port, secure } triple so that re-parsing them yields the same triple. The scheme
|
|
23
|
+
* is folded into the host field (there is no separate scheme control) and the port is left
|
|
24
|
+
* blank when it equals the scheme's implicit default (443 for https, 80 for http). */
|
|
25
|
+
export declare function formatHostInput(node: NodeEndpoint): {
|
|
26
|
+
host: string;
|
|
27
|
+
port: string;
|
|
28
|
+
};
|
|
29
|
+
/** Which IP address space a host belongs to, decided from the string alone — no
|
|
30
|
+
* DNS lookup, because the browsers that care about this decide the same way and
|
|
31
|
+
* at the same moment (before resolving). `public` therefore means "not knowable
|
|
32
|
+
* from the hostname", not "definitely on the internet": a DNS name pointing at a
|
|
33
|
+
* LAN box lands here, exactly as it does in Chrome. */
|
|
34
|
+
export declare function classifyAddressSpace(host: string): NodeAddressSpace;
|
|
35
|
+
//# sourceMappingURL=urls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urls.d.ts","sourceRoot":"","sources":["../../src/transport/urls.ts"],"names":[],"mappings":"AAEA;;2CAE2C;AAC3C,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAE3E,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAEtD;AAED,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAA;AAI9D;;;;qEAIqE;AACrE,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,WAAW,SAAoB,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA+CxD;AAED;;;uFAGuF;AACvF,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAQlF;AAED;;;;wDAIwD;AACxD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAoCnE"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { API_PREFIX, DEFAULT_NODE_PORT } from './constants.js';
|
|
2
|
+
export function nodeBaseUrl(node) {
|
|
3
|
+
return `${node.secure ? 'https' : 'http'}://${node.host}:${node.port}${API_PREFIX}`;
|
|
4
|
+
}
|
|
5
|
+
const IPV4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
6
|
+
/** Parse a user-typed host string into a canonical { host, port, secure } triple.
|
|
7
|
+
* Accepts: `host`, `host:port`, `http://host:port`, `https://host`, `[::1]:5001`.
|
|
8
|
+
* A scheme is kept (drives `secure` and the scheme-derived default port below);
|
|
9
|
+
* no scheme falls back to `defaultPort` (plain HTTP), preserving legacy local-node
|
|
10
|
+
* behavior. Returns null on empty input or an out-of-range port. */
|
|
11
|
+
export function parseHostInput(raw, defaultPort = DEFAULT_NODE_PORT) {
|
|
12
|
+
const trimmed = raw.trim();
|
|
13
|
+
if (!trimmed)
|
|
14
|
+
return null;
|
|
15
|
+
// Detect + strip scheme
|
|
16
|
+
const schemeMatch = trimmed.match(/^(https?):\/\//i);
|
|
17
|
+
const secure = schemeMatch ? schemeMatch[1].toLowerCase() === 'https' : false;
|
|
18
|
+
const withoutScheme = trimmed.replace(/^https?:\/\//i, '');
|
|
19
|
+
// Strip trailing slashes and path
|
|
20
|
+
const withoutPath = withoutScheme.split('/')[0];
|
|
21
|
+
// Scheme present but no explicit port → the scheme's own default port
|
|
22
|
+
// (80/443); no scheme at all → the caller's local-node default.
|
|
23
|
+
const schemeDefaultPort = schemeMatch ? (secure ? 443 : 80) : defaultPort;
|
|
24
|
+
let host;
|
|
25
|
+
let port;
|
|
26
|
+
// IPv6 with brackets: [::1] or [::1]:5001
|
|
27
|
+
const ipv6Match = withoutPath.match(/^\[([^\]]+)\](?::(\d+))?$/);
|
|
28
|
+
if (ipv6Match) {
|
|
29
|
+
host = `[${ipv6Match[1]}]`;
|
|
30
|
+
port = ipv6Match[2] ? parseInt(ipv6Match[2], 10) : schemeDefaultPort;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// host or host:port
|
|
34
|
+
const lastColon = withoutPath.lastIndexOf(':');
|
|
35
|
+
if (lastColon !== -1) {
|
|
36
|
+
const maybePart = withoutPath.slice(lastColon + 1);
|
|
37
|
+
if (/^\d+$/.test(maybePart)) {
|
|
38
|
+
// Explicit port number
|
|
39
|
+
host = withoutPath.slice(0, lastColon);
|
|
40
|
+
port = parseInt(maybePart, 10);
|
|
41
|
+
}
|
|
42
|
+
else if (maybePart.length > 0) {
|
|
43
|
+
// Non-empty, non-numeric after colon → invalid port spec
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
host = withoutPath;
|
|
48
|
+
port = schemeDefaultPort;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
host = withoutPath;
|
|
53
|
+
port = schemeDefaultPort;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!host || port < 1 || port > 65535 || isNaN(port))
|
|
57
|
+
return null;
|
|
58
|
+
return { host, port, secure };
|
|
59
|
+
}
|
|
60
|
+
/** Inverse of {@link parseHostInput}: rebuild the editable host/port strings from a stored
|
|
61
|
+
* { host, port, secure } triple so that re-parsing them yields the same triple. The scheme
|
|
62
|
+
* is folded into the host field (there is no separate scheme control) and the port is left
|
|
63
|
+
* blank when it equals the scheme's implicit default (443 for https, 80 for http). */
|
|
64
|
+
export function formatHostInput(node) {
|
|
65
|
+
const secure = !!node.secure;
|
|
66
|
+
const schemeDefault = secure ? 443 : 80;
|
|
67
|
+
const scheme = secure ? 'https://' : node.port === 80 ? 'http://' : '';
|
|
68
|
+
return {
|
|
69
|
+
host: `${scheme}${node.host}`,
|
|
70
|
+
port: node.port === schemeDefault ? '' : String(node.port),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** Which IP address space a host belongs to, decided from the string alone — no
|
|
74
|
+
* DNS lookup, because the browsers that care about this decide the same way and
|
|
75
|
+
* at the same moment (before resolving). `public` therefore means "not knowable
|
|
76
|
+
* from the hostname", not "definitely on the internet": a DNS name pointing at a
|
|
77
|
+
* LAN box lands here, exactly as it does in Chrome. */
|
|
78
|
+
export function classifyAddressSpace(host) {
|
|
79
|
+
// A trailing dot is the DNS root and does not change what the name refers to.
|
|
80
|
+
const trimmed = host.trim().toLowerCase().replace(/\.$/, '');
|
|
81
|
+
if (!trimmed)
|
|
82
|
+
return 'public';
|
|
83
|
+
// IPv6, bracketed (`[::1]`, the form parseHostInput keeps) or bare.
|
|
84
|
+
if (trimmed.startsWith('[') || trimmed.includes(':')) {
|
|
85
|
+
const v6 = trimmed.replace(/^\[/, '').replace(/\]$/, '').split('%')[0];
|
|
86
|
+
if (v6 === '::1')
|
|
87
|
+
return 'loopback';
|
|
88
|
+
// IPv4-mapped (`::ffff:127.0.0.1`) inherits the mapped address's own space.
|
|
89
|
+
const mapped = v6.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
|
90
|
+
if (mapped)
|
|
91
|
+
return classifyAddressSpace(mapped[1]);
|
|
92
|
+
if (/^f[cd][0-9a-f]{2}:/.test(v6))
|
|
93
|
+
return 'local'; // fc00::/7 unique-local
|
|
94
|
+
if (/^fe[89ab][0-9a-f]:/.test(v6))
|
|
95
|
+
return 'local'; // fe80::/10 link-local
|
|
96
|
+
return 'public';
|
|
97
|
+
}
|
|
98
|
+
const v4 = trimmed.match(IPV4);
|
|
99
|
+
if (v4) {
|
|
100
|
+
const octets = v4.slice(1, 5).map(Number);
|
|
101
|
+
// Out-of-range octets aren't an address literal at all; a browser would try
|
|
102
|
+
// to resolve the string as a name, so classify it the way a name is.
|
|
103
|
+
if (octets.some((o) => o > 255))
|
|
104
|
+
return 'public';
|
|
105
|
+
const [a, b] = octets;
|
|
106
|
+
if (a === 127)
|
|
107
|
+
return 'loopback';
|
|
108
|
+
if (a === 10)
|
|
109
|
+
return 'local';
|
|
110
|
+
if (a === 192 && b === 168)
|
|
111
|
+
return 'local';
|
|
112
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
113
|
+
return 'local';
|
|
114
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
115
|
+
return 'local'; // carrier-grade NAT
|
|
116
|
+
if (a === 169 && b === 254)
|
|
117
|
+
return 'local'; // link-local
|
|
118
|
+
return 'public';
|
|
119
|
+
}
|
|
120
|
+
if (trimmed === 'localhost' || trimmed.endsWith('.localhost'))
|
|
121
|
+
return 'loopback';
|
|
122
|
+
if (trimmed.endsWith('.local'))
|
|
123
|
+
return 'local'; // mDNS
|
|
124
|
+
return 'public';
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=urls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urls.js","sourceRoot":"","sources":["../../src/transport/urls.ts"],"names":[],"mappings":"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"}
|