@parity/product-sdk-host 0.12.0 → 0.13.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/chunk-3SWF5CWC.js +49 -0
- package/dist/chunk-3SWF5CWC.js.map +1 -0
- package/dist/index.d.ts +15 -37
- package/dist/index.js +9 -33
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +80 -0
- package/dist/testing.js +149 -0
- package/dist/testing.js.map +1 -0
- package/dist/transport-B0cdhwrp.d.ts +31 -0
- package/package.json +8 -2
- package/src/chains.ts +9 -3
- package/src/errors.ts +7 -2
- package/src/index.ts +1 -0
- package/src/result.ts +5 -48
- package/src/testing.ts +386 -0
- package/src/transport.ts +91 -3
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { TrUApiClient } from '@parity/truapi';
|
|
2
|
+
export { s as setTruApiClient } from './transport-B0cdhwrp.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Test fakes for `@parity/product-sdk-host`.
|
|
6
|
+
*
|
|
7
|
+
* The host reaches its container through one cached `TrUApiClient`.
|
|
8
|
+
* `createFakeHost()` injects a fake via `setTruApiClient` so every accessor
|
|
9
|
+
* (`getHostLocalStorage`, `getAccountsProvider`, `getStatementStore`, `getTruApi`,
|
|
10
|
+
* …) resolves against it and `isInsideContainer()` reports `true` — which also
|
|
11
|
+
* makes a default `SignerManager`, `local-storage` auto-detection, and the
|
|
12
|
+
* `statement-store` / `cloud-storage` host paths testable.
|
|
13
|
+
*
|
|
14
|
+
* Not modeled: the PAPI `chain` JSON-RPC surface behind `getHostProvider()` —
|
|
15
|
+
* there's no chain-read fake, by design; the host owns RPC selection — and the
|
|
16
|
+
* `chat` / `entropy` / `notifications` / `payment` / `permissions` /
|
|
17
|
+
* `resourceAllocation` / `theme` domains. Touching an unmodeled domain throws a
|
|
18
|
+
* descriptive error rather than failing with `undefined is not a function`.
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Options for {@link createFakeTruApiClient}. */
|
|
24
|
+
interface CreateFakeTruApiClientOptions {
|
|
25
|
+
/** `account.getUserId` primary username. Default `"alice.dot"`. */
|
|
26
|
+
primaryUsername?: string;
|
|
27
|
+
/** Product-account public key. Default 32 bytes of `0x11`. */
|
|
28
|
+
publicKey?: Uint8Array;
|
|
29
|
+
/** Bytes returned by the signing domain (signatures / proofs / transactions). Default 64 bytes of `0x22`. */
|
|
30
|
+
signature?: Uint8Array;
|
|
31
|
+
/** Whether `system.featureSupported` reports the chain supported. Default `true`. */
|
|
32
|
+
chainSupported?: boolean;
|
|
33
|
+
/** Seed the in-memory host localStorage, keyed by storage key. */
|
|
34
|
+
localStorage?: Record<string, Uint8Array>;
|
|
35
|
+
/** Legacy accounts returned by `account.getLegacyAccounts`. Default none. */
|
|
36
|
+
legacyAccounts?: Array<{
|
|
37
|
+
publicKey: Uint8Array;
|
|
38
|
+
name: string;
|
|
39
|
+
}>;
|
|
40
|
+
/** Seed the in-memory preimage store, keyed by the `0x` preimage key. */
|
|
41
|
+
preimages?: Record<string, Uint8Array>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build a fake `TrUApiClient` covering the domains the host accessors use:
|
|
45
|
+
* `localStorage` (real in-memory KV), `account` / `signing` (canned data),
|
|
46
|
+
* `statementStore`, `preimage`, and `system`. Unmodeled domains (`chain` et al —
|
|
47
|
+
* see the module header) throw on member access.
|
|
48
|
+
*/
|
|
49
|
+
declare function createFakeTruApiClient(options?: CreateFakeTruApiClientOptions): TrUApiClient;
|
|
50
|
+
/** Handle returned by {@link createFakeHost}. */
|
|
51
|
+
interface FakeHost extends Disposable {
|
|
52
|
+
/** The injected fake client (also what `getTruApi()` returns). */
|
|
53
|
+
client: TrUApiClient;
|
|
54
|
+
/** Clear the override. Idempotent; safe to call more than once. */
|
|
55
|
+
dispose(): void;
|
|
56
|
+
/** `using host = createFakeHost()` restores the real client at scope end. */
|
|
57
|
+
[Symbol.dispose](): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Inject a fake client via {@link setTruApiClient} and return a disposer.
|
|
61
|
+
*
|
|
62
|
+
* Cleanup is guaranteed three ways, so a forgotten reset can't leak the override
|
|
63
|
+
* into the next test or file: prefer `using` (scope-end disposal), otherwise the
|
|
64
|
+
* handle self-registers `onTestFinished` when created inside a Vitest run, and
|
|
65
|
+
* `dispose()` is always there to call by hand.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* import { createFakeHost } from "@parity/product-sdk-host/testing";
|
|
70
|
+
*
|
|
71
|
+
* test("host storage is reachable", async () => {
|
|
72
|
+
* using host = createFakeHost({ primaryUsername: "carol.dot" });
|
|
73
|
+
* // getHostLocalStorage(), getAccountsProvider(), a default SignerManager, etc.
|
|
74
|
+
* // resolve the fake; the real client is restored when the test scope ends.
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function createFakeHost(options?: CreateFakeTruApiClientOptions): FakeHost;
|
|
79
|
+
|
|
80
|
+
export { type CreateFakeTruApiClientOptions, type FakeHost, createFakeHost, createFakeTruApiClient };
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { setTruApiClient } from './chunk-3SWF5CWC.js';
|
|
2
|
+
export { setTruApiClient } from './chunk-3SWF5CWC.js';
|
|
3
|
+
import { okAsync } from 'neverthrow';
|
|
4
|
+
|
|
5
|
+
function toHex(bytes) {
|
|
6
|
+
return `0x${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
7
|
+
}
|
|
8
|
+
function fakeSubscription() {
|
|
9
|
+
return { unsubscribe: () => {
|
|
10
|
+
}, subscriptionId: "fake-subscription" };
|
|
11
|
+
}
|
|
12
|
+
function makeObservable(subscribe) {
|
|
13
|
+
const observable = {
|
|
14
|
+
subscribe,
|
|
15
|
+
[Symbol.observable]() {
|
|
16
|
+
return observable;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
return observable;
|
|
20
|
+
}
|
|
21
|
+
function inertObservable() {
|
|
22
|
+
return makeObservable(() => fakeSubscription());
|
|
23
|
+
}
|
|
24
|
+
function oneShotObservable(item) {
|
|
25
|
+
return makeObservable((observer) => {
|
|
26
|
+
queueMicrotask(() => observer?.next?.(item));
|
|
27
|
+
return fakeSubscription();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function notModeled(domain) {
|
|
31
|
+
return new Proxy({}, {
|
|
32
|
+
get(_target, member) {
|
|
33
|
+
if (typeof member === "symbol" || member === "then") return void 0;
|
|
34
|
+
throw new Error(
|
|
35
|
+
`createFakeTruApiClient: \`${domain}.${member}\` is not modeled by the fake. See the @parity/product-sdk-host/testing module docs for what is covered.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function preimageKey(hexValue) {
|
|
41
|
+
let h = 2166136261;
|
|
42
|
+
for (let i = 0; i < hexValue.length; i++) {
|
|
43
|
+
h ^= hexValue.charCodeAt(i);
|
|
44
|
+
h = Math.imul(h, 16777619);
|
|
45
|
+
}
|
|
46
|
+
return `0x${(h >>> 0).toString(16).padStart(8, "0")}`;
|
|
47
|
+
}
|
|
48
|
+
function createFakeTruApiClient(options) {
|
|
49
|
+
const primaryUsername = options?.primaryUsername ?? "alice.dot";
|
|
50
|
+
const publicKey = toHex(options?.publicKey ?? new Uint8Array(32).fill(17));
|
|
51
|
+
const signature = toHex(options?.signature ?? new Uint8Array(64).fill(34));
|
|
52
|
+
const chainSupported = options?.chainSupported ?? true;
|
|
53
|
+
const kv = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const [key, value] of Object.entries(options?.localStorage ?? {})) {
|
|
55
|
+
kv.set(key, toHex(value));
|
|
56
|
+
}
|
|
57
|
+
const legacyAccounts = (options?.legacyAccounts ?? []).map((a) => ({
|
|
58
|
+
publicKey: toHex(a.publicKey),
|
|
59
|
+
name: a.name
|
|
60
|
+
}));
|
|
61
|
+
const preimages = /* @__PURE__ */ new Map();
|
|
62
|
+
for (const [key, value] of Object.entries(options?.preimages ?? {})) {
|
|
63
|
+
preimages.set(key, toHex(value));
|
|
64
|
+
}
|
|
65
|
+
const client = {
|
|
66
|
+
localStorage: {
|
|
67
|
+
read: ({ key }) => okAsync({ value: kv.get(key) }),
|
|
68
|
+
write: ({ key, value }) => {
|
|
69
|
+
kv.set(key, value);
|
|
70
|
+
return okAsync(void 0);
|
|
71
|
+
},
|
|
72
|
+
clear: ({ key }) => {
|
|
73
|
+
kv.delete(key);
|
|
74
|
+
return okAsync(void 0);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
account: {
|
|
78
|
+
getUserId: () => okAsync({ primaryUsername }),
|
|
79
|
+
requestLogin: () => okAsync("Success"),
|
|
80
|
+
getAccount: () => okAsync({ account: { publicKey } }),
|
|
81
|
+
getAccountAlias: () => okAsync({ context: toHex(new Uint8Array([1])), alias: toHex(new Uint8Array([2])) }),
|
|
82
|
+
getLegacyAccounts: () => okAsync({ accounts: legacyAccounts }),
|
|
83
|
+
createAccountProof: () => okAsync({ proof: signature }),
|
|
84
|
+
connectionStatusSubscribe: () => inertObservable()
|
|
85
|
+
},
|
|
86
|
+
signing: {
|
|
87
|
+
createTransaction: () => okAsync({ transaction: signature }),
|
|
88
|
+
createTransactionWithLegacyAccount: () => okAsync({ transaction: signature }),
|
|
89
|
+
signRaw: () => okAsync({ signature }),
|
|
90
|
+
signRawWithLegacyAccount: () => okAsync({ signature }),
|
|
91
|
+
signPayload: () => okAsync({ signature }),
|
|
92
|
+
signPayloadWithLegacyAccount: () => okAsync({ signature })
|
|
93
|
+
},
|
|
94
|
+
statementStore: {
|
|
95
|
+
subscribe: () => inertObservable(),
|
|
96
|
+
createProof: () => okAsync({ proof: { tag: "Sr25519", value: { signature, signer: publicKey } } }),
|
|
97
|
+
createProofAuthorized: () => okAsync({ proof: { tag: "Sr25519", value: { signature, signer: publicKey } } }),
|
|
98
|
+
submit: () => okAsync(void 0)
|
|
99
|
+
},
|
|
100
|
+
system: {
|
|
101
|
+
handshake: () => okAsync(void 0),
|
|
102
|
+
featureSupported: () => okAsync({ supported: chainSupported }),
|
|
103
|
+
navigateTo: () => okAsync(void 0)
|
|
104
|
+
},
|
|
105
|
+
preimage: {
|
|
106
|
+
lookupSubscribe: ({ request: { key } }) => oneShotObservable({ value: preimages.get(key) }),
|
|
107
|
+
submit: (value) => {
|
|
108
|
+
const key = preimageKey(value);
|
|
109
|
+
preimages.set(key, value);
|
|
110
|
+
return okAsync(key);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
chain: notModeled("chain"),
|
|
114
|
+
chat: notModeled("chat"),
|
|
115
|
+
entropy: notModeled("entropy"),
|
|
116
|
+
notifications: notModeled("notifications"),
|
|
117
|
+
payment: notModeled("payment"),
|
|
118
|
+
permissions: notModeled("permissions"),
|
|
119
|
+
resourceAllocation: notModeled("resourceAllocation"),
|
|
120
|
+
theme: notModeled("theme")
|
|
121
|
+
};
|
|
122
|
+
return client;
|
|
123
|
+
}
|
|
124
|
+
function createFakeHost(options) {
|
|
125
|
+
const client = createFakeTruApiClient(options);
|
|
126
|
+
setTruApiClient(client);
|
|
127
|
+
let disposed = false;
|
|
128
|
+
const dispose = () => {
|
|
129
|
+
if (disposed) return;
|
|
130
|
+
disposed = true;
|
|
131
|
+
setTruApiClient(null);
|
|
132
|
+
};
|
|
133
|
+
const onTestFinished = globalThis.onTestFinished;
|
|
134
|
+
if (typeof onTestFinished === "function") {
|
|
135
|
+
try {
|
|
136
|
+
onTestFinished(dispose);
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
client,
|
|
142
|
+
dispose,
|
|
143
|
+
[Symbol.dispose]: dispose
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export { createFakeHost, createFakeTruApiClient };
|
|
148
|
+
//# sourceMappingURL=testing.js.map
|
|
149
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"names":[],"mappings":"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","file":"testing.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Test fakes for `@parity/product-sdk-host`.\n *\n * The host reaches its container through one cached `TrUApiClient`.\n * `createFakeHost()` injects a fake via `setTruApiClient` so every accessor\n * (`getHostLocalStorage`, `getAccountsProvider`, `getStatementStore`, `getTruApi`,\n * …) resolves against it and `isInsideContainer()` reports `true` — which also\n * makes a default `SignerManager`, `local-storage` auto-detection, and the\n * `statement-store` / `cloud-storage` host paths testable.\n *\n * Not modeled: the PAPI `chain` JSON-RPC surface behind `getHostProvider()` —\n * there's no chain-read fake, by design; the host owns RPC selection — and the\n * `chat` / `entropy` / `notifications` / `payment` / `permissions` /\n * `resourceAllocation` / `theme` domains. Touching an unmodeled domain throws a\n * descriptive error rather than failing with `undefined is not a function`.\n *\n * @packageDocumentation\n */\nimport type { ObservableLike, Observer, Subscription, TrUApiClient } from \"@parity/truapi\";\nimport { okAsync } from \"neverthrow\";\n\nimport { setTruApiClient } from \"./transport.js\";\n\nexport { setTruApiClient };\n\n/**\n * The public surface of a generated truapi domain client. `keyof` skips private\n * members, so this is the shape a plain object can implement — the classes\n * themselves are unimplementable outside truapi because of their\n * `private transport`.\n */\ntype PublicSurface<C> = { [K in keyof C]: C[K] };\n\n/**\n * `TrUApiClient` seen through public surfaces only. The fake is checked against\n * this member-by-member, so drift from the generated client — a renamed method,\n * a changed request or response type — fails compilation here instead of\n * surfacing as a runtime mismatch in consumers' tests.\n */\ntype PublicTruApiClient = { [D in keyof TrUApiClient]: PublicSurface<TrUApiClient[D]> };\n\n/** Inlined to keep the `truapi` facade out of this entry's bundle. */\nfunction toHex(bytes: Uint8Array): `0x${string}` {\n return `0x${Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\nfunction fakeSubscription(): Subscription {\n return { unsubscribe: () => {}, subscriptionId: \"fake-subscription\" };\n}\n\n/**\n * Wrap a `subscribe` implementation into a full `ObservableLike`. The\n * `Symbol.observable` interop member only lands on the object when the symbol\n * is polyfilled (e.g. by rxjs) — same as the generated client — and nothing in\n * the SDK reads it; it exists to satisfy the interface.\n */\nfunction makeObservable<Item, Reason = never>(\n subscribe: (observer?: Partial<Observer<Item, Reason>>) => Subscription,\n): ObservableLike<Item, Reason> {\n const observable: ObservableLike<Item, Reason> = {\n subscribe,\n [Symbol.observable]() {\n return observable;\n },\n };\n return observable;\n}\n\n/** An `ObservableLike` that never emits. Drive statement delivery with `createFakeStatementTransport`. */\nfunction inertObservable<Item, Reason = never>(): ObservableLike<Item, Reason> {\n return makeObservable(() => fakeSubscription());\n}\n\n/**\n * An `ObservableLike` that emits `item` once, asynchronously. The microtask defer\n * matters: the caller assigns its subscription handle from the return value, so\n * the item must arrive after `subscribe()` returns, not during it.\n */\nfunction oneShotObservable<Item>(item: Item): ObservableLike<Item> {\n return makeObservable((observer) => {\n queueMicrotask(() => observer?.next?.(item));\n return fakeSubscription();\n });\n}\n\n/**\n * A domain the fake deliberately doesn't model (see the module header). Any\n * member access throws with a pointer here, instead of the bare TypeError an\n * empty stub would give. The empty-object cast is the one concession a Proxy\n * needs; every modeled domain is checked structurally.\n */\nfunction notModeled<D extends keyof TrUApiClient>(domain: D): PublicSurface<TrUApiClient[D]> {\n return new Proxy({} as PublicSurface<TrUApiClient[D]>, {\n get(_target, member) {\n // Stay quiet for inspection probes (console.log, await-resolution).\n if (typeof member === \"symbol\" || member === \"then\") return undefined;\n throw new Error(\n `createFakeTruApiClient: \\`${domain}.${member}\\` is not modeled by the fake. See the @parity/product-sdk-host/testing module docs for what is covered.`,\n );\n },\n });\n}\n\n/** Deterministic `0x`-prefixed preimage key derived from a hex value (FNV-1a). */\nfunction preimageKey(hexValue: string): `0x${string}` {\n let h = 0x811c9dc5;\n for (let i = 0; i < hexValue.length; i++) {\n h ^= hexValue.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return `0x${(h >>> 0).toString(16).padStart(8, \"0\")}`;\n}\n\n/** Options for {@link createFakeTruApiClient}. */\nexport interface CreateFakeTruApiClientOptions {\n /** `account.getUserId` primary username. Default `\"alice.dot\"`. */\n primaryUsername?: string;\n /** Product-account public key. Default 32 bytes of `0x11`. */\n publicKey?: Uint8Array;\n /** Bytes returned by the signing domain (signatures / proofs / transactions). Default 64 bytes of `0x22`. */\n signature?: Uint8Array;\n /** Whether `system.featureSupported` reports the chain supported. Default `true`. */\n chainSupported?: boolean;\n /** Seed the in-memory host localStorage, keyed by storage key. */\n localStorage?: Record<string, Uint8Array>;\n /** Legacy accounts returned by `account.getLegacyAccounts`. Default none. */\n legacyAccounts?: Array<{ publicKey: Uint8Array; name: string }>;\n /** Seed the in-memory preimage store, keyed by the `0x` preimage key. */\n preimages?: Record<string, Uint8Array>;\n}\n\n/**\n * Build a fake `TrUApiClient` covering the domains the host accessors use:\n * `localStorage` (real in-memory KV), `account` / `signing` (canned data),\n * `statementStore`, `preimage`, and `system`. Unmodeled domains (`chain` et al —\n * see the module header) throw on member access.\n */\nexport function createFakeTruApiClient(options?: CreateFakeTruApiClientOptions): TrUApiClient {\n const primaryUsername = options?.primaryUsername ?? \"alice.dot\";\n const publicKey = toHex(options?.publicKey ?? new Uint8Array(32).fill(0x11));\n const signature = toHex(options?.signature ?? new Uint8Array(64).fill(0x22));\n const chainSupported = options?.chainSupported ?? true;\n\n // Real in-memory KV (hex values) so getHostLocalStorage()/createLocalKvStore() round-trip.\n const kv = new Map<string, `0x${string}`>();\n for (const [key, value] of Object.entries(options?.localStorage ?? {})) {\n kv.set(key, toHex(value));\n }\n\n const legacyAccounts = (options?.legacyAccounts ?? []).map((a) => ({\n publicKey: toHex(a.publicKey),\n name: a.name,\n }));\n\n // Preimage store (hex key -> hex value), for getPreimageManager() and submit/lookup round-trips.\n const preimages = new Map<string, `0x${string}`>();\n for (const [key, value] of Object.entries(options?.preimages ?? {})) {\n preimages.set(key, toHex(value));\n }\n\n // Typed against the generated client's public surface: every method below is\n // structurally checked, so a truapi signature change breaks this file's build,\n // not a consumer's test run.\n const client: PublicTruApiClient = {\n localStorage: {\n read: ({ key }) => okAsync({ value: kv.get(key) }),\n write: ({ key, value }) => {\n kv.set(key, value);\n return okAsync(undefined);\n },\n clear: ({ key }) => {\n kv.delete(key);\n return okAsync(undefined);\n },\n },\n account: {\n getUserId: () => okAsync({ primaryUsername }),\n requestLogin: () => okAsync(\"Success\"),\n getAccount: () => okAsync({ account: { publicKey } }),\n getAccountAlias: () =>\n okAsync({ context: toHex(new Uint8Array([1])), alias: toHex(new Uint8Array([2])) }),\n getLegacyAccounts: () => okAsync({ accounts: legacyAccounts }),\n createAccountProof: () => okAsync({ proof: signature }),\n connectionStatusSubscribe: () => inertObservable(),\n },\n signing: {\n createTransaction: () => okAsync({ transaction: signature }),\n createTransactionWithLegacyAccount: () => okAsync({ transaction: signature }),\n signRaw: () => okAsync({ signature }),\n signRawWithLegacyAccount: () => okAsync({ signature }),\n signPayload: () => okAsync({ signature }),\n signPayloadWithLegacyAccount: () => okAsync({ signature }),\n },\n statementStore: {\n subscribe: () => inertObservable(),\n createProof: () =>\n okAsync({ proof: { tag: \"Sr25519\", value: { signature, signer: publicKey } } }),\n createProofAuthorized: () =>\n okAsync({ proof: { tag: \"Sr25519\", value: { signature, signer: publicKey } } }),\n submit: () => okAsync(undefined),\n },\n system: {\n handshake: () => okAsync(undefined),\n featureSupported: () => okAsync({ supported: chainSupported }),\n navigateTo: () => okAsync(undefined),\n },\n preimage: {\n lookupSubscribe: ({ request: { key } }) =>\n oneShotObservable({ value: preimages.get(key) }),\n submit: (value) => {\n const key = preimageKey(value);\n preimages.set(key, value);\n return okAsync(key);\n },\n },\n chain: notModeled(\"chain\"),\n chat: notModeled(\"chat\"),\n entropy: notModeled(\"entropy\"),\n notifications: notModeled(\"notifications\"),\n payment: notModeled(\"payment\"),\n permissions: notModeled(\"permissions\"),\n resourceAllocation: notModeled(\"resourceAllocation\"),\n theme: notModeled(\"theme\"),\n };\n\n // A plain downcast, not an `unknown` bridge: each generated class is\n // assignable to its `PublicSurface`, and TS has already verified every fake\n // member against the generated signatures in the annotation above.\n return client as TrUApiClient;\n}\n\n/**\n * A `beforeEach`-registered test hook, e.g. Vitest's `onTestFinished`. Read off\n * `globalThis` so we couple to no test framework: present under Vitest's globals\n * mode, absent everywhere else (Jest, `node:test`, plain scripts).\n */\ntype TestFinishedHook = (fn: () => void) => void;\n\n/** Handle returned by {@link createFakeHost}. */\nexport interface FakeHost extends Disposable {\n /** The injected fake client (also what `getTruApi()` returns). */\n client: TrUApiClient;\n /** Clear the override. Idempotent; safe to call more than once. */\n dispose(): void;\n /** `using host = createFakeHost()` restores the real client at scope end. */\n [Symbol.dispose](): void;\n}\n\n/**\n * Inject a fake client via {@link setTruApiClient} and return a disposer.\n *\n * Cleanup is guaranteed three ways, so a forgotten reset can't leak the override\n * into the next test or file: prefer `using` (scope-end disposal), otherwise the\n * handle self-registers `onTestFinished` when created inside a Vitest run, and\n * `dispose()` is always there to call by hand.\n *\n * @example\n * ```ts\n * import { createFakeHost } from \"@parity/product-sdk-host/testing\";\n *\n * test(\"host storage is reachable\", async () => {\n * using host = createFakeHost({ primaryUsername: \"carol.dot\" });\n * // getHostLocalStorage(), getAccountsProvider(), a default SignerManager, etc.\n * // resolve the fake; the real client is restored when the test scope ends.\n * });\n * ```\n */\nexport function createFakeHost(options?: CreateFakeTruApiClientOptions): FakeHost {\n const client = createFakeTruApiClient(options);\n setTruApiClient(client);\n\n let disposed = false;\n const dispose = () => {\n if (disposed) return;\n disposed = true;\n setTruApiClient(null);\n };\n\n // Best-effort auto-cleanup for the common case (a bare `const host =\n // createFakeHost()` with no `using` and no manual reset). Under Vitest's\n // globals mode `onTestFinished` is on `globalThis`; elsewhere it's absent and\n // we quietly rely on `using` / manual `dispose()`.\n const onTestFinished = (globalThis as { onTestFinished?: TestFinishedHook }).onTestFinished;\n if (typeof onTestFinished === \"function\") {\n try {\n onTestFinished(dispose);\n } catch {\n // Not inside a running test (e.g. called at module scope) — ignore.\n }\n }\n\n return {\n client,\n dispose,\n [Symbol.dispose]: dispose,\n };\n}\n\nif (import.meta.vitest) {\n // Round-trip guard: drive the fake through the *real* host accessors.\n const { describe, test, expect, afterEach } = import.meta.vitest;\n const { getHostLocalStorage, getStatementStore, isInsideContainer } = await import(\n \"./container.js\"\n );\n const { getAccountsProvider } = await import(\"./accounts.js\");\n const { getPreimageManager } = await import(\"./truapi.js\");\n\n const lookupOnce = (\n manager: NonNullable<Awaited<ReturnType<typeof getPreimageManager>>>,\n key: `0x${string}`,\n ) =>\n new Promise<Uint8Array | null>((resolve) => {\n manager.lookup(key, (preimage) => {\n if (preimage !== null) resolve(preimage);\n });\n });\n\n afterEach(() => setTruApiClient(null));\n\n describe(\"createFakeHost / createFakeTruApiClient\", () => {\n test(\"host localStorage round-trips through the real adapter\", async () => {\n createFakeHost();\n const ls = await getHostLocalStorage();\n expect(ls).not.toBeNull();\n await ls?.writeString(\"k\", \"v\");\n expect(await ls?.readString(\"k\")).toBe(\"v\");\n await ls?.writeJSON(\"j\", { a: 1 });\n expect(await ls?.readJSON(\"j\")).toEqual({ a: 1 });\n expect(await ls?.readString(\"missing\")).toBe(\"\");\n });\n\n test(\"seeded localStorage is readable\", async () => {\n createFakeHost({ localStorage: { greeting: new TextEncoder().encode(\"hi\") } });\n const ls = await getHostLocalStorage();\n expect(await ls?.readString(\"greeting\")).toBe(\"hi\");\n });\n\n test(\"accounts provider resolves the configured user\", async () => {\n createFakeHost({ primaryUsername: \"carol.dot\" });\n const accounts = await getAccountsProvider();\n expect(accounts).not.toBeNull();\n const userId = await accounts?.getUserId().match(\n (v) => v,\n () => null,\n );\n expect(userId?.primaryUsername).toBe(\"carol.dot\");\n });\n\n test(\"statement store resolves\", async () => {\n createFakeHost();\n expect(await getStatementStore()).not.toBeNull();\n });\n\n test(\"preimage manager reads seeded bytes and round-trips submit -> lookup\", async () => {\n createFakeHost({ preimages: { \"0xabc\": new Uint8Array([1, 2, 3]) } });\n const pm = await getPreimageManager();\n expect(pm).not.toBeNull();\n\n const seeded = await lookupOnce(pm!, \"0xabc\");\n expect(Array.from(seeded ?? [])).toEqual([1, 2, 3]);\n\n const key = await pm!.submit(new Uint8Array([9, 9]));\n const back = await lookupOnce(pm!, key);\n expect(Array.from(back ?? [])).toEqual([9, 9]);\n });\n\n test(\"isInsideContainer is true while set, false after dispose\", async () => {\n const host = createFakeHost();\n expect(await isInsideContainer()).toBe(true);\n host.dispose();\n expect(await isInsideContainer()).toBe(false);\n expect(await getHostLocalStorage()).toBeNull();\n });\n\n test(\"Symbol.dispose clears the override and dispose is idempotent\", async () => {\n {\n using host = createFakeHost();\n expect(await isInsideContainer()).toBe(true);\n host.dispose(); // explicit + scope-end Symbol.dispose must not double-clear badly\n }\n expect(await isInsideContainer()).toBe(false);\n });\n });\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { TrUApiClient } from '@parity/truapi';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Access to the in-house TruAPI client (`@parity/truapi`) for the host package.
|
|
5
|
+
*
|
|
6
|
+
* Environment detection and the lazily-built, cached client come from
|
|
7
|
+
* `@parity/truapi/sandbox`; this module layers the product-sdk-specific glue on
|
|
8
|
+
* top — an async {@link getClient} accessor and {@link subscribeWithInterrupt},
|
|
9
|
+
* which adapts a truapi stream into the host's {@link HostSubscription} shape.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Test-only seam: force {@link getClient} / {@link getClientSync} to return
|
|
16
|
+
* `client`, and {@link isCorrectEnvironment} to report `true`. Pass `null` to
|
|
17
|
+
* restore normal detection. Exposed through `@parity/product-sdk-host/testing`,
|
|
18
|
+
* not the package's main entry.
|
|
19
|
+
*
|
|
20
|
+
* Calling this in a production build silently reroutes every host accessor to
|
|
21
|
+
* the injected client, so we warn — it almost always means a `/testing` import
|
|
22
|
+
* leaked into a production path.
|
|
23
|
+
*/
|
|
24
|
+
declare function setTruApiClient(client: TrUApiClient | null): void;
|
|
25
|
+
/**
|
|
26
|
+
* Host-container detection. `true` when a test client is injected, otherwise the
|
|
27
|
+
* sandbox heuristic (iframe / webview marker / injected message port).
|
|
28
|
+
*/
|
|
29
|
+
declare function isCorrectEnvironment(): boolean;
|
|
30
|
+
|
|
31
|
+
export { isCorrectEnvironment as i, setTruApiClient as s };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parity/product-sdk-host",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Host container detection and storage access for Polkadot Desktop and Mobile environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
".": {
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./testing": {
|
|
16
|
+
"import": "./dist/testing.js",
|
|
17
|
+
"types": "./dist/testing.d.ts"
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"files": [
|
|
@@ -23,7 +27,9 @@
|
|
|
23
27
|
"@polkadot-api/substrate-bindings": "^0.20.3",
|
|
24
28
|
"neverthrow": "^8.2.0",
|
|
25
29
|
"polkadot-api": "^2.1.6",
|
|
26
|
-
"@parity/product-sdk-logger": "0.1.1"
|
|
30
|
+
"@parity/product-sdk-logger": "0.1.1",
|
|
31
|
+
"@parity/product-sdk-errors": "0.2.0",
|
|
32
|
+
"@parity/result": "0.2.0"
|
|
27
33
|
},
|
|
28
34
|
"devDependencies": {
|
|
29
35
|
"tsup": "^8.5.1",
|
package/src/chains.ts
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Bulletin Chain RPC endpoints per network environment. `paseo`
|
|
10
|
-
*
|
|
11
|
-
* Bulletin deployments go live.
|
|
9
|
+
* Bulletin Chain RPC endpoints per network environment. `paseo` (Paseo Next v2),
|
|
10
|
+
* `summit`, and `devnet` (public Paseo testnet) are populated today; `polkadot`
|
|
11
|
+
* and `kusama` are reserved for when those Bulletin deployments go live.
|
|
12
12
|
*/
|
|
13
13
|
export const BULLETIN_RPCS = {
|
|
14
14
|
paseo: ["wss://paseo-bulletin-next-rpc.polkadot.io"],
|
|
15
15
|
summit: ["wss://summit-bulletin-rpc.polkadot.io"],
|
|
16
|
+
devnet: ["wss://bulletin-paseo.tservices.es:8443"],
|
|
16
17
|
polkadot: [] as string[],
|
|
17
18
|
kusama: [] as string[],
|
|
18
19
|
} as const;
|
|
@@ -34,6 +35,11 @@ if (import.meta.vitest) {
|
|
|
34
35
|
expect(BULLETIN_RPCS.summit[0]).toMatch(/^wss:\/\//);
|
|
35
36
|
});
|
|
36
37
|
|
|
38
|
+
test("BULLETIN_RPCS has devnet endpoint", () => {
|
|
39
|
+
expect(BULLETIN_RPCS.devnet.length).toBeGreaterThan(0);
|
|
40
|
+
expect(BULLETIN_RPCS.devnet[0]).toMatch(/^wss:\/\//);
|
|
41
|
+
});
|
|
42
|
+
|
|
37
43
|
test("BULLETIN_RPCS polkadot and kusama are empty until live", () => {
|
|
38
44
|
expect(BULLETIN_RPCS.polkadot).toEqual([]);
|
|
39
45
|
expect(BULLETIN_RPCS.kusama).toEqual([]);
|
package/src/errors.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* @module
|
|
19
19
|
*/
|
|
20
|
+
import type { SdkError } from "@parity/product-sdk-errors";
|
|
20
21
|
import type { GenericError } from "@parity/truapi";
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -93,9 +94,13 @@ export function formatHostError(error: unknown): string {
|
|
|
93
94
|
|
|
94
95
|
/**
|
|
95
96
|
* Base class for all host errors. Use `instanceof HostError` (or {@link isHostError})
|
|
96
|
-
* to catch any host-related failure.
|
|
97
|
+
* to catch any host-related failure. Implements the cross-package
|
|
98
|
+
* {@link SdkError} marker so `isSdkError(e)` also recognizes it.
|
|
97
99
|
*/
|
|
98
|
-
export class HostError extends Error {
|
|
100
|
+
export class HostError extends Error implements SdkError {
|
|
101
|
+
readonly isSdkError = true as const;
|
|
102
|
+
readonly source = "host";
|
|
103
|
+
|
|
99
104
|
constructor(message: string, options?: ErrorOptions) {
|
|
100
105
|
super(message, options);
|
|
101
106
|
this.name = "HostError";
|
package/src/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ export type {
|
|
|
56
56
|
// Result type + typed host errors (the throw→Result boundary)
|
|
57
57
|
export { ok, err } from "./result.js";
|
|
58
58
|
export type { Result } from "./result.js";
|
|
59
|
+
export { type SdkError, isSdkError } from "@parity/product-sdk-errors";
|
|
59
60
|
export {
|
|
60
61
|
HostError,
|
|
61
62
|
HostUnavailableError,
|
package/src/result.ts
CHANGED
|
@@ -1,56 +1,13 @@
|
|
|
1
1
|
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Re-export of the shared `Result` primitive from `@parity/result`.
|
|
5
5
|
*
|
|
6
6
|
* Host functions return `Promise<Result<T, HostError>>` rather than throwing, so
|
|
7
|
-
* consumers get typed errors on the `err` channel
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
* so the two layers compose with no adapter — host's `Result` flows straight into
|
|
11
|
-
* the signer's pattern matching.
|
|
12
|
-
*
|
|
13
|
-
* NOTE: host owns its own copy because the dependency edge runs `signer → host`,
|
|
14
|
-
* so host cannot import the signer's definition. If a third package ever needs
|
|
15
|
-
* this shape, extract it into a shared `@parity/product-sdk-result` package and
|
|
16
|
-
* have both depend on that instead of duplicating.
|
|
7
|
+
* consumers get typed errors on the `err` channel. The type is now owned by the
|
|
8
|
+
* zero-dependency `@parity/result` leaf so every package shares one
|
|
9
|
+
* definition; this module stays as the host-internal import path.
|
|
17
10
|
*
|
|
18
11
|
* @module
|
|
19
12
|
*/
|
|
20
|
-
|
|
21
|
-
/** A value that is either a success (`ok`) carrying `T`, or a failure (`err`) carrying `E`. */
|
|
22
|
-
export type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
|
|
23
|
-
|
|
24
|
-
/** Create a successful {@link Result}. */
|
|
25
|
-
export function ok<T>(value: T): Result<T, never> {
|
|
26
|
-
return { ok: true, value };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Create a failed {@link Result}. */
|
|
30
|
-
export function err<E>(error: E): Result<never, E> {
|
|
31
|
-
return { ok: false, error };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (import.meta.vitest) {
|
|
35
|
-
const { test, expect, describe } = import.meta.vitest;
|
|
36
|
-
|
|
37
|
-
describe("ok", () => {
|
|
38
|
-
test("produces an ok result with value", () => {
|
|
39
|
-
const result = ok(42);
|
|
40
|
-
expect(result.ok).toBe(true);
|
|
41
|
-
expect(result).toEqual({ ok: true, value: 42 });
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("works with null value", () => {
|
|
45
|
-
expect(ok(null)).toEqual({ ok: true, value: null });
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe("err", () => {
|
|
50
|
-
test("produces an error result", () => {
|
|
51
|
-
const result = err("boom");
|
|
52
|
-
expect(result.ok).toBe(false);
|
|
53
|
-
expect(result).toEqual({ ok: false, error: "boom" });
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
}
|
|
13
|
+
export { type Result, ok, err } from "@parity/result";
|