@novasamatech/host-container 0.7.9-4 → 0.7.9-6

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.
@@ -1,3 +1,3 @@
1
1
  import type { Provider } from '@novasamatech/host-api';
2
- import type { Container } from './types.js';
3
- export declare function createContainer(provider: Provider): Container;
2
+ import type { Container, CreateContainerOptions } from './types.js';
3
+ export declare function createContainer(provider: Provider, options?: CreateContainerOptions): Container;
@@ -1,6 +1,7 @@
1
- import { ChatBotRegistrationErr, ChatMessagePostingErr, ChatRoomRegistrationErr, CreateProofErr, CreateTransactionErr, DeriveEntropyErr, DevicePermission, GenericError, GetUserIdErr, LoginErr, NavigateToErr, PaymentBalanceErr, PaymentRequestErr, PaymentStatusErr, PaymentTopUpErr, PreimageSubmitErr, RemotePermission, RequestCredentialsErr, ResourceAllocationErr, SigningErr, StatementProofErr, StorageErr, createTransport, enumValue, isEnumVariant, resultErr, resultOk, } from '@novasamatech/host-api';
1
+ import { ChatBotRegistrationErr, ChatMessagePostingErr, ChatRoomRegistrationErr, CreateProofErr, CreateTransactionErr, DeriveEntropyErr, DevicePermission, GenericError, GetUserIdErr, LoginErr, NavigateToErr, PaymentBalanceErr, PaymentRequestErr, PaymentStatusErr, PaymentTopUpErr, PreimageSubmitErr, PushNotificationError, RemotePermission, RequestCredentialsErr, ResourceAllocationErr, SigningErr, StatementProofErr, StorageErr, createTransport, enumValue, isEnumVariant, resultErr, resultOk, } from '@novasamatech/host-api';
2
2
  import { err, errAsync, ok, okAsync } from 'neverthrow';
3
3
  import { createChainConnectionManager } from './chainConnectionManager.js';
4
+ import { emitHostApiDebugMessage, registerHostApiDebugSource } from './debugBus.js';
4
5
  const UNSUPPORTED_MESSAGE_FORMAT_ERROR = 'Unsupported message format';
5
6
  const NOT_IMPLEMENTED = 'Not implemented';
6
7
  function guardVersion(value, tag, error) {
@@ -12,11 +13,21 @@ function guardVersion(value, tag, error) {
12
13
  }
13
14
  return err(error);
14
15
  }
