@parity/product-sdk-host 0.14.0 → 0.15.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 → chunk-GDXSV7JV.js} +3 -2
- package/dist/chunk-GDXSV7JV.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +41 -17
- package/dist/index.js.map +1 -1
- package/dist/testing.js +3 -2
- package/dist/testing.js.map +1 -1
- package/package.json +4 -4
- package/src/accounts.ts +43 -17
- package/src/index.ts +1 -0
- package/src/papi-provider.ts +94 -26
- package/src/payments.ts +1 -1
- package/src/testing.ts +1 -0
- package/src/transport.ts +23 -1
- package/src/truapi.ts +2 -0
- package/dist/chunk-3SWF5CWC.js.map +0 -1
package/dist/testing.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { setTruApiClient } from './chunk-
|
|
2
|
-
export { setTruApiClient } from './chunk-
|
|
1
|
+
import { setTruApiClient } from './chunk-GDXSV7JV.js';
|
|
2
|
+
export { setTruApiClient } from './chunk-GDXSV7JV.js';
|
|
3
3
|
import { okAsync } from 'neverthrow';
|
|
4
4
|
|
|
5
5
|
function toHex(bytes) {
|
|
@@ -89,6 +89,7 @@ function createFakeTruApiClient(options) {
|
|
|
89
89
|
ringIndex: 0,
|
|
90
90
|
ringRevision: 0
|
|
91
91
|
}),
|
|
92
|
+
signVrf: () => okAsync({ preOutput: publicKey, proof: signature }),
|
|
92
93
|
connectionStatusSubscribe: () => inertObservable()
|
|
93
94
|
},
|
|
94
95
|
signing: {
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +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` / `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"]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"names":[],"mappings":";;;;AA6CA,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,kBAAA,EAAoB,MAChB,OAAA,CAAQ;AAAA,QACJ,KAAA,EAAO,SAAA;AAAA,QACP,eAAA,EAAiB;AAAA,UACb,SAAS,KAAA,CAAM,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;AAAA,UAClC,OAAO,KAAA,CAAM,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC;AAAA,SACpC;AAAA,QACA,SAAA,EAAW,CAAA;AAAA,QACX,YAAA,EAAc;AAAA,OACjB,CAAA;AAAA,MACL,OAAA,EAAS,MAAM,OAAA,CAAQ,EAAE,WAAW,SAAA,EAAW,KAAA,EAAO,WAAW,CAAA;AAAA,MACjE,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,WAAA,EAAa,WAAW,aAAa,CAAA;AAAA,IACrC,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` / `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 signVrf: () => okAsync({ preOutput: publicKey, 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 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.15.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.6.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
|
+
"@parity/product-sdk-errors": "0.2.0",
|
|
30
31
|
"@parity/product-sdk-logger": "0.1.1",
|
|
31
|
-
"@parity/result": "0.2.0"
|
|
32
|
-
"@parity/product-sdk-errors": "0.2.0"
|
|
32
|
+
"@parity/result": "0.2.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"tsup": "^8.5.1",
|
package/src/accounts.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { AccountId, type PolkadotSigner } from "polkadot-api";
|
|
|
27
27
|
|
|
28
28
|
import type {
|
|
29
29
|
ContextualAlias as WireAlias,
|
|
30
|
+
DerivationIndex,
|
|
30
31
|
HostAccountConnectionStatusSubscribeItem,
|
|
31
32
|
HostAccountCreateProofResponse as WireRingVRFProof,
|
|
32
33
|
HostRequestLoginResponse,
|
|
@@ -54,16 +55,22 @@ import type { HostSubscription } from "./types.js";
|
|
|
54
55
|
* - `RingLocation` — where the ring lives (`{ chainId, junctions }`; junctions
|
|
55
56
|
* address the ring via `PalletInstance` / `CollectionId` steps).
|
|
56
57
|
* - `ProductProofContext` — the product-scoped proof context
|
|
57
|
-
* (`{ productId, suffix }`),
|
|
58
|
+
* (`{ productId, suffix }`), expanded by the host into the 32-byte context a
|
|
58
59
|
* proof or alias is bound to.
|
|
60
|
+
* - `DerivationIndex` — the tagged selector `ProductProofContext.suffix`
|
|
61
|
+
* carries: `{ tag: "Left", value: number }` for a plain index, or
|
|
62
|
+
* `{ tag: "Right", value: HexString }` for a raw 32-byte index.
|
|
59
63
|
*/
|
|
60
|
-
export type { ProductProofContext, RingLocation } from "@parity/truapi";
|
|
64
|
+
export type { DerivationIndex, ProductProofContext, RingLocation } from "@parity/truapi";
|
|
61
65
|
|
|
62
66
|
// The account/alias shapes come from `@parity/truapi`'s generated specs; we
|
|
63
67
|
// derive the SDK-facing views from them so the field inventory tracks the
|
|
64
|
-
// protocol automatically, and override only the
|
|
65
|
-
//
|
|
66
|
-
//
|
|
68
|
+
// protocol automatically, and override only the fields the adapter re-encodes:
|
|
69
|
+
// byte fields decoded from `0x`-prefixed `HexString`s to `Uint8Array`s, and
|
|
70
|
+
// the tagged derivation-index selector kept as a plain `number` (wrapped back
|
|
71
|
+
// into `Left` at the wire boundary). Shapes re-exported verbatim (e.g.
|
|
72
|
+
// `ProductProofContext`) track the wire as-is. Same pattern as
|
|
73
|
+
// `@parity/product-sdk-statement-store`.
|
|
67
74
|
|
|
68
75
|
/**
|
|
69
76
|
* One of the user's existing wallet accounts, surfaced through the host and
|
|
@@ -86,10 +93,14 @@ export type HostAccount = Omit<WireLegacyAccount, "publicKey"> & {
|
|
|
86
93
|
*
|
|
87
94
|
* Combines `@parity/truapi`'s `ProductAccountId` (the `{ dotNsIdentifier,
|
|
88
95
|
* derivationIndex }` lookup key) with the `ProductAccount` payload, with
|
|
89
|
-
* `publicKey` decoded to bytes
|
|
96
|
+
* `publicKey` decoded to bytes and `derivationIndex` kept as the plain
|
|
97
|
+
* numeric index (the adapter wraps it into the wire's tagged
|
|
98
|
+
* {@link DerivationIndex} selector).
|
|
90
99
|
*/
|
|
91
|
-
export type ProductAccount = ProductAccountId &
|
|
100
|
+
export type ProductAccount = Omit<ProductAccountId, "derivationIndex"> &
|
|
92
101
|
Omit<WireProductAccount, "publicKey"> & {
|
|
102
|
+
/** Plain account index within the product subtree. */
|
|
103
|
+
derivationIndex: number;
|
|
93
104
|
/** Raw public key bytes. */
|
|
94
105
|
publicKey: Uint8Array;
|
|
95
106
|
};
|
|
@@ -217,6 +228,14 @@ function toHostExtensions(
|
|
|
217
228
|
}));
|
|
218
229
|
}
|
|
219
230
|
|
|
231
|
+
/** Build the wire `ProductAccountId`, wrapping the plain index as a `Left` selector. */
|
|
232
|
+
function toWireProductAccountId(
|
|
233
|
+
dotNsIdentifier: string,
|
|
234
|
+
derivationIndex: number,
|
|
235
|
+
): ProductAccountId {
|
|
236
|
+
return { dotNsIdentifier, derivationIndex: { tag: "Left", value: derivationIndex } };
|
|
237
|
+
}
|
|
238
|
+
|
|
220
239
|
/** Build an {@link AccountsProvider} over a TruAPI client's `account` / `signing` domains. */
|
|
221
240
|
function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
222
241
|
const account = client.account;
|
|
@@ -233,7 +252,9 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
233
252
|
},
|
|
234
253
|
getProductAccount(dotNsIdentifier, derivationIndex = 0) {
|
|
235
254
|
return account
|
|
236
|
-
.getAccount({
|
|
255
|
+
.getAccount({
|
|
256
|
+
productAccountId: toWireProductAccountId(dotNsIdentifier, derivationIndex),
|
|
257
|
+
})
|
|
237
258
|
.map((response) => ({
|
|
238
259
|
publicKey: fromHex(response.account.publicKey),
|
|
239
260
|
dotNsIdentifier,
|
|
@@ -272,10 +293,10 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
272
293
|
}));
|
|
273
294
|
},
|
|
274
295
|
getProductAccountSigner(account_) {
|
|
275
|
-
const productAccountId =
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
296
|
+
const productAccountId = toWireProductAccountId(
|
|
297
|
+
account_.dotNsIdentifier,
|
|
298
|
+
account_.derivationIndex,
|
|
299
|
+
);
|
|
279
300
|
|
|
280
301
|
return {
|
|
281
302
|
publicKey: account_.publicKey,
|
|
@@ -428,7 +449,12 @@ if (import.meta.vitest) {
|
|
|
428
449
|
);
|
|
429
450
|
expect(calls[0]).toEqual([
|
|
430
451
|
"getAccount",
|
|
431
|
-
{
|
|
452
|
+
{
|
|
453
|
+
productAccountId: {
|
|
454
|
+
dotNsIdentifier: "app.dot",
|
|
455
|
+
derivationIndex: { tag: "Left", value: 2 },
|
|
456
|
+
},
|
|
457
|
+
},
|
|
432
458
|
]);
|
|
433
459
|
expect(account).toEqual({
|
|
434
460
|
publicKey: fromHex("0xaa"),
|
|
@@ -443,7 +469,7 @@ if (import.meta.vitest) {
|
|
|
443
469
|
const provider = adaptAccountsProvider(client);
|
|
444
470
|
const proof = await provider
|
|
445
471
|
.createRingVRFProof(
|
|
446
|
-
{ productId: "app.dot", suffix: "
|
|
472
|
+
{ productId: "app.dot", suffix: { tag: "Left", value: 0 } },
|
|
447
473
|
{ chainId: "0x01", junctions: [{ tag: "PalletInstance", value: 1 }] },
|
|
448
474
|
new Uint8Array([1, 2, 3]),
|
|
449
475
|
)
|
|
@@ -453,7 +479,7 @@ if (import.meta.vitest) {
|
|
|
453
479
|
);
|
|
454
480
|
expect(calls[0][0]).toBe("createAccountProof");
|
|
455
481
|
expect(calls[0][1]).toEqual({
|
|
456
|
-
context: { productId: "app.dot", suffix: "
|
|
482
|
+
context: { productId: "app.dot", suffix: { tag: "Left", value: 0 } },
|
|
457
483
|
ringLocation: { chainId: "0x01", junctions: [{ tag: "PalletInstance", value: 1 }] },
|
|
458
484
|
message: toHex(new Uint8Array([1, 2, 3])),
|
|
459
485
|
});
|
|
@@ -478,7 +504,7 @@ if (import.meta.vitest) {
|
|
|
478
504
|
expect(calls.at(-1)).toEqual([
|
|
479
505
|
"signRaw",
|
|
480
506
|
{
|
|
481
|
-
account: { dotNsIdentifier: "app.dot", derivationIndex: 0 },
|
|
507
|
+
account: { dotNsIdentifier: "app.dot", derivationIndex: { tag: "Left", value: 0 } },
|
|
482
508
|
payload: { tag: "Bytes", value: { bytes: toHex(new Uint8Array([9, 9])) } },
|
|
483
509
|
},
|
|
484
510
|
]);
|
|
@@ -565,7 +591,7 @@ if (import.meta.vitest) {
|
|
|
565
591
|
expect(calls.at(-1)).toEqual([
|
|
566
592
|
"createTransaction",
|
|
567
593
|
{
|
|
568
|
-
signer: { dotNsIdentifier: "app.dot", derivationIndex: 0 },
|
|
594
|
+
signer: { dotNsIdentifier: "app.dot", derivationIndex: { tag: "Left", value: 0 } },
|
|
569
595
|
genesisHash: toHex(new Uint8Array([0x01, 0x02])),
|
|
570
596
|
callData: toHex(new Uint8Array([0xca, 0x11])),
|
|
571
597
|
extensions: expectedHostExtensions,
|
package/src/index.ts
CHANGED
package/src/papi-provider.ts
CHANGED
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
* This is a backport of `@novasamatech/host-api-wrapper`'s
|
|
7
7
|
* `createPapiProvider` (`dist/papiProvider.js`) into product-sdk, with the
|
|
8
8
|
* call layer swapped from the novasama `hostApi` to the
|
|
9
|
-
* `@parity/truapi` client. The JSON-RPC ↔ chainHead bridge
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* over from the upstream module; only the per-method transport calls and their
|
|
13
|
-
* error/response unwrapping are re-pointed at `truApi.chain.*`.
|
|
9
|
+
* `@parity/truapi` client. The JSON-RPC ↔ chainHead bridge handles request
|
|
10
|
+
* dispatch, `chainHead_v1_followEvent` notification synthesis, and operation /
|
|
11
|
+
* broadcast bookkeeping over the structured `truApi.chain.*` methods.
|
|
14
12
|
*
|
|
15
13
|
* **Why a bridge at all.** PAPI speaks the JSON-RPC `chainHead`/`chainSpec`/
|
|
16
14
|
* `transaction` API; the host exposes the same operations as structured,
|
|
@@ -51,8 +49,7 @@ import type {
|
|
|
51
49
|
import { createLogger } from "@parity/product-sdk-logger";
|
|
52
50
|
|
|
53
51
|
import { formatHostError } from "./errors.js";
|
|
54
|
-
import { subscribeWithInterrupt } from "./transport.js";
|
|
55
|
-
import type { HostSubscription } from "./types.js";
|
|
52
|
+
import { subscribeWithInterrupt, type TransportSubscription } from "./transport.js";
|
|
56
53
|
|
|
57
54
|
const log = createLogger("host:papi");
|
|
58
55
|
|
|
@@ -195,11 +192,8 @@ export function createHostPapiProvider(
|
|
|
195
192
|
const chain = client.chain;
|
|
196
193
|
|
|
197
194
|
return (onMessage: (message: JsonRpcMessage) => void): JsonRpcConnection => {
|
|
198
|
-
const activeFollows = new Map<string,
|
|
195
|
+
const activeFollows = new Map<string, TransportSubscription>();
|
|
199
196
|
const activeBroadcasts = new Set<string>();
|
|
200
|
-
let nextSubId = 0;
|
|
201
|
-
|
|
202
|
-
const getNextSubId = () => `follow_${nextSubId++}`;
|
|
203
197
|
|
|
204
198
|
function sendJsonRpcResponse(id: JsonRpcRequest["id"], result: unknown): void {
|
|
205
199
|
onMessage({ jsonrpc: "2.0", id, result } as JsonRpcMessage);
|
|
@@ -227,31 +221,55 @@ export function createHostPapiProvider(
|
|
|
227
221
|
switch (method) {
|
|
228
222
|
case "chainHead_v1_follow": {
|
|
229
223
|
const [withRuntime] = params as [boolean];
|
|
230
|
-
const syntheticSubId = getNextSubId();
|
|
231
224
|
// The Stop branch unsubscribes its own host subscription, but the
|
|
232
225
|
// handle is this call's return value — the ref breaks that
|
|
233
226
|
// chicken-and-egg. (Releasing before forwarding the Stop is just
|
|
234
227
|
// cleanup; the consumer's synchronous refollow gets a fresh wire
|
|
235
228
|
// subscription either way.)
|
|
236
|
-
const ref: { handle?:
|
|
229
|
+
const ref: { handle?: TransportSubscription } = {};
|
|
230
|
+
const pendingItems: RemoteChainHeadFollowItem[] = [];
|
|
231
|
+
const forwardItem = (
|
|
232
|
+
followSubscriptionId: string,
|
|
233
|
+
item: RemoteChainHeadFollowItem,
|
|
234
|
+
) => {
|
|
235
|
+
if (item.tag === "Stop" && activeFollows.delete(followSubscriptionId)) {
|
|
236
|
+
ref.handle?.unsubscribe();
|
|
237
|
+
}
|
|
238
|
+
sendFollowEvent(followSubscriptionId, convertFollowEventToJsonRpc(item));
|
|
239
|
+
};
|
|
237
240
|
ref.handle = subscribeWithInterrupt(
|
|
238
241
|
chain.followHeadSubscribe({ request: { genesisHash, withRuntime } }),
|
|
239
242
|
(item) => {
|
|
240
|
-
|
|
241
|
-
|
|
243
|
+
const followSubscriptionId = ref.handle?.subscriptionId;
|
|
244
|
+
if (!followSubscriptionId) {
|
|
245
|
+
pendingItems.push(item);
|
|
246
|
+
return;
|
|
242
247
|
}
|
|
243
|
-
|
|
248
|
+
forwardItem(followSubscriptionId, item);
|
|
244
249
|
},
|
|
245
250
|
);
|
|
251
|
+
const followSubscriptionId = ref.handle.subscriptionId;
|
|
252
|
+
if (!followSubscriptionId) {
|
|
253
|
+
ref.handle.unsubscribe();
|
|
254
|
+
sendJsonRpcError(
|
|
255
|
+
id,
|
|
256
|
+
JSON_RPC_INTERNAL_ERROR,
|
|
257
|
+
"Host follow subscription did not start",
|
|
258
|
+
);
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
246
261
|
// A transport interrupt/close ends the stream without a Stop
|
|
247
262
|
// item; synthesize one so the consumer refollows.
|
|
248
263
|
ref.handle.onInterrupt(() => {
|
|
249
|
-
if (activeFollows.delete(
|
|
250
|
-
sendFollowEvent(
|
|
264
|
+
if (activeFollows.delete(followSubscriptionId)) {
|
|
265
|
+
sendFollowEvent(followSubscriptionId, { event: "stop" });
|
|
251
266
|
}
|
|
252
267
|
});
|
|
253
|
-
activeFollows.set(
|
|
254
|
-
sendJsonRpcResponse(id,
|
|
268
|
+
activeFollows.set(followSubscriptionId, ref.handle);
|
|
269
|
+
sendJsonRpcResponse(id, followSubscriptionId);
|
|
270
|
+
for (const item of pendingItems) {
|
|
271
|
+
forwardItem(followSubscriptionId, item);
|
|
272
|
+
}
|
|
255
273
|
break;
|
|
256
274
|
}
|
|
257
275
|
case "chainHead_v1_unfollow": {
|
|
@@ -475,11 +493,15 @@ if (import.meta.vitest) {
|
|
|
475
493
|
errors?: Record<string, unknown>;
|
|
476
494
|
/** Unsubscribe spy used by the follow subscription (defaults to a fresh `vi.fn()`). */
|
|
477
495
|
unsubscribe?: () => void;
|
|
496
|
+
/** Item emitted synchronously while the transport subscription starts. */
|
|
497
|
+
initialItem?: unknown;
|
|
478
498
|
captureObserver?: (observer: {
|
|
479
499
|
next: (i: unknown) => void;
|
|
480
500
|
error: (e: unknown) => void;
|
|
481
501
|
complete: () => void;
|
|
482
502
|
}) => void;
|
|
503
|
+
/** Transport request id assigned to the follow subscription. */
|
|
504
|
+
subscriptionId?: string;
|
|
483
505
|
}) {
|
|
484
506
|
const okMatch = (value: unknown) => ({
|
|
485
507
|
match: (ok: (v: unknown) => unknown, _err: (e: unknown) => unknown) => ok(value),
|
|
@@ -501,7 +523,13 @@ if (import.meta.vitest) {
|
|
|
501
523
|
complete: () => void;
|
|
502
524
|
}) => {
|
|
503
525
|
opts.captureObserver?.(observer);
|
|
504
|
-
|
|
526
|
+
if (opts.initialItem !== undefined) {
|
|
527
|
+
observer.next(opts.initialItem);
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
subscriptionId: opts.subscriptionId ?? "p:41",
|
|
531
|
+
unsubscribe: opts.unsubscribe ?? vi.fn(),
|
|
532
|
+
};
|
|
505
533
|
},
|
|
506
534
|
[Symbol.observable as symbol]() {
|
|
507
535
|
return this;
|
|
@@ -530,11 +558,14 @@ if (import.meta.vitest) {
|
|
|
530
558
|
} as unknown as TrUApiClient;
|
|
531
559
|
}
|
|
532
560
|
|
|
533
|
-
test("follow
|
|
561
|
+
test("follow preserves the transport id for events and follow-up requests", () => {
|
|
534
562
|
let observer:
|
|
535
563
|
| { next: (i: unknown) => void; error: (e: unknown) => void; complete: () => void }
|
|
536
564
|
| undefined;
|
|
565
|
+
const calls: Array<[string, unknown]> = [];
|
|
537
566
|
const client = makeFakeClient({
|
|
567
|
+
subscriptionId: "p:17",
|
|
568
|
+
onCall: (method, args) => calls.push([method, args]),
|
|
538
569
|
captureObserver: (o) => {
|
|
539
570
|
observer = o;
|
|
540
571
|
},
|
|
@@ -545,8 +576,7 @@ if (import.meta.vitest) {
|
|
|
545
576
|
const conn = provider((m) => messages.push(m));
|
|
546
577
|
|
|
547
578
|
conn.send({ jsonrpc: "2.0", id: 1, method: "chainHead_v1_follow", params: [true] });
|
|
548
|
-
|
|
549
|
-
expect(messages[0]).toEqual({ jsonrpc: "2.0", id: 1, result: "follow_0" });
|
|
579
|
+
expect(messages[0]).toEqual({ jsonrpc: "2.0", id: 1, result: "p:17" });
|
|
550
580
|
|
|
551
581
|
// A typed BestBlockChanged item becomes a chainHead_v1_followEvent.
|
|
552
582
|
observer?.next({ tag: "BestBlockChanged", value: { bestBlockHash: "0xbeef" } });
|
|
@@ -554,10 +584,48 @@ if (import.meta.vitest) {
|
|
|
554
584
|
jsonrpc: "2.0",
|
|
555
585
|
method: "chainHead_v1_followEvent",
|
|
556
586
|
params: {
|
|
557
|
-
subscription: "
|
|
587
|
+
subscription: "p:17",
|
|
558
588
|
result: { event: "bestBlockChanged", bestBlockHash: "0xbeef" },
|
|
559
589
|
},
|
|
560
590
|
});
|
|
591
|
+
|
|
592
|
+
conn.send({
|
|
593
|
+
jsonrpc: "2.0",
|
|
594
|
+
id: 2,
|
|
595
|
+
method: "chainHead_v1_header",
|
|
596
|
+
params: ["p:17", "0xbeef"],
|
|
597
|
+
});
|
|
598
|
+
expect(calls).toContainEqual([
|
|
599
|
+
"getHeadHeader",
|
|
600
|
+
{
|
|
601
|
+
genesisHash: "0xfeed",
|
|
602
|
+
followSubscriptionId: "p:17",
|
|
603
|
+
hash: "0xbeef",
|
|
604
|
+
},
|
|
605
|
+
]);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test("follow buffers an item emitted while the transport subscription starts", () => {
|
|
609
|
+
const client = makeFakeClient({
|
|
610
|
+
subscriptionId: "p:18",
|
|
611
|
+
initialItem: { tag: "BestBlockChanged", value: { bestBlockHash: "0xbeef" } },
|
|
612
|
+
});
|
|
613
|
+
const messages: JsonRpcMessage[] = [];
|
|
614
|
+
const conn = createHostPapiProvider(client, "0xfeed")((message) => messages.push(message));
|
|
615
|
+
|
|
616
|
+
conn.send({ jsonrpc: "2.0", id: 1, method: "chainHead_v1_follow", params: [true] });
|
|
617
|
+
|
|
618
|
+
expect(messages).toEqual([
|
|
619
|
+
{ jsonrpc: "2.0", id: 1, result: "p:18" },
|
|
620
|
+
{
|
|
621
|
+
jsonrpc: "2.0",
|
|
622
|
+
method: "chainHead_v1_followEvent",
|
|
623
|
+
params: {
|
|
624
|
+
subscription: "p:18",
|
|
625
|
+
result: { event: "bestBlockChanged", bestBlockHash: "0xbeef" },
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
]);
|
|
561
629
|
});
|
|
562
630
|
|
|
563
631
|
test("chainSpec_v1_properties parses the JSON-encoded properties string", () => {
|
|
@@ -628,7 +696,7 @@ if (import.meta.vitest) {
|
|
|
628
696
|
expect(messages[1]).toEqual({
|
|
629
697
|
jsonrpc: "2.0",
|
|
630
698
|
method: "chainHead_v1_followEvent",
|
|
631
|
-
params: { subscription: "
|
|
699
|
+
params: { subscription: "p:41", result: { event: "stop" } },
|
|
632
700
|
});
|
|
633
701
|
});
|
|
634
702
|
|
package/src/payments.ts
CHANGED
|
@@ -97,7 +97,7 @@ function adaptPaymentManager(client: TrUApiClient): PaymentManager {
|
|
|
97
97
|
* const payments = await getPaymentManager();
|
|
98
98
|
* if (payments) {
|
|
99
99
|
* const sub = payments.subscribeBalance((b) => { ... });
|
|
100
|
-
* await payments.topUp(1_000_000n, { tag: "ProductAccount", value: { derivationIndex: 0 } });
|
|
100
|
+
* await payments.topUp(1_000_000n, { tag: "ProductAccount", value: { derivationIndex: { tag: "Left", value: 0 } } });
|
|
101
101
|
* const { id } = await payments.requestPayment(500n, "0x…");
|
|
102
102
|
* sub.unsubscribe();
|
|
103
103
|
* }
|
package/src/testing.ts
CHANGED
|
@@ -193,6 +193,7 @@ export function createFakeTruApiClient(options?: CreateFakeTruApiClientOptions):
|
|
|
193
193
|
ringIndex: 0,
|
|
194
194
|
ringRevision: 0,
|
|
195
195
|
}),
|
|
196
|
+
signVrf: () => okAsync({ preOutput: publicKey, proof: signature }),
|
|
196
197
|
connectionStatusSubscribe: () => inertObservable(),
|
|
197
198
|
},
|
|
198
199
|
signing: {
|
package/src/transport.ts
CHANGED
|
@@ -19,6 +19,11 @@ import {
|
|
|
19
19
|
|
|
20
20
|
import type { HostSubscription } from "./types.js";
|
|
21
21
|
|
|
22
|
+
/** A {@link HostSubscription} carrying the transport-assigned subscription id. */
|
|
23
|
+
export interface TransportSubscription extends HostSubscription {
|
|
24
|
+
readonly subscriptionId: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
// Test-only override. When set — via `setTruApiClient`, exposed through
|
|
23
28
|
// `@parity/product-sdk-host/testing` — every host accessor resolves this client
|
|
24
29
|
// instead of the sandbox one. `null` in production, so the branches below are
|
|
@@ -91,7 +96,7 @@ export async function getClient(): Promise<TrUApiClient | null> {
|
|
|
91
96
|
export function subscribeWithInterrupt<Item, Reason = never>(
|
|
92
97
|
observable: ObservableLike<Item, Reason>,
|
|
93
98
|
onNext: (item: Item) => void,
|
|
94
|
-
):
|
|
99
|
+
): TransportSubscription {
|
|
95
100
|
let interruptCallback: ((reason?: unknown) => void) | undefined;
|
|
96
101
|
const sub = observable.subscribe({
|
|
97
102
|
next: onNext,
|
|
@@ -99,6 +104,7 @@ export function subscribeWithInterrupt<Item, Reason = never>(
|
|
|
99
104
|
complete: () => interruptCallback?.(),
|
|
100
105
|
});
|
|
101
106
|
return {
|
|
107
|
+
subscriptionId: sub.subscriptionId,
|
|
102
108
|
unsubscribe: () => sub.unsubscribe(),
|
|
103
109
|
onInterrupt: (callback) => {
|
|
104
110
|
interruptCallback = callback;
|
|
@@ -156,4 +162,20 @@ if (import.meta.vitest) {
|
|
|
156
162
|
process.env.NODE_ENV = original;
|
|
157
163
|
}
|
|
158
164
|
});
|
|
165
|
+
|
|
166
|
+
test("subscribeWithInterrupt preserves the transport subscription id", () => {
|
|
167
|
+
const observable = {
|
|
168
|
+
subscribe: () => ({
|
|
169
|
+
subscriptionId: "p:17",
|
|
170
|
+
unsubscribe: () => {},
|
|
171
|
+
}),
|
|
172
|
+
[Symbol.observable]() {
|
|
173
|
+
return this;
|
|
174
|
+
},
|
|
175
|
+
} as ObservableLike<never>;
|
|
176
|
+
|
|
177
|
+
const subscription = subscribeWithInterrupt(observable, () => {});
|
|
178
|
+
|
|
179
|
+
expect(subscription.subscriptionId).toBe("p:17");
|
|
180
|
+
});
|
|
159
181
|
}
|
package/src/truapi.ts
CHANGED
|
@@ -183,6 +183,8 @@ export async function createHostPreimageManager(): Promise<PreimageManager | nul
|
|
|
183
183
|
// Resource-allocation / permission types, re-exported verbatim from
|
|
184
184
|
// `@parity/truapi` (imported above for the local signatures):
|
|
185
185
|
// - `AllocatableResource` — resource types requestable via `requestResourceAllocation`.
|
|
186
|
+
// Since truapi 0.6.0 its `SmartContractAllowance` variant carries the tagged
|
|
187
|
+
// `DerivationIndex` selector (`{ tag: "Left", value: number }` for a plain index).
|
|
186
188
|
// - `AllocationOutcome` — per-resource outcome, the string union
|
|
187
189
|
// `"Allocated" | "Rejected" | "NotAvailable"` (RFC-10).
|
|
188
190
|
// - `RemotePermission` — permission the dapp asks the host to grant via `requestPermission`.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/transport.ts"],"names":["sandboxGetClientSync","sandboxIsCorrectEnvironment"],"mappings":";;;AAyBA,IAAI,cAAA,GAAsC,IAAA;AAE1C,SAAS,iBAAA,GAA6B;AAClC,EAAA,IAAI;AAIA,IAAA,OAAO,OAAA,CAAQ,IAAI,QAAA,KAAa,YAAA;AAAA,EACpC,CAAA,CAAA,MAAQ;AAEJ,IAAA,OAAO,KAAA;AAAA,EACX;AACJ;AAYO,SAAS,gBAAgB,MAAA,EAAmC;AAC/D,EAAA,IAAI,MAAA,KAAW,IAAA,IAAQ,iBAAA,EAAkB,EAAG;AACxC,IAAA,OAAA,CAAQ,IAAA;AAAA,MACJ;AAAA,KACJ;AAAA,EACJ;AACA,EAAA,cAAA,GAAiB,MAAA;AACrB;AAMO,SAAS,aAAA,GAAqC;AACjD,EAAA,OAAO,kBAAkBA,eAAA,EAAqB;AAClD;AAMO,SAAS,oBAAA,GAAgC;AAC5C,EAAA,OAAO,cAAA,KAAmB,QAAQC,sBAAA,EAA4B;AAClE;AAMA,eAAsB,SAAA,GAA0C;AAC5D,EAAA,OAAO,aAAA,EAAc;AACzB;AAUO,SAAS,sBAAA,CACZ,YACA,MAAA,EACgB;AAChB,EAAA,IAAI,iBAAA;AACJ,EAAA,MAAM,GAAA,GAAM,WAAW,SAAA,CAAU;AAAA,IAC7B,IAAA,EAAM,MAAA;AAAA,IACN,KAAA,EAAO,CAAC,MAAA,KAAW,iBAAA,GAAoB,MAAM,CAAA;AAAA,IAC7C,QAAA,EAAU,MAAM,iBAAA;AAAoB,GACvC,CAAA;AACD,EAAA,OAAO;AAAA,IACH,WAAA,EAAa,MAAM,GAAA,CAAI,WAAA,EAAY;AAAA,IACnC,WAAA,EAAa,CAAC,QAAA,KAAa;AACvB,MAAA,iBAAA,GAAoB,QAAA;AACpB,MAAA,OAAO,MAAM;AACT,QAAA,IAAI,iBAAA,KAAsB,UAAU,iBAAA,GAAoB,MAAA;AAAA,MAC5D,CAAA;AAAA,IACJ;AAAA,GACJ;AACJ","file":"chunk-3SWF5CWC.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Access to the in-house TruAPI client (`@parity/truapi`) for the host package.\n *\n * Environment detection and the lazily-built, cached client come from\n * `@parity/truapi/sandbox`; this module layers the product-sdk-specific glue on\n * top — an async {@link getClient} accessor and {@link subscribeWithInterrupt},\n * which adapts a truapi stream into the host's {@link HostSubscription} shape.\n *\n * @module\n */\n\nimport type { ObservableLike, TrUApiClient } from \"@parity/truapi\";\nimport {\n getClientSync as sandboxGetClientSync,\n isCorrectEnvironment as sandboxIsCorrectEnvironment,\n} from \"@parity/truapi/sandbox\";\n\nimport type { HostSubscription } from \"./types.js\";\n\n// Test-only override. When set — via `setTruApiClient`, exposed through\n// `@parity/product-sdk-host/testing` — every host accessor resolves this client\n// instead of the sandbox one. `null` in production, so the branches below are\n// no-ops there.\nlet clientOverride: TrUApiClient | null = null;\n\nfunction isProductionBuild(): boolean {\n try {\n // Must stay a plain `process.env.NODE_ENV` member expression: bundlers\n // (Vite, esbuild, webpack) substitute it textually, which is how this\n // check works in browser builds where `process` doesn't exist.\n return process.env.NODE_ENV === \"production\";\n } catch {\n // No `process` and no bundler define — can't tell, stay quiet.\n return false;\n }\n}\n\n/**\n * Test-only seam: force {@link getClient} / {@link getClientSync} to return\n * `client`, and {@link isCorrectEnvironment} to report `true`. Pass `null` to\n * restore normal detection. Exposed through `@parity/product-sdk-host/testing`,\n * not the package's main entry.\n *\n * Calling this in a production build silently reroutes every host accessor to\n * the injected client, so we warn — it almost always means a `/testing` import\n * leaked into a production path.\n */\nexport function setTruApiClient(client: TrUApiClient | null): void {\n if (client !== null && isProductionBuild()) {\n console.warn(\n \"[product-sdk] setTruApiClient() was called in a production build. This is a test-only seam from @parity/product-sdk-host/testing; a leaked import will silently reroute all host access to the injected client.\",\n );\n }\n clientOverride = client;\n}\n\n/**\n * Synchronous TruAPI client accessor. Returns the injected test client when one\n * is set, otherwise the sandbox client (`null` outside a host container).\n */\nexport function getClientSync(): TrUApiClient | null {\n return clientOverride ?? sandboxGetClientSync();\n}\n\n/**\n * Host-container detection. `true` when a test client is injected, otherwise the\n * sandbox heuristic (iframe / webview marker / injected message port).\n */\nexport function isCorrectEnvironment(): boolean {\n return clientOverride !== null || sandboxIsCorrectEnvironment();\n}\n\n/**\n * Get the TruAPI client. Returns `null` outside a host container. Async wrapper\n * over {@link getClientSync} for the host wrappers that already `await` it.\n */\nexport async function getClient(): Promise<TrUApiClient | null> {\n return getClientSync();\n}\n\n/**\n * Adapt a truapi `ObservableLike` stream into the host's callback-style\n * {@link HostSubscription} (`unsubscribe` + `onInterrupt`). `onNext` fires for\n * each item; the registered `onInterrupt` callback fires when the host ends the\n * subscription server-side — which the generated client surfaces as either\n * `complete` (a host interrupt frame) or `error` (transport close). Shared by\n * the statement-store and preimage adapters, which both expose this shape.\n */\nexport function subscribeWithInterrupt<Item, Reason = never>(\n observable: ObservableLike<Item, Reason>,\n onNext: (item: Item) => void,\n): HostSubscription {\n let interruptCallback: ((reason?: unknown) => void) | undefined;\n const sub = observable.subscribe({\n next: onNext,\n error: (reason) => interruptCallback?.(reason),\n complete: () => interruptCallback?.(),\n });\n return {\n unsubscribe: () => sub.unsubscribe(),\n onInterrupt: (callback) => {\n interruptCallback = callback;\n return () => {\n if (interruptCallback === callback) interruptCallback = undefined;\n };\n },\n };\n}\n\nif (import.meta.vitest) {\n const { test, expect, afterEach } = import.meta.vitest;\n\n afterEach(() => setTruApiClient(null));\n\n // Environment detection and client building are covered by `@parity/truapi`'s\n // own sandbox tests; here we only assert the local glue degrades outside a\n // host container.\n test(\"getClientSync returns null outside a container\", () => {\n expect(getClientSync()).toBeNull();\n });\n\n test(\"getClient resolves null outside a container\", async () => {\n expect(await getClient()).toBeNull();\n });\n\n test(\"setTruApiClient overrides the client and container detection\", async () => {\n const fake = {} as TrUApiClient;\n setTruApiClient(fake);\n expect(getClientSync()).toBe(fake);\n expect(await getClient()).toBe(fake);\n expect(isCorrectEnvironment()).toBe(true);\n\n setTruApiClient(null);\n expect(getClientSync()).toBeNull();\n expect(isCorrectEnvironment()).toBe(false);\n });\n\n test(\"setTruApiClient warns when injecting in a production build\", () => {\n const original = process.env.NODE_ENV;\n const warnings: string[] = [];\n const realWarn = console.warn;\n console.warn = (...args: unknown[]) => void warnings.push(String(args[0]));\n try {\n process.env.NODE_ENV = \"production\";\n setTruApiClient({} as TrUApiClient);\n expect(warnings).toHaveLength(1);\n expect(warnings[0]).toContain(\"production build\");\n\n // Clearing the override must not warn.\n setTruApiClient(null);\n expect(warnings).toHaveLength(1);\n } finally {\n console.warn = realWarn;\n process.env.NODE_ENV = original;\n }\n });\n}\n"]}
|