@parity/product-sdk-host 0.16.0 → 0.18.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/{chain-discovery-nLrPzb3d.d.ts → chain-discovery-BEvu7HaV.d.ts} +42 -5
- package/dist/chunk-PAKEUP2Q.js +89 -0
- package/dist/chunk-PAKEUP2Q.js.map +1 -0
- package/dist/index.d.ts +52 -16
- package/dist/index.js +279 -93
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +9 -2
- package/dist/testing.js +3 -2
- package/dist/testing.js.map +1 -1
- package/package.json +4 -4
- package/src/accounts.ts +327 -101
- package/src/chains.ts +10 -3
- package/src/errors.ts +46 -0
- package/src/index.ts +7 -0
- package/src/papi-provider.ts +370 -37
- package/src/testing.ts +9 -2
- package/src/transport.ts +228 -4
- package/src/truapi.ts +139 -3
- package/dist/chunk-GDXSV7JV.js +0 -50
- package/dist/chunk-GDXSV7JV.js.map +0 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { TrUApiClient, ChainIdentifier, HexString } from '@parity/truapi';
|
|
2
|
+
import { ConnectionStatus } from '@parity/truapi/sandbox';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Access to the in-house TruAPI client (`@parity/truapi`) for the host package.
|
|
5
6
|
*
|
|
6
|
-
* Environment detection
|
|
7
|
-
* `@parity/truapi/sandbox`; this module layers the
|
|
8
|
-
* top — an async {@link getClient} accessor
|
|
9
|
-
*
|
|
7
|
+
* Environment detection, the lazily-built cached client, and the connection-status
|
|
8
|
+
* signal come from `@parity/truapi/sandbox`; this module layers the
|
|
9
|
+
* product-sdk-specific glue on top — an async {@link getClient} accessor,
|
|
10
|
+
* {@link subscribeConnectionStatus}, and {@link subscribeWithInterrupt}, which
|
|
11
|
+
* adapts a truapi stream into the host's {@link HostSubscription} shape.
|
|
10
12
|
*
|
|
11
13
|
* @module
|
|
12
14
|
*/
|
|
@@ -20,6 +22,9 @@ import { TrUApiClient, ChainIdentifier, HexString } from '@parity/truapi';
|
|
|
20
22
|
* Calling this in a production build silently reroutes every host accessor to
|
|
21
23
|
* the injected client, so we warn — it almost always means a `/testing` import
|
|
22
24
|
* leaked into a production path.
|
|
25
|
+
*
|
|
26
|
+
* Injecting or clearing notifies {@link subscribeConnectionStatus} subscribers,
|
|
27
|
+
* so a product's "host lost" path can be exercised by disposing the fake host.
|
|
23
28
|
*/
|
|
24
29
|
declare function setTruApiClient(client: TrUApiClient | null): void;
|
|
25
30
|
/**
|
|
@@ -27,6 +32,38 @@ declare function setTruApiClient(client: TrUApiClient | null): void;
|
|
|
27
32
|
* sandbox heuristic (iframe / webview marker / injected message port).
|
|
28
33
|
*/
|
|
29
34
|
declare function isCorrectEnvironment(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Connection lifecycle of the host channel: `"connecting"` while the client waits
|
|
37
|
+
* for the host, `"connected"` once the channel is established, `"disconnected"`
|
|
38
|
+
* outside a host container or after the channel closes.
|
|
39
|
+
*
|
|
40
|
+
* Not the same concept as `@parity/product-sdk-signer`'s identically-shaped
|
|
41
|
+
* `ConnectionStatus`, which tracks a signer provider rather than the transport.
|
|
42
|
+
*/
|
|
43
|
+
type HostConnectionStatus = ConnectionStatus;
|
|
44
|
+
/**
|
|
45
|
+
* Test-only: push `status` to every {@link subscribeConnectionStatus} subscriber,
|
|
46
|
+
* so a product can exercise its reconnecting / offline UI. The host-side
|
|
47
|
+
* counterpart of `@parity/product-sdk-signer`'s `FakeSignerProvider.emitStatus`.
|
|
48
|
+
* Exposed through `@parity/product-sdk-host/testing`, not the main entry.
|
|
49
|
+
*/
|
|
50
|
+
declare function emitConnectionStatus(status: HostConnectionStatus): void;
|
|
51
|
+
/**
|
|
52
|
+
* Subscribe to host-channel connection status. The callback fires synchronously
|
|
53
|
+
* with the current status and again on every change; the returned function
|
|
54
|
+
* unsubscribes. Repeats of the status you already have are suppressed.
|
|
55
|
+
*
|
|
56
|
+
* This is the **transport** channel. For the host's account-level connection —
|
|
57
|
+
* what drives `@parity/product-sdk-signer`'s `ConnectionStatus` — use
|
|
58
|
+
* `AccountsProvider.subscribeAccountConnectionStatus` instead.
|
|
59
|
+
*
|
|
60
|
+
* Subscribing is not passive: outside an established channel the first subscribe
|
|
61
|
+
* builds the client and provider, so this can be what constructs the transport.
|
|
62
|
+
*
|
|
63
|
+
* Honours the `setTruApiClient` seam — an injected client is connected by
|
|
64
|
+
* definition, and injecting or clearing one notifies live subscribers.
|
|
65
|
+
*/
|
|
66
|
+
declare function subscribeConnectionStatus(callback: (status: HostConnectionStatus) => void): () => void;
|
|
30
67
|
|
|
31
68
|
/**
|
|
32
69
|
* Host chain discovery. Resolves chain roles to genesis hashes against the
|
|
@@ -67,4 +104,4 @@ interface HostChainDiscovery {
|
|
|
67
104
|
*/
|
|
68
105
|
declare function getHostChainInfo(identifiers: readonly HostChainIdentifier[]): Promise<HostChainDiscovery | null>;
|
|
69
106
|
|
|
70
|
-
export { type HostChainDiscovery as H, type HostChainIdentifier as a, getHostChainInfo as g, isCorrectEnvironment as i,
|
|
107
|
+
export { type HostChainDiscovery as H, type HostChainIdentifier as a, type HostConnectionStatus as b, setTruApiClient as c, emitConnectionStatus as e, getHostChainInfo as g, isCorrectEnvironment as i, subscribeConnectionStatus as s };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { isCorrectEnvironment as isCorrectEnvironment$1, subscribeConnectionStatus as subscribeConnectionStatus$1, getClientSync as getClientSync$1 } from '@parity/truapi/sandbox';
|
|
2
|
+
|
|
3
|
+
// src/transport.ts
|
|
4
|
+
var clientOverride = null;
|
|
5
|
+
var localStatusListeners = /* @__PURE__ */ new Set();
|
|
6
|
+
function isProductionBuild() {
|
|
7
|
+
try {
|
|
8
|
+
return process.env.NODE_ENV === "production";
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function setTruApiClient(client) {
|
|
14
|
+
if (client !== null && isProductionBuild()) {
|
|
15
|
+
console.warn(
|
|
16
|
+
"[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."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const wasOverridden = clientOverride !== null;
|
|
20
|
+
clientOverride = client;
|
|
21
|
+
if (wasOverridden !== (client !== null)) {
|
|
22
|
+
notifyLocalStatusListeners(client !== null ? "connected" : "disconnected");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function getClientSync() {
|
|
26
|
+
return clientOverride ?? getClientSync$1();
|
|
27
|
+
}
|
|
28
|
+
function isCorrectEnvironment() {
|
|
29
|
+
return clientOverride !== null || isCorrectEnvironment$1();
|
|
30
|
+
}
|
|
31
|
+
async function getClient() {
|
|
32
|
+
return getClientSync();
|
|
33
|
+
}
|
|
34
|
+
function latchDisconnected(previous, next) {
|
|
35
|
+
return next === "connecting" && previous === "disconnected" ? "disconnected" : next;
|
|
36
|
+
}
|
|
37
|
+
function notifyLocalStatusListeners(status) {
|
|
38
|
+
for (const listener of [...localStatusListeners]) listener(status);
|
|
39
|
+
}
|
|
40
|
+
function emitConnectionStatus(status) {
|
|
41
|
+
if (isProductionBuild()) {
|
|
42
|
+
console.warn(
|
|
43
|
+
"[product-sdk] emitConnectionStatus() was called in a production build. This is a test-only seam from @parity/product-sdk-host/testing; a leaked import will report a fabricated connection status to real subscribers."
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
notifyLocalStatusListeners(status);
|
|
47
|
+
}
|
|
48
|
+
function subscribeConnectionStatus(callback) {
|
|
49
|
+
let last = null;
|
|
50
|
+
const deliver = (status, fromSandbox) => {
|
|
51
|
+
const next = fromSandbox ? latchDisconnected(last, status) : status;
|
|
52
|
+
if (next === last) return;
|
|
53
|
+
last = next;
|
|
54
|
+
callback(next);
|
|
55
|
+
};
|
|
56
|
+
const onLocal = (status) => deliver(status, false);
|
|
57
|
+
localStatusListeners.add(onLocal);
|
|
58
|
+
if (clientOverride !== null) {
|
|
59
|
+
onLocal("connected");
|
|
60
|
+
return () => void localStatusListeners.delete(onLocal);
|
|
61
|
+
}
|
|
62
|
+
const unsubscribeSandbox = subscribeConnectionStatus$1((status) => deliver(status, true));
|
|
63
|
+
return () => {
|
|
64
|
+
localStatusListeners.delete(onLocal);
|
|
65
|
+
unsubscribeSandbox();
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function subscribeWithInterrupt(observable, onNext) {
|
|
69
|
+
let interruptCallback;
|
|
70
|
+
const sub = observable.subscribe({
|
|
71
|
+
next: onNext,
|
|
72
|
+
error: (reason) => interruptCallback?.(reason),
|
|
73
|
+
complete: () => interruptCallback?.()
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
subscriptionId: sub.subscriptionId,
|
|
77
|
+
unsubscribe: () => sub.unsubscribe(),
|
|
78
|
+
onInterrupt: (callback) => {
|
|
79
|
+
interruptCallback = callback;
|
|
80
|
+
return () => {
|
|
81
|
+
if (interruptCallback === callback) interruptCallback = void 0;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { emitConnectionStatus, getClient, isCorrectEnvironment, setTruApiClient, subscribeConnectionStatus, subscribeWithInterrupt };
|
|
88
|
+
//# sourceMappingURL=chunk-PAKEUP2Q.js.map
|
|
89
|
+
//# sourceMappingURL=chunk-PAKEUP2Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transport.ts"],"names":["sandboxGetClientSync","sandboxIsCorrectEnvironment","sandboxSubscribeConnectionStatus"],"mappings":"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","file":"chunk-PAKEUP2Q.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, the lazily-built cached client, and the connection-status\n * signal come from `@parity/truapi/sandbox`; this module layers the\n * product-sdk-specific glue on top — an async {@link getClient} accessor,\n * {@link subscribeConnectionStatus}, and {@link subscribeWithInterrupt}, which\n * 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 type ConnectionStatus,\n getClientSync as sandboxGetClientSync,\n isCorrectEnvironment as sandboxIsCorrectEnvironment,\n subscribeConnectionStatus as sandboxSubscribeConnectionStatus,\n} from \"@parity/truapi/sandbox\";\n\nimport type { HostSubscription } from \"./types.js\";\n\n/** A {@link HostSubscription} carrying the transport-assigned subscription id. */\nexport interface TransportSubscription extends HostSubscription {\n readonly subscriptionId: string;\n}\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\n// Status subscribers registered here rather than only in the sandbox, so that\n// flipping the test seam is an *event*. The sandbox tracks only the client it\n// built itself, so it cannot know an injected client appeared or went away.\nconst localStatusListeners = new Set<(status: HostConnectionStatus) => void>();\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 *\n * Injecting or clearing notifies {@link subscribeConnectionStatus} subscribers,\n * so a product's \"host lost\" path can be exercised by disposing the fake host.\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 const wasOverridden = clientOverride !== null;\n clientOverride = client;\n if (wasOverridden !== (client !== null)) {\n notifyLocalStatusListeners(client !== null ? \"connected\" : \"disconnected\");\n }\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 * Connection lifecycle of the host channel: `\"connecting\"` while the client waits\n * for the host, `\"connected\"` once the channel is established, `\"disconnected\"`\n * outside a host container or after the channel closes.\n *\n * Not the same concept as `@parity/product-sdk-signer`'s identically-shaped\n * `ConnectionStatus`, which tracks a signer provider rather than the transport.\n */\nexport type HostConnectionStatus = ConnectionStatus;\n\n/**\n * Correct one defect in the sandbox's status signal: `@parity/truapi` never clears\n * its cached client when the pipe closes, so a subscriber arriving after a\n * disconnect re-derives `\"connecting\"` from the dead client — and because the\n * sandbox fans every change out to all listeners, that rewrites everyone's state\n * with no way back. Hold `\"disconnected\"` until a real `\"connected\"` arrives.\n *\n * Applies to sandbox-sourced statuses only. A status pushed by the test seam is\n * deliberate and passes through, so a fake host can still drive a reconnect.\n *\n * Outstanding upstream, not tied to the version we happen to be on: `sandbox.js`\n * is byte-identical from 0.7.0 through 0.9.0 (npm latest) and still unfixed on\n * `paritytech/host-rust-core` main, the repo formerly named truapi. Remove once\n * it clears the cached client on close.\n */\nfunction latchDisconnected(\n previous: HostConnectionStatus | null,\n next: HostConnectionStatus,\n): HostConnectionStatus {\n return next === \"connecting\" && previous === \"disconnected\" ? \"disconnected\" : next;\n}\n\nfunction notifyLocalStatusListeners(status: HostConnectionStatus): void {\n // Iterate a snapshot: a listener that unsubscribes itself, or re-enters\n // `setTruApiClient`, must not mutate the set mid-loop.\n for (const listener of [...localStatusListeners]) listener(status);\n}\n\n/**\n * Test-only: push `status` to every {@link subscribeConnectionStatus} subscriber,\n * so a product can exercise its reconnecting / offline UI. The host-side\n * counterpart of `@parity/product-sdk-signer`'s `FakeSignerProvider.emitStatus`.\n * Exposed through `@parity/product-sdk-host/testing`, not the main entry.\n */\nexport function emitConnectionStatus(status: HostConnectionStatus): void {\n if (isProductionBuild()) {\n console.warn(\n \"[product-sdk] emitConnectionStatus() was called in a production build. This is a test-only seam from @parity/product-sdk-host/testing; a leaked import will report a fabricated connection status to real subscribers.\",\n );\n }\n notifyLocalStatusListeners(status);\n}\n\n/**\n * Subscribe to host-channel connection status. The callback fires synchronously\n * with the current status and again on every change; the returned function\n * unsubscribes. Repeats of the status you already have are suppressed.\n *\n * This is the **transport** channel. For the host's account-level connection —\n * what drives `@parity/product-sdk-signer`'s `ConnectionStatus` — use\n * `AccountsProvider.subscribeAccountConnectionStatus` instead.\n *\n * Subscribing is not passive: outside an established channel the first subscribe\n * builds the client and provider, so this can be what constructs the transport.\n *\n * Honours the `setTruApiClient` seam — an injected client is connected by\n * definition, and injecting or clearing one notifies live subscribers.\n */\nexport function subscribeConnectionStatus(\n callback: (status: HostConnectionStatus) => void,\n): () => void {\n let last: HostConnectionStatus | null = null;\n\n // One wrapped callback for both sources, so `last` stays coherent: the seam\n // and the sandbox must not each keep their own idea of what was delivered.\n const deliver = (status: HostConnectionStatus, fromSandbox: boolean): void => {\n const next = fromSandbox ? latchDisconnected(last, status) : status;\n if (next === last) return;\n last = next;\n callback(next);\n };\n\n const onLocal = (status: HostConnectionStatus) => deliver(status, false);\n localStatusListeners.add(onLocal);\n\n if (clientOverride !== null) {\n onLocal(\"connected\");\n return () => void localStatusListeners.delete(onLocal);\n }\n\n const unsubscribeSandbox = sandboxSubscribeConnectionStatus((status) => deliver(status, true));\n return () => {\n localStatusListeners.delete(onLocal);\n unsubscribeSandbox();\n };\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): TransportSubscription {\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 subscriptionId: sub.subscriptionId,\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 test(\"subscribeConnectionStatus reports disconnected outside a container\", () => {\n const statuses: HostConnectionStatus[] = [];\n\n const unsubscribe = subscribeConnectionStatus((status) => statuses.push(status));\n\n expect(statuses).toEqual([\"disconnected\"]);\n unsubscribe();\n });\n\n test(\"subscribeConnectionStatus reports connected for an injected client\", () => {\n setTruApiClient({} as TrUApiClient);\n const statuses: HostConnectionStatus[] = [];\n\n const unsubscribe = subscribeConnectionStatus((status) => statuses.push(status));\n\n // The sandbox only tracks the client it built itself, so without the\n // override branch this would report \"disconnected\" while every other\n // accessor resolved the injected client.\n expect(statuses).toEqual([\"connected\"]);\n unsubscribe();\n });\n\n test(\"disposing the injected client notifies live subscribers\", () => {\n setTruApiClient({} as TrUApiClient);\n const statuses: HostConnectionStatus[] = [];\n const unsubscribe = subscribeConnectionStatus((status) => statuses.push(status));\n\n setTruApiClient(null);\n\n expect(statuses).toEqual([\"connected\", \"disconnected\"]);\n unsubscribe();\n });\n\n test(\"injecting a client notifies subscribers that started without one\", () => {\n const statuses: HostConnectionStatus[] = [];\n const unsubscribe = subscribeConnectionStatus((status) => statuses.push(status));\n\n setTruApiClient({} as TrUApiClient);\n\n expect(statuses).toEqual([\"disconnected\", \"connected\"]);\n unsubscribe();\n });\n\n test(\"unsubscribe stops seam notifications\", () => {\n setTruApiClient({} as TrUApiClient);\n const statuses: HostConnectionStatus[] = [];\n const unsubscribe = subscribeConnectionStatus((status) => statuses.push(status));\n\n unsubscribe();\n setTruApiClient(null);\n\n expect(statuses).toEqual([\"connected\"]);\n expect(localStatusListeners.size).toBe(0);\n });\n\n test(\"emitConnectionStatus drives transitions, including a reconnect\", () => {\n setTruApiClient({} as TrUApiClient);\n const statuses: HostConnectionStatus[] = [];\n const unsubscribe = subscribeConnectionStatus((status) => statuses.push(status));\n\n emitConnectionStatus(\"disconnected\");\n // A seam-pushed \"connecting\" after \"disconnected\" is deliberate, so it must\n // survive the sandbox latch — otherwise no test could drive a reconnect.\n emitConnectionStatus(\"connecting\");\n emitConnectionStatus(\"connected\");\n\n expect(statuses).toEqual([\"connected\", \"disconnected\", \"connecting\", \"connected\"]);\n unsubscribe();\n });\n\n test(\"repeats of the current status are suppressed\", () => {\n setTruApiClient({} as TrUApiClient);\n const statuses: HostConnectionStatus[] = [];\n const unsubscribe = subscribeConnectionStatus((status) => statuses.push(status));\n\n emitConnectionStatus(\"connected\");\n emitConnectionStatus(\"connected\");\n\n expect(statuses).toEqual([\"connected\"]);\n unsubscribe();\n });\n\n test(\"a listener that unsubscribes itself mid-notification is safe\", () => {\n setTruApiClient({} as TrUApiClient);\n const statuses: HostConnectionStatus[] = [];\n const handle: { unsubscribe?: () => void } = {};\n handle.unsubscribe = subscribeConnectionStatus((status) => {\n statuses.push(status);\n handle.unsubscribe?.();\n });\n\n setTruApiClient(null);\n\n expect(statuses).toEqual([\"connected\", \"disconnected\"]);\n expect(localStatusListeners.size).toBe(0);\n });\n\n // The sandbox latch can't be driven through the public surface — it needs a\n // real provider close — so the correction is pinned as a pure function.\n test(\"latchDisconnected holds disconnected through the stale-cache connecting\", () => {\n expect(latchDisconnected(\"disconnected\", \"connecting\")).toBe(\"disconnected\");\n });\n\n test(\"latchDisconnected passes every other transition through\", () => {\n expect(latchDisconnected(null, \"disconnected\")).toBe(\"disconnected\");\n expect(latchDisconnected(null, \"connecting\")).toBe(\"connecting\");\n expect(latchDisconnected(\"connecting\", \"connected\")).toBe(\"connected\");\n expect(latchDisconnected(\"connected\", \"disconnected\")).toBe(\"disconnected\");\n // A genuine reconnect still gets through once the channel re-establishes.\n expect(latchDisconnected(\"connected\", \"connecting\")).toBe(\"connecting\");\n });\n\n test(\"subscribeWithInterrupt preserves the transport subscription id\", () => {\n const observable = {\n subscribe: () => ({\n subscriptionId: \"p:17\",\n unsubscribe: () => {},\n }),\n [Symbol.observable]() {\n return this;\n },\n } as ObservableLike<never>;\n\n const subscription = subscribeWithInterrupt(observable, () => {});\n\n expect(subscription.subscriptionId).toBe(\"p:17\");\n });\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,9 @@ import { SdkError } from '@parity/product-sdk-errors';
|
|
|
5
5
|
export { SdkError, isSdkError } from '@parity/product-sdk-errors';
|
|
6
6
|
import { Result } from '@parity/result';
|
|
7
7
|
export { Result, err, ok } from '@parity/result';
|
|
8
|
-
export { H as HostChainDiscovery, a as HostChainIdentifier, g as getHostChainInfo, i as isInsideContainerSync } from './chain-discovery-
|
|
8
|
+
export { H as HostChainDiscovery, a as HostChainIdentifier, b as HostConnectionStatus, g as getHostChainInfo, i as isInsideContainerSync, s as subscribeConnectionStatus } from './chain-discovery-BEvu7HaV.js';
|
|
9
9
|
import { ResultAsync as ResultAsync$1 } from 'neverthrow';
|
|
10
|
+
import '@parity/truapi/sandbox';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Public types for the host wrappers.
|
|
@@ -156,12 +157,14 @@ declare function getStatementStore(): Promise<HostStatementStore | null>;
|
|
|
156
157
|
* chain-specific endpoints used by multiple packages.
|
|
157
158
|
*/
|
|
158
159
|
/**
|
|
159
|
-
* Bulletin Chain RPC endpoints per network environment. `paseo` (Paseo Next v2)
|
|
160
|
-
*
|
|
161
|
-
* `kusama` are reserved for when
|
|
160
|
+
* Bulletin Chain RPC endpoints per network environment. `paseo` (Paseo Next v2),
|
|
161
|
+
* `previewnet` (zombienet, a step ahead of paseo), and `devnet` (public Paseo
|
|
162
|
+
* testnet) are populated today; `polkadot` and `kusama` are reserved for when
|
|
163
|
+
* those Bulletin deployments go live.
|
|
162
164
|
*/
|
|
163
165
|
declare const BULLETIN_RPCS: {
|
|
164
166
|
readonly paseo: readonly ["wss://paseo-bulletin-next-rpc.polkadot.io"];
|
|
167
|
+
readonly previewnet: readonly ["wss://previewnet.substrate.dev/bulletin"];
|
|
165
168
|
readonly devnet: readonly ["wss://bulletin-paseo.tservices.es:8443"];
|
|
166
169
|
readonly polkadot: string[];
|
|
167
170
|
readonly kusama: string[];
|
|
@@ -247,6 +250,29 @@ declare class HostCallFailedError extends HostError {
|
|
|
247
250
|
readonly payload: HostErrorPayload;
|
|
248
251
|
constructor(label: string, payload: HostErrorPayload);
|
|
249
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* A host call could not be processed to completion: the `ResultAsync` the
|
|
255
|
+
* truapi client returns rejected instead of resolving to an ok/err. The usual
|
|
256
|
+
* cause is a response the client's SCALE codec can't decode (a
|
|
257
|
+
* `RangeError: Offset is outside the bounds of the DataView`) because the host
|
|
258
|
+
* and the `@parity/truapi` version the product is built against disagree on the
|
|
259
|
+
* wire shape of that call — a protocol-version skew. A host channel that closed
|
|
260
|
+
* mid-call looks identical from here, so this does not assert the skew; the
|
|
261
|
+
* real error is preserved on {@link cause}.
|
|
262
|
+
*
|
|
263
|
+
* The truapi client catches the decode throw in its message handler and turns
|
|
264
|
+
* it into a promise rejection, then wraps the call with
|
|
265
|
+
* `ResultAsync.fromSafePromise`, which installs no rejection handler — so the
|
|
266
|
+
* rejection escapes the `Result` channel rather than landing on its err side.
|
|
267
|
+
* Without this boundary that surfaces as a raw `RangeError` with a stack naming
|
|
268
|
+
* neither the call nor the cause. This names the call, so a bug report has
|
|
269
|
+
* somewhere to start.
|
|
270
|
+
*/
|
|
271
|
+
declare class HostResponseDecodeError extends HostError {
|
|
272
|
+
/** The host-API call whose response failed to decode, e.g. `"createRingVRFProof"`. */
|
|
273
|
+
readonly call: string;
|
|
274
|
+
constructor(call: string, cause: unknown);
|
|
275
|
+
}
|
|
250
276
|
/** Check whether a value is any {@link HostError}. */
|
|
251
277
|
declare function isHostError(error: unknown): error is HostError;
|
|
252
278
|
|
|
@@ -492,6 +518,13 @@ type VrfTranscriptItem = {
|
|
|
492
518
|
type VrfSignature = {
|
|
493
519
|
[K in keyof VrfSignature$1]: Uint8Array;
|
|
494
520
|
};
|
|
521
|
+
/**
|
|
522
|
+
* A call's declared `Err` channel, plus {@link HostResponseDecodeError}: any
|
|
523
|
+
* host reply can fail to decode if the host and the product's `@parity/truapi`
|
|
524
|
+
* client are on different protocol versions, so every decoded call can surface
|
|
525
|
+
* it in addition to its own typed errors.
|
|
526
|
+
*/
|
|
527
|
+
type WithDecodeError<E> = E | HostResponseDecodeError;
|
|
495
528
|
/**
|
|
496
529
|
* Accounts provider handle, backed by `truApi.account.*` / `truApi.signing.*`.
|
|
497
530
|
* Surfaces the user's wallet accounts, app-scoped product accounts, Ring VRF,
|
|
@@ -500,14 +533,17 @@ type VrfSignature = {
|
|
|
500
533
|
* Lookup methods return a neverthrow `ResultAsync` (use `.match(ok, err)`);
|
|
501
534
|
* the signer factories return a synchronous PAPI `PolkadotSigner`. The `err`
|
|
502
535
|
* channel carries truapi's canonical `CallErrorValue` envelope around the
|
|
503
|
-
* per-call versioned domain error, exactly as the generated client returns it
|
|
536
|
+
* per-call versioned domain error, exactly as the generated client returns it,
|
|
537
|
+
* plus a {@link HostResponseDecodeError} for the case where the host's reply
|
|
538
|
+
* cannot be decoded at all (a host/client protocol-version skew) — see
|
|
539
|
+
* {@link WithDecodeError}.
|
|
504
540
|
*/
|
|
505
541
|
interface AccountsProvider {
|
|
506
542
|
getUserId(): ResultAsync$1<{
|
|
507
543
|
primaryUsername: string;
|
|
508
|
-
}, scale.CallErrorValue<VersionedHostGetUserIdError
|
|
509
|
-
requestLogin(reason?: string): ResultAsync$1<HostRequestLoginResponse, scale.CallErrorValue<VersionedHostRequestLoginError
|
|
510
|
-
getProductAccount(dotNsIdentifier: string, derivationIndex?: number): ResultAsync$1<ProductAccount, scale.CallErrorValue<VersionedHostAccountGetError
|
|
544
|
+
}, WithDecodeError<scale.CallErrorValue<VersionedHostGetUserIdError>>>;
|
|
545
|
+
requestLogin(reason?: string): ResultAsync$1<HostRequestLoginResponse, WithDecodeError<scale.CallErrorValue<VersionedHostRequestLoginError>>>;
|
|
546
|
+
getProductAccount(dotNsIdentifier: string, derivationIndex?: number): ResultAsync$1<ProductAccount, WithDecodeError<scale.CallErrorValue<VersionedHostAccountGetError>>>;
|
|
511
547
|
/**
|
|
512
548
|
* Register a ring-VRF key owned by the calling product.
|
|
513
549
|
*
|
|
@@ -517,17 +553,17 @@ interface AccountsProvider {
|
|
|
517
553
|
* Registration returns the key's public key. Call {@link listRingVrfKeys}
|
|
518
554
|
* afterward to obtain the opaque handle required by alias and proof calls.
|
|
519
555
|
*/
|
|
520
|
-
registerRingVrfKey(index: number, ring: RingLocation): ResultAsync$1<RingVrfPublicKey, scale.CallErrorValue<VersionedHostAccountRegisterRingVrfKeyError
|
|
556
|
+
registerRingVrfKey(index: number, ring: RingLocation): ResultAsync$1<RingVrfPublicKey, WithDecodeError<scale.CallErrorValue<VersionedHostAccountRegisterRingVrfKeyError>>>;
|
|
521
557
|
/** List an owner's registered ring-VRF keys. */
|
|
522
|
-
listRingVrfKeys(owner: string, disclosure?: RingVrfKeyDisclosure): ResultAsync$1<RegisteredRingVrfKey[], scale.CallErrorValue<VersionedHostAccountListRingVrfKeysError
|
|
558
|
+
listRingVrfKeys(owner: string, disclosure?: RingVrfKeyDisclosure): ResultAsync$1<RegisteredRingVrfKey[], WithDecodeError<scale.CallErrorValue<VersionedHostAccountListRingVrfKeysError>>>;
|
|
523
559
|
/** Derive a contextual alias with an explicitly registered ring-VRF key. */
|
|
524
|
-
getProductAccountAlias(keyHandle: RingVrfKeyHandle, context: ProductProofContext, location: RingLocation): ResultAsync$1<ContextualAlias, scale.CallErrorValue<VersionedHostAccountGetAliasError
|
|
525
|
-
getLegacyAccounts(): ResultAsync$1<HostAccount[], scale.CallErrorValue<VersionedHostGetLegacyAccountsError
|
|
560
|
+
getProductAccountAlias(keyHandle: RingVrfKeyHandle, context: ProductProofContext, location: RingLocation): ResultAsync$1<ContextualAlias, WithDecodeError<scale.CallErrorValue<VersionedHostAccountGetAliasError>>>;
|
|
561
|
+
getLegacyAccounts(): ResultAsync$1<HostAccount[], WithDecodeError<scale.CallErrorValue<VersionedHostGetLegacyAccountsError>>>;
|
|
526
562
|
/**
|
|
527
563
|
* Generate a Ring VRF proof with an explicitly registered key, binding
|
|
528
564
|
* `message` to the product-scoped `context`.
|
|
529
565
|
*/
|
|
530
|
-
createRingVRFProof(keyHandle: RingVrfKeyHandle, context: ProductProofContext, location: RingLocation, message: Uint8Array): ResultAsync$1<RingVRFProof, scale.CallErrorValue<VersionedHostAccountCreateProofError
|
|
566
|
+
createRingVRFProof(keyHandle: RingVrfKeyHandle, context: ProductProofContext, location: RingLocation, message: Uint8Array): ResultAsync$1<RingVRFProof, WithDecodeError<scale.CallErrorValue<VersionedHostAccountCreateProofError>>>;
|
|
531
567
|
/**
|
|
532
568
|
* Sign `message` directly with an explicitly registered ring-VRF key.
|
|
533
569
|
*
|
|
@@ -535,7 +571,7 @@ interface AccountsProvider {
|
|
|
535
571
|
* membership; it is the plain signature under the member key, for
|
|
536
572
|
* protocols that carry their own proof.
|
|
537
573
|
*/
|
|
538
|
-
ringVrfSign(keyHandle: RingVrfKeyHandle, message: Uint8Array): ResultAsync$1<Uint8Array, scale.CallErrorValue<VersionedHostAccountRingVrfSignError
|
|
574
|
+
ringVrfSign(keyHandle: RingVrfKeyHandle, message: Uint8Array): ResultAsync$1<Uint8Array, WithDecodeError<scale.CallErrorValue<VersionedHostAccountRingVrfSignError>>>;
|
|
539
575
|
/**
|
|
540
576
|
* Produce an sr25519 VRF signature from a product account (RFC-0023).
|
|
541
577
|
*
|
|
@@ -555,7 +591,7 @@ interface AccountsProvider {
|
|
|
555
591
|
*
|
|
556
592
|
* Hosts predating the call reject it through the error channel.
|
|
557
593
|
*/
|
|
558
|
-
signVrf(account: ProductAccountLookup, transcriptLabel: Uint8Array, items: VrfTranscriptItem[]): ResultAsync$1<VrfSignature, scale.CallErrorValue<VersionedHostAccountSignVrfError
|
|
594
|
+
signVrf(account: ProductAccountLookup, transcriptLabel: Uint8Array, items: VrfTranscriptItem[]): ResultAsync$1<VrfSignature, WithDecodeError<scale.CallErrorValue<VersionedHostAccountSignVrfError>>>;
|
|
559
595
|
/**
|
|
560
596
|
* Build a `PolkadotSigner` for a product account. Signing routes through the
|
|
561
597
|
* host's `createTransaction` path: the host decodes the metadata and forwards
|
|
@@ -1107,4 +1143,4 @@ declare function broadcastTransaction(genesisHash: HexString, transaction: HexSt
|
|
|
1107
1143
|
*/
|
|
1108
1144
|
declare function stopTransaction(genesisHash: HexString, operationId: string): Promise<Result<void, HostError>>;
|
|
1109
1145
|
|
|
1110
|
-
export { type AccountsProvider, BULLETIN_RPCS, ChainNotSupportedError, type ChainProperties, type ChainSpec, type ChatBotRegistrationResult, type ChatManager, type ChatReceivedAction, type ChatRoomRegistrationResult, type ContextualAlias, DEFAULT_BULLETIN_ENDPOINT, type DevicePermissionKind, type Feature, type HostAccount, HostCallFailedError, HostError, type HostErrorPayload, type HostLocalStorage, type HostStatementStore, type HostSubscription, HostUnavailableError, type NotificationManager, type PaymentManager, type PreimageManager, type ProductAccount, type ProductAccountLookup, type PushNotificationInput, type RegisteredRingVrfKey, type RemotePermissionItem, type ResultAsync, type RingVRFProof, type RingVrfKeyHandle, type RingVrfPublicKey, type StatementTopicFilter, type StatementsPage, type ThemeMode, type ThemeProvider, type TruApi, type VrfSignature, type VrfTranscriptItem, broadcastTransaction, createHostLocalStorage, createHostPreimageManager, createProofAuthorized, deriveEntropy, featureSupported, findRingVrfKeyHandle, formatHostError, fromHex, getAccountsProvider, getChainSpec, getChatManager, getHostLocalStorage, getHostProvider, getNotificationManager, getPaymentManager, getPreimageManager, getStatementStore, getThemeProvider, getTruApi, isChainSupported, isHostError, isInsideContainer, navigateTo, requestDevicePermission, requestPermission, requestResourceAllocation, stopTransaction, toHex };
|
|
1146
|
+
export { type AccountsProvider, BULLETIN_RPCS, ChainNotSupportedError, type ChainProperties, type ChainSpec, type ChatBotRegistrationResult, type ChatManager, type ChatReceivedAction, type ChatRoomRegistrationResult, type ContextualAlias, DEFAULT_BULLETIN_ENDPOINT, type DevicePermissionKind, type Feature, type HostAccount, HostCallFailedError, HostError, type HostErrorPayload, type HostLocalStorage, HostResponseDecodeError, type HostStatementStore, type HostSubscription, HostUnavailableError, type NotificationManager, type PaymentManager, type PreimageManager, type ProductAccount, type ProductAccountLookup, type PushNotificationInput, type RegisteredRingVrfKey, type RemotePermissionItem, type ResultAsync, type RingVRFProof, type RingVrfKeyHandle, type RingVrfPublicKey, type StatementTopicFilter, type StatementsPage, type ThemeMode, type ThemeProvider, type TruApi, type VrfSignature, type VrfTranscriptItem, type WithDecodeError, broadcastTransaction, createHostLocalStorage, createHostPreimageManager, createProofAuthorized, deriveEntropy, featureSupported, findRingVrfKeyHandle, formatHostError, fromHex, getAccountsProvider, getChainSpec, getChatManager, getHostLocalStorage, getHostProvider, getNotificationManager, getPaymentManager, getPreimageManager, getStatementStore, getThemeProvider, getTruApi, isChainSupported, isHostError, isInsideContainer, navigateTo, requestDevicePermission, requestPermission, requestResourceAllocation, stopTransaction, toHex };
|