15
- export function createContainer(provider) {
16
+ export function createContainer(provider, options = {}) {
16
17
  const transport = createTransport(provider);
17
18
  if (!transport.isCorrectEnvironment()) {
18
19
  throw new Error('Transport is not available: dapp provider has incorrect environment');
19
20
  }
21
+ const { productId } = options;
22
+ // EXPERIMENTAL: forward every transport-level message into the
23
+ // process-global debug bus, tagged with this container's productId.
24
+ // The forwarder is registered as a bus *source* and only attaches to
25
+ // `transport.onDebugMessage` while the bus has at least one subscriber —
26
+ // otherwise the transport's lazy `Message.dec` path stays cold.
27
+ const unregisterGlobalDebugSource = registerHostApiDebugSource(() => transport.onDebugMessage(({ direction, requestId, payload }) => {
28
+ emitHostApiDebugMessage({ direction, productId, requestId, payload });
29
+ }));
30
+ transport.onDestroy(unregisterGlobalDebugSource);
20
31
  function init() {
21
32
  // init status subscription
22
33
  transport.isReady();
@@ -176,7 +187,8 @@ export function createContainer(provider) {
176
187
  const handleFeatureSupportedSlot = makeNotImplementedSlot('host_feature_supported', () => new GenericError({ reason: NOT_IMPLEMENTED }));
177
188
  const handleDevicePermissionSlot = makeNotImplementedSlot('host_device_permission', () => new GenericError({ reason: NOT_IMPLEMENTED }));
178
189
  const handleRemotePermissionSlot = makeNotImplementedSlot('remote_permission', () => new GenericError({ reason: NOT_IMPLEMENTED }));
179
- const handlePushNotificationSlot = makeDevicePermissionGatedRequestSlot('host_push_notification', 'Notifications', () => new GenericError({ reason: NOT_IMPLEMENTED }));
190
+ const handlePushNotificationSlot = makeDevicePermissionGatedRequestSlot('host_push_notification', 'Notifications', () => new PushNotificationError.Unknown({ reason: NOT_IMPLEMENTED }));
191
+ const handlePushNotificationCancelSlot = makeDevicePermissionGatedRequestSlot('host_push_notification_cancel', 'Notifications', () => new GenericError({ reason: NOT_IMPLEMENTED }));
180
192
  const handleNavigateToSlot = makeNotImplementedSlot('host_navigate_to', () => new NavigateToErr.Unknown({ reason: NOT_IMPLEMENTED }));
181
193
  const handleChatCreateRoomSlot = makeNotImplementedSlot('host_chat_create_room', () => new ChatRoomRegistrationErr.Unknown({ reason: NOT_IMPLEMENTED }));
182
194
  const handleChatBotRegistrationSlot = makeNotImplementedSlot('host_chat_register_bot', () => new ChatBotRegistrationErr.Unknown({ reason: NOT_IMPLEMENTED }));
@@ -211,7 +223,10 @@ export function createContainer(provider) {
211
223
  return handleV1Request(handleRemotePermissionSlot, () => new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR }), handler);
212
224
  },
213
225
  handlePushNotification(handler) {
214
- return handleV1Request(handlePushNotificationSlot, () => new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR }), handler);
226
+ return handleV1Request(handlePushNotificationSlot, () => new PushNotificationError.Unknown({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR }), handler);
227
+ },
228
+ handlePushNotificationCancel(handler) {
229
+ return handleV1Request(handlePushNotificationCancelSlot, () => new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR }), handler);
215
230
  },
216
231
  handleNavigateTo(handler) {
217
232
  return handleV1Request(handleNavigateToSlot, () => new NavigateToErr.Unknown({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR }), handler);
@@ -621,5 +636,10 @@ export function createContainer(provider) {
621
636
  dispose() {
622
637
  transport.destroy();
623
638
  },
639
+ onDebugMessage(callback) {
640
+ return transport.onDebugMessage(({ direction, requestId, payload }) => {
641
+ callback({ direction, productId, requestId, payload });
642
+ });
643
+ },
624
644
  };
625
645
  }
