@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,186 @@
|
|
|
1
|
+
import { seal, unseal } from '@drakkar.software/starfish-keyring';
|
|
2
|
+
import { ConflictError } from '@drakkar.software/starfish-client';
|
|
3
|
+
import { createRendezvousClient, pullRendezvousDoc, pushRendezvousDoc, clearRendezvousDoc, joinSessionPath, } from '../../transport/rendezvous.js';
|
|
4
|
+
import { pollDelay } from '../../protocol/poll.js';
|
|
5
|
+
import { abortableSleep } from './pairingRequest.js';
|
|
6
|
+
import { OctoBotConnectionError, OctoBotConflictError, rethrowAsOctoBotError } from '../core/errors.js';
|
|
7
|
+
import { readMirrorCollections, parseMirrorGrantBundle, } from './mirrorReader.js';
|
|
8
|
+
/**
|
|
9
|
+
* Website↔phone exchange for the read-only-device grant this package mints
|
|
10
|
+
* against the space-mirror (see `mirrorGrant.ts`). This replaced an older
|
|
11
|
+
* sealed data-snapshot exchange, which has since been deleted.
|
|
12
|
+
*
|
|
13
|
+
* Request and grant now share ONE rendezvous address, `joinSessionPath(code)`
|
|
14
|
+
* (the `joinsessions` collection — see `transport/rendezvous.ts`), not two
|
|
15
|
+
* separate collections. `startPairingRequest`/`fetchPairingRequestByCode`
|
|
16
|
+
* publish/read the "request" phase there; this file's `publishPairingGrant`
|
|
17
|
+
* OVERWRITES that same doc with the "grant" phase once the phone approves.
|
|
18
|
+
* The sealing mechanism itself is unchanged (`starfish-keyring`'s
|
|
19
|
+
* `seal`/`unseal`, sealed to the website's ephemeral KEM key, signed by the
|
|
20
|
+
* wallet's own root key) — what's new is the OUTER wire envelope
|
|
21
|
+
* (`v`/`kind`/`sealed`, see `JoinSessionGrantDoc` below) wrapping the sealed
|
|
22
|
+
* blob, so a poller can tell "still just a request" from "a grant has been
|
|
23
|
+
* published" by reading one unsealed field, without attempting `unseal()`
|
|
24
|
+
* on a doc that might not even be sealed yet.
|
|
25
|
+
*/
|
|
26
|
+
const GRANT_ENVELOPE_KIND = 'octobot-pairing-grant';
|
|
27
|
+
const GRANT_ENVELOPE_VERSION = 1;
|
|
28
|
+
/** Phone side, step 2: seal and publish the minted grant to the request's
|
|
29
|
+
* `code` slot — this OVERWRITES the request doc still published there,
|
|
30
|
+
* since request and grant now share one address.
|
|
31
|
+
*
|
|
32
|
+
* `rendezvous` is the caller's OWN trusted server config — the same one it
|
|
33
|
+
* used to look up `request` via `fetchPairingRequestByCode` — never
|
|
34
|
+
* `request.rendezvous`. That field rides inside a document anyone can
|
|
35
|
+
* publish to the public `joinsessions` collection, so trusting it here
|
|
36
|
+
* would let a malicious "website" party point this phone's outbound POST at
|
|
37
|
+
* an arbitrary host of its choosing.
|
|
38
|
+
*
|
|
39
|
+
* `baseHash` MUST be the `hash` `fetchPairingRequestByCode` returned
|
|
40
|
+
* alongside `request` — this is what makes "claim this specific, exact
|
|
41
|
+
* request" atomic: the push only succeeds if the slot still holds exactly
|
|
42
|
+
* the request doc this caller read, so a request that was swapped or
|
|
43
|
+
* already claimed by someone else between the read and this write is
|
|
44
|
+
* detected as a conflict rather than silently overwritten. There is no
|
|
45
|
+
* longer a meaningful "first publish, baseHash: null" case the way the
|
|
46
|
+
* retired sessionId-keyed design had one — the slot is never empty by the
|
|
47
|
+
* time a caller reaches this function, it already holds the request. A
|
|
48
|
+
* caller legitimately RE-publishing later (a refresh) must pass back the
|
|
49
|
+
* hash THIS function returned from its own previous call. Returns the
|
|
50
|
+
* resulting hash for exactly that purpose. */
|
|
51
|
+
export async function publishPairingGrant(opts) {
|
|
52
|
+
const client = createRendezvousClient({
|
|
53
|
+
baseUrl: opts.rendezvous.baseUrl,
|
|
54
|
+
namespace: opts.rendezvous.namespace,
|
|
55
|
+
fetch: opts.fetch,
|
|
56
|
+
timeoutMs: opts.timeoutMs,
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
const envelope = {
|
|
60
|
+
v: GRANT_ENVELOPE_VERSION,
|
|
61
|
+
kind: GRANT_ENVELOPE_KIND,
|
|
62
|
+
bundle: opts.grant.bundle,
|
|
63
|
+
};
|
|
64
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(envelope));
|
|
65
|
+
const sealed = await seal(plaintext, opts.request.devKemPub, opts.sealer, opts.request.code);
|
|
66
|
+
const doc = { v: GRANT_ENVELOPE_VERSION, kind: GRANT_ENVELOPE_KIND, sealed };
|
|
67
|
+
return await pushRendezvousDoc(client, joinSessionPath(opts.request.code), doc, opts.baseHash);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (err instanceof ConflictError) {
|
|
71
|
+
throw new OctoBotConflictError(err.currentHash || null, err, 'pairing request slot was modified since it was read — this pairing may be compromised, or someone else already claimed it');
|
|
72
|
+
}
|
|
73
|
+
rethrowAsOctoBotError(err);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Phone side: stop publishing and clear the current slot — since request
|
|
77
|
+
* and grant share one address, this clears BOTH together (there is no way
|
|
78
|
+
* to clear one but keep the other). It stops future reads but cannot
|
|
79
|
+
* recall a grant a website already fetched — with this design, "cannot
|
|
80
|
+
* recall" is narrow: the grant itself only proves space membership, and
|
|
81
|
+
* `removeSpaceMember`-based revocation (real, immediate, checked live on
|
|
82
|
+
* every request) is the actual unpair mechanism a caller should use
|
|
83
|
+
* ALONGSIDE this, not instead of it — this only stops the CODE from
|
|
84
|
+
* resolving to a usable grant again, it does not by itself revoke a grant
|
|
85
|
+
* already handed out. */
|
|
86
|
+
export async function clearPairingGrant(opts) {
|
|
87
|
+
const client = createRendezvousClient({
|
|
88
|
+
baseUrl: opts.rendezvous.baseUrl,
|
|
89
|
+
namespace: opts.rendezvous.namespace,
|
|
90
|
+
fetch: opts.fetch,
|
|
91
|
+
timeoutMs: opts.timeoutMs,
|
|
92
|
+
});
|
|
93
|
+
try {
|
|
94
|
+
return await clearRendezvousDoc(client, joinSessionPath(opts.request.code));
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
rethrowAsOctoBotError(err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Website side: read whatever is currently published at this session's
|
|
101
|
+
* slot. Three outcomes:
|
|
102
|
+
* - nothing published (wrong/expired code) → `null`, "keep waiting".
|
|
103
|
+
* - still `kind: 'octobot-pairing-request'` (the phone hasn't approved
|
|
104
|
+
* yet) → also `null`, "keep waiting" — this is the normal, expected
|
|
105
|
+
* state for most of the polling loop's life, NOT an error.
|
|
106
|
+
* - `kind: 'octobot-pairing-grant'` → unseal it and immediately do a live
|
|
107
|
+
* read of every mirror collection it covers.
|
|
108
|
+
* Anything else (an unrecognized `kind`, or a `kind: 'octobot-pairing-grant'`
|
|
109
|
+
* doc whose `sealed` field isn't actually a sealed blob) throws — that's a
|
|
110
|
+
* genuine malformed-document case, not a wait state. */
|
|
111
|
+
export async function fetchPairingGrant(session, opts = {}) {
|
|
112
|
+
const client = createRendezvousClient({
|
|
113
|
+
baseUrl: session.rendezvous.baseUrl,
|
|
114
|
+
namespace: session.rendezvous.namespace,
|
|
115
|
+
fetch: opts.fetch,
|
|
116
|
+
timeoutMs: opts.timeoutMs,
|
|
117
|
+
});
|
|
118
|
+
const doc = await pullRendezvousDoc(client, joinSessionPath(session.code));
|
|
119
|
+
if (!doc)
|
|
120
|
+
return null;
|
|
121
|
+
const data = doc.data;
|
|
122
|
+
if (data.kind === 'octobot-pairing-request')
|
|
123
|
+
return null;
|
|
124
|
+
if (data.v !== GRANT_ENVELOPE_VERSION || data.kind !== GRANT_ENVELOPE_KIND) {
|
|
125
|
+
throw new Error('pairing grant: unrecognized document at this code\'s slot');
|
|
126
|
+
}
|
|
127
|
+
if (typeof data.sealed !== 'object' || data.sealed === null || typeof data.sealed.entry !== 'object' || data.sealed.entry === null) {
|
|
128
|
+
throw new Error('pairing grant: malformed sealed blob at this code\'s slot');
|
|
129
|
+
}
|
|
130
|
+
const sealed = data.sealed;
|
|
131
|
+
const plaintext = await unseal(sealed, session.device.kemPriv, {
|
|
132
|
+
aad: session.code,
|
|
133
|
+
...(opts.expectedSealer !== undefined ? { requireSealer: opts.expectedSealer } : {}),
|
|
134
|
+
});
|
|
135
|
+
const envelopeRaw = JSON.parse(new TextDecoder().decode(plaintext));
|
|
136
|
+
if (typeof envelopeRaw !== 'object' || envelopeRaw === null
|
|
137
|
+
|| envelopeRaw.v !== GRANT_ENVELOPE_VERSION
|
|
138
|
+
|| envelopeRaw.kind !== GRANT_ENVELOPE_KIND
|
|
139
|
+
|| typeof envelopeRaw.bundle !== 'string') {
|
|
140
|
+
throw new Error('pairing grant: malformed grant envelope');
|
|
141
|
+
}
|
|
142
|
+
const { spaceId, nodes } = parseMirrorGrantBundle(envelopeRaw.bundle);
|
|
143
|
+
const sealedBy = sealed.entry.addedBy;
|
|
144
|
+
const collections = await readMirrorCollections({
|
|
145
|
+
rendezvous: session.rendezvous,
|
|
146
|
+
spaceId,
|
|
147
|
+
nodes,
|
|
148
|
+
devEdPrivHex: session.device.edPriv,
|
|
149
|
+
devKemPrivHex: session.device.kemPriv,
|
|
150
|
+
fetch: opts.fetch,
|
|
151
|
+
timeoutMs: opts.timeoutMs,
|
|
152
|
+
});
|
|
153
|
+
return { spaceId, collections, sealedBy, nodes };
|
|
154
|
+
}
|
|
155
|
+
/** Website side: poll until the phone's FIRST approval publishes a grant,
|
|
156
|
+
* or `timeoutMs` elapses. This is the initial-approval wait, not an ongoing
|
|
157
|
+
* refresh loop — for later refreshes call `readMirrorCollections` directly.
|
|
158
|
+
*
|
|
159
|
+
* A `fetchPairingGrant` failure (a network blip, a transient server error)
|
|
160
|
+
* does NOT end the wait — it's swallowed and retried on the next tick, same
|
|
161
|
+
* as `fetchPairingGrant` returning `null` for "nothing published yet". Only
|
|
162
|
+
* reaching `deadline` ends it: with the last error if there was one (more
|
|
163
|
+
* informative than a bare timeout), else the generic timeout. */
|
|
164
|
+
export async function awaitPairingGrant(session, opts = {}) {
|
|
165
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 5 * 60 * 1000);
|
|
166
|
+
let lastErr;
|
|
167
|
+
for (let attempt = 0;; attempt++) {
|
|
168
|
+
if (opts.signal?.aborted)
|
|
169
|
+
throw new DOMException('aborted', 'AbortError');
|
|
170
|
+
try {
|
|
171
|
+
const result = await fetchPairingGrant(session, { fetch: opts.fetch });
|
|
172
|
+
if (result)
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
lastErr = err;
|
|
177
|
+
}
|
|
178
|
+
if (Date.now() >= deadline) {
|
|
179
|
+
if (lastErr)
|
|
180
|
+
rethrowAsOctoBotError(lastErr);
|
|
181
|
+
throw new OctoBotConnectionError('timeout', 'timed out waiting for the pairing to be approved');
|
|
182
|
+
}
|
|
183
|
+
await abortableSleep(pollDelay(attempt), opts.signal);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=pairingGrantExchange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairingGrantExchange.js","sourceRoot":"","sources":["../../../src/client/pairing/pairingGrantExchange.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { GeneratedDeviceKeys } from '@drakkar.software/starfish-identities';
|
|
2
|
+
import { type PairingRequestPayload } from '../../identity/pairingRequest.js';
|
|
3
|
+
import type { NodeCollectionKey } from '../../collections/nodeCollections.js';
|
|
4
|
+
export type StartPairingRequestOptions = {
|
|
5
|
+
origin: string;
|
|
6
|
+
rendezvous: {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
namespace: string;
|
|
9
|
+
};
|
|
10
|
+
label?: string;
|
|
11
|
+
requestedCollections?: NodeCollectionKey[];
|
|
12
|
+
ttlSec?: number;
|
|
13
|
+
/** See `PairingRequestPayload.requesterKind`. Defaults to `'website'`. */
|
|
14
|
+
requesterKind?: 'website' | 'device';
|
|
15
|
+
fetch?: typeof fetch;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
};
|
|
18
|
+
/** A website's own end of a device-code pairing: its request record plus
|
|
19
|
+
* everything needed to publish it and later unseal the phone's response.
|
|
20
|
+
* `code` is what the site displays for the user to type into their OctoBot
|
|
21
|
+
* app — this package never renders it, just returns the string. Carries its
|
|
22
|
+
* own `rendezvous` (the same value passed to `startPairingRequest`) so
|
|
23
|
+
* `awaitPairingGrant`/`fetchPairingGrant` can take the session directly,
|
|
24
|
+
* with no re-spreading required at the call site. */
|
|
25
|
+
export interface PairingRequestSession {
|
|
26
|
+
request: PairingRequestPayload;
|
|
27
|
+
device: GeneratedDeviceKeys;
|
|
28
|
+
code: string;
|
|
29
|
+
rendezvous: {
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
namespace: string;
|
|
32
|
+
};
|
|
33
|
+
/** Publish (or re-publish) the request record to the rendezvous. */
|
|
34
|
+
publish(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Website side, step 1: create and publish a pairing request.
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* const rendezvous = { baseUrl: 'https://sync.drakkar.software/sync', namespace: 'dk' }
|
|
41
|
+
* const session = await startPairingRequest({ origin: 'https://myapp.example', rendezvous })
|
|
42
|
+
* await session.publish()
|
|
43
|
+
* showCodeToUser(session.code)
|
|
44
|
+
* const grant = await awaitPairingGrant(session)
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function startPairingRequest(opts: StartPairingRequestOptions): Promise<PairingRequestSession>;
|
|
48
|
+
/** The result of a request lookup: the parsed request plus the pulled
|
|
49
|
+
* document's `hash`. The hash matters — it's what the phone must pass as
|
|
50
|
+
* `baseHash` when it later mints and publishes a grant, so that write is a
|
|
51
|
+
* compare-and-swap against the EXACT request doc it read, not a blind
|
|
52
|
+
* overwrite. See `client/pairing/pairingGrantExchange.ts`'s
|
|
53
|
+
* `publishPairingGrant`. */
|
|
54
|
+
export interface PairingRequestLookup {
|
|
55
|
+
request: PairingRequestPayload;
|
|
56
|
+
hash: string;
|
|
57
|
+
}
|
|
58
|
+
/** Phone side, step 1: look up a request by the code the user typed. Returns
|
|
59
|
+
* `null` only when nothing is published under that code at all (wrong code,
|
|
60
|
+
* or the rendezvous slot's own TTL already reclaimed it). A request that IS
|
|
61
|
+
* still present but past its own `expiresAt` does NOT return `null` — it
|
|
62
|
+
* throws (via `parsePairingRequest`), so the caller can tell "wrong code"
|
|
63
|
+
* apart from "right code, but it expired" and say so accurately. If the
|
|
64
|
+
* slot has ALREADY moved past the request phase (a grant is published
|
|
65
|
+
* there — `kind === 'octobot-pairing-grant'`, detectable without unsealing
|
|
66
|
+
* anything, see `pairingGrantExchange.ts`), this throws a distinct
|
|
67
|
+
* `OctoBotConflictError` rather than a generic parse failure, so a caller
|
|
68
|
+
* re-checking an already-approved code gets an accurate "already used"
|
|
69
|
+
* signal instead of "malformed pairing request payload". */
|
|
70
|
+
export declare function fetchPairingRequestByCode(opts: {
|
|
71
|
+
code: string;
|
|
72
|
+
rendezvous: {
|
|
73
|
+
baseUrl: string;
|
|
74
|
+
namespace: string;
|
|
75
|
+
};
|
|
76
|
+
fetch?: typeof fetch;
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
}): Promise<PairingRequestLookup | null>;
|
|
79
|
+
/** Shared by `pairingGrantExchange.ts`'s polling loops — keep the
|
|
80
|
+
* abort/timeout/cleanup semantics in exactly one place. */
|
|
81
|
+
export declare function abortableSleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
82
|
+
//# sourceMappingURL=pairingRequest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairingRequest.d.ts","sourceRoot":"","sources":["../../../src/client/pairing/pairingRequest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAA;AAChF,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,kCAAkC,CAAA;AAQzC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAA;AAE7E,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oBAAoB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0EAA0E;IAC1E,aAAa,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACpC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;sDAMsD;AACtD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,qBAAqB,CAAA;IAC9B,MAAM,EAAE,mBAAmB,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAClD,oEAAoE;IACpE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA2D1G;AAED;;;;;6BAK6B;AAC7B,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,qBAAqB,CAAA;IAC9B,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;;;6DAW6D;AAC7D,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IACpD,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAClD,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAmBvC;AAED;4DAC4D;AAC5D,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAS9E"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { ConflictError } from '@drakkar.software/starfish-client';
|
|
2
|
+
import { createPairingRequest, parsePairingRequest, } from '../../identity/pairingRequest.js';
|
|
3
|
+
import { createRendezvousClient, pullRendezvousDoc, pushRendezvousDoc, joinSessionPath, } from '../../transport/rendezvous.js';
|
|
4
|
+
import { OctoBotConflictError, rethrowAsOctoBotError } from '../core/errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Website side, step 1: create and publish a pairing request.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const rendezvous = { baseUrl: 'https://sync.drakkar.software/sync', namespace: 'dk' }
|
|
10
|
+
* const session = await startPairingRequest({ origin: 'https://myapp.example', rendezvous })
|
|
11
|
+
* await session.publish()
|
|
12
|
+
* showCodeToUser(session.code)
|
|
13
|
+
* const grant = await awaitPairingGrant(session)
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export async function startPairingRequest(opts) {
|
|
17
|
+
const { request, device, code } = await createPairingRequest(opts);
|
|
18
|
+
const client = createRendezvousClient({
|
|
19
|
+
baseUrl: opts.rendezvous.baseUrl,
|
|
20
|
+
namespace: opts.rendezvous.namespace,
|
|
21
|
+
fetch: opts.fetch,
|
|
22
|
+
timeoutMs: opts.timeoutMs,
|
|
23
|
+
});
|
|
24
|
+
// This session's own remembered hash from its last successful publish —
|
|
25
|
+
// starts null (a fresh code has nothing published under it yet, so the
|
|
26
|
+
// FIRST publish() is create-only: it fails rather than silently adopting
|
|
27
|
+
// whatever's already occupying the slot). Every later publish() call uses
|
|
28
|
+
// its own remembered hash instead of re-pulling and trusting whatever the
|
|
29
|
+
// server currently reports, so a hostile overwrite between two publish()
|
|
30
|
+
// calls surfaces as a loud conflict instead of silently becoming this
|
|
31
|
+
// session's new baseline.
|
|
32
|
+
let lastHash = null;
|
|
33
|
+
// Serializes overlapping publish() calls on the SAME session — without
|
|
34
|
+
// this, two calls in flight at once both read the same lastHash before
|
|
35
|
+
// either awaits, so the one the server processes second gets a real
|
|
36
|
+
// ConflictError caused only by this session's own overlapping write, not
|
|
37
|
+
// third-party tampering, yet (post-own-write-CAS) it hits the exact same
|
|
38
|
+
// "treat this code as compromised" error a genuine hijack would.
|
|
39
|
+
let queue = Promise.resolve();
|
|
40
|
+
async function doPublish() {
|
|
41
|
+
try {
|
|
42
|
+
const result = await pushRendezvousDoc(client, joinSessionPath(code), request, lastHash);
|
|
43
|
+
lastHash = result.hash;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err instanceof ConflictError) {
|
|
47
|
+
// Thrown as OctoBotConflictError directly (not wrapped in a plain
|
|
48
|
+
// Error then run through rethrowAsOctoBotError) so `code === 'conflict'`
|
|
49
|
+
// and `instanceof OctoBotConflictError` survive — the documented
|
|
50
|
+
// "switch on code" convention callers rely on to distinguish a
|
|
51
|
+
// tampered slot from a generic unreachable-node failure.
|
|
52
|
+
throw new OctoBotConflictError(err.currentHash || null, err, 'pairing request was modified by another party — treat this code as compromised');
|
|
53
|
+
}
|
|
54
|
+
rethrowAsOctoBotError(err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
request,
|
|
59
|
+
device,
|
|
60
|
+
code,
|
|
61
|
+
rendezvous: opts.rendezvous,
|
|
62
|
+
publish() {
|
|
63
|
+
const run = queue.then(() => doPublish());
|
|
64
|
+
// Swallow so a failed publish() doesn't permanently wedge the queue
|
|
65
|
+
// for the NEXT caller's publish() — each call still observes its own
|
|
66
|
+
// rejection via the returned promise.
|
|
67
|
+
queue = run.catch(() => { });
|
|
68
|
+
return run;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Phone side, step 1: look up a request by the code the user typed. Returns
|
|
73
|
+
* `null` only when nothing is published under that code at all (wrong code,
|
|
74
|
+
* or the rendezvous slot's own TTL already reclaimed it). A request that IS
|
|
75
|
+
* still present but past its own `expiresAt` does NOT return `null` — it
|
|
76
|
+
* throws (via `parsePairingRequest`), so the caller can tell "wrong code"
|
|
77
|
+
* apart from "right code, but it expired" and say so accurately. If the
|
|
78
|
+
* slot has ALREADY moved past the request phase (a grant is published
|
|
79
|
+
* there — `kind === 'octobot-pairing-grant'`, detectable without unsealing
|
|
80
|
+
* anything, see `pairingGrantExchange.ts`), this throws a distinct
|
|
81
|
+
* `OctoBotConflictError` rather than a generic parse failure, so a caller
|
|
82
|
+
* re-checking an already-approved code gets an accurate "already used"
|
|
83
|
+
* signal instead of "malformed pairing request payload". */
|
|
84
|
+
export async function fetchPairingRequestByCode(opts) {
|
|
85
|
+
const client = createRendezvousClient({
|
|
86
|
+
baseUrl: opts.rendezvous.baseUrl,
|
|
87
|
+
namespace: opts.rendezvous.namespace,
|
|
88
|
+
fetch: opts.fetch,
|
|
89
|
+
timeoutMs: opts.timeoutMs,
|
|
90
|
+
});
|
|
91
|
+
try {
|
|
92
|
+
const doc = await pullRendezvousDoc(client, joinSessionPath(opts.code));
|
|
93
|
+
if (!doc)
|
|
94
|
+
return null;
|
|
95
|
+
if (doc.data.kind === 'octobot-pairing-grant') {
|
|
96
|
+
throw new OctoBotConflictError(doc.hash, undefined, 'this code has already been used to complete a pairing');
|
|
97
|
+
}
|
|
98
|
+
const request = parsePairingRequest(JSON.stringify(doc.data), opts.code);
|
|
99
|
+
return { request, hash: doc.hash };
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err instanceof OctoBotConflictError)
|
|
103
|
+
throw err;
|
|
104
|
+
rethrowAsOctoBotError(err);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Shared by `pairingGrantExchange.ts`'s polling loops — keep the
|
|
108
|
+
* abort/timeout/cleanup semantics in exactly one place. */
|
|
109
|
+
export function abortableSleep(ms, signal) {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
if (signal?.aborted)
|
|
112
|
+
return reject(new DOMException('aborted', 'AbortError'));
|
|
113
|
+
const timer = setTimeout(resolve, ms);
|
|
114
|
+
signal?.addEventListener('abort', () => {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
reject(new DOMException('aborted', 'AbortError'));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=pairingRequest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairingRequest.js","sourceRoot":"","sources":["../../../src/client/pairing/pairingRequest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Strategy as ProtocolStrategy } from '@drakkar.software/octobot-protocol';
|
|
2
|
+
import { type BuildStrategyOptions, type StrategyInput, type MmInput, type GridInput, type DcaInput, type IndexInput, type CopyInput, type SignalInput, type StrategyInputPatch } from '../protocol/strategy/index.js';
|
|
3
|
+
/** Pure, synchronous(-ish — everything here is actually sync, `Promise`-free),
|
|
4
|
+
* zero-I/O strategy builders. Needs no connection: build a strategy, then
|
|
5
|
+
* pass it to `client.automations.create()` or `client.strategies.create()`. */
|
|
6
|
+
export interface StrategyBuilders {
|
|
7
|
+
dca(input: DcaInput, opts?: BuildStrategyOptions): ProtocolStrategy;
|
|
8
|
+
grid(input: GridInput, opts?: BuildStrategyOptions): ProtocolStrategy;
|
|
9
|
+
index(input: IndexInput, opts?: BuildStrategyOptions): ProtocolStrategy;
|
|
10
|
+
marketMaking(input: MmInput, opts?: BuildStrategyOptions): ProtocolStrategy;
|
|
11
|
+
copy(input: CopyInput, opts?: BuildStrategyOptions): ProtocolStrategy;
|
|
12
|
+
signal(input: SignalInput, opts?: BuildStrategyOptions): ProtocolStrategy;
|
|
13
|
+
genericProcess(opts?: BuildStrategyOptions): ProtocolStrategy;
|
|
14
|
+
/** The discriminated-union entry point (`{ kind: 'dca', dca: {...} }`),
|
|
15
|
+
* for callers that already have a `StrategyInput` rather than picking a
|
|
16
|
+
* kind-specific method. */
|
|
17
|
+
build(input: StrategyInput, opts?: BuildStrategyOptions): ProtocolStrategy;
|
|
18
|
+
/** Recover an editable input from a strategy the node returned — the
|
|
19
|
+
* inverse of every builder above. Feed the result's fields into a new
|
|
20
|
+
* `strategy.dca(...)`/etc call (with `bumpVersion` applied) to edit it. */
|
|
21
|
+
toInput(existing: ProtocolStrategy): StrategyInputPatch;
|
|
22
|
+
/** Bump a `MAJOR.MINOR.PATCH` version string's patch component by one. */
|
|
23
|
+
bumpVersion(version: string): string;
|
|
24
|
+
}
|
|
25
|
+
/** Needs no `ClientSession` — unlike every other `create*Api` in
|
|
26
|
+
* `client/adapters/`, these builders do no I/O and touch no connection,
|
|
27
|
+
* so there is nothing to construct them from. Kept as a factory anyway, for
|
|
28
|
+
* the same reason those are: `strategy` below is just `createStrategyBuilders()`
|
|
29
|
+
* called once, so the object literal isn't duplicated between this module's
|
|
30
|
+
* own export and a hypothetical future caller that wants its own instance. */
|
|
31
|
+
export declare function createStrategyBuilders(): StrategyBuilders;
|
|
32
|
+
/** The public entry point every caller uses — `strategy.dca(...)`, etc. A
|
|
33
|
+
* single shared instance, since these builders carry no state and creating
|
|
34
|
+
* a fresh one per call would be pointless. */
|
|
35
|
+
export declare const strategy: StrategyBuilders;
|
|
36
|
+
//# sourceMappingURL=strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../../src/client/strategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,IAAI,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AACtF,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,WAAW,EAGhB,KAAK,kBAAkB,EACxB,MAAM,+BAA+B,CAAA;AAEtC;;gFAEgF;AAChF,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAAA;IACnE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAAA;IACrE,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAAA;IACvE,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAAA;IAC3E,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAAA;IACrE,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAAA;IACzE,cAAc,CAAC,IAAI,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAAA;IAC7D;;gCAE4B;IAC5B,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CAAA;IAC1E;;gFAE4E;IAC5E,OAAO,CAAC,QAAQ,EAAE,gBAAgB,GAAG,kBAAkB,CAAA;IACvD,0EAA0E;IAC1E,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;CACrC;AAED;;;;;+EAK+E;AAC/E,wBAAgB,sBAAsB,IAAI,gBAAgB,CAazD;AAED;;+CAE+C;AAC/C,eAAO,MAAM,QAAQ,EAAE,gBAA2C,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { buildStrategy, protocolStrategyToInput, bumpStrategyPatchVersion, } from '../protocol/strategy/index.js';
|
|
2
|
+
/** Needs no `ClientSession` — unlike every other `create*Api` in
|
|
3
|
+
* `client/adapters/`, these builders do no I/O and touch no connection,
|
|
4
|
+
* so there is nothing to construct them from. Kept as a factory anyway, for
|
|
5
|
+
* the same reason those are: `strategy` below is just `createStrategyBuilders()`
|
|
6
|
+
* called once, so the object literal isn't duplicated between this module's
|
|
7
|
+
* own export and a hypothetical future caller that wants its own instance. */
|
|
8
|
+
export function createStrategyBuilders() {
|
|
9
|
+
return {
|
|
10
|
+
dca: (input, opts) => buildStrategy({ kind: 'dca', dca: input }, opts),
|
|
11
|
+
grid: (input, opts) => buildStrategy({ kind: 'grid', grid: input }, opts),
|
|
12
|
+
index: (input, opts) => buildStrategy({ kind: 'basket', basket: input }, opts),
|
|
13
|
+
marketMaking: (input, opts) => buildStrategy({ kind: 'mm', mm: input }, opts),
|
|
14
|
+
copy: (input, opts) => buildStrategy({ kind: 'copy', copy: input }, opts),
|
|
15
|
+
signal: (input, opts) => buildStrategy({ kind: 'signal', signal: input }, opts),
|
|
16
|
+
genericProcess: (opts) => buildStrategy({ kind: 'custom' }, opts),
|
|
17
|
+
build: (input, opts) => buildStrategy(input, opts),
|
|
18
|
+
toInput: (existing) => protocolStrategyToInput(existing),
|
|
19
|
+
bumpVersion: (version) => bumpStrategyPatchVersion(version),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** The public entry point every caller uses — `strategy.dca(...)`, etc. A
|
|
23
|
+
* single shared instance, since these builders carry no state and creating
|
|
24
|
+
* a fresh one per call would be pointless. */
|
|
25
|
+
export const strategy = createStrategyBuilders();
|
|
26
|
+
//# sourceMappingURL=strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy.js","sourceRoot":"","sources":["../../src/client/strategy.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,EASb,uBAAuB,EACvB,wBAAwB,GAEzB,MAAM,+BAA+B,CAAA;AAyBtC;;;;;+EAK+E;AAC/E,MAAM,UAAU,sBAAsB;IACpC,OAAO;QACL,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC;QACtE,IAAI,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC;QACzE,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC;QAC9E,YAAY,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC;QAC7E,IAAI,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC;QACzE,MAAM,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC;QAC/E,cAAc,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC;QACjE,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC;QAClD,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,uBAAuB,CAAC,QAAQ,CAAC;QACxD,WAAW,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,wBAAwB,CAAC,OAAO,CAAC;KAC5D,CAAA;AACH,CAAC;AAED;;+CAE+C;AAC/C,MAAM,CAAC,MAAM,QAAQ,GAAqB,sBAAsB,EAAE,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAmD,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** The collections a self-hosted OctoBot node actually serves under
|
|
2
|
+
* `users/{identity}/...`. Each `encryptionInfo` is the HKDF `info` string the
|
|
3
|
+
* node derives its per-collection key from — it MUST equal
|
|
4
|
+
* `'octobot-sync-' + <node's own Collections enum value>`.
|
|
5
|
+
* @see packages/sync/octobot_sync/enums.py — Collections
|
|
6
|
+
* @see packages/sync/octobot_sync/crypto.py */
|
|
7
|
+
export type NodeCollectionInfo = {
|
|
8
|
+
name: string;
|
|
9
|
+
storagePath: string;
|
|
10
|
+
encryptionInfo: string;
|
|
11
|
+
/** Node never accepts a push for this collection — pull only. */
|
|
12
|
+
pullOnly?: boolean;
|
|
13
|
+
/** Append-only, push-only: each push publishes ONE queue element the node
|
|
14
|
+
* consumes and executes. Re-sending an element is re-executing it. */
|
|
15
|
+
appendOnly?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare const NODE_COLLECTIONS: {
|
|
18
|
+
/** Bundles the node-computed `automations`/`user_actions` fields alongside
|
|
19
|
+
* whatever else a higher-level client stores in the same document. */
|
|
20
|
+
readonly userData: {
|
|
21
|
+
readonly name: "user-data";
|
|
22
|
+
readonly storagePath: "users/{identity}/data";
|
|
23
|
+
readonly encryptionInfo: "octobot-sync-user-data";
|
|
24
|
+
};
|
|
25
|
+
readonly accounts: {
|
|
26
|
+
readonly name: "accounts";
|
|
27
|
+
readonly storagePath: "users/{identity}/accounts";
|
|
28
|
+
readonly encryptionInfo: "octobot-sync-user-accounts";
|
|
29
|
+
};
|
|
30
|
+
/** The node stores this encrypted and opaque — it never reads a field. */
|
|
31
|
+
readonly settings: {
|
|
32
|
+
readonly name: "settings";
|
|
33
|
+
readonly storagePath: "users/{identity}/settings";
|
|
34
|
+
readonly encryptionInfo: "octobot-sync-user-settings";
|
|
35
|
+
};
|
|
36
|
+
readonly strategies: {
|
|
37
|
+
readonly name: "strategies";
|
|
38
|
+
readonly storagePath: "users/{identity}/strategies";
|
|
39
|
+
readonly encryptionInfo: "octobot-sync-user-strategies";
|
|
40
|
+
};
|
|
41
|
+
readonly actions: {
|
|
42
|
+
readonly name: "actions";
|
|
43
|
+
readonly storagePath: "users/{identity}/actions";
|
|
44
|
+
readonly encryptionInfo: "octobot-sync-user-actions";
|
|
45
|
+
readonly appendOnly: true;
|
|
46
|
+
};
|
|
47
|
+
/** One document per exchange account — fan out over `{accountId}` yourself;
|
|
48
|
+
* there is no single "list all trading docs" pull. */
|
|
49
|
+
readonly accountTrading: {
|
|
50
|
+
readonly name: "user-accounts-trading";
|
|
51
|
+
readonly storagePath: "users/{identity}/accounts/{accountId}/trading";
|
|
52
|
+
readonly encryptionInfo: "octobot-sync-user-accounts-trading";
|
|
53
|
+
readonly pullOnly: true;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export type NodeCollectionKey = keyof typeof NODE_COLLECTIONS;
|
|
57
|
+
//# sourceMappingURL=nodeCollections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeCollections.d.ts","sourceRoot":"","sources":["../../src/collections/nodeCollections.ts"],"names":[],"mappings":"AAAA;;;;;gDAKgD;AAChD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;2EACuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,eAAO,MAAM,gBAAgB;IAC3B;2EACuE;;;;;;;;;;;IAWvE,0EAA0E;;;;;;;;;;;;;;;;;IAiB1E;2DACuD;;;;;;;CAOF,CAAA;AAEvD,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const NODE_COLLECTIONS = {
|
|
2
|
+
/** Bundles the node-computed `automations`/`user_actions` fields alongside
|
|
3
|
+
* whatever else a higher-level client stores in the same document. */
|
|
4
|
+
userData: {
|
|
5
|
+
name: 'user-data',
|
|
6
|
+
storagePath: 'users/{identity}/data',
|
|
7
|
+
encryptionInfo: 'octobot-sync-user-data',
|
|
8
|
+
},
|
|
9
|
+
accounts: {
|
|
10
|
+
name: 'accounts',
|
|
11
|
+
storagePath: 'users/{identity}/accounts',
|
|
12
|
+
encryptionInfo: 'octobot-sync-user-accounts',
|
|
13
|
+
},
|
|
14
|
+
/** The node stores this encrypted and opaque — it never reads a field. */
|
|
15
|
+
settings: {
|
|
16
|
+
name: 'settings',
|
|
17
|
+
storagePath: 'users/{identity}/settings',
|
|
18
|
+
encryptionInfo: 'octobot-sync-user-settings',
|
|
19
|
+
},
|
|
20
|
+
strategies: {
|
|
21
|
+
name: 'strategies',
|
|
22
|
+
storagePath: 'users/{identity}/strategies',
|
|
23
|
+
encryptionInfo: 'octobot-sync-user-strategies',
|
|
24
|
+
},
|
|
25
|
+
actions: {
|
|
26
|
+
name: 'actions',
|
|
27
|
+
storagePath: 'users/{identity}/actions',
|
|
28
|
+
encryptionInfo: 'octobot-sync-user-actions',
|
|
29
|
+
appendOnly: true,
|
|
30
|
+
},
|
|
31
|
+
/** One document per exchange account — fan out over `{accountId}` yourself;
|
|
32
|
+
* there is no single "list all trading docs" pull. */
|
|
33
|
+
accountTrading: {
|
|
34
|
+
name: 'user-accounts-trading',
|
|
35
|
+
storagePath: 'users/{identity}/accounts/{accountId}/trading',
|
|
36
|
+
encryptionInfo: 'octobot-sync-user-accounts-trading',
|
|
37
|
+
pullOnly: true,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=nodeCollections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeCollections.js","sourceRoot":"","sources":["../../src/collections/nodeCollections.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B;2EACuE;IACvE,QAAQ,EAAE;QACR,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,uBAAuB;QACpC,cAAc,EAAE,wBAAwB;KACzC;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,2BAA2B;QACxC,cAAc,EAAE,4BAA4B;KAC7C;IACD,0EAA0E;IAC1E,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,2BAA2B;QACxC,cAAc,EAAE,4BAA4B;KAC7C;IACD,UAAU,EAAE;QACV,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,6BAA6B;QAC1C,cAAc,EAAE,8BAA8B;KAC/C;IACD,OAAO,EAAE;QACP,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,0BAA0B;QACvC,cAAc,EAAE,2BAA2B;QAC3C,UAAU,EAAE,IAAI;KACjB;IACD;2DACuD;IACvD,cAAc,EAAE;QACd,IAAI,EAAE,uBAAuB;QAC7B,WAAW,EAAE,+CAA+C;QAC5D,cAAc,EAAE,oCAAoC;QACpD,QAAQ,EAAE,IAAI;KACf;CACoD,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Substitutes `{key}` placeholders in a storage-path template, e.g.
|
|
2
|
+
* `resolveStoragePath('users/{identity}/accounts', { identity: userId })`.
|
|
3
|
+
* Generic — carries no product knowledge of which collections exist. */
|
|
4
|
+
export declare function resolveStoragePath(storagePath: string, params: Record<string, string>): string;
|
|
5
|
+
export declare function pullPath(storagePath: string, params: Record<string, string>): string;
|
|
6
|
+
export declare function pushPath(storagePath: string, params: Record<string, string>): string;
|
|
7
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/collections/paths.ts"],"names":[],"mappings":"AAAA;;yEAEyE;AACzE,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAK9F;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAEpF;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAEpF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Substitutes `{key}` placeholders in a storage-path template, e.g.
|
|
2
|
+
* `resolveStoragePath('users/{identity}/accounts', { identity: userId })`.
|
|
3
|
+
* Generic — carries no product knowledge of which collections exist. */
|
|
4
|
+
export function resolveStoragePath(storagePath, params) {
|
|
5
|
+
return Object.entries(params).reduce((path, [key, value]) => path.replaceAll(`{${key}}`, encodeURIComponent(value)), storagePath);
|
|
6
|
+
}
|
|
7
|
+
export function pullPath(storagePath, params) {
|
|
8
|
+
return `/pull/${resolveStoragePath(storagePath, params)}`;
|
|
9
|
+
}
|
|
10
|
+
export function pushPath(storagePath, params) {
|
|
11
|
+
return `/push/${resolveStoragePath(storagePath, params)}`;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=paths.js.map
|