@parity/product-sdk-host 0.10.2 → 0.11.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.
@@ -0,0 +1,127 @@
1
+ // Copyright 2026 Parity Technologies (UK) Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Higher-level wrapper for the host's deep-link navigation.
5
+ *
6
+ * `hostApi.navigateTo` is reachable via {@link getTruApi}, but consumers have
7
+ * to wrap the URL in the versioned envelope (`enumValue("v1", ...)`) and
8
+ * unwrap the neverthrow `ResultAsync` themselves. {@link navigateTo} collapses
9
+ * that to a throw-on-error Promise that matches the shape of
10
+ * {@link requestPermission} and {@link deriveEntropy}.
11
+ *
12
+ * @module
13
+ */
14
+
15
+ import { createLogger } from "@parity/product-sdk-logger";
16
+
17
+ import { enumValue, formatHostError, getTruApi } from "./truapi.js";
18
+
19
+ const log = createLogger("host:navigation");
20
+
21
+ /**
22
+ * Ask the host to navigate to a URL (deep link or external link).
23
+ *
24
+ * Builds the `v1` envelope, calls `hostApi.navigateTo`, and unwraps the
25
+ * response. The host resolves the destination itself — a `dot`-suffixed
26
+ * deep link (e.g. `"https://search.dot"`) routes to another app/route inside
27
+ * the container, an `https://` URL opens externally.
28
+ *
29
+ * @param url - The URL to navigate to.
30
+ * @throws If the host is unavailable, denies the navigation
31
+ * (`NavigateToErr::PermissionDenied`), or fails for any other reason
32
+ * (`NavigateToErr::Unknown`).
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * import { navigateTo } from "@parity/product-sdk-host";
37
+ *
38
+ * await navigateTo("https://search.dot");
39
+ * ```
40
+ */
41
+ export async function navigateTo(url: string): Promise<void> {
42
+ const truApi = await getTruApi();
43
+ if (!truApi) {
44
+ throw new Error("navigateTo: TruAPI unavailable");
45
+ }
46
+ log.debug("navigateTo", { url });
47
+
48
+ // `.match()` because the host returns a neverthrow ResultAsync, not a Promise.
49
+ await truApi.navigateTo(enumValue("v1", url)).match(
50
+ (_envelope: { tag: "v1"; value: undefined }) => undefined,
51
+ (err: unknown) => {
52
+ throw new Error(`navigateTo failed: ${formatHostError(err)}`, { cause: err });
53
+ },
54
+ );
55
+ }
56
+
57
+ if (import.meta.vitest) {
58
+ const { test, expect, describe, vi } = import.meta.vitest;
59
+
60
+ async function withMockedTruApi<T>(
61
+ bridge: { navigateTo?: (req: unknown) => unknown } | null,
62
+ fn: (mod: typeof import("./navigation.js")) => Promise<T>,
63
+ ): Promise<T> {
64
+ vi.resetModules();
65
+ vi.doMock("./truapi.js", async (importOriginal) => {
66
+ const original = await importOriginal<typeof import("./truapi.js")>();
67
+ return {
68
+ ...original,
69
+ getTruApi: async () => bridge,
70
+ enumValue: (version: string, value: unknown) => ({ tag: version, value }),
71
+ };
72
+ });
73
+ try {
74
+ const mod = await import("./navigation.js");
75
+ return await fn(mod);
76
+ } finally {
77
+ vi.doUnmock("./truapi.js");
78
+ vi.resetModules();
79
+ }
80
+ }
81
+
82
+ describe("navigateTo", () => {
83
+ test("throws when TruAPI is unavailable", async () => {
84
+ await withMockedTruApi(null, async (mod) => {
85
+ await expect(mod.navigateTo("https://search.dot")).rejects.toThrow(
86
+ /TruAPI unavailable/,
87
+ );
88
+ });
89
+ });
90
+
91
+ test("resolves on the v1 success envelope", async () => {
92
+ await withMockedTruApi(
93
+ {
94
+ navigateTo: vi.fn().mockReturnValue({
95
+ match: async (onOk: (v: unknown) => unknown) =>
96
+ onOk({ tag: "v1", value: undefined }),
97
+ }),
98
+ },
99
+ async (mod) => {
100
+ await expect(mod.navigateTo("https://search.dot")).resolves.toBeUndefined();
101
+ },
102
+ );
103
+ });
104
+
105
+ test("wraps host errors with a diagnostic message", async () => {
106
+ await withMockedTruApi(
107
+ {
108
+ navigateTo: vi.fn().mockReturnValue({
109
+ match: async (
110
+ _onOk: (v: unknown) => unknown,
111
+ onErr: (e: unknown) => unknown,
112
+ ) =>
113
+ onErr({
114
+ tag: "v1",
115
+ value: { name: "NavigateToErr::PermissionDenied", message: "no" },
116
+ }),
117
+ }),
118
+ },
119
+ async (mod) => {
120
+ await expect(mod.navigateTo("https://search.dot")).rejects.toThrow(
121
+ /navigateTo failed: NavigateToErr::PermissionDenied: no/,
122
+ );
123
+ },
124
+ );
125
+ });
126
+ });
127
+ }