@@ -0,0 +1,18 @@
1
+ import type { HostApiDebugMessageEvent } from './types.js';
2
+ type DebugSource = () => VoidFunction;
3
+ /** @internal Used by `createContainer` to forward its transport's debug events. */
4
+ export declare function emitHostApiDebugMessage(event: HostApiDebugMessageEvent): void;
5
+ /**
6
+ * @internal Register a transport-level forwarder for the global bus.
7
+ * The source is activated only while the bus has at least one subscriber
8
+ * and deactivated when the last one unsubscribes — this preserves the
9
+ * transport's lazy decode path (no `Message.dec` per frame) when nobody
10
+ * is listening downstream.
11
+ */
12
+ export declare function registerHostApiDebugSource(source: DebugSource): VoidFunction;
13
+ /**
14
+ * EXPERIMENTAL. Subscribe to every host ↔ product message across all
15
+ * containers in the current process. Returns an unsubscribe function.
16
+ */
17
+ export declare function onHostApiDebugMessage(callback: (event: HostApiDebugMessageEvent) => void): VoidFunction;
18
+ export {};
@@ -0,0 +1,73 @@
1
+ import { createNanoEvents } from 'nanoevents';
2
+ /**
3
+ * EXPERIMENTAL: process-global bus that aggregates debug events from
4
+ * every container created in this process. Subscribing here gives a
5
+ * single subscriber visibility into all host ↔ product traffic across
6
+ * every active container, annotated with the `productId` passed to
7
+ * `createContainer` (if any).
8
+ */
9
+ const bus = createNanoEvents();
10
+ const sources = new Set();
11
+ const activeSources = new Map();
12
+ let subscriberCount = 0;
13
+ function activateSource(source) {
14
+ if (activeSources.has(source))
15
+ return;
16
+ activeSources.set(source, source());
17
+ }
18
+ function deactivateSource(source) {
19
+ const unsubscribe = activeSources.get(source);
20
+ if (!unsubscribe)
21
+ return;
22
+ activeSources.delete(source);
23
+ unsubscribe();
24
+ }
25
+ /** @internal Used by `createContainer` to forward its transport's debug events. */
26
+ export function emitHostApiDebugMessage(event) {
27
+ bus.emit('message', event);
28
+ }
29
+ /**
30
+ * @internal Register a transport-level forwarder for the global bus.
31
+ * The source is activated only while the bus has at least one subscriber
32
+ * and deactivated when the last one unsubscribes — this preserves the
33
+ * transport's lazy decode path (no `Message.dec` per frame) when nobody
34
+ * is listening downstream.
35
+ */
36
+ export function registerHostApiDebugSource(source) {
37
+ sources.add(source);
38
+ if (subscriberCount > 0)
39
+ activateSource(source);
40
+ let disposed = false;
41
+ return () => {
42
+ if (disposed)
43
+ return;
44
+ disposed = true;
45
+ sources.delete(source);
46
+ deactivateSource(source);
47
+ };
48
+ }
49
+ /**
50
+ * EXPERIMENTAL. Subscribe to every host ↔ product message across all
51
+ * containers in the current process. Returns an unsubscribe function.
52
+ */
53
+ export function onHostApiDebugMessage(callback) {
54
+ const wasZero = subscriberCount === 0;
55
+ subscriberCount++;
56
+ if (wasZero) {
57
+ for (const source of sources)
58
+ activateSource(source);
59
+ }
60
+ const unsubscribe = bus.on('message', callback);
61
+ let disposed = false;
62
+ return () => {
63
+ if (disposed)
64
+ return;
65
+ disposed = true;
66
+ unsubscribe();
67
+ subscriberCount--;
68
+ if (subscriberCount === 0) {
69
+ for (const source of [...activeSources.keys()])
70
+ deactivateSource(source);
71
+ }
72
+ };
73
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export { createWebviewProvider } from './createWebviewProvider.js';
2
2
  export { createIframeProvider } from './createIframeProvider.js';
3
3
  export { createContainer } from './createContainer.js';
4
- export type { Container, ContainerHandlerOf } from './types.js';
4
+ export type { Container, ContainerHandlerOf, CreateContainerOptions, HostApiDebugMessageEvent } from './types.js';
5
+ export { onHostApiDebugMessage } from './debugBus.js';
5
6
  export { deriveProductEntropy } from './deriveEntropy.js';
6
7
  export { createRateLimiter } from './rateLimiter.js';
7
8
  export type { CreateRateLimiterConfig, RateLimiter, RateLimiterConfig, RateLimiterStrategy } from './rateLimiter.js';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { createWebviewProvider } from './createWebviewProvider.js';
2
2
  export { createIframeProvider } from './createIframeProvider.js';
3
3
  export { createContainer } from './createContainer.js';
4
+ export { onHostApiDebugMessage } from './debugBus.js';
4
5
  export { deriveProductEntropy } from './deriveEntropy.js';
5
6
  export { createRateLimiter } from './rateLimiter.js';
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Codec, CodecType, ConnectionStatus, HexString, HostApiProtocol, Subscription, VersionedProtocolRequest, VersionedProtocolSubscription } from '@novasamatech/host-api';
1
+ import type { Codec, CodecType, ConnectionStatus, HexString, HostApiProtocol, MessagePayloadSchema, Subscription, VersionedProtocolRequest, VersionedProtocolSubscription } from '@novasamatech/host-api';
2
2
  import { CustomRendererNode } from '@novasamatech/host-api';
3
3
  import type { ResultAsync, errAsync } from 'neverthrow';
