@freqhole/haruspex 0.0.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/dist/flows/add-peer/machine.d.ts +36 -0
- package/dist/flows/add-peer/machine.d.ts.map +1 -0
- package/dist/flows/add-peer/machine.js +500 -0
- package/dist/flows/add-peer/machine.test.d.ts +2 -0
- package/dist/flows/add-peer/machine.test.d.ts.map +1 -0
- package/dist/flows/add-peer/machine.test.js +421 -0
- package/dist/flows/add-peer/runner.d.ts +16 -0
- package/dist/flows/add-peer/runner.d.ts.map +1 -0
- package/dist/flows/add-peer/runner.js +349 -0
- package/dist/flows/add-peer/runner.test.d.ts +2 -0
- package/dist/flows/add-peer/runner.test.d.ts.map +1 -0
- package/dist/flows/add-peer/runner.test.js +205 -0
- package/dist/flows/add-peer/types.d.ts +271 -0
- package/dist/flows/add-peer/types.d.ts.map +1 -0
- package/dist/flows/add-peer/types.js +10 -0
- package/dist/flows/index.d.ts +6 -0
- package/dist/flows/index.d.ts.map +1 -0
- package/dist/flows/index.js +6 -0
- package/dist/identity/idbStore.d.ts +51 -0
- package/dist/identity/idbStore.d.ts.map +1 -0
- package/dist/identity/idbStore.js +183 -0
- package/dist/identity/idbStore.test.d.ts +2 -0
- package/dist/identity/idbStore.test.d.ts.map +1 -0
- package/dist/identity/idbStore.test.js +103 -0
- package/dist/identity/index.d.ts +8 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +3 -0
- package/dist/identity/resolve.d.ts +31 -0
- package/dist/identity/resolve.d.ts.map +1 -0
- package/dist/identity/resolve.js +37 -0
- package/dist/identity/resolve.test.d.ts +2 -0
- package/dist/identity/resolve.test.d.ts.map +1 -0
- package/dist/identity/resolve.test.js +105 -0
- package/dist/identity/types.d.ts +21 -0
- package/dist/identity/types.d.ts.map +1 -0
- package/dist/identity/types.js +1 -0
- package/dist/identity/webLocks.d.ts +22 -0
- package/dist/identity/webLocks.d.ts.map +1 -0
- package/dist/identity/webLocks.js +67 -0
- package/dist/identity/webLocks.test.d.ts +2 -0
- package/dist/identity/webLocks.test.d.ts.map +1 -0
- package/dist/identity/webLocks.test.js +125 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +7 -0
- package/dist/knock/index.d.ts +10 -0
- package/dist/knock/index.d.ts.map +1 -0
- package/dist/knock/index.js +5 -0
- package/dist/knock/pending-checker.d.ts +19 -0
- package/dist/knock/pending-checker.d.ts.map +1 -0
- package/dist/knock/pending-checker.js +27 -0
- package/dist/knock/pending-checker.test.d.ts +2 -0
- package/dist/knock/pending-checker.test.d.ts.map +1 -0
- package/dist/knock/pending-checker.test.js +93 -0
- package/dist/knock/requester.d.ts +26 -0
- package/dist/knock/requester.d.ts.map +1 -0
- package/dist/knock/requester.js +72 -0
- package/dist/knock/requester.test.d.ts +2 -0
- package/dist/knock/requester.test.d.ts.map +1 -0
- package/dist/knock/requester.test.js +108 -0
- package/dist/knock/responder.d.ts +13 -0
- package/dist/knock/responder.d.ts.map +1 -0
- package/dist/knock/responder.js +40 -0
- package/dist/knock/responder.test.d.ts +2 -0
- package/dist/knock/responder.test.d.ts.map +1 -0
- package/dist/knock/responder.test.js +89 -0
- package/dist/knock/store.d.ts +11 -0
- package/dist/knock/store.d.ts.map +1 -0
- package/dist/knock/store.js +211 -0
- package/dist/knock/store.test.d.ts +2 -0
- package/dist/knock/store.test.d.ts.map +1 -0
- package/dist/knock/store.test.js +195 -0
- package/dist/knock/types.d.ts +144 -0
- package/dist/knock/types.d.ts.map +1 -0
- package/dist/knock/types.js +26 -0
- package/dist/permissions/access.d.ts +54 -0
- package/dist/permissions/access.d.ts.map +1 -0
- package/dist/permissions/access.js +56 -0
- package/dist/permissions/access.test.d.ts +2 -0
- package/dist/permissions/access.test.d.ts.map +1 -0
- package/dist/permissions/access.test.js +72 -0
- package/dist/permissions/index.d.ts +5 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +2 -0
- package/dist/permissions/role-table.d.ts +11 -0
- package/dist/permissions/role-table.d.ts.map +1 -0
- package/dist/permissions/role-table.js +16 -0
- package/dist/protocol/client.d.ts +67 -0
- package/dist/protocol/client.d.ts.map +1 -0
- package/dist/protocol/client.js +195 -0
- package/dist/protocol/client.test.d.ts +2 -0
- package/dist/protocol/client.test.d.ts.map +1 -0
- package/dist/protocol/client.test.js +282 -0
- package/dist/protocol/codec.d.ts +45 -0
- package/dist/protocol/codec.d.ts.map +1 -0
- package/dist/protocol/codec.js +111 -0
- package/dist/protocol/codec.test.d.ts +2 -0
- package/dist/protocol/codec.test.d.ts.map +1 -0
- package/dist/protocol/codec.test.js +93 -0
- package/dist/protocol/extensions.d.ts +33 -0
- package/dist/protocol/extensions.d.ts.map +1 -0
- package/dist/protocol/extensions.js +36 -0
- package/dist/protocol/extensions.test.d.ts +2 -0
- package/dist/protocol/extensions.test.d.ts.map +1 -0
- package/dist/protocol/extensions.test.js +65 -0
- package/dist/protocol/fixtures.test.d.ts +2 -0
- package/dist/protocol/fixtures.test.d.ts.map +1 -0
- package/dist/protocol/fixtures.test.js +51 -0
- package/dist/protocol/index.d.ts +9 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +4 -0
- package/dist/protocol/messages.d.ts +480 -0
- package/dist/protocol/messages.d.ts.map +1 -0
- package/dist/protocol/messages.js +255 -0
- package/dist/protocol/messages.test.d.ts +2 -0
- package/dist/protocol/messages.test.d.ts.map +1 -0
- package/dist/protocol/messages.test.js +75 -0
- package/dist/share/codec.d.ts +58 -0
- package/dist/share/codec.d.ts.map +1 -0
- package/dist/share/codec.js +222 -0
- package/dist/share/codec.test.d.ts +2 -0
- package/dist/share/codec.test.d.ts.map +1 -0
- package/dist/share/codec.test.js +150 -0
- package/dist/share/detect.d.ts +24 -0
- package/dist/share/detect.d.ts.map +1 -0
- package/dist/share/detect.js +25 -0
- package/dist/share/detect.test.d.ts +2 -0
- package/dist/share/detect.test.d.ts.map +1 -0
- package/dist/share/detect.test.js +31 -0
- package/dist/share/index.d.ts +7 -0
- package/dist/share/index.d.ts.map +1 -0
- package/dist/share/index.js +3 -0
- package/dist/share/peer-addr.d.ts +32 -0
- package/dist/share/peer-addr.d.ts.map +1 -0
- package/dist/share/peer-addr.js +72 -0
- package/dist/share/peer-addr.test.d.ts +2 -0
- package/dist/share/peer-addr.test.d.ts.map +1 -0
- package/dist/share/peer-addr.test.js +58 -0
- package/dist/solid/auth-form.d.ts +43 -0
- package/dist/solid/auth-form.d.ts.map +1 -0
- package/dist/solid/auth-form.js +55 -0
- package/dist/solid/auth-form.test.d.ts +2 -0
- package/dist/solid/auth-form.test.d.ts.map +1 -0
- package/dist/solid/auth-form.test.js +131 -0
- package/dist/solid/auth-status.d.ts +21 -0
- package/dist/solid/auth-status.d.ts.map +1 -0
- package/dist/solid/auth-status.js +58 -0
- package/dist/solid/auth-status.test.d.ts +2 -0
- package/dist/solid/auth-status.test.d.ts.map +1 -0
- package/dist/solid/auth-status.test.js +60 -0
- package/dist/solid/index.d.ts +5 -0
- package/dist/solid/index.d.ts.map +1 -0
- package/dist/solid/index.js +10 -0
- package/dist/solid/knock-inbox.d.ts +61 -0
- package/dist/solid/knock-inbox.d.ts.map +1 -0
- package/dist/solid/knock-inbox.js +123 -0
- package/dist/solid/knock-inbox.test.d.ts +2 -0
- package/dist/solid/knock-inbox.test.d.ts.map +1 -0
- package/dist/solid/knock-inbox.test.js +153 -0
- package/dist/solid/reauth-flow.d.ts +26 -0
- package/dist/solid/reauth-flow.d.ts.map +1 -0
- package/dist/solid/reauth-flow.js +34 -0
- package/dist/solid/reauth-flow.test.d.ts +2 -0
- package/dist/solid/reauth-flow.test.d.ts.map +1 -0
- package/dist/solid/reauth-flow.test.js +91 -0
- package/dist/state/endpoint-control.d.ts +22 -0
- package/dist/state/endpoint-control.d.ts.map +1 -0
- package/dist/state/endpoint-control.js +34 -0
- package/dist/state/endpoint-control.test.d.ts +2 -0
- package/dist/state/endpoint-control.test.d.ts.map +1 -0
- package/dist/state/endpoint-control.test.js +63 -0
- package/dist/state/friend-directory.d.ts +37 -0
- package/dist/state/friend-directory.d.ts.map +1 -0
- package/dist/state/friend-directory.js +44 -0
- package/dist/state/friend-directory.test.d.ts +2 -0
- package/dist/state/friend-directory.test.d.ts.map +1 -0
- package/dist/state/friend-directory.test.js +63 -0
- package/dist/state/index.d.ts +6 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +3 -0
- package/dist/state/peer-names.d.ts +7 -0
- package/dist/state/peer-names.d.ts.map +1 -0
- package/dist/state/peer-names.js +21 -0
- package/dist/state/peer-names.test.d.ts +2 -0
- package/dist/state/peer-names.test.d.ts.map +1 -0
- package/dist/state/peer-names.test.js +29 -0
- package/dist/testing/idb-harness.d.ts +9 -0
- package/dist/testing/idb-harness.d.ts.map +1 -0
- package/dist/testing/idb-harness.js +19 -0
- package/dist/testing/idb-harness.test.d.ts +2 -0
- package/dist/testing/idb-harness.test.d.ts.map +1 -0
- package/dist/testing/idb-harness.test.js +18 -0
- package/dist/testing/identity-fixtures.d.ts +10 -0
- package/dist/testing/identity-fixtures.d.ts.map +1 -0
- package/dist/testing/identity-fixtures.js +21 -0
- package/dist/testing/identity-fixtures.test.d.ts +2 -0
- package/dist/testing/identity-fixtures.test.d.ts.map +1 -0
- package/dist/testing/identity-fixtures.test.js +28 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +11 -0
- package/dist/testing/knock-fixtures.d.ts +13 -0
- package/dist/testing/knock-fixtures.d.ts.map +1 -0
- package/dist/testing/knock-fixtures.js +45 -0
- package/dist/testing/knock-fixtures.test.d.ts +2 -0
- package/dist/testing/knock-fixtures.test.d.ts.map +1 -0
- package/dist/testing/knock-fixtures.test.js +65 -0
- package/dist/testing/protocol-fixtures.d.ts +7 -0
- package/dist/testing/protocol-fixtures.d.ts.map +1 -0
- package/dist/testing/protocol-fixtures.js +28 -0
- package/dist/testing/protocol-fixtures.test.d.ts +2 -0
- package/dist/testing/protocol-fixtures.test.d.ts.map +1 -0
- package/dist/testing/protocol-fixtures.test.js +19 -0
- package/dist/testing/state-fixtures.d.ts +33 -0
- package/dist/testing/state-fixtures.d.ts.map +1 -0
- package/dist/testing/state-fixtures.js +67 -0
- package/dist/testing/state-fixtures.test.d.ts +2 -0
- package/dist/testing/state-fixtures.test.d.ts.map +1 -0
- package/dist/testing/state-fixtures.test.js +68 -0
- package/dist/testing/webauthn-mocks.d.ts +27 -0
- package/dist/testing/webauthn-mocks.d.ts.map +1 -0
- package/dist/testing/webauthn-mocks.js +78 -0
- package/dist/testing/webauthn-mocks.test.d.ts +2 -0
- package/dist/testing/webauthn-mocks.test.d.ts.map +1 -0
- package/dist/testing/webauthn-mocks.test.js +40 -0
- package/dist/webauthn/ceremony.d.ts +76 -0
- package/dist/webauthn/ceremony.d.ts.map +1 -0
- package/dist/webauthn/ceremony.js +136 -0
- package/dist/webauthn/ceremony.test.d.ts +2 -0
- package/dist/webauthn/ceremony.test.d.ts.map +1 -0
- package/dist/webauthn/ceremony.test.js +245 -0
- package/dist/webauthn/codec.d.ts +39 -0
- package/dist/webauthn/codec.d.ts.map +1 -0
- package/dist/webauthn/codec.js +118 -0
- package/dist/webauthn/codec.test.d.ts +2 -0
- package/dist/webauthn/codec.test.d.ts.map +1 -0
- package/dist/webauthn/codec.test.js +126 -0
- package/dist/webauthn/index.d.ts +7 -0
- package/dist/webauthn/index.d.ts.map +1 -0
- package/dist/webauthn/index.js +3 -0
- package/dist/webauthn/transport.d.ts +58 -0
- package/dist/webauthn/transport.d.ts.map +1 -0
- package/dist/webauthn/transport.js +6 -0
- package/dist/webauthn/types.d.ts +78 -0
- package/dist/webauthn/types.d.ts.map +1 -0
- package/dist/webauthn/types.js +29 -0
- package/package.json +83 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AddPeerEffect, AddPeerEvent, AddPeerState, PendingRemote, PeerServerInfo, PeerTarget, SavedRemote } from "./types.js";
|
|
2
|
+
export declare const COMPLETE_DISMISS_MS = 1500;
|
|
3
|
+
export declare const DISMISS_TIMER_ID = "dismiss";
|
|
4
|
+
/** internal machine context. the public `AddPeerState` union is projected
|
|
5
|
+
* from this via `projectState`. */
|
|
6
|
+
export interface AddPeerContext {
|
|
7
|
+
step: "url" | "testing" | "auth" | "knock_sent" | "complete";
|
|
8
|
+
subStep: "input" | "knock_form";
|
|
9
|
+
input: string;
|
|
10
|
+
/** normalized http base url ("" when the target is p2p). */
|
|
11
|
+
url: string;
|
|
12
|
+
/** staged p2p peer address (null when the target is http). */
|
|
13
|
+
peerAddr: string | null;
|
|
14
|
+
/** classified target of the in-flight attempt. */
|
|
15
|
+
target: PeerTarget | null;
|
|
16
|
+
error: string | null;
|
|
17
|
+
progress: string | null;
|
|
18
|
+
serverInfo: PeerServerInfo | null;
|
|
19
|
+
pendingRemotes: PendingRemote[];
|
|
20
|
+
/** knock form values staged between SEND_KNOCK and its result. */
|
|
21
|
+
knockDraft: {
|
|
22
|
+
username: string;
|
|
23
|
+
message: string;
|
|
24
|
+
} | null;
|
|
25
|
+
/** the pending record a knock-status re-check is running for. */
|
|
26
|
+
retryPending: PendingRemote | null;
|
|
27
|
+
remote: SavedRemote | null;
|
|
28
|
+
}
|
|
29
|
+
export declare function initialContext(): AddPeerContext;
|
|
30
|
+
export declare function projectState(ctx: AddPeerContext): AddPeerState;
|
|
31
|
+
export interface TransitionResult {
|
|
32
|
+
ctx: AddPeerContext;
|
|
33
|
+
effects: AddPeerEffect[];
|
|
34
|
+
}
|
|
35
|
+
export declare function transition(ctx: AddPeerContext, event: AddPeerEvent): TransitionResult;
|
|
36
|
+
//# sourceMappingURL=machine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../../src/flows/add-peer/machine.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C;oCACoC;AACpC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC;IAC7D,OAAO,EAAE,OAAO,GAAG,YAAY,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kDAAkD;IAClD,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,kEAAkE;IAClE,UAAU,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzD,iEAAiE;IACjE,YAAY,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAgB,cAAc,IAAI,cAAc,CAgB/C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CA2B9D;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,cAAc,CAAC;IACpB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AA0BD,wBAAgB,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,gBAAgB,CAwarF"}
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
// the add-peer flow machine: a pure, synchronous state machine driving
|
|
2
|
+
// "add a remote peer" ui (address entry -> connection test -> knock
|
|
3
|
+
// request or auth -> saved remote).
|
|
4
|
+
//
|
|
5
|
+
// `send(event)` computes the next state and returns effect descriptors;
|
|
6
|
+
// it performs no io. an adapter executes the effects and feeds their
|
|
7
|
+
// outcomes back in as events - the bundled `runEffect` (runner.ts) does
|
|
8
|
+
// exactly that against an injected `AddPeerFlowDeps`. timers, abort
|
|
9
|
+
// controllers, and object urls live in the adapter, never here.
|
|
10
|
+
//
|
|
11
|
+
// load-bearing rules encoded in the table:
|
|
12
|
+
// - a pending record is persisted (stage "testing") BEFORE the probe
|
|
13
|
+
// runs, so an abandoned attempt is resumable from the pending list.
|
|
14
|
+
// - every probe failure path persists stage "failed" with a specific
|
|
15
|
+
// error string and returns to url/input.
|
|
16
|
+
// - an accepted knock deletes its pending record, then whoami decides
|
|
17
|
+
// complete-vs-auth; a denied knock persists stage "knock_rejected".
|
|
18
|
+
// - COMPLETE_PEER_ADDR (a knock-accepted / device-linked push) completes
|
|
19
|
+
// the flow from ANY state when the address matches the staged peer or
|
|
20
|
+
// a known pending record.
|
|
21
|
+
// - `complete` auto-dismisses via a SCHEDULE_TIMER effect (1500ms), never
|
|
22
|
+
// an inline setTimeout.
|
|
23
|
+
import { parsePeerAddress } from "../../share/peer-addr.js";
|
|
24
|
+
export const COMPLETE_DISMISS_MS = 1500;
|
|
25
|
+
export const DISMISS_TIMER_ID = "dismiss";
|
|
26
|
+
export function initialContext() {
|
|
27
|
+
return {
|
|
28
|
+
step: "url",
|
|
29
|
+
subStep: "input",
|
|
30
|
+
input: "",
|
|
31
|
+
url: "",
|
|
32
|
+
peerAddr: null,
|
|
33
|
+
target: null,
|
|
34
|
+
error: null,
|
|
35
|
+
progress: null,
|
|
36
|
+
serverInfo: null,
|
|
37
|
+
pendingRemotes: [],
|
|
38
|
+
knockDraft: null,
|
|
39
|
+
retryPending: null,
|
|
40
|
+
remote: null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function projectState(ctx) {
|
|
44
|
+
switch (ctx.step) {
|
|
45
|
+
case "url":
|
|
46
|
+
return {
|
|
47
|
+
step: "url",
|
|
48
|
+
subStep: ctx.subStep,
|
|
49
|
+
error: ctx.error,
|
|
50
|
+
pendingRemotes: ctx.pendingRemotes,
|
|
51
|
+
serverInfo: ctx.serverInfo,
|
|
52
|
+
peerAddr: ctx.peerAddr,
|
|
53
|
+
};
|
|
54
|
+
case "testing":
|
|
55
|
+
return { step: "testing", progress: ctx.progress, peerAddr: ctx.peerAddr, url: ctx.url };
|
|
56
|
+
case "auth":
|
|
57
|
+
return {
|
|
58
|
+
step: "auth",
|
|
59
|
+
error: ctx.error,
|
|
60
|
+
serverInfo: ctx.serverInfo,
|
|
61
|
+
peerAddr: ctx.peerAddr,
|
|
62
|
+
url: ctx.url,
|
|
63
|
+
};
|
|
64
|
+
case "knock_sent":
|
|
65
|
+
return { step: "knock_sent" };
|
|
66
|
+
case "complete":
|
|
67
|
+
// remote is always set by the REMOTE_CREATED transition that enters this step
|
|
68
|
+
return { step: "complete", remote: ctx.remote };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function noop(ctx) {
|
|
72
|
+
return { ctx, effects: [] };
|
|
73
|
+
}
|
|
74
|
+
/** the key a pending record / saved remote is addressed by for the
|
|
75
|
+
* current attempt: the p2p peer addr, or the http url. */
|
|
76
|
+
function addrKey(ctx) {
|
|
77
|
+
return ctx.peerAddr ?? ctx.url;
|
|
78
|
+
}
|
|
79
|
+
function toUrlInput(ctx, error) {
|
|
80
|
+
return { ...ctx, step: "url", subStep: "input", error, progress: null };
|
|
81
|
+
}
|
|
82
|
+
/** patch fields recording a probe's server info onto a pending record. */
|
|
83
|
+
function serverInfoPatch(info) {
|
|
84
|
+
if (!info)
|
|
85
|
+
return {};
|
|
86
|
+
return {
|
|
87
|
+
server_name: info.name,
|
|
88
|
+
server_description: info.description ?? null,
|
|
89
|
+
server_version: info.version,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export function transition(ctx, event) {
|
|
93
|
+
switch (event.type) {
|
|
94
|
+
case "MODAL_OPEN": {
|
|
95
|
+
const fresh = initialContext();
|
|
96
|
+
fresh.input = event.initialInput ?? "";
|
|
97
|
+
return { ctx: fresh, effects: [{ type: "LOAD_PENDING_REMOTES" }] };
|
|
98
|
+
}
|
|
99
|
+
case "MODAL_CLOSE":
|
|
100
|
+
return {
|
|
101
|
+
ctx: initialContext(),
|
|
102
|
+
effects: [{ type: "CANCEL_IN_FLIGHT" }, { type: "CALL_ON_CLOSE" }],
|
|
103
|
+
};
|
|
104
|
+
case "INPUT_CHANGE":
|
|
105
|
+
case "QR_SCAN":
|
|
106
|
+
return noop({ ...ctx, input: event.input, error: null });
|
|
107
|
+
case "PENDING_LOADED":
|
|
108
|
+
return noop({ ...ctx, pendingRemotes: event.records });
|
|
109
|
+
case "SUBMIT_URL": {
|
|
110
|
+
if (ctx.step !== "url")
|
|
111
|
+
return noop(ctx);
|
|
112
|
+
const target = parsePeerAddress(event.input);
|
|
113
|
+
if (!target) {
|
|
114
|
+
return noop({ ...ctx, error: "please enter a server url or peer id" });
|
|
115
|
+
}
|
|
116
|
+
if (target.type === "http") {
|
|
117
|
+
try {
|
|
118
|
+
new URL(target.url);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return noop({
|
|
122
|
+
...ctx,
|
|
123
|
+
error: "please enter a valid url (e.g. https://music.example.com)",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const next = {
|
|
128
|
+
...ctx,
|
|
129
|
+
step: "testing",
|
|
130
|
+
input: event.input,
|
|
131
|
+
target,
|
|
132
|
+
peerAddr: target.type === "p2p" ? target.peerAddr : null,
|
|
133
|
+
url: target.type === "http" ? target.url : "",
|
|
134
|
+
error: null,
|
|
135
|
+
progress: null,
|
|
136
|
+
serverInfo: null,
|
|
137
|
+
retryPending: null,
|
|
138
|
+
};
|
|
139
|
+
return { ctx: next, effects: [{ type: "CHECK_DUPLICATE", target }] };
|
|
140
|
+
}
|
|
141
|
+
case "DUPLICATE_RESULT": {
|
|
142
|
+
if (ctx.step !== "testing")
|
|
143
|
+
return noop(ctx);
|
|
144
|
+
if (event.duplicateName) {
|
|
145
|
+
return noop(toUrlInput(ctx, `this server is already added as "${event.duplicateName}". each server can only be added once.`));
|
|
146
|
+
}
|
|
147
|
+
// persist the attempt BEFORE probing, so a closed tab can resume it
|
|
148
|
+
return {
|
|
149
|
+
ctx,
|
|
150
|
+
effects: [
|
|
151
|
+
{
|
|
152
|
+
type: "UPSERT_PENDING",
|
|
153
|
+
peerAddr: addrKey(ctx),
|
|
154
|
+
patch: { stage: "testing", error_message: null },
|
|
155
|
+
},
|
|
156
|
+
{ type: "CLEAR_QUERY_PARAM" },
|
|
157
|
+
{ type: "CHECK_CONNECTION", target: ctx.target },
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
case "CONNECTION_RESULT": {
|
|
162
|
+
if (ctx.step !== "testing")
|
|
163
|
+
return noop(ctx);
|
|
164
|
+
const { outcome } = event;
|
|
165
|
+
switch (outcome.kind) {
|
|
166
|
+
case "already_authed":
|
|
167
|
+
return {
|
|
168
|
+
ctx: { ...ctx, serverInfo: outcome.serverInfo, progress: "creating remote..." },
|
|
169
|
+
effects: [{ type: "CREATE_REMOTE", peerAddr: ctx.peerAddr, url: ctx.url }],
|
|
170
|
+
};
|
|
171
|
+
case "needs_knock":
|
|
172
|
+
return {
|
|
173
|
+
ctx: {
|
|
174
|
+
...ctx,
|
|
175
|
+
step: "url",
|
|
176
|
+
subStep: "knock_form",
|
|
177
|
+
serverInfo: outcome.serverInfo,
|
|
178
|
+
error: outcome.error ?? null,
|
|
179
|
+
progress: null,
|
|
180
|
+
},
|
|
181
|
+
effects: [
|
|
182
|
+
{
|
|
183
|
+
type: "UPSERT_PENDING",
|
|
184
|
+
peerAddr: addrKey(ctx),
|
|
185
|
+
patch: { stage: "connected", ...serverInfoPatch(outcome.serverInfo) },
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
case "needs_auth":
|
|
190
|
+
return {
|
|
191
|
+
ctx: {
|
|
192
|
+
...ctx,
|
|
193
|
+
step: "auth",
|
|
194
|
+
serverInfo: outcome.serverInfo,
|
|
195
|
+
error: null,
|
|
196
|
+
progress: null,
|
|
197
|
+
},
|
|
198
|
+
effects: [
|
|
199
|
+
{
|
|
200
|
+
type: "UPSERT_PENDING",
|
|
201
|
+
peerAddr: addrKey(ctx),
|
|
202
|
+
patch: { stage: "connected", ...serverInfoPatch(outcome.serverInfo) },
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
};
|
|
206
|
+
case "failed":
|
|
207
|
+
return {
|
|
208
|
+
ctx: toUrlInput(ctx, outcome.error),
|
|
209
|
+
effects: [
|
|
210
|
+
{
|
|
211
|
+
type: "UPSERT_PENDING",
|
|
212
|
+
peerAddr: addrKey(ctx),
|
|
213
|
+
patch: { stage: "failed", error_message: outcome.error },
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
case "SUBMIT_KNOCK": {
|
|
221
|
+
if (ctx.step !== "url" || ctx.subStep !== "knock_form")
|
|
222
|
+
return noop(ctx);
|
|
223
|
+
if (!ctx.peerAddr) {
|
|
224
|
+
return noop({ ...ctx, error: "no peer address available" });
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
ctx: { ...ctx, knockDraft: { username: event.username, message: event.message }, error: null },
|
|
228
|
+
effects: [
|
|
229
|
+
{
|
|
230
|
+
type: "SEND_KNOCK",
|
|
231
|
+
peerAddr: ctx.peerAddr,
|
|
232
|
+
username: event.username,
|
|
233
|
+
message: event.message,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
case "KNOCK_SENT_RESULT": {
|
|
239
|
+
if (ctx.step !== "url" || ctx.subStep !== "knock_form")
|
|
240
|
+
return noop(ctx);
|
|
241
|
+
if (!event.ok) {
|
|
242
|
+
return noop({ ...ctx, error: event.error ?? "failed to send access request" });
|
|
243
|
+
}
|
|
244
|
+
const draft = ctx.knockDraft;
|
|
245
|
+
return {
|
|
246
|
+
ctx: { ...ctx, step: "knock_sent", knockDraft: null },
|
|
247
|
+
effects: [
|
|
248
|
+
{
|
|
249
|
+
type: "UPSERT_PENDING",
|
|
250
|
+
peerAddr: addrKey(ctx),
|
|
251
|
+
patch: {
|
|
252
|
+
stage: "knock_pending",
|
|
253
|
+
knock_username: draft?.username ?? null,
|
|
254
|
+
knock_message: draft?.message ?? null,
|
|
255
|
+
...serverInfoPatch(ctx.serverInfo),
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
case "CANCEL_KNOCK":
|
|
262
|
+
if (ctx.step !== "url" || ctx.subStep !== "knock_form")
|
|
263
|
+
return noop(ctx);
|
|
264
|
+
return noop(toUrlInput(ctx, null));
|
|
265
|
+
case "USE_INVITE_CODE":
|
|
266
|
+
case "PASSKEY_SIGNIN":
|
|
267
|
+
if (ctx.step !== "url" || ctx.subStep !== "knock_form")
|
|
268
|
+
return noop(ctx);
|
|
269
|
+
return noop({ ...ctx, step: "auth", error: null });
|
|
270
|
+
case "SUBMIT_AUTH": {
|
|
271
|
+
if (ctx.step !== "auth")
|
|
272
|
+
return noop(ctx);
|
|
273
|
+
if (ctx.peerAddr) {
|
|
274
|
+
if (event.mode === "login") {
|
|
275
|
+
return noop({
|
|
276
|
+
...ctx,
|
|
277
|
+
error: "login not yet supported for p2p remotes - please register with an invite code",
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
if (!event.inviteCode) {
|
|
281
|
+
return noop({ ...ctx, error: "invite code required for p2p registration" });
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
ctx: { ...ctx, error: null },
|
|
285
|
+
effects: [
|
|
286
|
+
{
|
|
287
|
+
type: "AUTH_REDEEM_INVITE",
|
|
288
|
+
peerAddr: ctx.peerAddr,
|
|
289
|
+
username: event.username,
|
|
290
|
+
inviteCode: event.inviteCode,
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
ctx: { ...ctx, error: null },
|
|
297
|
+
effects: [
|
|
298
|
+
{
|
|
299
|
+
type: "AUTH_HTTP",
|
|
300
|
+
url: ctx.url,
|
|
301
|
+
mode: event.mode,
|
|
302
|
+
username: event.username,
|
|
303
|
+
inviteCode: event.inviteCode,
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
case "PASSKEY_AUTH": {
|
|
309
|
+
if (ctx.step !== "auth")
|
|
310
|
+
return noop(ctx);
|
|
311
|
+
if (event.mode === "register") {
|
|
312
|
+
if (!ctx.peerAddr) {
|
|
313
|
+
return noop({ ...ctx, error: "no peer address available" });
|
|
314
|
+
}
|
|
315
|
+
if (!event.inviteCode?.trim()) {
|
|
316
|
+
return noop({ ...ctx, error: "invite code is required to register a new passkey" });
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
ctx: { ...ctx, error: null },
|
|
320
|
+
effects: [
|
|
321
|
+
{
|
|
322
|
+
type: "AUTH_PASSKEY_REGISTER",
|
|
323
|
+
peerAddr: ctx.peerAddr,
|
|
324
|
+
username: event.username?.trim() || undefined,
|
|
325
|
+
inviteCode: event.inviteCode.trim(),
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
ctx: { ...ctx, error: null },
|
|
332
|
+
effects: [
|
|
333
|
+
{
|
|
334
|
+
type: "AUTH_PASSKEY_LOGIN",
|
|
335
|
+
target: ctx.target ?? { type: "http", url: ctx.url },
|
|
336
|
+
username: event.username?.trim() || undefined,
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
case "AUTH_RESULT": {
|
|
342
|
+
if (ctx.step !== "auth")
|
|
343
|
+
return noop(ctx);
|
|
344
|
+
if (!event.ok) {
|
|
345
|
+
return noop({ ...ctx, error: event.error ?? "authentication failed" });
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
ctx: { ...ctx, error: null },
|
|
349
|
+
effects: [{ type: "CREATE_REMOTE", peerAddr: ctx.peerAddr, url: ctx.url }],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
case "REMOTE_CREATED": {
|
|
353
|
+
if (!event.ok || !event.remote) {
|
|
354
|
+
return noop(toUrlInput(ctx, event.error ?? "failed to save remote"));
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
ctx: { ...ctx, step: "complete", remote: event.remote, error: null, progress: null },
|
|
358
|
+
effects: [
|
|
359
|
+
{ type: "DELETE_PENDING_BY_ADDR", peerAddr: addrKey(ctx) },
|
|
360
|
+
{ type: "SCHEDULE_TIMER", id: DISMISS_TIMER_ID, ms: COMPLETE_DISMISS_MS },
|
|
361
|
+
],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
case "COMPLETE_PEER_ADDR": {
|
|
365
|
+
if (ctx.step === "complete")
|
|
366
|
+
return noop(ctx);
|
|
367
|
+
const matchesStaged = ctx.peerAddr === event.peerAddr;
|
|
368
|
+
const matchesPending = ctx.pendingRemotes.some((p) => p.peer_addr === event.peerAddr);
|
|
369
|
+
if (!matchesStaged && !matchesPending)
|
|
370
|
+
return noop(ctx);
|
|
371
|
+
return {
|
|
372
|
+
ctx: {
|
|
373
|
+
...ctx,
|
|
374
|
+
step: "testing",
|
|
375
|
+
peerAddr: event.peerAddr,
|
|
376
|
+
url: "",
|
|
377
|
+
target: { type: "p2p", peerAddr: event.peerAddr },
|
|
378
|
+
progress: "completing setup...",
|
|
379
|
+
error: null,
|
|
380
|
+
},
|
|
381
|
+
effects: [{ type: "CREATE_REMOTE", peerAddr: event.peerAddr, url: "" }],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
case "RETRY_PENDING": {
|
|
385
|
+
if (ctx.step !== "url")
|
|
386
|
+
return noop(ctx);
|
|
387
|
+
const { pending } = event;
|
|
388
|
+
const knockStages = ["knock_pending", "knock_accepted", "knock_rejected"];
|
|
389
|
+
if (!knockStages.includes(pending.stage)) {
|
|
390
|
+
// not a knock: re-run the normal connection flow for its address
|
|
391
|
+
return transition({ ...ctx, subStep: "input" }, { type: "SUBMIT_URL", input: pending.peer_addr });
|
|
392
|
+
}
|
|
393
|
+
const target = parsePeerAddress(pending.peer_addr) ?? {
|
|
394
|
+
type: "p2p",
|
|
395
|
+
peerAddr: pending.peer_addr,
|
|
396
|
+
};
|
|
397
|
+
return {
|
|
398
|
+
ctx: {
|
|
399
|
+
...ctx,
|
|
400
|
+
step: "testing",
|
|
401
|
+
input: pending.peer_addr,
|
|
402
|
+
peerAddr: target.type === "p2p" ? target.peerAddr : null,
|
|
403
|
+
url: target.type === "http" ? target.url : "",
|
|
404
|
+
target,
|
|
405
|
+
retryPending: pending,
|
|
406
|
+
error: null,
|
|
407
|
+
progress: "checking access request status...",
|
|
408
|
+
},
|
|
409
|
+
effects: [{ type: "CHECK_KNOCK_STATUS", pending }],
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
case "KNOCK_STATUS_RESULT": {
|
|
413
|
+
if (ctx.step !== "testing")
|
|
414
|
+
return noop(ctx);
|
|
415
|
+
const pending = ctx.retryPending;
|
|
416
|
+
const { outcome } = event;
|
|
417
|
+
switch (outcome.kind) {
|
|
418
|
+
case "accepted_authed":
|
|
419
|
+
return {
|
|
420
|
+
ctx: {
|
|
421
|
+
...ctx,
|
|
422
|
+
serverInfo: outcome.serverInfo ?? ctx.serverInfo,
|
|
423
|
+
retryPending: null,
|
|
424
|
+
progress: "creating remote...",
|
|
425
|
+
},
|
|
426
|
+
effects: [
|
|
427
|
+
...(pending ? [{ type: "DELETE_PENDING", id: pending.id }] : []),
|
|
428
|
+
{ type: "CREATE_REMOTE", peerAddr: ctx.peerAddr, url: ctx.url },
|
|
429
|
+
],
|
|
430
|
+
};
|
|
431
|
+
case "accepted_needs_auth":
|
|
432
|
+
return {
|
|
433
|
+
ctx: {
|
|
434
|
+
...ctx,
|
|
435
|
+
step: "auth",
|
|
436
|
+
serverInfo: outcome.serverInfo ?? ctx.serverInfo,
|
|
437
|
+
retryPending: null,
|
|
438
|
+
error: null,
|
|
439
|
+
progress: null,
|
|
440
|
+
},
|
|
441
|
+
effects: pending ? [{ type: "DELETE_PENDING", id: pending.id }] : [],
|
|
442
|
+
};
|
|
443
|
+
case "denied":
|
|
444
|
+
return {
|
|
445
|
+
ctx: { ...toUrlInput(ctx, "your access request was rejected by the server admin"), retryPending: null },
|
|
446
|
+
effects: pending
|
|
447
|
+
? [
|
|
448
|
+
{
|
|
449
|
+
type: "UPSERT_PENDING",
|
|
450
|
+
peerAddr: pending.peer_addr,
|
|
451
|
+
patch: { stage: "knock_rejected" },
|
|
452
|
+
},
|
|
453
|
+
]
|
|
454
|
+
: [],
|
|
455
|
+
};
|
|
456
|
+
case "pending":
|
|
457
|
+
return noop({
|
|
458
|
+
...toUrlInput(ctx, "access request is still pending - the server admin has not yet responded"),
|
|
459
|
+
retryPending: null,
|
|
460
|
+
});
|
|
461
|
+
case "unreachable":
|
|
462
|
+
return noop({
|
|
463
|
+
...toUrlInput(ctx, "still waiting for access approval - server may be offline"),
|
|
464
|
+
retryPending: null,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
case "DELETE_PENDING":
|
|
470
|
+
return {
|
|
471
|
+
ctx,
|
|
472
|
+
effects: [
|
|
473
|
+
{ type: "DELETE_PENDING", id: event.pending.id },
|
|
474
|
+
{ type: "LOAD_PENDING_REMOTES" },
|
|
475
|
+
],
|
|
476
|
+
};
|
|
477
|
+
case "BACK": {
|
|
478
|
+
if (ctx.step === "auth" || ctx.step === "knock_sent") {
|
|
479
|
+
return noop(toUrlInput(ctx, null));
|
|
480
|
+
}
|
|
481
|
+
if (ctx.step === "url" && ctx.subStep === "knock_form") {
|
|
482
|
+
return noop(toUrlInput(ctx, null));
|
|
483
|
+
}
|
|
484
|
+
if (ctx.step === "testing") {
|
|
485
|
+
return { ctx: toUrlInput(ctx, null), effects: [{ type: "CANCEL_IN_FLIGHT" }] };
|
|
486
|
+
}
|
|
487
|
+
return noop(ctx);
|
|
488
|
+
}
|
|
489
|
+
case "TIMER_FIRED": {
|
|
490
|
+
if (event.id !== DISMISS_TIMER_ID || ctx.step !== "complete")
|
|
491
|
+
return noop(ctx);
|
|
492
|
+
const remote = ctx.remote;
|
|
493
|
+
return {
|
|
494
|
+
ctx: initialContext(),
|
|
495
|
+
effects: [{ type: "CALL_ON_CLOSE" }, { type: "CALL_ON_SUCCESS", remote }],
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return noop(ctx);
|
|
500
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"machine.test.d.ts","sourceRoot":"","sources":["../../../src/flows/add-peer/machine.test.ts"],"names":[],"mappings":""}
|