@novasamatech/host-container 0.6.1 → 0.6.3

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,115 @@
1
+ import type { HexString } from '@novasamatech/host-api';
2
+ import type { JsonRpcProvider } from '@polkadot-api/json-rpc-provider';
3
+ type PendingRequest = {
4
+ resolve: (result: unknown) => void;
5
+ reject: (error: unknown) => void;
6
+ };
7
+ type FollowSubscription = {
8
+ chainSubId: string;
9
+ eventListener: (event: unknown) => void;
10
+ pendingRequestId?: string;
11
+ };
12
+ type ChainEntry = {
13
+ connection: {
14
+ send: (msg: string) => void;
15
+ disconnect: () => void;
16
+ };
17
+ pendingRequests: Map<string, PendingRequest>;
18
+ followSubscriptions: Map<string, FollowSubscription>;
19
+ refCount: number;
20
+ };
21
+ export type ChainConnectionManager = ReturnType<typeof createChainConnectionManager>;
22
+ export declare function createChainConnectionManager(factory: (genesisHash: HexString) => JsonRpcProvider | null): {
23
+ getOrCreateChain: (genesisHash: HexString) => ChainEntry | null;
24
+ sendRequest: (genesisHash: HexString, method: string, params: unknown[]) => Promise<unknown>;
25
+ startFollow: (genesisHash: HexString, withRuntime: boolean, onEvent: (event: unknown) => void) => {
26
+ followId: string;
27
+ };
28
+ stopFollow: (genesisHash: HexString, followId: string) => void;
29
+ getChainFollowSubId: (genesisHash: HexString) => string | null;
30
+ releaseChain: (genesisHash: HexString) => void;
31
+ dispose: () => void;
32
+ convertJsonRpcEventToTyped: (event: Record<string, unknown>) => {
33
+ tag: "Initialized";
34
+ value: {
35
+ readonly finalizedBlockHashes: HexString[];
36
+ readonly finalizedBlockRuntime: unknown;
37
+ };
38
+ } | {
39
+ tag: "NewBlock";
40
+ value: {
41
+ readonly blockHash: HexString;
42
+ readonly parentBlockHash: HexString;
43
+ readonly newRuntime: unknown;
44
+ };
45
+ } | {
46
+ tag: "BestBlockChanged";
47
+ value: {
48
+ readonly bestBlockHash: HexString;
49
+ };
50
+ } | {
51
+ tag: "Finalized";
52
+ value: {
53
+ readonly finalizedBlockHashes: HexString[];
54
+ readonly prunedBlockHashes: HexString[];
55
+ };
56
+ } | {
57
+ tag: "OperationBodyDone";
58
+ value: {
59
+ readonly operationId: string;
60
+ readonly value: HexString[];
61
+ };
62
+ } | {
63
+ tag: "OperationCallDone";
64
+ value: {
65
+ readonly operationId: string;
66
+ readonly output: HexString;
67
+ };
68
+ } | {
69
+ tag: "OperationStorageItems";
70
+ value: {
71
+ readonly operationId: string;
72
+ readonly items: {
73
+ key: HexString;
74
+ value: `0x${string}`;
75
+ hash: `0x${string}`;
76
+ closestDescendantMerkleValue: `0x${string}`;
77
+ }[];
78
+ };
79
+ } | {
80
+ tag: "OperationStorageDone";
81
+ value: {
82
+ readonly operationId: string;
83
+ };
84
+ } | {
85
+ tag: "OperationWaitingForContinue";
86
+ value: {
87
+ readonly operationId: string;
88
+ };
89
+ } | {
90
+ tag: "OperationInaccessible";
91
+ value: {
92
+ readonly operationId: string;
93
+ };
94
+ } | {
95
+ tag: "OperationError";
96
+ value: {
97
+ readonly operationId: string;
98
+ readonly error: string;
99
+ };
100
+ } | {
101
+ tag: "Stop";
102
+ value: undefined;
103
+ };
104
+ convertOperationStartedResult: (result: unknown) => {
105
+ tag: "Started";
106
+ value: {
107
+ readonly operationId: string;
108
+ };
109
+ } | {
110
+ tag: "LimitReached";
111
+ value: undefined;
112
+ };
113
+ convertStorageQueryTypeToJsonRpc: (type: string) => string;
114
+ };
115
+ export {};
@@ -0,0 +1,290 @@
1
+ import { enumValue } from '@novasamatech/host-api';
2
+ let instanceCounter = 0;
3
+ export function createChainConnectionManager(factory) {
4
+ const chains = new Map();
5
+ const instanceId = instanceCounter++;
6
+ let nextId = 0;
7
+ function getNextId() {
8
+ return `ccm_${instanceId}_${nextId++}`;
9
+ }
10
+ function getOrCreateChain(genesisHash) {
11
+ const existing = chains.get(genesisHash);
12
+ if (existing) {
13
+ existing.refCount++;
14
+ return existing;
15
+ }
16
+ const provider = factory(genesisHash);
17
+ if (!provider)
18
+ return null;
19
+ const pendingRequests = new Map();
20
+ const followSubscriptions = new Map();
21
+ const entry = {
22
+ connection: null,
23
+ pendingRequests,
24
+ followSubscriptions,
25
+ refCount: 1,
26
+ };
27
+ entry.connection = provider((message) => {
28
+ let parsed;
29
+ try {
30
+ parsed = JSON.parse(message);
31
+ }
32
+ catch {
33
+ return;
34
+ }
35
+ // Request-response (has 'id' field)
36
+ if ('id' in parsed && parsed.id != null) {
37
+ const pending = pendingRequests.get(String(parsed.id));
38
+ if (pending) {
39
+ pendingRequests.delete(String(parsed.id));
40
+ if ('error' in parsed) {
41
+ pending.reject(parsed.error);
42
+ }
43
+ else {
44
+ pending.resolve(parsed.result);
45
+ }
46
+ return;
47
+ }
48
+ }
49
+ // Subscription notification (has params.subscription)
50
+ const params = parsed.params;
51
+ if (params?.subscription) {
52
+ const subId = String(params.subscription);
53
+ for (const follow of followSubscriptions.values()) {
54
+ if (follow.chainSubId === subId) {
55
+ follow.eventListener(params.result);
56
+ break;
57
+ }
58
+ }
59
+ }
60
+ });
61
+ chains.set(genesisHash, entry);
62
+ return entry;
63
+ }
64
+ function sendRequest(genesisHash, method, params) {
65
+ const entry = chains.get(genesisHash);
66
+ if (!entry)
67
+ return Promise.reject(new Error(`No connection for chain ${genesisHash}`));
68
+ const id = getNextId();
69
+ return new Promise((resolve, reject) => {
70
+ entry.pendingRequests.set(id, { resolve, reject });
71
+ entry.connection.send(JSON.stringify({ jsonrpc: '2.0', id, method, params }));
72
+ });
73
+ }
74
+ function startFollow(genesisHash, withRuntime, onEvent) {
75
+ const entry = chains.get(genesisHash);
76
+ if (!entry)
77
+ throw new Error(`No connection for chain ${genesisHash}`);
78
+ const followId = getNextId();
79
+ const requestId = getNextId();
80
+ const follow = {
81
+ chainSubId: '', // will be set synchronously when response arrives
82
+ eventListener: onEvent,
83
+ pendingRequestId: requestId,
84
+ };
85
+ entry.followSubscriptions.set(followId, follow);
86
+ // Bypass sendRequest to avoid Promise microtask deferral.
87
+ // The response handler sets chainSubId synchronously, so when the next
88
+ // message (the notification) is processed, chainSubId is already set.
89
+ entry.pendingRequests.set(requestId, {
90
+ resolve: result => {
91
+ follow.chainSubId = result;
92
+ follow.pendingRequestId = undefined;
93
+ },
94
+ reject: () => {
95
+ follow.pendingRequestId = undefined;
96
+ entry.followSubscriptions.delete(followId);
97
+ },
98
+ });
99
+ entry.connection.send(JSON.stringify({ jsonrpc: '2.0', id: requestId, method: 'chainHead_v1_follow', params: [withRuntime] }));
100
+ return { followId };
101
+ }
102
+ function stopFollow(genesisHash, followId) {
103
+ const entry = chains.get(genesisHash);
104
+ if (!entry)
105
+ return;
106
+ const follow = entry.followSubscriptions.get(followId);
107
+ if (!follow)
108
+ return;
109
+ entry.followSubscriptions.delete(followId);
110
+ if (follow.chainSubId) {
111
+ const id = getNextId();
112
+ entry.connection.send(JSON.stringify({ jsonrpc: '2.0', id, method: 'chainHead_v1_unfollow', params: [follow.chainSubId] }));
113
+ }
114
+ else if (follow.pendingRequestId) {
115
+ // Follow response hasn't arrived yet — replace the pending resolve to send unfollow when it does
116
+ entry.pendingRequests.set(follow.pendingRequestId, {
117
+ resolve: result => {
118
+ const chainSubId = result;
119
+ if (chainSubId) {
120
+ const unfollowId = getNextId();
121
+ entry.connection.send(JSON.stringify({ jsonrpc: '2.0', id: unfollowId, method: 'chainHead_v1_unfollow', params: [chainSubId] }));
122
+ }
123
+ },
124
+ reject: () => {
125
+ /* follow already cleaned up */
126
+ },
127
+ });
128
+ }
129
+ }
130
+ function getChainFollowSubId(genesisHash) {
131
+ const entry = chains.get(genesisHash);
132
+ if (!entry)
133
+ return null;
134
+ // Return the first active follow subscription ID for this chain
135
+ for (const follow of entry.followSubscriptions.values()) {
136
+ if (follow.chainSubId)
137
+ return follow.chainSubId;
138
+ }
139
+ return null;
140
+ }
141
+ function releaseChain(genesisHash) {
142
+ const entry = chains.get(genesisHash);
143
+ if (!entry)
144
+ return;
145
+ entry.refCount--;
146
+ if (entry.refCount <= 0) {
147
+ for (const follow of entry.followSubscriptions.values()) {
148
+ if (follow.chainSubId) {
149
+ const id = getNextId();
150
+ entry.connection.send(JSON.stringify({ jsonrpc: '2.0', id, method: 'chainHead_v1_unfollow', params: [follow.chainSubId] }));
151
+ }
152
+ }
153
+ entry.followSubscriptions.clear();
154
+ entry.connection.disconnect();
155
+ chains.delete(genesisHash);
156
+ }
157
+ }
158
+ function dispose() {
159
+ for (const entry of chains.values()) {
160
+ for (const follow of entry.followSubscriptions.values()) {
161
+ if (follow.chainSubId) {
162
+ const id = getNextId();
163
+ entry.connection.send(JSON.stringify({ jsonrpc: '2.0', id, method: 'chainHead_v1_unfollow', params: [follow.chainSubId] }));
164
+ }
165
+ }
166
+ entry.followSubscriptions.clear();
167
+ entry.connection.disconnect();
168
+ }
169
+ chains.clear();
170
+ }
171
+ // === JSON RPC ↔ typed conversion helpers ===
172
+ function convertJsonRpcEventToTyped(event) {
173
+ const eventType = event.event;
174
+ switch (eventType) {
175
+ case 'initialized':
176
+ return enumValue('Initialized', {
177
+ finalizedBlockHashes: event.finalizedBlockHashes,
178
+ finalizedBlockRuntime: convertRuntime(event.finalizedBlockRuntime),
179
+ });
180
+ case 'newBlock':
181
+ return enumValue('NewBlock', {
182
+ blockHash: event.blockHash,
183
+ parentBlockHash: event.parentBlockHash,
184
+ newRuntime: convertRuntime(event.newRuntime),
185
+ });
186
+ case 'bestBlockChanged':
187
+ return enumValue('BestBlockChanged', {
188
+ bestBlockHash: event.bestBlockHash,
189
+ });
190
+ case 'finalized':
191
+ return enumValue('Finalized', {
192
+ finalizedBlockHashes: event.finalizedBlockHashes,
193
+ prunedBlockHashes: event.prunedBlockHashes,
194
+ });
195
+ case 'operationBodyDone':
196
+ return enumValue('OperationBodyDone', {
197
+ operationId: event.operationId,
198
+ value: event.value,
199
+ });
200
+ case 'operationCallDone':
201
+ return enumValue('OperationCallDone', {
202
+ operationId: event.operationId,
203
+ output: event.output,
204
+ });
205
+ case 'operationStorageItems':
206
+ return enumValue('OperationStorageItems', {
207
+ operationId: event.operationId,
208
+ items: event.items.map(item => ({
209
+ key: item.key,
210
+ value: item.value ?? null,
211
+ hash: item.hash ?? null,
212
+ closestDescendantMerkleValue: item.closestDescendantMerkleValue ?? null,
213
+ })),
214
+ });
215
+ case 'operationStorageDone':
216
+ return enumValue('OperationStorageDone', {
217
+ operationId: event.operationId,
218
+ });
219
+ case 'operationWaitingForContinue':
220
+ return enumValue('OperationWaitingForContinue', {
221
+ operationId: event.operationId,
222
+ });
223
+ case 'operationInaccessible':
224
+ return enumValue('OperationInaccessible', {
225
+ operationId: event.operationId,
226
+ });
227
+ case 'operationError':
228
+ return enumValue('OperationError', {
229
+ operationId: event.operationId,
230
+ error: event.error,
231
+ });
232
+ case 'stop':
233
+ return enumValue('Stop', undefined);
234
+ default:
235
+ return enumValue('Stop', undefined);
236
+ }
237
+ }
238
+ function convertRuntime(runtime) {
239
+ if (!runtime || typeof runtime !== 'object')
240
+ return undefined;
241
+ const rt = runtime;
242
+ if (rt.type === 'valid') {
243
+ const spec = rt.spec;
244
+ const apis = spec.apis;
245
+ return enumValue('Valid', {
246
+ specName: spec.specName,
247
+ implName: spec.implName,
248
+ specVersion: spec.specVersion,
249
+ implVersion: spec.implVersion,
250
+ transactionVersion: spec.transactionVersion,
251
+ apis: apis ? Object.entries(apis).map(([name, version]) => [name, version]) : [],
252
+ });
253
+ }
254
+ if (rt.type === 'invalid') {
255
+ return enumValue('Invalid', { error: rt.error });
256
+ }
257
+ return undefined;
258
+ }
259
+ function convertOperationStartedResult(result) {
260
+ if (typeof result === 'object' && result !== null) {
261
+ const r = result;
262
+ if (r.result === 'started') {
263
+ return enumValue('Started', { operationId: r.operationId });
264
+ }
265
+ }
266
+ return enumValue('LimitReached', undefined);
267
+ }
268
+ function convertStorageQueryTypeToJsonRpc(type) {
269
+ const map = {
270
+ Value: 'value',
271
+ Hash: 'hash',
272
+ ClosestDescendantMerkleValue: 'closestDescendantMerkleValue',
273
+ DescendantsValues: 'descendantsValues',
274
+ DescendantsHashes: 'descendantsHashes',
275
+ };
276
+ return map[type] ?? 'value';
277
+ }
278
+ return {
279
+ getOrCreateChain,
280
+ sendRequest,
281
+ startFollow,
282
+ stopFollow,
283
+ getChainFollowSubId,
284
+ releaseChain,
285
+ dispose,
286
+ convertJsonRpcEventToTyped,
287
+ convertOperationStartedResult,
288
+ convertStorageQueryTypeToJsonRpc,
289
+ };
290
+ }
@@ -1,5 +1,6 @@
1
- import { ChatBotRegistrationErr, ChatMessagePostingErr, ChatRoomRegistrationErr, CreateProofErr, CreateTransactionErr, GenericError, NavigateToErr, PreimageSubmitErr, RequestCredentialsErr, SigningErr, StatementProofErr, StorageErr, assertEnumVariant, createTransport, enumValue, isEnumVariant, resultErr, resultOk, } from '@novasamatech/host-api';
1
+ import { ChatBotRegistrationErr, ChatMessagePostingErr, ChatRoomRegistrationErr, CreateProofErr, CreateTransactionErr, GenericError, NavigateToErr, PreimageSubmitErr, RequestCredentialsErr, SigningErr, StatementProofErr, StorageErr, createTransport, enumValue, isEnumVariant, resultErr, resultOk, } from '@novasamatech/host-api';
2
2
  import { err, errAsync, ok, okAsync } from 'neverthrow';
