@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,26 @@
|
|
|
1
|
+
import { type Accessor } from "solid-js";
|
|
2
|
+
import type { AuthFormData } from "./auth-form.js";
|
|
3
|
+
export interface ReauthFlowDeps {
|
|
4
|
+
/** performs the actual re-authentication against whatever transport the
|
|
5
|
+
* caller is using. resolve with `{ success: false }` (or reject) on
|
|
6
|
+
* failure - both are treated as failures and surface `error`. */
|
|
7
|
+
authenticate(data: AuthFormData): Promise<{
|
|
8
|
+
success: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
}>;
|
|
11
|
+
/** called once `authenticate` resolves with `success: true`. */
|
|
12
|
+
onSuccess(): void;
|
|
13
|
+
/** called from `close()`, but only when not currently loading. */
|
|
14
|
+
onClose(): void;
|
|
15
|
+
}
|
|
16
|
+
export interface ReauthFlowState {
|
|
17
|
+
isLoading: Accessor<boolean>;
|
|
18
|
+
error: Accessor<string | null>;
|
|
19
|
+
/** runs `authenticate`, calling `onSuccess` on success or setting
|
|
20
|
+
* `error` on failure. */
|
|
21
|
+
submit(data: AuthFormData): Promise<void>;
|
|
22
|
+
/** clears any error and calls `onClose`, unless a submit is in flight. */
|
|
23
|
+
close(): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function createReauthFlow(deps: ReauthFlowDeps): ReauthFlowState;
|
|
26
|
+
//# sourceMappingURL=reauth-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reauth-flow.d.ts","sourceRoot":"","sources":["../../src/solid/reauth-flow.ts"],"names":[],"mappings":"AAMA,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC7B;;sEAEkE;IAClE,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChF,gEAAgE;IAChE,SAAS,IAAI,IAAI,CAAC;IAClB,kEAAkE;IAClE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC7B,KAAK,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/B;8BAC0B;IAC1B,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,0EAA0E;IAC1E,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,eAAe,CA2BtE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// headless re-authentication flow: loading/error state around a single
|
|
2
|
+
// injected `authenticate` call, plus a close guard that refuses to close
|
|
3
|
+
// while a request is in flight. pairs naturally with `createAuthForm` -
|
|
4
|
+
// pass this flow's `isLoading`/`submit` straight through as that
|
|
5
|
+
// primitive's `loading`/`onSubmit` deps.
|
|
6
|
+
import { createSignal } from "solid-js";
|
|
7
|
+
export function createReauthFlow(deps) {
|
|
8
|
+
const [isLoading, setIsLoading] = createSignal(false);
|
|
9
|
+
const [error, setError] = createSignal(null);
|
|
10
|
+
async function submit(data) {
|
|
11
|
+
setError(null);
|
|
12
|
+
setIsLoading(true);
|
|
13
|
+
try {
|
|
14
|
+
const result = await deps.authenticate(data);
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
throw new Error(result.error ?? "authentication failed");
|
|
17
|
+
}
|
|
18
|
+
deps.onSuccess();
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
setError(err instanceof Error ? err.message : "authentication failed");
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
setIsLoading(false);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function close() {
|
|
28
|
+
if (isLoading())
|
|
29
|
+
return;
|
|
30
|
+
setError(null);
|
|
31
|
+
deps.onClose();
|
|
32
|
+
}
|
|
33
|
+
return { isLoading, error, submit, close };
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reauth-flow.test.d.ts","sourceRoot":"","sources":["../../src/solid/reauth-flow.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createRoot } from "solid-js";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { createReauthFlow } from "./reauth-flow.js";
|
|
4
|
+
describe("createReauthFlow", () => {
|
|
5
|
+
it("submit calls authenticate and onSuccess on success", async () => {
|
|
6
|
+
await createRoot(async (dispose) => {
|
|
7
|
+
const authenticate = vi.fn().mockResolvedValue({ success: true });
|
|
8
|
+
const onSuccess = vi.fn();
|
|
9
|
+
const onClose = vi.fn();
|
|
10
|
+
const flow = createReauthFlow({ authenticate, onSuccess, onClose });
|
|
11
|
+
await flow.submit({ username: "viz", mode: "login" });
|
|
12
|
+
expect(authenticate).toHaveBeenCalledWith({ username: "viz", mode: "login" });
|
|
13
|
+
expect(onSuccess).toHaveBeenCalled();
|
|
14
|
+
expect(flow.error()).toBeNull();
|
|
15
|
+
expect(flow.isLoading()).toBe(false);
|
|
16
|
+
dispose();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
it("submit sets error and does not call onSuccess when authenticate resolves unsuccessfully", async () => {
|
|
20
|
+
await createRoot(async (dispose) => {
|
|
21
|
+
const authenticate = vi.fn().mockResolvedValue({ success: false, error: "bad credentials" });
|
|
22
|
+
const onSuccess = vi.fn();
|
|
23
|
+
const flow = createReauthFlow({ authenticate, onSuccess, onClose: vi.fn() });
|
|
24
|
+
await flow.submit({ username: "viz", mode: "login" });
|
|
25
|
+
expect(onSuccess).not.toHaveBeenCalled();
|
|
26
|
+
expect(flow.error()).toBe("bad credentials");
|
|
27
|
+
expect(flow.isLoading()).toBe(false);
|
|
28
|
+
dispose();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
it("submit sets a generic error message when authenticate throws a non-Error", async () => {
|
|
32
|
+
await createRoot(async (dispose) => {
|
|
33
|
+
const authenticate = vi.fn().mockRejectedValue("network down");
|
|
34
|
+
const flow = createReauthFlow({ authenticate, onSuccess: vi.fn(), onClose: vi.fn() });
|
|
35
|
+
await flow.submit({ username: "viz", mode: "login" });
|
|
36
|
+
expect(flow.error()).toBe("authentication failed");
|
|
37
|
+
dispose();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it("submit surfaces a thrown Error's message", async () => {
|
|
41
|
+
await createRoot(async (dispose) => {
|
|
42
|
+
const authenticate = vi.fn().mockRejectedValue(new Error("peer unreachable"));
|
|
43
|
+
const flow = createReauthFlow({ authenticate, onSuccess: vi.fn(), onClose: vi.fn() });
|
|
44
|
+
await flow.submit({ username: "viz", mode: "login" });
|
|
45
|
+
expect(flow.error()).toBe("peer unreachable");
|
|
46
|
+
dispose();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it("submit clears a previous error before retrying", async () => {
|
|
50
|
+
await createRoot(async (dispose) => {
|
|
51
|
+
const authenticate = vi
|
|
52
|
+
.fn()
|
|
53
|
+
.mockResolvedValueOnce({ success: false, error: "first failure" })
|
|
54
|
+
.mockResolvedValueOnce({ success: true });
|
|
55
|
+
const flow = createReauthFlow({ authenticate, onSuccess: vi.fn(), onClose: vi.fn() });
|
|
56
|
+
await flow.submit({ username: "viz", mode: "login" });
|
|
57
|
+
expect(flow.error()).toBe("first failure");
|
|
58
|
+
await flow.submit({ username: "viz", mode: "login" });
|
|
59
|
+
expect(flow.error()).toBeNull();
|
|
60
|
+
dispose();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
it("close calls onClose and clears any error when not loading", () => {
|
|
64
|
+
createRoot((dispose) => {
|
|
65
|
+
const onClose = vi.fn();
|
|
66
|
+
const flow = createReauthFlow({
|
|
67
|
+
authenticate: vi.fn(),
|
|
68
|
+
onSuccess: vi.fn(),
|
|
69
|
+
onClose,
|
|
70
|
+
});
|
|
71
|
+
flow.close();
|
|
72
|
+
expect(onClose).toHaveBeenCalled();
|
|
73
|
+
dispose();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
it("close is a no-op while a submit is in flight", async () => {
|
|
77
|
+
await createRoot(async (dispose) => {
|
|
78
|
+
const onClose = vi.fn();
|
|
79
|
+
let resolveAuth;
|
|
80
|
+
const authenticate = vi.fn(() => new Promise((resolve) => (resolveAuth = resolve)));
|
|
81
|
+
const flow = createReauthFlow({ authenticate, onSuccess: vi.fn(), onClose });
|
|
82
|
+
const submitPromise = flow.submit({ username: "viz", mode: "login" });
|
|
83
|
+
expect(flow.isLoading()).toBe(true);
|
|
84
|
+
flow.close();
|
|
85
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
86
|
+
resolveAuth({ success: true });
|
|
87
|
+
await submitPromise;
|
|
88
|
+
dispose();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** the p2p endpoint's lifecycle state, for ui display. */
|
|
2
|
+
export type EndpointState = "off" | "starting" | "online" | "error";
|
|
3
|
+
/** the structural surface this subpath needs from a p2p transport adapter. */
|
|
4
|
+
export interface EndpointAdapter {
|
|
5
|
+
stop(): void;
|
|
6
|
+
restart(): Promise<void>;
|
|
7
|
+
getEndpointState(): EndpointState;
|
|
8
|
+
onEndpointStateChange(handler: (state: EndpointState) => void): () => void;
|
|
9
|
+
}
|
|
10
|
+
/** register the adapter - call once, after the adapter is created. */
|
|
11
|
+
export declare function registerEndpointAdapter(next: EndpointAdapter): void;
|
|
12
|
+
/** forget the registered adapter (teardown, test isolation). */
|
|
13
|
+
export declare function clearEndpointAdapter(): void;
|
|
14
|
+
/** stop the p2p endpoint; can be resumed with restartEndpoint(). */
|
|
15
|
+
export declare function stopEndpoint(): void;
|
|
16
|
+
/** restart the p2p endpoint after it was stopped. */
|
|
17
|
+
export declare function restartEndpoint(): Promise<void>;
|
|
18
|
+
/** the current endpoint state, synchronously. "off" when no adapter is registered. */
|
|
19
|
+
export declare function getEndpointState(): EndpointState;
|
|
20
|
+
/** subscribe to endpoint state changes. returns an unsubscribe function. */
|
|
21
|
+
export declare function onEndpointStateChange(handler: (state: EndpointState) => void): () => void;
|
|
22
|
+
//# sourceMappingURL=endpoint-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-control.d.ts","sourceRoot":"","sources":["../../src/state/endpoint-control.ts"],"names":[],"mappings":"AAUA,0DAA0D;AAC1D,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEpE,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,gBAAgB,IAAI,aAAa,CAAC;IAClC,qBAAqB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC5E;AAID,sEAAsE;AACtE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAEnE;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED,oEAAoE;AACpE,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED,qDAAqD;AACrD,wBAAgB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,sFAAsF;AACtF,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI,CAEzF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// endpoint-control - a module-level singleton bridging a p2p transport
|
|
2
|
+
// adapter to any ui code that needs to read or toggle its lifecycle state
|
|
3
|
+
// without holding a direct reference to the adapter itself. an app calls
|
|
4
|
+
// registerEndpointAdapter() once at startup; anything else (a settings
|
|
5
|
+
// panel, a status pill) imports the helpers below to read/toggle.
|
|
6
|
+
//
|
|
7
|
+
// the adapter is a structural type, not an import of any concrete
|
|
8
|
+
// transport - this subpath never depends on midden or any particular p2p
|
|
9
|
+
// implementation, only on something shaped like `EndpointAdapter`.
|
|
10
|
+
let adapter = null;
|
|
11
|
+
/** register the adapter - call once, after the adapter is created. */
|
|
12
|
+
export function registerEndpointAdapter(next) {
|
|
13
|
+
adapter = next;
|
|
14
|
+
}
|
|
15
|
+
/** forget the registered adapter (teardown, test isolation). */
|
|
16
|
+
export function clearEndpointAdapter() {
|
|
17
|
+
adapter = null;
|
|
18
|
+
}
|
|
19
|
+
/** stop the p2p endpoint; can be resumed with restartEndpoint(). */
|
|
20
|
+
export function stopEndpoint() {
|
|
21
|
+
adapter?.stop();
|
|
22
|
+
}
|
|
23
|
+
/** restart the p2p endpoint after it was stopped. */
|
|
24
|
+
export function restartEndpoint() {
|
|
25
|
+
return adapter?.restart() ?? Promise.resolve();
|
|
26
|
+
}
|
|
27
|
+
/** the current endpoint state, synchronously. "off" when no adapter is registered. */
|
|
28
|
+
export function getEndpointState() {
|
|
29
|
+
return adapter?.getEndpointState() ?? "off";
|
|
30
|
+
}
|
|
31
|
+
/** subscribe to endpoint state changes. returns an unsubscribe function. */
|
|
32
|
+
export function onEndpointStateChange(handler) {
|
|
33
|
+
return adapter?.onEndpointStateChange(handler) ?? (() => { });
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-control.test.d.ts","sourceRoot":"","sources":["../../src/state/endpoint-control.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { clearEndpointAdapter, getEndpointState, onEndpointStateChange, registerEndpointAdapter, restartEndpoint, stopEndpoint, } from "./endpoint-control.js";
|
|
3
|
+
function makeAdapter(initial = "online") {
|
|
4
|
+
let state = initial;
|
|
5
|
+
const handlers = new Set();
|
|
6
|
+
const adapter = {
|
|
7
|
+
stop: vi.fn(() => {
|
|
8
|
+
state = "off";
|
|
9
|
+
for (const h of handlers)
|
|
10
|
+
h(state);
|
|
11
|
+
}),
|
|
12
|
+
restart: vi.fn(async () => {
|
|
13
|
+
state = "online";
|
|
14
|
+
for (const h of handlers)
|
|
15
|
+
h(state);
|
|
16
|
+
}),
|
|
17
|
+
getEndpointState: () => state,
|
|
18
|
+
onEndpointStateChange: (handler) => {
|
|
19
|
+
handlers.add(handler);
|
|
20
|
+
return () => handlers.delete(handler);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
return adapter;
|
|
24
|
+
}
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
clearEndpointAdapter();
|
|
27
|
+
});
|
|
28
|
+
describe("endpoint-control", () => {
|
|
29
|
+
it("reports 'off' when no adapter is registered", () => {
|
|
30
|
+
expect(getEndpointState()).toBe("off");
|
|
31
|
+
});
|
|
32
|
+
it("no-ops stop/restart/subscribe when no adapter is registered", async () => {
|
|
33
|
+
expect(() => stopEndpoint()).not.toThrow();
|
|
34
|
+
await expect(restartEndpoint()).resolves.toBeUndefined();
|
|
35
|
+
const unsubscribe = onEndpointStateChange(() => { });
|
|
36
|
+
expect(() => unsubscribe()).not.toThrow();
|
|
37
|
+
});
|
|
38
|
+
it("delegates state reads to the registered adapter", () => {
|
|
39
|
+
const adapter = makeAdapter("online");
|
|
40
|
+
registerEndpointAdapter(adapter);
|
|
41
|
+
expect(getEndpointState()).toBe("online");
|
|
42
|
+
});
|
|
43
|
+
it("delegates stop/restart to the registered adapter", async () => {
|
|
44
|
+
const adapter = makeAdapter("online");
|
|
45
|
+
registerEndpointAdapter(adapter);
|
|
46
|
+
stopEndpoint();
|
|
47
|
+
expect(adapter.stop).toHaveBeenCalledOnce();
|
|
48
|
+
expect(getEndpointState()).toBe("off");
|
|
49
|
+
await restartEndpoint();
|
|
50
|
+
expect(adapter.restart).toHaveBeenCalledOnce();
|
|
51
|
+
expect(getEndpointState()).toBe("online");
|
|
52
|
+
});
|
|
53
|
+
it("subscribes to state changes through the registered adapter", () => {
|
|
54
|
+
const adapter = makeAdapter("online");
|
|
55
|
+
registerEndpointAdapter(adapter);
|
|
56
|
+
const seen = [];
|
|
57
|
+
const unsubscribe = onEndpointStateChange((state) => seen.push(state));
|
|
58
|
+
stopEndpoint();
|
|
59
|
+
expect(seen).toEqual(["off"]);
|
|
60
|
+
unsubscribe();
|
|
61
|
+
void restartEndpoint();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** one raw friend entry, as read from wherever an app stores its friend list. */
|
|
2
|
+
export interface FriendDirectoryEntry {
|
|
3
|
+
/** a display name override the local user set for this friend, if any. */
|
|
4
|
+
alias?: string;
|
|
5
|
+
/** the friend's own reported username, if known. */
|
|
6
|
+
username?: string;
|
|
7
|
+
/** every node id this friend is known to use, each with its own optional
|
|
8
|
+
* profile doc pointer and reported username. */
|
|
9
|
+
nodeIds: Array<{
|
|
10
|
+
nodeId: string;
|
|
11
|
+
profileDocId?: string;
|
|
12
|
+
username?: string;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
/** one friend candidate for a "pick a friend" picker ui. */
|
|
16
|
+
export interface FriendPickerCandidate {
|
|
17
|
+
nodeId: string;
|
|
18
|
+
profileDocId: string;
|
|
19
|
+
displayName: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* pure filter/map step: turn raw friend entries into picker candidates.
|
|
23
|
+
* a friend is only offered as a candidate once it has at least one node id
|
|
24
|
+
* with a known profile doc pointer - there'd be nothing for the picker to
|
|
25
|
+
* open otherwise. display name preference: alias, then the friend's own
|
|
26
|
+
* username, then the chosen node id's reported username, then "friend".
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildFriendDirectory(entries: FriendDirectoryEntry[]): FriendPickerCandidate[];
|
|
29
|
+
/**
|
|
30
|
+
* best-effort candidate list for a "pick a friend" picker: calls the
|
|
31
|
+
* injected loader for the raw friend entries and runs them through
|
|
32
|
+
* buildFriendDirectory(). returns `[]` on any failure (no friend list yet,
|
|
33
|
+
* the loader's own backend unreachable) rather than throwing - matches
|
|
34
|
+
* this kind of picker's usual "best effort, no error ui" convention.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getFriendsForPicker(loadEntries: () => Promise<FriendDirectoryEntry[]>): Promise<FriendPickerCandidate[]>;
|
|
37
|
+
//# sourceMappingURL=friend-directory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"friend-directory.d.ts","sourceRoot":"","sources":["../../src/state/friend-directory.ts"],"names":[],"mappings":"AAOA,iFAAiF;AACjF,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;qDACiD;IACjD,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E;AAED,4DAA4D;AAC5D,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,qBAAqB,EAAE,CAa7F;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,OAAO,CAAC,oBAAoB,EAAE,CAAC,GACjD,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAOlC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// friend-directory - a best-effort friend list for "pick one of your
|
|
2
|
+
// friends" pickers, kept framework-free: this subpath never touches
|
|
3
|
+
// automerge/pixi/solid directly. resolving the actual friend records
|
|
4
|
+
// (wherever they live - an automerge doc, a sqlite table, an api call) is
|
|
5
|
+
// always the caller's job; this module only owns the pure candidate
|
|
6
|
+
// shape/selection logic and a best-effort async wrapper around it.
|
|
7
|
+
/**
|
|
8
|
+
* pure filter/map step: turn raw friend entries into picker candidates.
|
|
9
|
+
* a friend is only offered as a candidate once it has at least one node id
|
|
10
|
+
* with a known profile doc pointer - there'd be nothing for the picker to
|
|
11
|
+
* open otherwise. display name preference: alias, then the friend's own
|
|
12
|
+
* username, then the chosen node id's reported username, then "friend".
|
|
13
|
+
*/
|
|
14
|
+
export function buildFriendDirectory(entries) {
|
|
15
|
+
const candidates = [];
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
const nodeEntry = entry.nodeIds.find((n) => n.profileDocId);
|
|
18
|
+
if (!nodeEntry || !nodeEntry.profileDocId)
|
|
19
|
+
continue;
|
|
20
|
+
const displayName = entry.alias || entry.username || nodeEntry.username || "friend";
|
|
21
|
+
candidates.push({
|
|
22
|
+
nodeId: nodeEntry.nodeId,
|
|
23
|
+
profileDocId: nodeEntry.profileDocId,
|
|
24
|
+
displayName,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return candidates;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* best-effort candidate list for a "pick a friend" picker: calls the
|
|
31
|
+
* injected loader for the raw friend entries and runs them through
|
|
32
|
+
* buildFriendDirectory(). returns `[]` on any failure (no friend list yet,
|
|
33
|
+
* the loader's own backend unreachable) rather than throwing - matches
|
|
34
|
+
* this kind of picker's usual "best effort, no error ui" convention.
|
|
35
|
+
*/
|
|
36
|
+
export async function getFriendsForPicker(loadEntries) {
|
|
37
|
+
try {
|
|
38
|
+
const entries = await loadEntries();
|
|
39
|
+
return buildFriendDirectory(entries);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"friend-directory.test.d.ts","sourceRoot":"","sources":["../../src/state/friend-directory.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildFriendDirectory, getFriendsForPicker, } from "./friend-directory.js";
|
|
3
|
+
describe("buildFriendDirectory", () => {
|
|
4
|
+
it("excludes friends with no profile-doc-bearing node id", () => {
|
|
5
|
+
const entries = [
|
|
6
|
+
{ username: "alice", nodeIds: [{ nodeId: "node-1" }] },
|
|
7
|
+
];
|
|
8
|
+
expect(buildFriendDirectory(entries)).toEqual([]);
|
|
9
|
+
});
|
|
10
|
+
it("prefers alias, then username, then the node's own username, then 'friend'", () => {
|
|
11
|
+
const entries = [
|
|
12
|
+
{
|
|
13
|
+
alias: "besty",
|
|
14
|
+
username: "alice",
|
|
15
|
+
nodeIds: [{ nodeId: "node-1", profileDocId: "doc-1", username: "alice-node" }],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
username: "bob",
|
|
19
|
+
nodeIds: [{ nodeId: "node-2", profileDocId: "doc-2", username: "bob-node" }],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
nodeIds: [{ nodeId: "node-3", profileDocId: "doc-3", username: "carol-node" }],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
nodeIds: [{ nodeId: "node-4", profileDocId: "doc-4" }],
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
expect(buildFriendDirectory(entries)).toEqual([
|
|
29
|
+
{ nodeId: "node-1", profileDocId: "doc-1", displayName: "besty" },
|
|
30
|
+
{ nodeId: "node-2", profileDocId: "doc-2", displayName: "bob" },
|
|
31
|
+
{ nodeId: "node-3", profileDocId: "doc-3", displayName: "carol-node" },
|
|
32
|
+
{ nodeId: "node-4", profileDocId: "doc-4", displayName: "friend" },
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
it("picks the first node id that has a profile doc pointer", () => {
|
|
36
|
+
const entries = [
|
|
37
|
+
{
|
|
38
|
+
nodeIds: [
|
|
39
|
+
{ nodeId: "node-1" },
|
|
40
|
+
{ nodeId: "node-2", profileDocId: "doc-2" },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
expect(buildFriendDirectory(entries)).toEqual([
|
|
45
|
+
{ nodeId: "node-2", profileDocId: "doc-2", displayName: "friend" },
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("getFriendsForPicker", () => {
|
|
50
|
+
it("resolves candidates from the injected loader", async () => {
|
|
51
|
+
const entries = [
|
|
52
|
+
{ username: "alice", nodeIds: [{ nodeId: "node-1", profileDocId: "doc-1" }] },
|
|
53
|
+
];
|
|
54
|
+
const result = await getFriendsForPicker(async () => entries);
|
|
55
|
+
expect(result).toEqual([{ nodeId: "node-1", profileDocId: "doc-1", displayName: "alice" }]);
|
|
56
|
+
});
|
|
57
|
+
it("returns [] when the loader throws", async () => {
|
|
58
|
+
const result = await getFriendsForPicker(async () => {
|
|
59
|
+
throw new Error("no friend list yet");
|
|
60
|
+
});
|
|
61
|
+
expect(result).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { registerPeerName, peerNameFor, clearPeerNames } from "./peer-names.js";
|
|
2
|
+
export type { EndpointAdapter, EndpointState } from "./endpoint-control.js";
|
|
3
|
+
export { clearEndpointAdapter, getEndpointState, onEndpointStateChange, registerEndpointAdapter, restartEndpoint, stopEndpoint, } from "./endpoint-control.js";
|
|
4
|
+
export type { FriendDirectoryEntry, FriendPickerCandidate } from "./friend-directory.js";
|
|
5
|
+
export { buildFriendDirectory, getFriendsForPicker } from "./friend-directory.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/state/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEhF,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,YAAY,GACb,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { registerPeerName, peerNameFor, clearPeerNames } from "./peer-names.js";
|
|
2
|
+
export { clearEndpointAdapter, getEndpointState, onEndpointStateChange, registerEndpointAdapter, restartEndpoint, stopEndpoint, } from "./endpoint-control.js";
|
|
3
|
+
export { buildFriendDirectory, getFriendsForPicker } from "./friend-directory.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** register (or update) a display name for a node id. empty values are ignored. */
|
|
2
|
+
export declare function registerPeerName(nodeId: string, name: string): void;
|
|
3
|
+
/** the display name for a node id, or null when unknown. */
|
|
4
|
+
export declare function peerNameFor(nodeId: string): string | null;
|
|
5
|
+
/** clear the registry (identity switch, sign-out, test teardown). */
|
|
6
|
+
export declare function clearPeerNames(): void;
|
|
7
|
+
//# sourceMappingURL=peer-names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer-names.d.ts","sourceRoot":"","sources":["../../src/state/peer-names.ts"],"names":[],"mappings":"AASA,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAGnE;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEzD;AAED,qEAAqE;AACrE,wBAAgB,cAAc,IAAI,IAAI,CAErC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// peer-names - a tiny session-scoped registry mapping node ids to display
|
|
2
|
+
// names. exists so deep ui code (a property tray, a picker row, a log
|
|
3
|
+
// line) can show a human name without threading a social/friends doc
|
|
4
|
+
// through every mount context. read-side consumers must always have a
|
|
5
|
+
// fallback (e.g. a shortened node id) for a node id the registry hasn't
|
|
6
|
+
// seen a name for yet - it only knows about peers registered this session.
|
|
7
|
+
const names = new Map();
|
|
8
|
+
/** register (or update) a display name for a node id. empty values are ignored. */
|
|
9
|
+
export function registerPeerName(nodeId, name) {
|
|
10
|
+
if (!nodeId || !name)
|
|
11
|
+
return;
|
|
12
|
+
names.set(nodeId, name);
|
|
13
|
+
}
|
|
14
|
+
/** the display name for a node id, or null when unknown. */
|
|
15
|
+
export function peerNameFor(nodeId) {
|
|
16
|
+
return names.get(nodeId) ?? null;
|
|
17
|
+
}
|
|
18
|
+
/** clear the registry (identity switch, sign-out, test teardown). */
|
|
19
|
+
export function clearPeerNames() {
|
|
20
|
+
names.clear();
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer-names.test.d.ts","sourceRoot":"","sources":["../../src/state/peer-names.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { clearPeerNames, peerNameFor, registerPeerName } from "./peer-names.js";
|
|
3
|
+
afterEach(() => {
|
|
4
|
+
clearPeerNames();
|
|
5
|
+
});
|
|
6
|
+
describe("peer-names", () => {
|
|
7
|
+
it("returns null for an unknown node id", () => {
|
|
8
|
+
expect(peerNameFor("node-unknown")).toBeNull();
|
|
9
|
+
});
|
|
10
|
+
it("registers and looks up a display name", () => {
|
|
11
|
+
registerPeerName("node-abc", "alice");
|
|
12
|
+
expect(peerNameFor("node-abc")).toBe("alice");
|
|
13
|
+
});
|
|
14
|
+
it("updates an existing registration", () => {
|
|
15
|
+
registerPeerName("node-abc", "alice");
|
|
16
|
+
registerPeerName("node-abc", "alice-2");
|
|
17
|
+
expect(peerNameFor("node-abc")).toBe("alice-2");
|
|
18
|
+
});
|
|
19
|
+
it("ignores empty node ids or names", () => {
|
|
20
|
+
registerPeerName("", "alice");
|
|
21
|
+
registerPeerName("node-abc", "");
|
|
22
|
+
expect(peerNameFor("node-abc")).toBeNull();
|
|
23
|
+
});
|
|
24
|
+
it("clears every registration", () => {
|
|
25
|
+
registerPeerName("node-abc", "alice");
|
|
26
|
+
clearPeerNames();
|
|
27
|
+
expect(peerNameFor("node-abc")).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** installs fake-indexeddb's `IDBFactory` and `IDBKeyRange` as globals,
|
|
2
|
+
* so idb-based code runs in a node test environment. call this in
|
|
3
|
+
* `beforeEach` for tests that use idb stores. */
|
|
4
|
+
export declare function setupFakeIndexedDB(): void;
|
|
5
|
+
/** clears the fake idb instance (useful in `afterEach` if you want a
|
|
6
|
+
* fresh db for each test, though most tests just call `setupFakeIndexedDB`
|
|
7
|
+
* in `beforeEach` and let the instance get replaced). */
|
|
8
|
+
export declare function teardownFakeIndexedDB(): void;
|
|
9
|
+
//# sourceMappingURL=idb-harness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idb-harness.d.ts","sourceRoot":"","sources":["../../src/testing/idb-harness.ts"],"names":[],"mappings":"AAKA;;kDAEkD;AAClD,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC;AAED;;0DAE0D;AAC1D,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// helper to set up fake-indexeddb for tests that need idb stores without a
|
|
2
|
+
// real browser environment.
|
|
3
|
+
import { IDBFactory, IDBKeyRange } from "fake-indexeddb";
|
|
4
|
+
/** installs fake-indexeddb's `IDBFactory` and `IDBKeyRange` as globals,
|
|
5
|
+
* so idb-based code runs in a node test environment. call this in
|
|
6
|
+
* `beforeEach` for tests that use idb stores. */
|
|
7
|
+
export function setupFakeIndexedDB() {
|
|
8
|
+
globalThis.indexedDB = new IDBFactory();
|
|
9
|
+
globalThis.IDBKeyRange = IDBKeyRange;
|
|
10
|
+
}
|
|
11
|
+
/** clears the fake idb instance (useful in `afterEach` if you want a
|
|
12
|
+
* fresh db for each test, though most tests just call `setupFakeIndexedDB`
|
|
13
|
+
* in `beforeEach` and let the instance get replaced). */
|
|
14
|
+
export function teardownFakeIndexedDB() {
|
|
15
|
+
// @ts-expect-error - clearing the global
|
|
16
|
+
globalThis.indexedDB = undefined;
|
|
17
|
+
// @ts-expect-error - clearing the global
|
|
18
|
+
globalThis.IDBKeyRange = undefined;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idb-harness.test.d.ts","sourceRoot":"","sources":["../../src/testing/idb-harness.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { setupFakeIndexedDB, teardownFakeIndexedDB } from "./idb-harness.js";
|
|
3
|
+
describe("setupFakeIndexedDB", () => {
|
|
4
|
+
it("installs fake globals", () => {
|
|
5
|
+
setupFakeIndexedDB();
|
|
6
|
+
expect(globalThis.indexedDB).toBeDefined();
|
|
7
|
+
expect(globalThis.IDBKeyRange).toBeDefined();
|
|
8
|
+
teardownFakeIndexedDB();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
describe("teardownFakeIndexedDB", () => {
|
|
12
|
+
it("clears the fake globals", () => {
|
|
13
|
+
setupFakeIndexedDB();
|
|
14
|
+
teardownFakeIndexedDB();
|
|
15
|
+
expect(globalThis.indexedDB).toBeUndefined();
|
|
16
|
+
expect(globalThis.IDBKeyRange).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { P2PIdentity } from "../identity/types.js";
|
|
2
|
+
/** creates a test identity with deterministic fields. `nodeId` defaults to
|
|
3
|
+
* a repeating pattern for easy recognition in test output; `secretKey` is
|
|
4
|
+
* a short byte sequence sufficient for shape validation but not usable for
|
|
5
|
+
* real crypto. */
|
|
6
|
+
export declare function makeIdentity(nodeId?: string, createdAt?: number): P2PIdentity;
|
|
7
|
+
/** creates a batch of distinct test identities, each with a unique node id
|
|
8
|
+
* derived from the given prefix and an incrementing counter. */
|
|
9
|
+
export declare function makeIdentities(count: number, nodeIdPrefix?: string): P2PIdentity[];
|
|
10
|
+
//# sourceMappingURL=identity-fixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-fixtures.d.ts","sourceRoot":"","sources":["../../src/testing/identity-fixtures.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD;;;mBAGmB;AACnB,wBAAgB,YAAY,CAAC,MAAM,SAAkB,EAAE,SAAS,SAAO,GAAG,WAAW,CAMpF;AAED;iEACiE;AACjE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,SAAS,GAAG,WAAW,EAAE,CAKlF"}
|