@parity/product-sdk-host 0.10.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +681 -462
- package/dist/index.js +890 -219
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/accounts.ts +544 -0
- package/src/chain-spec.ts +272 -0
- package/src/chain-transaction.ts +241 -0
- package/src/chat.ts +81 -85
- package/src/container.ts +211 -246
- package/src/entropy.ts +63 -25
- package/src/errors.ts +198 -0
- package/src/features.ts +172 -0
- package/src/index.ts +47 -22
- package/src/navigation.ts +128 -0
- package/src/notifications.ts +59 -69
- package/src/papi-provider.ts +673 -0
- package/src/payments.ts +77 -61
- package/src/permissions.ts +107 -105
- package/src/result.ts +56 -0
- package/src/theme.ts +35 -63
- package/src/transport.ts +71 -0
- package/src/truapi.ts +166 -409
- package/src/types.ts +69 -61
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
* `truApi.system.navigateTo` returns a neverthrow `ResultAsync`; consumers
|
|
7
|
+
* still have to unwrap it themselves. {@link navigateTo} collapses that to a
|
|
8
|
+
* `Result<void, HostError>`-returning Promise.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createLogger } from "@parity/product-sdk-logger";
|
|
14
|
+
|
|
15
|
+
import { type HostError, HostUnavailableError } from "./errors.js";
|
|
16
|
+
import { type Result, err } from "./result.js";
|
|
17
|
+
import { getTruApi, mapHostResult } 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
|
+
* Calls `truApi.system.navigateTo` and unwraps the response. The host resolves
|
|
25
|
+
* the destination itself — a `dot`-suffixed deep link (e.g.
|
|
26
|
+
* `"https://search.dot"`) routes to another app/route inside the container, an
|
|
27
|
+
* `https://` URL opens externally.
|
|
28
|
+
*
|
|
29
|
+
* @param url - The URL to navigate to.
|
|
30
|
+
* @returns `ok` on success, or `err`: {@link HostUnavailableError} if the host
|
|
31
|
+
* is unavailable, or {@link HostCallFailedError} if it denies the navigation
|
|
32
|
+
* (`NavigateToErr::PermissionDenied`) or fails otherwise (`NavigateToErr::Unknown`).
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { navigateTo } from "@parity/product-sdk-host";
|
|
37
|
+
*
|
|
38
|
+
* const r = await navigateTo("https://search.dot");
|
|
39
|
+
* if (!r.ok) handle(r.error);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export async function navigateTo(url: string): Promise<Result<void, HostError>> {
|
|
43
|
+
const truApi = await getTruApi();
|
|
44
|
+
if (!truApi) {
|
|
45
|
+
return err(new HostUnavailableError("navigateTo: TruAPI unavailable"));
|
|
46
|
+
}
|
|
47
|
+
log.debug("navigateTo", { url });
|
|
48
|
+
|
|
49
|
+
return mapHostResult(truApi.system.navigateTo({ url }), () => undefined, "navigateTo failed");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (import.meta.vitest) {
|
|
53
|
+
const { test, expect, describe, vi } = import.meta.vitest;
|
|
54
|
+
|
|
55
|
+
async function withMockedTruApi<T>(
|
|
56
|
+
bridge: { system?: { navigateTo?: (req: unknown) => unknown } } | null,
|
|
57
|
+
fn: (mod: typeof import("./navigation.js")) => Promise<T>,
|
|
58
|
+
): Promise<T> {
|
|
59
|
+
vi.resetModules();
|
|
60
|
+
vi.doMock("./truapi.js", async (importOriginal) => {
|
|
61
|
+
const original = await importOriginal<typeof import("./truapi.js")>();
|
|
62
|
+
return {
|
|
63
|
+
...original,
|
|
64
|
+
getTruApi: async () => bridge,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
const mod = await import("./navigation.js");
|
|
69
|
+
return await fn(mod);
|
|
70
|
+
} finally {
|
|
71
|
+
vi.doUnmock("./truapi.js");
|
|
72
|
+
vi.resetModules();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe("navigateTo", () => {
|
|
77
|
+
test("returns err(HostUnavailableError) when TruAPI is unavailable", async () => {
|
|
78
|
+
await withMockedTruApi(null, async (mod) => {
|
|
79
|
+
const result = await mod.navigateTo("https://search.dot");
|
|
80
|
+
expect(result.ok).toBe(false);
|
|
81
|
+
if (!result.ok) {
|
|
82
|
+
expect(result.error.name).toBe("HostUnavailableError");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("returns ok on success", async () => {
|
|
88
|
+
await withMockedTruApi(
|
|
89
|
+
{
|
|
90
|
+
system: {
|
|
91
|
+
navigateTo: vi.fn().mockReturnValue({
|
|
92
|
+
match: async (onOk: (v: unknown) => unknown) => onOk(undefined),
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
async (mod) => {
|
|
97
|
+
expect(await mod.navigateTo("https://search.dot")).toEqual({
|
|
98
|
+
ok: true,
|
|
99
|
+
value: undefined,
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("wraps host errors in err(HostCallFailedError) with a diagnostic message", async () => {
|
|
106
|
+
await withMockedTruApi(
|
|
107
|
+
{
|
|
108
|
+
system: {
|
|
109
|
+
navigateTo: vi.fn().mockReturnValue({
|
|
110
|
+
match: async (
|
|
111
|
+
_onOk: (v: unknown) => unknown,
|
|
112
|
+
onErr: (e: unknown) => unknown,
|
|
113
|
+
) => onErr({ tag: "PermissionDenied" }),
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
async (mod) => {
|
|
118
|
+
const result = await mod.navigateTo("https://search.dot");
|
|
119
|
+
expect(result.ok).toBe(false);
|
|
120
|
+
if (!result.ok) {
|
|
121
|
+
expect(result.error.name).toBe("HostCallFailedError");
|
|
122
|
+
expect(result.error.message).toMatch(/navigateTo failed: PermissionDenied/);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
package/src/notifications.ts
CHANGED
|
@@ -1,80 +1,86 @@
|
|
|
1
1
|
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
/**
|
|
4
|
-
* Wrapper for the host's scheduled push-notification surface
|
|
4
|
+
* Wrapper for the host's scheduled push-notification surface (RFC-0019),
|
|
5
|
+
* backed by `truApi.notifications.*`.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* {@link getPreimageManager}, and {@link getHostLocalStorage}.
|
|
11
|
-
*
|
|
12
|
-
* Returns the shared `notificationManager` singleton from
|
|
13
|
-
* `@novasamatech/host-api-wrapper` (not a fresh `createNotificationManager()`
|
|
14
|
-
* instance) so callers share one wrapper + hostApi closure across the app.
|
|
15
|
-
*
|
|
16
|
-
* {@link PushNotificationError} is re-exported from `@novasamatech/host-api`
|
|
17
|
-
* so consumers can branch on `err instanceof
|
|
18
|
-
* PushNotificationError.ScheduleLimitReached` (the host's pending-notification
|
|
19
|
-
* cap) without importing the novasama packages directly.
|
|
7
|
+
* `getNotificationManager()` returns a handle exposing `push(input)` (resolves
|
|
8
|
+
* to a {@link NotificationId}) and `cancel(id)`, matching the singleton
|
|
9
|
+
* pattern already used by {@link getPaymentManager}, {@link getPreimageManager},
|
|
10
|
+
* and {@link getHostLocalStorage}.
|
|
20
11
|
*
|
|
21
12
|
* @module
|
|
22
13
|
*/
|
|
23
14
|
|
|
24
|
-
import {
|
|
15
|
+
import type { HostPushNotificationRequest, NotificationId, TrUApiClient } from "@parity/truapi";
|
|
25
16
|
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
const log = createLogger("host:notifications");
|
|
17
|
+
import { getClient } from "./transport.js";
|
|
18
|
+
import { unwrapHostResult } from "./truapi.js";
|
|
29
19
|
|
|
30
20
|
/**
|
|
31
21
|
* Error variants the host raises when scheduling a push notification.
|
|
32
22
|
*
|
|
33
|
-
* A
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* `
|
|
23
|
+
* A `{ tag }` tagged union re-exported from `@parity/truapi`:
|
|
24
|
+
* `{ tag: "ScheduleLimitReached" }` (the host-wide pending-notification cap) or
|
|
25
|
+
* `{ tag: "Unknown"; value: { reason } }`. {@link NotificationManager.push} /
|
|
26
|
+
* {@link NotificationManager.cancel} reject with an `Error` whose `cause`
|
|
27
|
+
* carries this value, so branch on it — e.g.
|
|
28
|
+
* `(err as Error).cause?.tag === "ScheduleLimitReached"`.
|
|
39
29
|
*/
|
|
40
|
-
export { PushNotificationError } from "@
|
|
30
|
+
export type { HostPushNotificationError as PushNotificationError } from "@parity/truapi";
|
|
41
31
|
|
|
42
32
|
/**
|
|
43
|
-
* Host
|
|
44
|
-
* {@link
|
|
45
|
-
*
|
|
46
|
-
* Type identical to `notificationManager` from
|
|
47
|
-
* `@novasamatech/host-api-wrapper`.
|
|
33
|
+
* Host-assigned id for a scheduled notification — pass to
|
|
34
|
+
* {@link NotificationManager.cancel}. Re-exported from `@parity/truapi`.
|
|
48
35
|
*/
|
|
49
|
-
export type
|
|
36
|
+
export type { NotificationId };
|
|
50
37
|
|
|
51
38
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
39
|
+
* Push payload: `text`, an optional `deeplink`, and an optional `scheduledAt`
|
|
40
|
+
* (Unix timestamp in milliseconds; omit for immediate delivery). Re-exported
|
|
41
|
+
* from the truapi wire request type so the shape stays in lockstep with the
|
|
42
|
+
* protocol.
|
|
55
43
|
*/
|
|
56
|
-
export type
|
|
44
|
+
export type PushNotificationInput = HostPushNotificationRequest;
|
|
57
45
|
|
|
58
46
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* `push` parameter so the shape stays in lockstep with upstream.
|
|
47
|
+
* Host notification manager handle. Exposes `push(input)` (resolves to a
|
|
48
|
+
* {@link NotificationId}) and `cancel(id)`.
|
|
62
49
|
*/
|
|
63
|
-
export
|
|
50
|
+
export interface NotificationManager {
|
|
51
|
+
push(input: PushNotificationInput): Promise<NotificationId>;
|
|
52
|
+
cancel(id: NotificationId): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Build a {@link NotificationManager} over a TruAPI client's `notifications` domain. */
|
|
56
|
+
function adaptNotificationManager(client: TrUApiClient): NotificationManager {
|
|
57
|
+
const notifications = client.notifications;
|
|
58
|
+
return {
|
|
59
|
+
async push(input) {
|
|
60
|
+
const response = await unwrapHostResult(
|
|
61
|
+
notifications.sendPushNotification(input),
|
|
62
|
+
"notification push failed",
|
|
63
|
+
);
|
|
64
|
+
return response.id;
|
|
65
|
+
},
|
|
66
|
+
async cancel(id) {
|
|
67
|
+
await unwrapHostResult(
|
|
68
|
+
notifications.cancelPushNotification({ id }),
|
|
69
|
+
"notification cancel failed",
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
64
74
|
|
|
65
75
|
/**
|
|
66
|
-
* Get the host notification manager.
|
|
67
|
-
*
|
|
68
|
-
* Returns the shared `notificationManager` singleton from
|
|
69
|
-
* `@novasamatech/host-api-wrapper`, or `null` if the package is unavailable
|
|
70
|
-
* (running outside a host container or the optional peer dep isn't
|
|
71
|
-
* installed).
|
|
76
|
+
* Get the host notification manager, backed by `truApi.notifications.*`.
|
|
77
|
+
* Returns `null` when running outside a host container.
|
|
72
78
|
*
|
|
73
79
|
* @returns The notification manager, or `null` if unavailable.
|
|
74
80
|
*
|
|
75
81
|
* @example
|
|
76
82
|
* ```ts
|
|
77
|
-
* import { getNotificationManager, PushNotificationError } from "@parity/product-sdk-host";
|
|
83
|
+
* import { getNotificationManager, type PushNotificationError } from "@parity/product-sdk-host";
|
|
78
84
|
*
|
|
79
85
|
* const notifications = await getNotificationManager();
|
|
80
86
|
* if (notifications) {
|
|
@@ -85,7 +91,8 @@ export type PushNotificationInput = Parameters<NotificationManager["push"]>[0];
|
|
|
85
91
|
* });
|
|
86
92
|
* // later: await notifications.cancel(id);
|
|
87
93
|
* } catch (err) {
|
|
88
|
-
*
|
|
94
|
+
* const cause = (err as Error).cause as PushNotificationError | undefined;
|
|
95
|
+
* if (cause?.tag === "ScheduleLimitReached") {
|
|
89
96
|
* // host hit its pending-notification cap — surface to the user
|
|
90
97
|
* }
|
|
91
98
|
* }
|
|
@@ -93,31 +100,14 @@ export type PushNotificationInput = Parameters<NotificationManager["push"]>[0];
|
|
|
93
100
|
* ```
|
|
94
101
|
*/
|
|
95
102
|
export async function getNotificationManager(): Promise<NotificationManager | null> {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return sdk.notificationManager;
|
|
99
|
-
} catch (err) {
|
|
100
|
-
log.debug("getNotificationManager unavailable", err);
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
+
const client = await getClient();
|
|
104
|
+
return client ? adaptNotificationManager(client) : null;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
if (import.meta.vitest) {
|
|
106
108
|
const { test, expect } = import.meta.vitest;
|
|
107
109
|
|
|
108
|
-
test("getNotificationManager returns
|
|
109
|
-
|
|
110
|
-
if (notifications === null) {
|
|
111
|
-
// Acceptable: SDK couldn't load (e.g. peer dep missing in some envs).
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
expect(typeof notifications.push).toBe("function");
|
|
115
|
-
expect(typeof notifications.cancel).toBe("function");
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("PushNotificationError is re-exported with its ScheduleLimitReached variant", async () => {
|
|
119
|
-
const { PushNotificationError } = await import("./notifications.js");
|
|
120
|
-
expect(PushNotificationError).toBeDefined();
|
|
121
|
-
expect(PushNotificationError.ScheduleLimitReached).toBeDefined();
|
|
110
|
+
test("getNotificationManager returns null outside a container", async () => {
|
|
111
|
+
expect(await getNotificationManager()).toBeNull();
|
|
122
112
|
});
|
|
123
113
|
}
|