@parity/product-sdk-signer 0.8.2 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as polkadot_api from 'polkadot-api';
2
2
  import { PolkadotSigner } from 'polkadot-api';
3
3
  import { SS58String } from '@parity/product-sdk-address';
4
- import { AllocatableResource, AllocationOutcome } from '@parity/product-sdk-host';
4
+ import { AllocatableResource, AllocationOutcome, RemotePermission } from '@parity/product-sdk-host';
5
5
 
6
6
  /** Function that unsubscribes a listener when called. */
7
7
  type Unsubscribe = () => void;
@@ -151,10 +151,11 @@ interface SignerManagerOptions {
151
151
  * plus an `AbortSignal` that fires if the user disconnects or
152
152
  * destroys the manager mid-flight.
153
153
  *
154
- * `requestResourceAllocation` throws on failure (matches the
155
- * `@parity/product-sdk-host` export of the same name); errors thrown
156
- * from `onConnect` are logged but do not affect the connected state —
157
- * the next reconnect retries.
154
+ * `requestResourceAllocation` throws on failure (it adapts the
155
+ * `Result`-returning `@parity/product-sdk-host` export of the same name,
156
+ * re-throwing the typed error on the `err` channel); errors thrown from
157
+ * `onConnect` are logged but do not affect the connected state — the next
158
+ * reconnect retries.
158
159
  *
159
160
  * @example
160
161
  * ```ts
@@ -165,7 +166,7 @@ interface SignerManagerOptions {
165
166
  * { tag: "AutoSigning", value: undefined },
166
167
  * ]);
167
168
  * if (signal.aborted) return;
168
- * if (outcomes.some((o) => o.tag !== "Allocated")) {
169
+ * if (outcomes.some((o) => o !== "Allocated")) {
169
170
  * logWarning("partial permissions", outcomes);
170
171
  * }
171
172
  * } catch (cause) {
@@ -186,9 +187,10 @@ interface ConnectContext {
186
187
  */
187
188
  signal: AbortSignal;
188
189
  /**
189
- * Request a batch of host resource allocations. Bound shorthand for
190
- * `requestResourceAllocation` from `@parity/product-sdk-host`
191
- * throws on failure, returns the unwrapped outcomes on success.
190
+ * Request a batch of host resource allocations. Adapts
191
+ * `requestResourceAllocation` from `@parity/product-sdk-host` (which returns
192
+ * a `Result`) to this throwing contract — returns the unwrapped outcomes on
193
+ * success, throws the typed host error on failure.
192
194
  */
193
195
  requestResourceAllocation: (resources: AllocatableResource[]) => Promise<AllocationOutcome[]>;
194
196
  }
@@ -206,7 +208,8 @@ declare class SignerError extends Error {
206
208
  * - The app is loaded outside a Polkadot host container (a regular browser tab
207
209
  * under `npm run dev`, no iframe, no WebView). This is the dominant case
208
210
  * during local development.
209
- * - The optional `@novasamatech/host-api(-wrapper)` peer is not installed.
211
+ * - The host environment is detected but the TruAPI transport can't be reached
212
+ * (no injected message port / unresolvable host origin).
210
213
  *
211
214
  * Branch with `instanceof HostUnavailableError` to surface a "open this app
212
215
  * in a Polkadot host, or pick a dev provider" message to the user.
@@ -274,17 +277,19 @@ interface HostProviderOptions {
274
277
  */
275
278
  dappName?: string;
276
279
  /**
277
- * Custom SDK loader. Defaults to `import("@novasamatech/host-api-wrapper")`.
278
- * Override this for testing or custom SDK setups.
280
+ * Custom accounts-provider loader. Defaults to `@parity/product-sdk-host`'s
281
+ * `getAccountsProvider`, which returns `null` outside a host container.
282
+ * Override for testing or custom host setups.
279
283
  * @internal
280
284
  */
281
- loadSdk?: () => Promise<ProductSdkModule>;
285
+ loadAccountsProvider?: () => Promise<AccountsProvider | null>;
282
286
  /**
283
- * Custom loader for `@novasamatech/host-api` (used to construct the
284
- * `ChainSubmit` permission request). Defaults to dynamic import.
287
+ * Custom `ChainSubmit` permission requester. Defaults to a thin adapter over
288
+ * `@parity/product-sdk-host`'s `requestPermission` that unwraps its `Result`
289
+ * (throwing the typed error on the `err` channel). Override for testing.
285
290
  * @internal
286
291
  */
287
- loadHostApiEnum?: () => Promise<HostApiEnumHelper>;
292
+ requestChainSubmitPermissionFn?: (permission: RemotePermission) => Promise<boolean>;
288
293
  /**
289
294
  * Whether to request the host's `ChainSubmit` permission after a
290
295
  * successful `connect()`. Without this, subsequent signing requests are
@@ -304,7 +309,7 @@ interface HostProviderOptions {
304
309
  * `dotNsIdentifier`, skipping the legacy fetch entirely. For apps
305
310
  * that sign exclusively with a per-dapp derived account.
306
311
  *
307
- * Signing is pinned to `createTransaction` (see PR #96).
312
+ * Signing goes through the host's `createTransaction` path (see PR #96).
308
313
  */
309
314
  productAccount?: {
310
315
  /** App identifier (e.g., `"playground.dot"`). */
@@ -373,9 +378,12 @@ interface NeverthrowResultAsync<T, E> {
373
378
  }
374
379
  /** @internal */
375
380
  interface AccountsProvider {
376
- getLegacyAccountSigner: (account: ProductAccount) => polkadot_api.PolkadotSigner;
381
+ getLegacyAccounts: () => NeverthrowResultAsync<RawAccount[], unknown>;
382
+ getLegacyAccountSigner: (account: {
383
+ publicKey: Uint8Array;
384
+ }) => polkadot_api.PolkadotSigner;
377
385
  getProductAccount: (dotNsIdentifier: string, derivationIndex?: number) => NeverthrowResultAsync<RawAccount, unknown>;
378
- getProductAccountSigner: (account: ProductAccount, signerType?: "signPayload" | "createTransaction") => polkadot_api.PolkadotSigner;
386
+ getProductAccountSigner: (account: ProductAccount) => polkadot_api.PolkadotSigner;
379
387
  getProductAccountAlias: (dotNsIdentifier: string, derivationIndex?: number) => NeverthrowResultAsync<ContextualAlias, unknown>;
380
388
  getUserId: () => NeverthrowResultAsync<{
381
389
  primaryUsername: string;
@@ -385,49 +393,14 @@ interface AccountsProvider {
385
393
  unsubscribe: () => void;
386
394
  } | (() => void);
387
395
  }
388
- /** @internal */
389
- interface HostApiPermissionBridge {
390
- /**
391
- * Request a Host API permission. Product-sdk's `hostApi.permission(...)`
392
- * takes a tagged enum like `enumValue("v1", { tag: "TransactionSubmit" })`
393
- * and returns a neverthrow ResultAsync.
394
- */
395
- permission: (request: unknown) => NeverthrowResultAsync<unknown, unknown>;
396
- }
397
- /** @internal */
398
- interface HostApiEnumHelper {
399
- enumValue: (version: string, value: {
400
- tag: string;
401
- value?: unknown;
402
- }) => unknown;
403
- }
404
- /** @internal */
405
- interface ProductSdkModule {
406
- createAccountsProvider: () => AccountsProvider;
407
- /** Present from product-sdk ≥ 0.6; used to request TransactionSubmit. */
408
- hostApi?: HostApiPermissionBridge;
409
- /**
410
- * `sandboxTransport.isCorrectEnvironment()` returns `false` when the app
411
- * is loaded outside a Polkadot host container (e.g. a regular browser
412
- * tab). Calling `getLegacyAccounts()` / `getProductAccount()` in that
413
- * state surfaces the upstream `Environment is not correct` exception,
414
- * so we pre-check during `connect()` and raise a specific
415
- * {@link HostUnavailableError} with actionable guidance instead.
416
- */
417
- sandboxTransport?: {
418
- isCorrectEnvironment(): boolean;
419
- };
420
- }
421
396
  /**
422
397
  * Provider for the Host API (Polkadot Desktop / Android).
423
398
  *
424
- * Dynamically imports `@novasamatech/host-api-wrapper` at runtime. Apps running
425
- * outside a host container — e.g. a plain browser tab during `npm run dev` —
426
- * resolve to {@link HostUnavailableError} with guidance on what to do (open
427
- * the app inside a Polkadot host or pick a non-host provider). The check uses
428
- * the wrapper's `sandboxTransport.isCorrectEnvironment()` predicate and runs
429
- * before any host RPC call, so the user never sees the upstream
430
- * `Environment is not correct` exception leaking through.
399
+ * Backed by `@parity/product-sdk-host`'s `getAccountsProvider`, which talks to
400
+ * the host over `@parity/truapi`. Apps running outside a host container — e.g.
401
+ * a plain browser tab during `npm run dev` get a `HOST_UNAVAILABLE` error
402
+ * (the provider resolves to `null`) with actionable guidance, surfaced before
403
+ * any host RPC call.
431
404
  *
432
405
  * Supports both non-product accounts (user's external wallets) and product
433
406
  * accounts (app-scoped derived accounts managed by the host).
@@ -437,8 +410,8 @@ declare class HostProvider implements SignerProvider {
437
410
  private readonly ss58Prefix;
438
411
  private readonly maxRetries;
439
412
  private readonly retryDelay;
440
- private readonly loadSdk;
441
- private readonly loadHostApiEnum;
413
+ private readonly loadAccountsProvider;
414
+ private readonly requestChainSubmitPermissionFn;
442
415
  private readonly requestChainSubmitPermission;
443
416
  private readonly productAccount;
444
417
  private readonly dappName;
@@ -467,11 +440,9 @@ declare class HostProvider implements SignerProvider {
467
440
  * Convenience method for when you already have the product account details.
468
441
  * Requires a prior successful `connect()` call.
469
442
  *
470
- * Routing is pinned to `signerType: "createTransaction"` via
471
- * {@link PRODUCT_SIGNER_TYPE} so unknown signed extensions (e.g. `AsPgas`
472
- * on Paseo Next) are forwarded to the host as opaque bytes for
473
- * metadata-driven decoding, rather than going through the PJS bridge
474
- * that throws on unknown extensions.
443
+ * Signing routes through the host's `createTransaction` path, so unknown
444
+ * signed extensions (e.g. `AsPgas` on Paseo Next) are forwarded to the host
445
+ * as opaque bytes for metadata-driven decoding.
475
446
  */
476
447
  getProductAccountSigner(account: ProductAccount): polkadot_api.PolkadotSigner;
477
448
  /**
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from '@parity/product-sdk-logger';
2
- import { requestResourceAllocation, getHostLocalStorage } from '@parity/product-sdk-host';
2
+ import { requestResourceAllocation, getAccountsProvider, requestPermission, getHostLocalStorage } from '@parity/product-sdk-host';
3
3
  import { seedToAccount } from '@parity/product-sdk-keys';
4
4
  import { ss58Encode, deriveH160 } from '@parity/product-sdk-address';
5
5
 
@@ -180,20 +180,21 @@ async function withRetry(fn, options) {
180
180
 
181
181
  // src/providers/host.ts
182
182
  var log2 = createLogger("signer:host");
183
- var PRODUCT_SIGNER_TYPE = "createTransaction";
184
- async function defaultLoadSdk() {
185
- return await import('@novasamatech/host-api-wrapper');
183
+ async function defaultLoadAccountsProvider() {
184
+ return await getAccountsProvider();
186
185
  }
187
- async function defaultLoadHostApiEnum() {
188
- return await import('@novasamatech/host-api');
186
+ async function defaultRequestChainSubmitPermission(permission) {
187
+ const result = await requestPermission(permission);
188
+ if (!result.ok) throw result.error;
189
+ return result.value;
189
190
  }
190
191
  var HostProvider = class {
191
192
  type = "host";
192
193
  ss58Prefix;
193
194
  maxRetries;
194
195
  retryDelay;
195
- loadSdk;
196
- loadHostApiEnum;
196
+ loadAccountsProvider;
197
+ requestChainSubmitPermissionFn;
197
198
  requestChainSubmitPermission;
198
199
  productAccount;
199
200
  dappName;
@@ -205,8 +206,8 @@ var HostProvider = class {
205
206
  this.ss58Prefix = options?.ss58Prefix ?? 42;
206
207
  this.maxRetries = options?.maxRetries ?? 3;
207
208
  this.retryDelay = options?.retryDelay ?? 500;
208
- this.loadSdk = options?.loadSdk ?? defaultLoadSdk;
209
- this.loadHostApiEnum = options?.loadHostApiEnum ?? defaultLoadHostApiEnum;
209
+ this.loadAccountsProvider = options?.loadAccountsProvider ?? defaultLoadAccountsProvider;
210
+ this.requestChainSubmitPermissionFn = options?.requestChainSubmitPermissionFn ?? defaultRequestChainSubmitPermission;
210
211
  this.requestChainSubmitPermission = options?.requestChainSubmitPermission ?? options?.requestTransactionSubmitPermission ?? true;
211
212
  this.productAccount = options?.productAccount;
212
213
  this.dappName = options?.dappName;
@@ -288,10 +289,7 @@ var HostProvider = class {
288
289
  if (!this.accountsProvider) {
289
290
  throw new Error("Host provider is disconnected");
290
291
  }
291
- return this.accountsProvider.getProductAccountSigner(
292
- productAccount,
293
- PRODUCT_SIGNER_TYPE
294
- );
292
+ return this.accountsProvider.getProductAccountSigner(productAccount);
295
293
  }
296
294
  });
297
295
  } catch (cause) {
@@ -309,17 +307,15 @@ var HostProvider = class {
309
307
  * Convenience method for when you already have the product account details.
310
308
  * Requires a prior successful `connect()` call.
311
309
  *
312
- * Routing is pinned to `signerType: "createTransaction"` via
313
- * {@link PRODUCT_SIGNER_TYPE} so unknown signed extensions (e.g. `AsPgas`
314
- * on Paseo Next) are forwarded to the host as opaque bytes for
315
- * metadata-driven decoding, rather than going through the PJS bridge
316
- * that throws on unknown extensions.
310
+ * Signing routes through the host's `createTransaction` path, so unknown
311
+ * signed extensions (e.g. `AsPgas` on Paseo Next) are forwarded to the host
312
+ * as opaque bytes for metadata-driven decoding.
317
313
  */
318
314
  getProductAccountSigner(account) {
319
315
  if (!this.accountsProvider) {
320
316
  throw new Error("Host provider is not connected");
321
317
  }
322
- return this.accountsProvider.getProductAccountSigner(account, PRODUCT_SIGNER_TYPE);
318
+ return this.accountsProvider.getProductAccountSigner(account);
323
319
  }
324
320
  /**
325
321
  * Get a contextual alias for a product account via Ring VRF.
@@ -417,18 +413,18 @@ var HostProvider = class {
417
413
  }
418
414
  // ── Private ──────────────────────────────────────────────────────
419
415
  async tryConnect() {
420
- let sdk;
416
+ let provider;
421
417
  try {
422
- sdk = await this.loadSdk();
418
+ provider = await this.loadAccountsProvider();
423
419
  } catch (cause) {
424
- log2.warn("product-sdk not available", { cause });
420
+ log2.warn("host accounts provider unavailable", { cause });
425
421
  return err(
426
422
  new HostUnavailableError(
427
- cause instanceof Error ? `product-sdk import failed: ${cause.message}` : "product-sdk is not installed"
423
+ cause instanceof Error ? `host accounts provider failed: ${cause.message}` : "host accounts provider is unavailable"
428
424
  )
429
425
  );
430
426
  }
431
- if (sdk.sandboxTransport && !sdk.sandboxTransport.isCorrectEnvironment()) {
427
+ if (!provider) {
432
428
  log2.warn("not inside a host container \u2014 Host API unavailable");
433
429
  return err(
434
430
  new HostUnavailableError(
@@ -436,7 +432,6 @@ var HostProvider = class {
436
432
  )
437
433
  );
438
434
  }
439
- const provider = sdk.createAccountsProvider();
440
435
  this.accountsProvider = provider;
441
436
  let signerAccounts;
442
437
  if (this.productAccount) {
@@ -474,30 +469,20 @@ var HostProvider = class {
474
469
  );
475
470
  signerAccounts = [];
476
471
  }
477
- if (this.requestChainSubmitPermission && sdk.hostApi) {
472
+ if (this.requestChainSubmitPermission) {
478
473
  try {
479
- const hostApiEnum = await this.loadHostApiEnum();
480
- const request = hostApiEnum.enumValue("v1", {
474
+ const granted = await this.requestChainSubmitPermissionFn({
481
475
  tag: "ChainSubmit",
482
476
  value: void 0
483
477
  });
484
- await sdk.hostApi.permission(request).match(
485
- () => {
486
- log2.debug("ChainSubmit permission granted");
487
- },
488
- (error) => {
489
- log2.warn("ChainSubmit permission rejected by host", {
490
- error: formatError(error)
491
- });
492
- }
493
- );
478
+ log2.debug("ChainSubmit permission result", { granted });
494
479
  } catch (cause) {
495
480
  log2.warn("failed to request ChainSubmit permission", { cause });
496
481
  }
497
482
  }
498
483
  log2.info("host connected", { accounts: signerAccounts.length });
499
484
  const sub = provider.subscribeAccountConnectionStatus((status) => {
500
- const mapped = status === "connected" ? "connected" : "disconnected";
485
+ const mapped = String(status).toLowerCase() === "connected" ? "connected" : "disconnected";
501
486
  log2.debug("host status changed", { status: mapped });
502
487
  for (const listener of this.statusListeners) {
503
488
  listener(mapped);
@@ -536,7 +521,11 @@ var HostProvider = class {
536
521
  function formatError(error) {
537
522
  if (!error || typeof error !== "object") return String(error);
538
523
  const e = error;
539
- if (!("tag" in e)) return String(error);
524
+ if (!("tag" in e)) {
525
+ if (typeof e.reason === "string") return e.reason;
526
+ if (typeof e.message === "string") return e.message;
527
+ return String(error);
528
+ }
540
529
  const outerTag = String(e.tag);
541
530
  const inner = e.value;
542
531
  if (inner && typeof inner === "object") {
@@ -1015,7 +1004,14 @@ var SignerManager = class {
1015
1004
  this.onConnectController = controller;
1016
1005
  const ctx = {
1017
1006
  signal: controller.signal,
1018
- requestResourceAllocation
1007
+ // Bridge host's `Result`-returning `requestResourceAllocation` back to
1008
+ // the `ConnectContext` contract (`Promise<AllocationOutcome[]>`, throws
1009
+ // on failure) by unwrapping the typed error on the `err` channel.
1010
+ requestResourceAllocation: async (resources) => {
1011
+ const result = await requestResourceAllocation(resources);
1012
+ if (!result.ok) throw result.error;
1013
+ return result.value;
1014
+ }
1019
1015
  };
1020
1016
  queueMicrotask(async () => {
1021
1017
  try {