4
4
  import { okAsync } from 'neverthrow';
@@ -52,11 +52,31 @@ type InferRequestHandler<V extends string, T extends VersionedProtocolRequest> =
52
52
  type InferSubscribeHandler<V extends string, T extends VersionedProtocolSubscription> = (callback: (params: WithVersion<V, CodecValue<T['start']>>, send: (payload: WithVersion<V, CodecValue<T['receive']>>) => void, interrupt: (payload: WithVersion<V, CodecValue<T['interrupt']>>) => void) => VoidFunction) => VoidFunction;
53
53
  type InferHandler<V extends string, T extends VersionedProtocolRequest | VersionedProtocolSubscription> = T extends VersionedProtocolRequest ? InferRequestHandler<V, T> : T extends VersionedProtocolSubscription ? InferSubscribeHandler<V, T> : never;
54
54
  export type ContainerHandlerOf<T extends (...args: any[]) => any> = Parameters<T>[0];
55
+ /**
56
+ * EXPERIMENTAL. Event describing a single message observed on a
57
+ * container's transport, in decoded form, tagged with the productId
58
+ * that was passed to `createContainer`.
59
+ */
60
+ export type HostApiDebugMessageEvent = {
61
+ direction: 'incoming' | 'outgoing';
62
+ productId: string | undefined;
63
+ requestId: string;
64
+ payload: MessagePayloadSchema;
65
+ };
66
+ export type CreateContainerOptions = {
67
+ /**
68
+ * Optional identifier for the product this container talks to.
69
+ * When set, every debug event emitted via `onDebugMessage` and the
70
+ * global `onHostApiDebugMessage` bus is tagged with this value.
71
+ */
72
+ productId?: string;
73
+ };
55
74
  export type Container = {
56
75
  handleFeatureSupported: InferHandler<'v1', HostApiProtocol['host_feature_supported']>;
57
76
  handleDevicePermission: InferHandler<'v1', HostApiProtocol['host_device_permission']>;
58
77
  handlePermission: InferHandler<'v1', HostApiProtocol['remote_permission']>;
59
78
  handlePushNotification: InferHandler<'v1', HostApiProtocol['host_push_notification']>;
79
+ handlePushNotificationCancel: InferHandler<'v1', HostApiProtocol['host_push_notification_cancel']>;
60
80
  handleNavigateTo: InferHandler<'v1', HostApiProtocol['host_navigate_to']>;
61
81
  handleDeriveEntropy: InferHandler<'v1', HostApiProtocol['host_derive_entropy']>;
62
82
  handleLocalStorageRead: InferHandler<'v1', HostApiProtocol['host_local_storage_read']>;
@@ -101,5 +121,11 @@ export type Container = {
101
121
  isReady(): Promise<boolean>;
102
122
  dispose(): void;
103
123
  subscribeProductConnectionStatus(callback: (connectionStatus: ConnectionStatus) => void): VoidFunction;
124
+ /**
125
+ * EXPERIMENTAL. Subscribe to every message crossing this container's
126
+ * transport in either direction, in decoded form. Returns an
127
+ * unsubscribe function.
128
+ */
129
+ onDebugMessage(callback: (event: HostApiDebugMessageEvent) => void): VoidFunction;
104
130
  };
105
131
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@novasamatech/host-container",
3
3
  "type": "module",
4
- "version": "0.7.9-4",
4
+ "version": "0.7.9-6",
5
5
  "description": "Host container for hosting and managing products within the Polkadot ecosystem.",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -28,7 +28,8 @@
28
28
  "@noble/hashes": "2.2.0",
29
29
  "polkadot-api": ">=2",
30
30
  "@polkadot-api/substrate-client": "^0.7.0",
31
- "@novasamatech/host-api": "0.7.9-4",
31
+ "@novasamatech/host-api": "0.7.9-6",
32
+ "nanoevents": "9.1.0",
32
33
  "nanoid": "5.1.9",
33
34
  "neverthrow": "^8.2.0"
34
35
  },