3
+ import { createChainConnectionManager } from './chainConnectionManager.js';
3
4
  const UNSUPPORTED_MESSAGE_FORMAT_ERROR = 'Unsupported message format';
4
5
  function guardVersion(value, tag, error) {
5
6
  if (isEnumVariant(value, tag)) {
@@ -281,9 +282,9 @@ export function createContainer(provider) {
281
282
  });
282
283
  });
283
284
  },
284
- renderChatCustomMessage(messageType, payload, callback) {
285
+ renderChatCustomMessage({ messageId, messageType, payload }, callback) {
285
286
  init();
286
- return transport.subscribe('product_chat_custom_message_render_subscribe', enumValue('v1', { messageType, payload }), value => {
287
+ return transport.subscribe('product_chat_custom_message_render_subscribe', enumValue('v1', { messageId, messageType, payload }), value => {
287
288
  if (value.tag === 'v1') {
288
289
  callback(value.value);
289
290
  }
@@ -349,37 +350,288 @@ export function createContainer(provider) {
349
350
  .unwrapOr(enumValue(version, resultErr(error)));
350
351
  });
351
352
  },
353
+ // chain interaction
352
354
  handleChainConnection(factory) {
353
355
  init();
354
- return transport.handleSubscription('host_jsonrpc_message_subscribe', (params, send) => {
355
- assertEnumVariant(params, 'v1', UNSUPPORTED_MESSAGE_FORMAT_ERROR);
356
- const genesisHash = params.value;
357
- const provider = factory(params.value);
358
- if (provider === null) {
356
+ const manager = createChainConnectionManager(factory);
357
+ const cleanups = [];
358
+ // Follow subscription
359
+ cleanups.push(transport.handleSubscription('remote_chain_head_follow', (params, send, interrupt) => {
360
+ if (!isEnumVariant(params, 'v1')) {
361
+ interrupt();
359
362
  return () => {
360
- // empty subscription, we don't want to react to foreign chain subscription request
363
+ /* unsupported version */
361
364
  };
362
365
  }
363
- const connection = provider(message => send(enumValue('v1', message)));
364
- const unsubscribeDestroy = transport.onDestroy(() => {
365
- unsubRequests();
366
- unsubscribeDestroy();
367
- connection.disconnect();
368
- });
369
- const unsubRequests = transport.handleRequest('host_jsonrpc_message_send', async (message) => {
370
- assertEnumVariant(message, 'v1', UNSUPPORTED_MESSAGE_FORMAT_ERROR);
371
- const [requestedGenesisHash, payload] = message.value;
372
- if (requestedGenesisHash === genesisHash) {
373
- connection.send(payload);
374
- }
375
- return enumValue('v1', resultOk(undefined));
366
+ const { genesisHash, withRuntime } = params.value;
367
+ const entry = manager.getOrCreateChain(genesisHash);
368
+ if (!entry) {
369
+ interrupt();
370
+ return () => {
371
+ /* no chain provider available */
372
+ };
373
+ }
374
+ const { followId } = manager.startFollow(genesisHash, withRuntime, (event) => {
375
+ const typedEvent = manager.convertJsonRpcEventToTyped(event);
376
+ send(enumValue('v1', typedEvent));
376
377
  });
377
378
  return () => {
378
- unsubRequests();
379
- unsubscribeDestroy();
380
- connection.disconnect();
379
+ manager.stopFollow(genesisHash, followId);
380
+ manager.releaseChain(genesisHash);
381
381
  };
382
- });
382
+ }));
383
+ // Header request
384
+ cleanups.push(transport.handleRequest('remote_chain_head_header', async (message) => {
385
+ if (!isEnumVariant(message, 'v1')) {
386
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
387
+ }
388
+ const { genesisHash, hash } = message.value;
389
+ const realSubId = manager.getChainFollowSubId(genesisHash);
390
+ if (!realSubId) {
391
+ return enumValue('v1', resultErr(new GenericError({ reason: 'No active follow for this chain' })));
392
+ }
393
+ try {
394
+ const result = await manager.sendRequest(genesisHash, 'chainHead_v1_header', [realSubId, hash]);
395
+ return enumValue('v1', resultOk(result));
396
+ }
397
+ catch (e) {
398
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
399
+ }
400
+ }));
401
+ // Body request
402
+ cleanups.push(transport.handleRequest('remote_chain_head_body', async (message) => {
403
+ if (!isEnumVariant(message, 'v1')) {
404
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
405
+ }
406
+ const { genesisHash, hash } = message.value;
407
+ const realSubId = manager.getChainFollowSubId(genesisHash);
408
+ if (!realSubId) {
409
+ return enumValue('v1', resultErr(new GenericError({ reason: 'No active follow for this chain' })));
410
+ }
411
+ try {
412
+ const result = await manager.sendRequest(genesisHash, 'chainHead_v1_body', [realSubId, hash]);
413
+ return enumValue('v1', resultOk(manager.convertOperationStartedResult(result)));
414
+ }
415
+ catch (e) {
416
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
417
+ }
418
+ }));
419
+ // Storage request
420
+ cleanups.push(transport.handleRequest('remote_chain_head_storage', async (message) => {
421
+ if (!isEnumVariant(message, 'v1')) {
422
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
423
+ }
424
+ const { genesisHash, hash, items, childTrie } = message.value;
425
+ const realSubId = manager.getChainFollowSubId(genesisHash);
426
+ if (!realSubId) {
427
+ return enumValue('v1', resultErr(new GenericError({ reason: 'No active follow for this chain' })));
428
+ }
429
+ const jsonRpcItems = items.map((item) => ({
430
+ key: item.key,
431
+ type: manager.convertStorageQueryTypeToJsonRpc(item.type),
432
+ }));
433
+ try {
434
+ const result = await manager.sendRequest(genesisHash, 'chainHead_v1_storage', [
435
+ realSubId,
436
+ hash,
437
+ jsonRpcItems,
438
+ childTrie,
439
+ ]);
440
+ return enumValue('v1', resultOk(manager.convertOperationStartedResult(result)));
441
+ }
442
+ catch (e) {
443
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
444
+ }
445
+ }));
446
+ // Call request
447
+ cleanups.push(transport.handleRequest('remote_chain_head_call', async (message) => {
448
+ if (!isEnumVariant(message, 'v1')) {
449
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
450
+ }
451
+ const params = message.value;
452
+ const realSubId = manager.getChainFollowSubId(params.genesisHash);
453
+ if (!realSubId) {
454
+ return enumValue('v1', resultErr(new GenericError({ reason: 'No active follow for this chain' })));
455
+ }
456
+ try {
457
+ const result = await manager.sendRequest(params.genesisHash, 'chainHead_v1_call', [
458
+ realSubId,
459
+ params.hash,
460
+ params.function,
461
+ params.callParameters,
462
+ ]);
463
+ return enumValue('v1', resultOk(manager.convertOperationStartedResult(result)));
464
+ }
465
+ catch (e) {
466
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
467
+ }
468
+ }));
469
+ // Unpin request
470
+ cleanups.push(transport.handleRequest('remote_chain_head_unpin', async (message) => {
471
+ if (!isEnumVariant(message, 'v1')) {
472
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
473
+ }
474
+ const { genesisHash, hashes } = message.value;
475
+ const realSubId = manager.getChainFollowSubId(genesisHash);
476
+ if (!realSubId) {
477
+ return enumValue('v1', resultErr(new GenericError({ reason: 'No active follow for this chain' })));
478
+ }
479
+ try {
480
+ await manager.sendRequest(genesisHash, 'chainHead_v1_unpin', [realSubId, hashes]);
481
+ return enumValue('v1', resultOk(undefined));
482
+ }
483
+ catch (e) {
484
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
485
+ }
486
+ }));
487
+ // Continue request
488
+ cleanups.push(transport.handleRequest('remote_chain_head_continue', async (message) => {
489
+ if (!isEnumVariant(message, 'v1')) {
490
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
491
+ }
492
+ const { genesisHash, operationId } = message.value;
493
+ const realSubId = manager.getChainFollowSubId(genesisHash);
494
+ if (!realSubId) {
495
+ return enumValue('v1', resultErr(new GenericError({ reason: 'No active follow for this chain' })));
496
+ }
497
+ try {
498
+ await manager.sendRequest(genesisHash, 'chainHead_v1_continue', [realSubId, operationId]);
499
+ return enumValue('v1', resultOk(undefined));
500
+ }
501
+ catch (e) {
502
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
503
+ }
504
+ }));
505
+ // StopOperation request
506
+ cleanups.push(transport.handleRequest('remote_chain_head_stop_operation', async (message) => {
507
+ if (!isEnumVariant(message, 'v1')) {
508
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
509
+ }
510
+ const { genesisHash, operationId } = message.value;
511
+ const realSubId = manager.getChainFollowSubId(genesisHash);
512
+ if (!realSubId) {
513
+ return enumValue('v1', resultErr(new GenericError({ reason: 'No active follow for this chain' })));
514
+ }
515
+ try {
516
+ await manager.sendRequest(genesisHash, 'chainHead_v1_stopOperation', [realSubId, operationId]);
517
+ return enumValue('v1', resultOk(undefined));
518
+ }
519
+ catch (e) {
520
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
521
+ }
522
+ }));
523
+ // ChainSpec: genesis hash
524
+ cleanups.push(transport.handleRequest('remote_chain_spec_genesis_hash', async (message) => {
525
+ if (!isEnumVariant(message, 'v1')) {
526
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
527
+ }
528
+ const genesisHash = message.value;
529
+ const entry = manager.getOrCreateChain(genesisHash);
530
+ if (!entry) {
531
+ return enumValue('v1', resultErr(new GenericError({ reason: 'Chain not supported' })));
532
+ }
533
+ try {
534
+ const result = await manager.sendRequest(genesisHash, 'chainSpec_v1_genesisHash', []);
535
+ manager.releaseChain(genesisHash);
536
+ return enumValue('v1', resultOk(result));
537
+ }
538
+ catch (e) {
539
+ manager.releaseChain(genesisHash);
540
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
541
+ }
542
+ }));
543
+ // ChainSpec: chain name
544
+ cleanups.push(transport.handleRequest('remote_chain_spec_chain_name', async (message) => {
545
+ if (!isEnumVariant(message, 'v1')) {
546
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
547
+ }
548
+ const genesisHash = message.value;
549
+ const entry = manager.getOrCreateChain(genesisHash);
550
+ if (!entry) {
551
+ return enumValue('v1', resultErr(new GenericError({ reason: 'Chain not supported' })));
552
+ }
553
+ try {
554
+ const result = await manager.sendRequest(genesisHash, 'chainSpec_v1_chainName', []);
555
+ manager.releaseChain(genesisHash);
556
+ return enumValue('v1', resultOk(result));
557
+ }
558
+ catch (e) {
559
+ manager.releaseChain(genesisHash);
560
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
561
+ }
562
+ }));
563
+ // ChainSpec: properties
564
+ cleanups.push(transport.handleRequest('remote_chain_spec_properties', async (message) => {
565
+ if (!isEnumVariant(message, 'v1')) {
566
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
567
+ }
568
+ const genesisHash = message.value;
569
+ const entry = manager.getOrCreateChain(genesisHash);
570
+ if (!entry) {
571
+ return enumValue('v1', resultErr(new GenericError({ reason: 'Chain not supported' })));
572
+ }
573
+ try {
574
+ const result = await manager.sendRequest(genesisHash, 'chainSpec_v1_properties', []);
575
+ manager.releaseChain(genesisHash);
576
+ return enumValue('v1', resultOk(typeof result === 'string' ? result : JSON.stringify(result)));
577
+ }
578
+ catch (e) {
579
+ manager.releaseChain(genesisHash);
580
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
581
+ }
582
+ }));
583
+ // Transaction broadcast
584
+ cleanups.push(transport.handleRequest('remote_chain_transaction_broadcast', async (message) => {
585
+ if (!isEnumVariant(message, 'v1')) {
586
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
587
+ }
588
+ const { genesisHash, transaction } = message.value;
589
+ const entry = manager.getOrCreateChain(genesisHash);
590
+ if (!entry) {
591
+ return enumValue('v1', resultErr(new GenericError({ reason: 'Chain not supported' })));
592
+ }
593
+ try {
594
+ const result = await manager.sendRequest(genesisHash, 'transaction_v1_broadcast', [transaction]);
595
+ manager.releaseChain(genesisHash);
596
+ return enumValue('v1', resultOk(result ?? null));
597
+ }
598
+ catch (e) {
599
+ manager.releaseChain(genesisHash);
600
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
601
+ }
602
+ }));
603
+ // Transaction stop
604
+ cleanups.push(transport.handleRequest('remote_chain_transaction_stop', async (message) => {
605
+ if (!isEnumVariant(message, 'v1')) {
606
+ return enumValue('v1', resultErr(new GenericError({ reason: UNSUPPORTED_MESSAGE_FORMAT_ERROR })));
607
+ }
608
+ const { genesisHash, operationId } = message.value;
609
+ const entry = manager.getOrCreateChain(genesisHash);
610
+ if (!entry) {
611
+ return enumValue('v1', resultErr(new GenericError({ reason: 'Chain not supported' })));
612
+ }
613
+ try {
614
+ await manager.sendRequest(genesisHash, 'transaction_v1_stop', [operationId]);
615
+ manager.releaseChain(genesisHash);
616
+ return enumValue('v1', resultOk(undefined));
617
+ }
618
+ catch (e) {
619
+ manager.releaseChain(genesisHash);
620
+ return enumValue('v1', resultErr(new GenericError({ reason: String(e) })));
621
+ }
622
+ }));
623
+ let disposed = false;
624
+ const dispose = () => {
625
+ if (disposed)
626
+ return;
627
+ disposed = true;
628
+ unsubscribeDestroy();
629
+ for (const fn of cleanups)
630
+ fn();
631
+ manager.dispose();
632
+ };
633
+ const unsubscribeDestroy = transport.onDestroy(dispose);
634
+ return dispose;
383
635
  },
384
636
  isReady() {
385
637
  return transport.isReady();
package/dist/types.d.ts CHANGED
@@ -73,7 +73,11 @@ export type Container = {
73
73
  handleChatListSubscribe: InferHandler<'v1', HostApiProtocol['host_chat_list_subscribe']>;
74
74
  handleChatPostMessage: InferHandler<'v1', HostApiProtocol['host_chat_post_message']>;
75
75
  handleChatActionSubscribe: InferHandler<'v1', HostApiProtocol['host_chat_action_subscribe']>;
76
- renderChatCustomMessage(messageType: string, payload: Uint8Array, callback: (node: CodecType<typeof CustomRendererNode>) => void): Subscription;
76
+ renderChatCustomMessage(params: {
77
+ messageId: string;
78
+ messageType: string;
79
+ payload: Uint8Array;
80
+ }, callback: (node: CodecType<typeof CustomRendererNode>) => void): Subscription;
77
81
  handleStatementStoreSubscribe: InferHandler<'v1', HostApiProtocol['remote_statement_store_subscribe']>;
78
82
  handleStatementStoreCreateProof: InferHandler<'v1', HostApiProtocol['remote_statement_store_create_proof']>;
79
83
  handleStatementStoreSubmit: InferHandler<'v1', HostApiProtocol['remote_statement_store_submit']>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@novasamatech/host-container",
3
3
  "type": "module",
4
- "version": "0.6.1",
4
+ "version": "0.6.3",
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,7 @@
28
28
  "dependencies": {
29
29
  "@polkadot-api/utils": "^0.2.0",
30
30
  "@polkadot-api/json-rpc-provider": "^0.0.4",
31
- "@novasamatech/host-api": "0.6.1",
31
+ "@novasamatech/host-api": "0.6.3",
32
32
  "nanoid": "5.1.6"
33
33
  },
34
34
  "devDependencies": {