@parity/product-sdk-host 0.13.0 → 0.14.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/index.d.ts +63 -47
- package/dist/index.js +28 -17
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +4 -3
- package/dist/testing.js +10 -1
- package/dist/testing.js.map +1 -1
- package/package.json +6 -5
- package/src/accounts.ts +101 -37
- package/src/chains.ts +3 -9
- package/src/errors.ts +77 -41
- package/src/index.ts +2 -0
- package/src/payments.ts +8 -7
- package/src/testing.ts +15 -4
package/dist/testing.d.ts
CHANGED
|
@@ -13,9 +13,10 @@ export { s as setTruApiClient } from './transport-B0cdhwrp.js';
|
|
|
13
13
|
*
|
|
14
14
|
* Not modeled: the PAPI `chain` JSON-RPC surface behind `getHostProvider()` —
|
|
15
15
|
* there's no chain-read fake, by design; the host owns RPC selection — and the
|
|
16
|
-
* `chat` / `
|
|
17
|
-
* `resourceAllocation` / `theme` domains. Touching an unmodeled
|
|
18
|
-
* descriptive error rather than failing with `undefined is not
|
|
16
|
+
* `chat` / `coinPayment` / `entropy` / `notifications` / `payment` /
|
|
17
|
+
* `permissions` / `resourceAllocation` / `theme` domains. Touching an unmodeled
|
|
18
|
+
* domain throws a descriptive error rather than failing with `undefined is not
|
|
19
|
+
* a function`.
|
|
19
20
|
*
|
|
20
21
|
* @packageDocumentation
|
|
21
22
|
*/
|
package/dist/testing.js
CHANGED
|
@@ -80,7 +80,15 @@ function createFakeTruApiClient(options) {
|
|
|
80
80
|
getAccount: () => okAsync({ account: { publicKey } }),
|
|
81
81
|
getAccountAlias: () => okAsync({ context: toHex(new Uint8Array([1])), alias: toHex(new Uint8Array([2])) }),
|
|
82
82
|
getLegacyAccounts: () => okAsync({ accounts: legacyAccounts }),
|
|
83
|
-
createAccountProof: () => okAsync({
|
|
83
|
+
createAccountProof: () => okAsync({
|
|
84
|
+
proof: signature,
|
|
85
|
+
contextualAlias: {
|
|
86
|
+
context: toHex(new Uint8Array([1])),
|
|
87
|
+
alias: toHex(new Uint8Array([2]))
|
|
88
|
+
},
|
|
89
|
+
ringIndex: 0,
|
|
90
|
+
ringRevision: 0
|
|
91
|
+
}),
|
|
84
92
|
connectionStatusSubscribe: () => inertObservable()
|
|
85
93
|
},
|
|
86
94
|
signing: {
|
|
@@ -112,6 +120,7 @@ function createFakeTruApiClient(options) {
|
|
|
112
120
|
},
|
|
113
121
|
chain: notModeled("chain"),
|
|
114
122
|
chat: notModeled("chat"),
|
|
123
|
+
coinPayment: notModeled("coinPayment"),
|
|
115
124
|
entropy: notModeled("entropy"),
|
|
116
125
|
notifications: notModeled("notifications"),
|
|
117
126
|
payment: notModeled("payment"),
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts"],"names":[],"mappings":";;;;AA4CA,SAAS,MAAM,KAAA,EAAkC;AAC7C,EAAA,OAAO,KAAK,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,SAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC,CAAA,CAAA;AAClF;AAEA,SAAS,gBAAA,GAAiC;AACtC,EAAA,OAAO,EAAE,aAAa,MAAM;AAAA,EAAC,CAAA,EAAG,gBAAgB,mBAAA,EAAoB;AACxE;AAQA,SAAS,eACL,SAAA,EAC4B;AAC5B,EAAA,MAAM,UAAA,GAA2C;AAAA,IAC7C,SAAA;AAAA,IACA,CAAC,MAAA,CAAO,UAAU,CAAA,GAAI;AAClB,MAAA,OAAO,UAAA;AAAA,IACX;AAAA,GACJ;AACA,EAAA,OAAO,UAAA;AACX;AAGA,SAAS,eAAA,GAAsE;AAC3E,EAAA,OAAO,cAAA,CAAe,MAAM,gBAAA,EAAkB,CAAA;AAClD;AAOA,SAAS,kBAAwB,IAAA,EAAkC;AAC/D,EAAA,OAAO,cAAA,CAAe,CAAC,QAAA,KAAa;AAChC,IAAA,cAAA,CAAe,MAAM,QAAA,EAAU,IAAA,GAAO,IAAI,CAAC,CAAA;AAC3C,IAAA,OAAO,gBAAA,EAAiB;AAAA,EAC5B,CAAC,CAAA;AACL;AAQA,SAAS,WAAyC,MAAA,EAA2C;AACzF,EAAA,OAAO,IAAI,KAAA,CAAM,EAAC,EAAqC;AAAA,IACnD,GAAA,CAAI,SAAS,MAAA,EAAQ;AAEjB,MAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,KAAW,QAAQ,OAAO,MAAA;AAC5D,MAAA,MAAM,IAAI,KAAA;AAAA,QACN,CAAA,0BAAA,EAA6B,MAAM,CAAA,CAAA,EAAI,MAAM,CAAA,wGAAA;AAAA,OACjD;AAAA,IACJ;AAAA,GACH,CAAA;AACL;AAGA,SAAS,YAAY,QAAA,EAAiC;AAClD,EAAA,IAAI,CAAA,GAAI,UAAA;AACR,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACtC,IAAA,CAAA,IAAK,QAAA,CAAS,WAAW,CAAC,CAAA;AAC1B,IAAA,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,QAAU,CAAA;AAAA,EAC/B;AACA,EAAA,OAAO,CAAA,EAAA,EAAA,CAAM,MAAM,CAAA,EAAG,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AACvD;AA0BO,SAAS,uBAAuB,OAAA,EAAuD;AAC1F,EAAA,MAAM,eAAA,GAAkB,SAAS,eAAA,IAAmB,WAAA;AACpD,EAAA,MAAM,SAAA,GAAY,KAAA,CAAM,OAAA,EAAS,SAAA,IAAa,IAAI,WAAW,EAAE,CAAA,CAAE,IAAA,CAAK,EAAI,CAAC,CAAA;AAC3E,EAAA,MAAM,SAAA,GAAY,KAAA,CAAM,OAAA,EAAS,SAAA,IAAa,IAAI,WAAW,EAAE,CAAA,CAAE,IAAA,CAAK,EAAI,CAAC,CAAA;AAC3E,EAAA,MAAM,cAAA,GAAiB,SAAS,cAAA,IAAkB,IAAA;AAGlD,EAAA,MAAM,EAAA,uBAAS,GAAA,EAA2B;AAC1C,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,MAAA,CAAO,QAAQ,OAAA,EAAS,YAAA,IAAgB,EAAE,CAAA,EAAG;AACpE,IAAA,EAAA,CAAG,GAAA,CAAI,GAAA,EAAK,KAAA,CAAM,KAAK,CAAC,CAAA;AAAA,EAC5B;AAEA,EAAA,MAAM,kBAAkB,OAAA,EAAS,cAAA,IAAkB,EAAC,EAAG,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,IAC/D,SAAA,EAAW,KAAA,CAAM,CAAA,CAAE,SAAS,CAAA;AAAA,IAC5B,MAAM,CAAA,CAAE;AAAA,GACZ,CAAE,CAAA;AAGF,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAA2B;AACjD,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,MAAA,CAAO,QAAQ,OAAA,EAAS,SAAA,IAAa,EAAE,CAAA,EAAG;AACjE,IAAA,SAAA,CAAU,GAAA,CAAI,GAAA,EAAK,KAAA,CAAM,KAAK,CAAC,CAAA;AAAA,EACnC;AAKA,EAAA,MAAM,MAAA,GAA6B;AAAA,IAC/B,YAAA,EAAc;AAAA,MACV,IAAA,EAAM,CAAC,EAAE,GAAA,EAAI,KAAM,OAAA,CAAQ,EAAE,KAAA,EAAO,EAAA,CAAG,GAAA,CAAI,GAAG,CAAA,EAAG,CAAA;AAAA,MACjD,KAAA,EAAO,CAAC,EAAE,GAAA,EAAK,OAAM,KAAM;AACvB,QAAA,EAAA,CAAG,GAAA,CAAI,KAAK,KAAK,CAAA;AACjB,QAAA,OAAO,QAAQ,MAAS,CAAA;AAAA,MAC5B,CAAA;AAAA,MACA,KAAA,EAAO,CAAC,EAAE,GAAA,EAAI,KAAM;AAChB,QAAA,EAAA,CAAG,OAAO,GAAG,CAAA;AACb,QAAA,OAAO,QAAQ,MAAS,CAAA;AAAA,MAC5B;AAAA,KACJ;AAAA,IACA,OAAA,EAAS;AAAA,MACL,SAAA,EAAW,MAAM,OAAA,CAAQ,EAAE,iBAAiB,CAAA;AAAA,MAC5C,YAAA,EAAc,MAAM,OAAA,CAAQ,SAAS,CAAA;AAAA,MACrC,UAAA,EAAY,MAAM,OAAA,CAAQ,EAAE,SAAS,EAAE,SAAA,IAAa,CAAA;AAAA,MACpD,eAAA,EAAiB,MACb,OAAA,CAAQ,EAAE,SAAS,KAAA,CAAM,IAAI,UAAA,CAAW,CAAC,CAAC,CAAC,CAAC,CAAA,EAAG,KAAA,EAAO,MAAM,IAAI,UAAA,CAAW,CAAC,CAAC,CAAC,CAAC,CAAA,EAAG,CAAA;AAAA,MACtF,mBAAmB,MAAM,OAAA,CAAQ,EAAE,QAAA,EAAU,gBAAgB,CAAA;AAAA,MAC7D,oBAAoB,MAAM,OAAA,CAAQ,EAAE,KAAA,EAAO,WAAW,CAAA;AAAA,MACtD,yBAAA,EAA2B,MAAM,eAAA;AAAgB,KACrD;AAAA,IACA,OAAA,EAAS;AAAA,MACL,mBAAmB,MAAM,OAAA,CAAQ,EAAE,WAAA,EAAa,WAAW,CAAA;AAAA,MAC3D,oCAAoC,MAAM,OAAA,CAAQ,EAAE,WAAA,EAAa,WAAW,CAAA;AAAA,MAC5E,OAAA,EAAS,MAAM,OAAA,CAAQ,EAAE,WAAW,CAAA;AAAA,MACpC,wBAAA,EAA0B,MAAM,OAAA,CAAQ,EAAE,WAAW,CAAA;AAAA,MACrD,WAAA,EAAa,MAAM,OAAA,CAAQ,EAAE,WAAW,CAAA;AAAA,MACxC,4BAAA,EAA8B,MAAM,OAAA,CAAQ,EAAE,WAAW;AAAA,KAC7D;AAAA,IACA,cAAA,EAAgB;AAAA,MACZ,SAAA,EAAW,MAAM,eAAA,EAAgB;AAAA,MACjC,WAAA,EAAa,MACT,OAAA,CAAQ,EAAE,OAAO,EAAE,GAAA,EAAK,SAAA,EAAW,KAAA,EAAO,EAAE,SAAA,EAAW,MAAA,EAAQ,SAAA,EAAU,IAAK,CAAA;AAAA,MAClF,qBAAA,EAAuB,MACnB,OAAA,CAAQ,EAAE,OAAO,EAAE,GAAA,EAAK,SAAA,EAAW,KAAA,EAAO,EAAE,SAAA,EAAW,MAAA,EAAQ,SAAA,EAAU,IAAK,CAAA;AAAA,MAClF,MAAA,EAAQ,MAAM,OAAA,CAAQ,MAAS;AAAA,KACnC;AAAA,IACA,MAAA,EAAQ;AAAA,MACJ,SAAA,EAAW,MAAM,OAAA,CAAQ,MAAS,CAAA;AAAA,MAClC,kBAAkB,MAAM,OAAA,CAAQ,EAAE,SAAA,EAAW,gBAAgB,CAAA;AAAA,MAC7D,UAAA,EAAY,MAAM,OAAA,CAAQ,MAAS;AAAA,KACvC;AAAA,IACA,QAAA,EAAU;AAAA,MACN,eAAA,EAAiB,CAAC,EAAE,OAAA,EAAS,EAAE,GAAA,EAAI,EAAE,KACjC,iBAAA,CAAkB,EAAE,KAAA,EAAO,SAAA,CAAU,GAAA,CAAI,GAAG,GAAG,CAAA;AAAA,MACnD,MAAA,EAAQ,CAAC,KAAA,KAAU;AACf,QAAA,MAAM,GAAA,GAAM,YAAY,KAAK,CAAA;AAC7B,QAAA,SAAA,CAAU,GAAA,CAAI,KAAK,KAAK,CAAA;AACxB,QAAA,OAAO,QAAQ,GAAG,CAAA;AAAA,MACtB;AAAA,KACJ;AAAA,IACA,KAAA,EAAO,WAAW,OAAO,CAAA;AAAA,IACzB,IAAA,EAAM,WAAW,MAAM,CAAA;AAAA,IACvB,OAAA,EAAS,WAAW,SAAS,CAAA;AAAA,IAC7B,aAAA,EAAe,WAAW,eAAe,CAAA;AAAA,IACzC,OAAA,EAAS,WAAW,SAAS,CAAA;AAAA,IAC7B,WAAA,EAAa,WAAW,aAAa,CAAA;AAAA,IACrC,kBAAA,EAAoB,WAAW,oBAAoB,CAAA;AAAA,IACnD,KAAA,EAAO,WAAW,OAAO;AAAA,GAC7B;AAKA,EAAA,OAAO,MAAA;AACX;AAsCO,SAAS,eAAe,OAAA,EAAmD;AAC9E,EAAA,MAAM,MAAA,GAAS,uBAAuB,OAAO,CAAA;AAC7C,EAAA,eAAA,CAAgB,MAAM,CAAA;AAEtB,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,MAAM,UAAU,MAAM;AAClB,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,QAAA,GAAW,IAAA;AACX,IAAA,eAAA,CAAgB,IAAI,CAAA;AAAA,EACxB,CAAA;AAMA,EAAA,MAAM,iBAAkB,UAAA,CAAqD,cAAA;AAC7E,EAAA,IAAI,OAAO,mBAAmB,UAAA,EAAY;AACtC,IAAA,IAAI;AACA,MAAA,cAAA,CAAe,OAAO,CAAA;AAAA,IAC1B,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACJ;AAEA,EAAA,OAAO;AAAA,IACH,MAAA;AAAA,IACA,OAAA;AAAA,IACA,CAAC,MAAA,CAAO,OAAO,GAAG;AAAA,GACtB;AACJ","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"]}
|
|
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` / `coinPayment` / `entropy` / `notifications` / `payment` /\n * `permissions` / `resourceAllocation` / `theme` domains. Touching an unmodeled\n * domain throws a descriptive error rather than failing with `undefined is not\n * 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: () =>\n okAsync({\n proof: signature,\n contextualAlias: {\n context: toHex(new Uint8Array([1])),\n alias: toHex(new Uint8Array([2])),\n },\n ringIndex: 0,\n ringRevision: 0,\n }),\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 coinPayment: notModeled(\"coinPayment\"),\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parity/product-sdk-host",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Host container detection and storage access for Polkadot Desktop and Mobile environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"src"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@parity/truapi": "^0.
|
|
25
|
+
"@parity/truapi": "^0.5.0",
|
|
26
26
|
"@polkadot-api/json-rpc-provider": "^0.2.0",
|
|
27
27
|
"@polkadot-api/substrate-bindings": "^0.20.3",
|
|
28
28
|
"neverthrow": "^8.2.0",
|
|
29
29
|
"polkadot-api": "^2.1.6",
|
|
30
30
|
"@parity/product-sdk-logger": "0.1.1",
|
|
31
|
-
"@parity/
|
|
32
|
-
"@parity/
|
|
31
|
+
"@parity/result": "0.2.0",
|
|
32
|
+
"@parity/product-sdk-errors": "0.2.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"tsup": "^8.5.1",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsup",
|
|
42
42
|
"test": "vitest",
|
|
43
|
-
"clean": "rm -rf dist"
|
|
43
|
+
"clean": "rm -rf dist",
|
|
44
|
+
"typecheck": "tsc -p tsconfig.typecheck.json"
|
|
44
45
|
}
|
|
45
46
|
}
|
package/src/accounts.ts
CHANGED
|
@@ -26,26 +26,38 @@ import type { ResultAsync } from "neverthrow";
|
|
|
26
26
|
import { AccountId, type PolkadotSigner } from "polkadot-api";
|
|
27
27
|
|
|
28
28
|
import type {
|
|
29
|
+
ContextualAlias as WireAlias,
|
|
29
30
|
HostAccountConnectionStatusSubscribeItem,
|
|
30
|
-
|
|
31
|
-
HostAccountGetAliasResponse as WireAlias,
|
|
32
|
-
HostAccountGetError,
|
|
33
|
-
HostGetUserIdError,
|
|
34
|
-
HostRequestLoginError,
|
|
31
|
+
HostAccountCreateProofResponse as WireRingVRFProof,
|
|
35
32
|
HostRequestLoginResponse,
|
|
36
33
|
LegacyAccount as WireLegacyAccount,
|
|
37
34
|
ProductAccount as WireProductAccount,
|
|
38
35
|
ProductAccountId,
|
|
36
|
+
ProductProofContext,
|
|
39
37
|
RingLocation,
|
|
40
38
|
TrUApiClient,
|
|
39
|
+
VersionedHostAccountCreateProofError,
|
|
40
|
+
VersionedHostAccountGetAliasError,
|
|
41
|
+
VersionedHostAccountGetError,
|
|
42
|
+
VersionedHostGetLegacyAccountsError,
|
|
43
|
+
VersionedHostGetUserIdError,
|
|
44
|
+
VersionedHostRequestLoginError,
|
|
45
|
+
scale,
|
|
41
46
|
} from "@parity/truapi";
|
|
42
47
|
|
|
43
48
|
import { getClient, subscribeWithInterrupt } from "./transport.js";
|
|
44
49
|
import { fromHex, toHex, unwrapHostResult } from "./truapi.js";
|
|
45
50
|
import type { HostSubscription } from "./types.js";
|
|
46
51
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Ring VRF request shapes, re-exported from `@parity/truapi`:
|
|
54
|
+
* - `RingLocation` — where the ring lives (`{ chainId, junctions }`; junctions
|
|
55
|
+
* address the ring via `PalletInstance` / `CollectionId` steps).
|
|
56
|
+
* - `ProductProofContext` — the product-scoped proof context
|
|
57
|
+
* (`{ productId, suffix }`), hashed by the host into the 32-byte context a
|
|
58
|
+
* proof or alias is bound to.
|
|
59
|
+
*/
|
|
60
|
+
export type { ProductProofContext, RingLocation } from "@parity/truapi";
|
|
49
61
|
|
|
50
62
|
// The account/alias shapes come from `@parity/truapi`'s generated specs; we
|
|
51
63
|
// derive the SDK-facing views from them so the field inventory tracks the
|
|
@@ -87,36 +99,69 @@ export type ProductAccount = ProductAccountId &
|
|
|
87
99
|
*
|
|
88
100
|
* Proves account membership in a ring without revealing which account.
|
|
89
101
|
*
|
|
90
|
-
* Derived from `@parity/truapi`'s
|
|
102
|
+
* Derived from `@parity/truapi`'s `ContextualAlias`, with both fields decoded to bytes.
|
|
91
103
|
*/
|
|
92
104
|
export type ContextualAlias = { [K in keyof WireAlias]: Uint8Array };
|
|
93
105
|
|
|
106
|
+
/**
|
|
107
|
+
* A Ring VRF proof plus the values needed to verify it downstream (e.g.
|
|
108
|
+
* against a precompile): the alias it commits to, and the ring member index /
|
|
109
|
+
* revision the proof was generated against.
|
|
110
|
+
*
|
|
111
|
+
* Derived from `@parity/truapi`'s `HostAccountCreateProofResponse`, with the
|
|
112
|
+
* byte fields decoded.
|
|
113
|
+
*/
|
|
114
|
+
export type RingVRFProof = Omit<WireRingVRFProof, "proof" | "contextualAlias"> & {
|
|
115
|
+
/** Raw ring VRF proof bytes. */
|
|
116
|
+
proof: Uint8Array;
|
|
117
|
+
/** Alias derived for the request's context. */
|
|
118
|
+
contextualAlias: ContextualAlias;
|
|
119
|
+
};
|
|
120
|
+
|
|
94
121
|
/**
|
|
95
122
|
* Accounts provider handle, backed by `truApi.account.*` / `truApi.signing.*`.
|
|
96
123
|
* Surfaces the user's wallet accounts, app-scoped product accounts, Ring VRF,
|
|
97
124
|
* user identity, connection status, and `PolkadotSigner` factories.
|
|
98
125
|
*
|
|
99
126
|
* Lookup methods return a neverthrow `ResultAsync` (use `.match(ok, err)`);
|
|
100
|
-
* the signer factories return a synchronous PAPI `PolkadotSigner`.
|
|
127
|
+
* the signer factories return a synchronous PAPI `PolkadotSigner`. The `err`
|
|
128
|
+
* channel carries truapi's canonical `CallErrorValue` envelope around the
|
|
129
|
+
* per-call versioned domain error, exactly as the generated client returns it.
|
|
101
130
|
*/
|
|
102
131
|
export interface AccountsProvider {
|
|
103
|
-
getUserId(): ResultAsync<
|
|
104
|
-
|
|
132
|
+
getUserId(): ResultAsync<
|
|
133
|
+
{ primaryUsername: string },
|
|
134
|
+
scale.CallErrorValue<VersionedHostGetUserIdError>
|
|
135
|
+
>;
|
|
136
|
+
requestLogin(
|
|
137
|
+
reason?: string,
|
|
138
|
+
): ResultAsync<HostRequestLoginResponse, scale.CallErrorValue<VersionedHostRequestLoginError>>;
|
|
105
139
|
getProductAccount(
|
|
106
140
|
dotNsIdentifier: string,
|
|
107
141
|
derivationIndex?: number,
|
|
108
|
-
): ResultAsync<ProductAccount,
|
|
142
|
+
): ResultAsync<ProductAccount, scale.CallErrorValue<VersionedHostAccountGetError>>;
|
|
143
|
+
/**
|
|
144
|
+
* Derive the contextual alias for a proof context and ring. The host
|
|
145
|
+
* selects the member key within the ring — no per-account addressing.
|
|
146
|
+
*/
|
|
109
147
|
getProductAccountAlias(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
): ResultAsync<ContextualAlias,
|
|
113
|
-
getLegacyAccounts(): ResultAsync<
|
|
148
|
+
context: ProductProofContext,
|
|
149
|
+
location: RingLocation,
|
|
150
|
+
): ResultAsync<ContextualAlias, scale.CallErrorValue<VersionedHostAccountGetAliasError>>;
|
|
151
|
+
getLegacyAccounts(): ResultAsync<
|
|
152
|
+
HostAccount[],
|
|
153
|
+
scale.CallErrorValue<VersionedHostGetLegacyAccountsError>
|
|
154
|
+
>;
|
|
155
|
+
/**
|
|
156
|
+
* Generate a Ring VRF proof binding `message` to the product-scoped
|
|
157
|
+
* `context`. The host selects the member key within the ring; the result
|
|
158
|
+
* carries the proof plus its verification values ({@link RingVRFProof}).
|
|
159
|
+
*/
|
|
114
160
|
createRingVRFProof(
|
|
115
|
-
|
|
116
|
-
derivationIndex: number,
|
|
161
|
+
context: ProductProofContext,
|
|
117
162
|
location: RingLocation,
|
|
118
163
|
message: Uint8Array,
|
|
119
|
-
): ResultAsync<
|
|
164
|
+
): ResultAsync<RingVRFProof, scale.CallErrorValue<VersionedHostAccountCreateProofError>>;
|
|
120
165
|
/**
|
|
121
166
|
* Build a `PolkadotSigner` for a product account. Signing routes through the
|
|
122
167
|
* host's `createTransaction` path: the host decodes the metadata and forwards
|
|
@@ -195,13 +240,11 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
195
240
|
derivationIndex,
|
|
196
241
|
}));
|
|
197
242
|
},
|
|
198
|
-
getProductAccountAlias(
|
|
199
|
-
return account
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
alias: fromHex(response.alias),
|
|
204
|
-
}));
|
|
243
|
+
getProductAccountAlias(context, location) {
|
|
244
|
+
return account.getAccountAlias({ context, ringLocation: location }).map((response) => ({
|
|
245
|
+
context: fromHex(response.context),
|
|
246
|
+
alias: fromHex(response.alias),
|
|
247
|
+
}));
|
|
205
248
|
},
|
|
206
249
|
getLegacyAccounts() {
|
|
207
250
|
return account.getLegacyAccounts().map((response) =>
|
|
@@ -211,14 +254,22 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
211
254
|
})),
|
|
212
255
|
);
|
|
213
256
|
},
|
|
214
|
-
createRingVRFProof(
|
|
257
|
+
createRingVRFProof(context, location, message) {
|
|
215
258
|
return account
|
|
216
259
|
.createAccountProof({
|
|
217
|
-
|
|
260
|
+
context,
|
|
218
261
|
ringLocation: location,
|
|
219
|
-
|
|
262
|
+
message: toHex(message),
|
|
220
263
|
})
|
|
221
|
-
.map((response) =>
|
|
264
|
+
.map((response) => ({
|
|
265
|
+
proof: fromHex(response.proof),
|
|
266
|
+
contextualAlias: {
|
|
267
|
+
context: fromHex(response.contextualAlias.context),
|
|
268
|
+
alias: fromHex(response.contextualAlias.alias),
|
|
269
|
+
},
|
|
270
|
+
ringIndex: response.ringIndex,
|
|
271
|
+
ringRevision: response.ringRevision,
|
|
272
|
+
}));
|
|
222
273
|
},
|
|
223
274
|
getProductAccountSigner(account_) {
|
|
224
275
|
const productAccountId = {
|
|
@@ -337,7 +388,12 @@ if (import.meta.vitest) {
|
|
|
337
388
|
getLegacyAccounts: method("getLegacyAccounts", {
|
|
338
389
|
accounts: [{ publicKey: "0xbb", name: "Bob" }],
|
|
339
390
|
}),
|
|
340
|
-
createAccountProof: method("createAccountProof", {
|
|
391
|
+
createAccountProof: method("createAccountProof", {
|
|
392
|
+
proof: "0xc0ffee",
|
|
393
|
+
contextualAlias: { context: "0x01", alias: "0x02" },
|
|
394
|
+
ringIndex: 3,
|
|
395
|
+
ringRevision: 7,
|
|
396
|
+
}),
|
|
341
397
|
connectionStatusSubscribe: () => ({
|
|
342
398
|
subscribe: () => ({ unsubscribe: vi.fn() }),
|
|
343
399
|
[Symbol.observable as symbol]() {
|
|
@@ -381,15 +437,14 @@ if (import.meta.vitest) {
|
|
|
381
437
|
});
|
|
382
438
|
});
|
|
383
439
|
|
|
384
|
-
test("createRingVRFProof hex-encodes the message
|
|
440
|
+
test("createRingVRFProof hex-encodes the message and decodes the proof response", async () => {
|
|
385
441
|
const calls: Array<[string, unknown]> = [];
|
|
386
442
|
const client = makeFakeClient({ onCall: (m, a) => calls.push([m, a]) });
|
|
387
443
|
const provider = adaptAccountsProvider(client);
|
|
388
444
|
const proof = await provider
|
|
389
445
|
.createRingVRFProof(
|
|
390
|
-
"app.dot",
|
|
391
|
-
|
|
392
|
-
{ genesisHash: "0x01", ringRootHash: "0x02" },
|
|
446
|
+
{ productId: "app.dot", suffix: "0x00" },
|
|
447
|
+
{ chainId: "0x01", junctions: [{ tag: "PalletInstance", value: 1 }] },
|
|
393
448
|
new Uint8Array([1, 2, 3]),
|
|
394
449
|
)
|
|
395
450
|
.match(
|
|
@@ -397,8 +452,17 @@ if (import.meta.vitest) {
|
|
|
397
452
|
() => null,
|
|
398
453
|
);
|
|
399
454
|
expect(calls[0][0]).toBe("createAccountProof");
|
|
400
|
-
expect(
|
|
401
|
-
|
|
455
|
+
expect(calls[0][1]).toEqual({
|
|
456
|
+
context: { productId: "app.dot", suffix: "0x00" },
|
|
457
|
+
ringLocation: { chainId: "0x01", junctions: [{ tag: "PalletInstance", value: 1 }] },
|
|
458
|
+
message: toHex(new Uint8Array([1, 2, 3])),
|
|
459
|
+
});
|
|
460
|
+
expect(proof).toEqual({
|
|
461
|
+
proof: fromHex("0xc0ffee"),
|
|
462
|
+
contextualAlias: { context: fromHex("0x01"), alias: fromHex("0x02") },
|
|
463
|
+
ringIndex: 3,
|
|
464
|
+
ringRevision: 7,
|
|
465
|
+
});
|
|
402
466
|
});
|
|
403
467
|
|
|
404
468
|
test("the product signer signs bytes via signing.signRaw", async () => {
|
package/src/chains.ts
CHANGED
|
@@ -6,13 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Bulletin Chain RPC endpoints per network environment. `paseo` (Paseo Next v2)
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Bulletin Chain RPC endpoints per network environment. `paseo` (Paseo Next v2)
|
|
10
|
+
* and `devnet` (public Paseo testnet) are populated today; `polkadot` and
|
|
11
|
+
* `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
|
-
summit: ["wss://summit-bulletin-rpc.polkadot.io"],
|
|
16
15
|
devnet: ["wss://bulletin-paseo.tservices.es:8443"],
|
|
17
16
|
polkadot: [] as string[],
|
|
18
17
|
kusama: [] as string[],
|
|
@@ -30,11 +29,6 @@ if (import.meta.vitest) {
|
|
|
30
29
|
expect(BULLETIN_RPCS.paseo[0]).toMatch(/^wss:\/\//);
|
|
31
30
|
});
|
|
32
31
|
|
|
33
|
-
test("BULLETIN_RPCS has summit endpoint", () => {
|
|
34
|
-
expect(BULLETIN_RPCS.summit.length).toBeGreaterThan(0);
|
|
35
|
-
expect(BULLETIN_RPCS.summit[0]).toMatch(/^wss:\/\//);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
32
|
test("BULLETIN_RPCS has devnet endpoint", () => {
|
|
39
33
|
expect(BULLETIN_RPCS.devnet.length).toBeGreaterThan(0);
|
|
40
34
|
expect(BULLETIN_RPCS.devnet[0]).toMatch(/^wss:\/\//);
|
package/src/errors.ts
CHANGED
|
@@ -18,38 +18,43 @@
|
|
|
18
18
|
* @module
|
|
19
19
|
*/
|
|
20
20
|
import type { SdkError } from "@parity/product-sdk-errors";
|
|
21
|
-
import type {
|
|
21
|
+
import type { scale } from "@parity/truapi";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* members with that import so the type is protocol-sourced rather than
|
|
37
|
-
* hand-widened.
|
|
24
|
+
* What a `Domain`-tagged call error carries. Widened from truapi's per-domain
|
|
25
|
+
* `Versioned*Error` types (all `{ tag: "V1", value: <domain error> }` today)
|
|
26
|
+
* so one payload type covers every call.
|
|
27
|
+
*/
|
|
28
|
+
type VersionedDomainError = { tag: string; value?: unknown };
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The error a host call puts on its `Err` channel — truapi's canonical
|
|
32
|
+
* {@link scale.CallErrorValue} envelope. `Denied` / `Unsupported` /
|
|
33
|
+
* `MalformedFrame` / `HostFailure` are transport-level failures; `Domain`
|
|
34
|
+
* wraps the actual per-domain error in a versioned envelope, which
|
|
35
|
+
* {@link formatHostError} digs through when rendering.
|
|
38
36
|
*
|
|
39
|
-
* This is the
|
|
40
|
-
*
|
|
41
|
-
* the error type consumers branch on.
|
|
37
|
+
* This is the payload {@link HostCallFailedError} carries — not the error
|
|
38
|
+
* type consumers branch on.
|
|
42
39
|
*/
|
|
43
|
-
export type HostErrorPayload =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
export type HostErrorPayload = scale.CallErrorValue<VersionedDomainError>;
|
|
41
|
+
|
|
42
|
+
/** Narrow to a tagged-union member: `{ tag, value? }`. */
|
|
43
|
+
function isTagged(value: unknown): value is { tag: string; value?: unknown } {
|
|
44
|
+
return (
|
|
45
|
+
value != null &&
|
|
46
|
+
typeof value === "object" &&
|
|
47
|
+
typeof (value as { tag?: unknown }).tag === "string"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Narrow to a reason-carrying payload — truapi's `GenericError` shape. */
|
|
52
|
+
function hasReason(value: unknown): value is { reason: string } {
|
|
53
|
+
return (
|
|
54
|
+
value != null &&
|
|
55
|
+
typeof value === "object" &&
|
|
56
|
+
typeof (value as { reason?: unknown }).reason === "string"
|
|
57
|
+
);
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
/**
|
|
@@ -67,16 +72,20 @@ export function formatHostError(error: unknown): string {
|
|
|
67
72
|
if (error instanceof Error) return error.message;
|
|
68
73
|
if (typeof error === "string") return error;
|
|
69
74
|
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return error.tag
|
|
75
|
+
if (isTagged(error)) {
|
|
76
|
+
// `Domain` carries the real error inside a versioned envelope — unwrap it.
|
|
77
|
+
if (error.tag === "Domain" && isTagged(error.value) && error.value.value !== undefined) {
|
|
78
|
+
return formatHostError(error.value.value);
|
|
79
|
+
}
|
|
80
|
+
// Tagged variant carrying a reason: { tag, value: { reason } }
|
|
81
|
+
if (hasReason(error.value)) {
|
|
82
|
+
return `${error.tag}: ${error.value.reason}`;
|
|
78
83
|
}
|
|
79
|
-
//
|
|
84
|
+
// Unit tagged variant, e.g. { tag: "Denied" } / { tag: "PermissionDenied" }
|
|
85
|
+
return error.tag;
|
|
86
|
+
}
|
|
87
|
+
// GenericError: { reason }
|
|
88
|
+
if (hasReason(error)) {
|
|
80
89
|
return error.reason;
|
|
81
90
|
}
|
|
82
91
|
|
|
@@ -161,7 +170,13 @@ if (import.meta.vitest) {
|
|
|
161
170
|
});
|
|
162
171
|
|
|
163
172
|
test("HostCallFailedError renders payload and preserves it", () => {
|
|
164
|
-
const payload
|
|
173
|
+
const payload: HostErrorPayload = {
|
|
174
|
+
tag: "Domain",
|
|
175
|
+
value: {
|
|
176
|
+
tag: "V1",
|
|
177
|
+
value: { tag: "PermissionDenied", value: { reason: "user said no" } },
|
|
178
|
+
},
|
|
179
|
+
};
|
|
165
180
|
const e = new HostCallFailedError("requestPermission failed", payload);
|
|
166
181
|
expect(e).toBeInstanceOf(HostError);
|
|
167
182
|
expect(e.payload).toBe(payload);
|
|
@@ -169,14 +184,17 @@ if (import.meta.vitest) {
|
|
|
169
184
|
expect(e.message).toBe("requestPermission failed: PermissionDenied: user said no");
|
|
170
185
|
});
|
|
171
186
|
|
|
172
|
-
test("HostCallFailedError renders a GenericError payload", () => {
|
|
173
|
-
const e = new HostCallFailedError("submit failed", {
|
|
187
|
+
test("HostCallFailedError renders a Domain-wrapped GenericError payload", () => {
|
|
188
|
+
const e = new HostCallFailedError("submit failed", {
|
|
189
|
+
tag: "Domain",
|
|
190
|
+
value: { tag: "V1", value: { reason: "timeout" } },
|
|
191
|
+
});
|
|
174
192
|
expect(e.message).toBe("submit failed: timeout");
|
|
175
193
|
});
|
|
176
194
|
|
|
177
195
|
test("isHostError narrows host errors only", () => {
|
|
178
196
|
expect(isHostError(new HostUnavailableError())).toBe(true);
|
|
179
|
-
expect(isHostError(new HostCallFailedError("x", {
|
|
197
|
+
expect(isHostError(new HostCallFailedError("x", { tag: "Denied" }))).toBe(true);
|
|
180
198
|
expect(isHostError(new Error("plain"))).toBe(false);
|
|
181
199
|
expect(isHostError("string")).toBe(false);
|
|
182
200
|
});
|
|
@@ -194,6 +212,24 @@ if (import.meta.vitest) {
|
|
|
194
212
|
expect(formatHostError({ tag: "Full" })).toBe("Full");
|
|
195
213
|
});
|
|
196
214
|
|
|
215
|
+
test("unwraps the CallError Domain envelope to the domain error", () => {
|
|
216
|
+
// { tag: "Domain", value: { tag: "V1", value: <domain error> } }
|
|
217
|
+
expect(
|
|
218
|
+
formatHostError({
|
|
219
|
+
tag: "Domain",
|
|
220
|
+
value: { tag: "V1", value: { tag: "PermissionDenied" } },
|
|
221
|
+
}),
|
|
222
|
+
).toBe("PermissionDenied");
|
|
223
|
+
expect(
|
|
224
|
+
formatHostError({ tag: "Domain", value: { tag: "V1", value: { reason: "boom" } } }),
|
|
225
|
+
).toBe("boom");
|
|
226
|
+
// Transport-level CallError variants render as-is.
|
|
227
|
+
expect(formatHostError({ tag: "Denied" })).toBe("Denied");
|
|
228
|
+
expect(formatHostError({ tag: "HostFailure", value: { reason: "crashed" } })).toBe(
|
|
229
|
+
"HostFailure: crashed",
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
|
|
197
233
|
test("falls back for non-host-error input", () => {
|
|
198
234
|
expect(formatHostError(new Error("plain"))).toBe("plain");
|
|
199
235
|
expect(formatHostError("string err")).toBe("string err");
|