@novasamatech/host-api-wrapper 0.7.9-5

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,157 @@
1
+ import { assertEnumVariant, createHostApi, enumValue, fromHex, toHex } from '@novasamatech/host-api';
2
+ import { injectExtension } from '@polkadot/extension-inject';
3
+ import { AccountId } from 'polkadot-api';
4
+ import { createAccountsProvider } from './accounts.js';
5
+ import { SpektrExtensionName, Version } from './constants.js';
6
+ import { sandboxTransport } from './sandboxTransport.js';
7
+ const UNSUPPORTED_VERSION_ERROR = 'Unsupported message version';
8
+ export async function createLegacyExtensionEnableFactory(transport) {
9
+ const ready = await transport.isReady();
10
+ if (!ready)
11
+ return null;
12
+ const accountsManager = createAccountsProvider(transport);
13
+ const hostApi = createHostApi(transport);
14
+ const accountId = AccountId();
15
+ async function enable() {
16
+ async function getAccounts() {
17
+ return await accountsManager
18
+ .getLegacyAccounts()
19
+ .map(response => {
20
+ return response.map(account => ({
21
+ name: account.name,
22
+ address: accountId.dec(account.publicKey),
23
+ type: 'sr25519',
24
+ }));
25
+ })
26
+ .match(x => x, x => {
27
+ throw x;
28
+ });
29
+ }
30
+ return {
31
+ accounts: {
32
+ async get() {
33
+ return getAccounts();
34
+ },
35
+ subscribe(callback) {
36
+ getAccounts().then(callback);
37
+ return () => {
38
+ // empty
39
+ };
40
+ },
41
+ },
42
+ signer: {
43
+ async signRaw(raw) {
44
+ const payload = {
45
+ signer: raw.address,
46
+ payload: raw.type === 'bytes'
47
+ ? {
48
+ tag: 'Bytes',
49
+ value: fromHex(raw.data),
50
+ }
51
+ : {
52
+ tag: 'Payload',
53
+ value: raw.data,
54
+ },
55
+ };
56
+ const response = await hostApi.signRawWithLegacyAccount(enumValue('v1', payload));
57
+ return response.match(response => {
58
+ assertEnumVariant(response, 'v1', UNSUPPORTED_VERSION_ERROR);
59
+ return {
60
+ id: 0,
61
+ signature: response.value.signature,
62
+ signedTransaction: response.value.signedTransaction,
63
+ };
64
+ }, err => {
65
+ assertEnumVariant(err, 'v1', UNSUPPORTED_VERSION_ERROR);
66
+ throw err.value;
67
+ });
68
+ },
69
+ async signPayload(payload) {
70
+ const codecPayload = {
71
+ signer: payload.address,
72
+ payload: {
73
+ blockHash: payload.blockHash,
74
+ blockNumber: payload.blockNumber,
75
+ era: payload.era,
76
+ genesisHash: payload.genesisHash,
77
+ nonce: payload.nonce,
78
+ method: payload.method,
79
+ specVersion: payload.specVersion,
80
+ transactionVersion: payload.transactionVersion,
81
+ metadataHash: payload.metadataHash,
82
+ tip: payload.tip,
83
+ assetId: payload.assetId,
84
+ mode: payload.mode,
85
+ withSignedTransaction: payload.withSignedTransaction,
86
+ signedExtensions: payload.signedExtensions,
87
+ version: payload.version,
88
+ },
89
+ };
90
+ const response = await hostApi.signPayloadWithLegacyAccount(enumValue('v1', codecPayload));
91
+ return response.match(response => {
92
+ assertEnumVariant(response, 'v1', UNSUPPORTED_VERSION_ERROR);
93
+ return {
94
+ id: 0,
95
+ signature: response.value.signature,
96
+ signedTransaction: response.value.signedTransaction,
97
+ };
98
+ }, err => {
99
+ assertEnumVariant(err, 'v1', UNSUPPORTED_VERSION_ERROR);
100
+ throw err.value;
101
+ });
102
+ },
103
+ async createTransaction(payload) {
104
+ if (payload.version !== 1) {
105
+ throw new Error(`Signer support only v1 transaction, got version = ${payload.version}`);
106
+ }
107
+ const { signer } = payload;
108
+ if (!signer) {
109
+ throw new Error("Signer can't route transaction to the right account without signer hint.");
110
+ }
111
+ const checkGenesis = payload.extensions.find(x => x.id === 'CheckGenesis');
112
+ if (!checkGenesis) {
113
+ throw new Error("Can't find genesis hash on transaction");
114
+ }
115
+ const possibleAccountId = accountId.enc(signer);
116
+ const response = await hostApi.createTransactionWithLegacyAccount(enumValue('v1', {
117
+ signer: possibleAccountId,
118
+ genesisHash: fromHex(checkGenesis.additionalSigned),
119
+ callData: fromHex(payload.callData),
120
+ txExtVersion: payload.txExtVersion,
121
+ extensions: payload.extensions.map(e => ({
122
+ id: e.id,
123
+ additionalSigned: fromHex(e.additionalSigned),
124
+ extra: fromHex(e.extra),
125
+ })),
126
+ }));
127
+ return response.match(response => {
128
+ assertEnumVariant(response, 'v1', UNSUPPORTED_VERSION_ERROR);
129
+ return toHex(response.value);
130
+ }, err => {
131
+ assertEnumVariant(err, 'v1', UNSUPPORTED_VERSION_ERROR);
132
+ throw err.value;
133
+ });
134
+ },
135
+ },
136
+ };
137
+ }
138
+ return enable;
139
+ }
140
+ export async function injectSpektrExtension(transport = sandboxTransport) {
141
+ if (!transport)
142
+ return false;
143
+ try {
144
+ const enable = await createLegacyExtensionEnableFactory(transport);
145
+ if (enable) {
146
+ injectExtension(enable, { name: SpektrExtensionName, version: Version });
147
+ return true;
148
+ }
149
+ else {
150
+ return false;
151
+ }
152
+ }
153
+ catch (e) {
154
+ transport.provider.logger.error('Error injecting extension', e);
155
+ return false;
156
+ }
157
+ }
@@ -0,0 +1,18 @@
1
+ export declare const createLocalStorage: (transport?: import("@novasamatech/host-api").Transport) => {
2
+ clear(key: string): Promise<undefined>;
3
+ readBytes(key: string): Promise<Uint8Array<ArrayBufferLike> | undefined>;
4
+ writeBytes(key: string, value: Uint8Array): Promise<undefined>;
5
+ readString(key: string): Promise<string>;
6
+ writeString(key: string, value: string): Promise<undefined>;
7
+ readJSON(key: string): Promise<any>;
8
+ writeJSON(key: string, value: unknown): Promise<undefined>;
9
+ };
10
+ export declare const hostLocalStorage: {
11
+ clear(key: string): Promise<undefined>;
12
+ readBytes(key: string): Promise<Uint8Array<ArrayBufferLike> | undefined>;
13
+ writeBytes(key: string, value: Uint8Array): Promise<undefined>;
14
+ readString(key: string): Promise<string>;
15
+ writeString(key: string, value: string): Promise<undefined>;
16
+ readJSON(key: string): Promise<any>;
17
+ writeJSON(key: string, value: unknown): Promise<undefined>;
18
+ };
@@ -0,0 +1,44 @@
1
+ import { createHostApi, enumValue } from '@novasamatech/host-api';
2
+ import { resultToPromise, unwrapVersionedResult } from './helpers.js';
3
+ import { sandboxTransport } from './sandboxTransport.js';
4
+ export const createLocalStorage = (transport = sandboxTransport) => {
5
+ const supportedVersion = 'v1';
6
+ const hostApi = createHostApi(transport);
7
+ const textEncoder = new TextEncoder();
8
+ const textDecoder = new TextDecoder();
9
+ function readBytes(key) {
10
+ return resultToPromise(unwrapVersionedResult(supportedVersion, hostApi.localStorageRead(enumValue(supportedVersion, key))));
11
+ }
12
+ function writeBytes(key, value) {
13
+ return resultToPromise(unwrapVersionedResult(supportedVersion, hostApi.localStorageWrite(enumValue(supportedVersion, [key, value]))));
14
+ }
15
+ function clearKey(key) {
16
+ return resultToPromise(unwrapVersionedResult(supportedVersion, hostApi.localStorageClear(enumValue(supportedVersion, key))));
17
+ }
18
+ return {
19
+ async clear(key) {
20
+ return clearKey(key);
21
+ },
22
+ async readBytes(key) {
23
+ return readBytes(key);
24
+ },
25
+ async writeBytes(key, value) {
26
+ return writeBytes(key, value);
27
+ },
28
+ async readString(key) {
29
+ return readBytes(key).then(bytes => textDecoder.decode(bytes));
30
+ },
31
+ async writeString(key, value) {
32
+ return writeBytes(key, textEncoder.encode(value));
33
+ },
34
+ async readJSON(key) {
35
+ return readBytes(key)
36
+ .then(bytes => textDecoder.decode(bytes))
37
+ .then(JSON.parse);
38
+ },
39
+ async writeJSON(key, value) {
40
+ return writeBytes(key, textEncoder.encode(JSON.stringify(value)));
41
+ },
42
+ };
43
+ };
44
+ export const hostLocalStorage = createLocalStorage();
@@ -0,0 +1,7 @@
1
+ import type { ConnectionStatus, Transport } from '@novasamatech/host-api';
2
+ export declare function createMetaProvider(transport?: Transport): {
3
+ subscribeConnectionStatus(callback: (connectionStatus: ConnectionStatus) => void): VoidFunction;
4
+ };
5
+ export declare const metaProvider: {
6
+ subscribeConnectionStatus(callback: (connectionStatus: ConnectionStatus) => void): VoidFunction;
7
+ };
@@ -0,0 +1,24 @@
1
+ import { sandboxTransport } from './sandboxTransport.js';
2
+ export function createMetaProvider(transport = sandboxTransport) {
3
+ // if (transport.isCorrectEnvironment() && typeof window !== 'undefined') {
4
+ // const getUrl = () => {
5
+ // return window.location.pathname + window.location.hash + window.location.search;
6
+ // };
7
+ //
8
+ // window.addEventListener('hashchange', () => {
9
+ // transport.postMessage('_', { tag: 'locationChangedV1', value: getUrl() });
10
+ // });
11
+ //
12
+ // window.addEventListener('popstate', () => {
13
+ // transport.postMessage('_', { tag: 'locationChangedV1', value: getUrl() });
14
+ // });
15
+ //
16
+ // transport.postMessage('_', { tag: 'locationChangedV1', value: getUrl() });
17
+ // }
18
+ return {
19
+ subscribeConnectionStatus(callback) {
20
+ return transport.onConnectionStatusChange(callback);
21
+ },
22
+ };
23
+ }
24
+ export const metaProvider = createMetaProvider();
@@ -0,0 +1,15 @@
1
+ export type NotificationId = number;
2
+ export type PushNotificationInput = {
3
+ text: string;
4
+ deeplink?: string;
5
+ /** Unix timestamp in milliseconds (UTC). Omit for immediate delivery. Past values fire immediately. */
6
+ scheduledAt?: number;
7
+ };
8
+ export declare const createNotificationManager: (transport?: import("@novasamatech/host-api").Transport) => {
9
+ push({ text, deeplink, scheduledAt }: PushNotificationInput): Promise<NotificationId>;
10
+ cancel(id: NotificationId): Promise<void>;
11
+ };
12
+ export declare const notificationManager: {
13
+ push({ text, deeplink, scheduledAt }: PushNotificationInput): Promise<NotificationId>;
14
+ cancel(id: NotificationId): Promise<void>;
15
+ };
@@ -0,0 +1,20 @@
1
+ import { createHostApi, enumValue } from '@novasamatech/host-api';
2
+ import { resultToPromise, unwrapVersionedResult } from './helpers.js';
3
+ import { sandboxTransport } from './sandboxTransport.js';
4
+ export const createNotificationManager = (transport = sandboxTransport) => {
5
+ const supportedVersion = 'v1';
6
+ const hostApi = createHostApi(transport);
7
+ return {
8
+ push({ text, deeplink, scheduledAt }) {
9
+ return resultToPromise(unwrapVersionedResult(supportedVersion, hostApi.pushNotification(enumValue(supportedVersion, {
10
+ text,
11
+ deeplink,
12
+ scheduledAt: scheduledAt === undefined ? undefined : BigInt(scheduledAt),
13
+ }))));
14
+ },
15
+ cancel(id) {
16
+ return resultToPromise(unwrapVersionedResult(supportedVersion, hostApi.pushNotificationCancel(enumValue(supportedVersion, id))));
17
+ },
18
+ };
19
+ };
20
+ export const notificationManager = createNotificationManager();
@@ -0,0 +1,7 @@
1
+ import type { HexString, Transport } from '@novasamatech/host-api';
2
+ import type { JsonRpcProvider } from 'polkadot-api';
3
+ type InternalParams = {
4
+ transport?: Transport;
5
+ };
6
+ export declare function createPapiProvider(genesisHash: HexString, __fallback?: JsonRpcProvider, internal?: InternalParams): JsonRpcProvider;
7
+ export {};
@@ -0,0 +1,349 @@
1
+ import { createHostApi, enumValue, unwrapResultOrThrow } from '@novasamatech/host-api';
2
+ import { getSyncProvider } from '@polkadot-api/json-rpc-provider-proxy';
3
+ import { sandboxTransport } from './sandboxTransport.js';
4
+ export function createPapiProvider(genesisHash,
5
+ // for testing purposes only, should not be used in real production code
6
+ __fallback, internal) {
7
+ const version = 'v1';
8
+ const transport = internal?.transport ?? sandboxTransport;
9
+ if (!transport.isCorrectEnvironment()) {
10
+ throw new Error('PapiProvider can only be used in a product environment');
11
+ }
12
+ const hostApi = createHostApi(transport);
13
+ const typedProvider = onMessage => {
14
+ const activeFollows = new Map();
15
+ const activeBroadcasts = new Set();
16
+ let nextSubId = 0;
17
+ function getNextSubId() {
18
+ return `follow_${nextSubId++}`;
19
+ }
20
+ function sendJsonRpcResponse(id, result) {
21
+ onMessage({ jsonrpc: '2.0', id, result });
22
+ }
23
+ function sendJsonRpcError(id, code, message) {
24
+ onMessage({ jsonrpc: '2.0', id, error: { code, message } });
25
+ }
26
+ function sendFollowEvent(syntheticSubId, event) {
27
+ onMessage({
28
+ jsonrpc: '2.0',
29
+ method: 'chainHead_v1_followEvent',
30
+ params: { subscription: syntheticSubId, result: event },
31
+ });
32
+ }
33
+ function convertTypedEventToJsonRpc(event) {
34
+ switch (event.tag) {
35
+ case 'Initialized': {
36
+ const v = event.value;
37
+ return {
38
+ event: 'initialized',
39
+ finalizedBlockHashes: v.finalizedBlockHashes,
40
+ finalizedBlockRuntime: convertRuntimeToJsonRpc(v.finalizedBlockRuntime),
41
+ };
42
+ }
43
+ case 'NewBlock': {
44
+ const v = event.value;
45
+ return {
46
+ event: 'newBlock',
47
+ blockHash: v.blockHash,
48
+ parentBlockHash: v.parentBlockHash,
49
+ newRuntime: convertRuntimeToJsonRpc(v.newRuntime),
50
+ };
51
+ }
52
+ case 'BestBlockChanged': {
53
+ const v = event.value;
54
+ return { event: 'bestBlockChanged', bestBlockHash: v.bestBlockHash };
55
+ }
56
+ case 'Finalized': {
57
+ const v = event.value;
58
+ return {
59
+ event: 'finalized',
60
+ finalizedBlockHashes: v.finalizedBlockHashes,
61
+ prunedBlockHashes: v.prunedBlockHashes,
62
+ };
63
+ }
64
+ case 'OperationBodyDone': {
65
+ const v = event.value;
66
+ return { event: 'operationBodyDone', operationId: v.operationId, value: v.value };
67
+ }
68
+ case 'OperationCallDone': {
69
+ const v = event.value;
70
+ return { event: 'operationCallDone', operationId: v.operationId, output: v.output };
71
+ }
72
+ case 'OperationStorageItems': {
73
+ const v = event.value;
74
+ return {
75
+ event: 'operationStorageItems',
76
+ operationId: v.operationId,
77
+ items: v.items,
78
+ };
79
+ }
80
+ case 'OperationStorageDone': {
81
+ const v = event.value;
82
+ return { event: 'operationStorageDone', operationId: v.operationId };
83
+ }
84
+ case 'OperationWaitingForContinue': {
85
+ const v = event.value;
86
+ return { event: 'operationWaitingForContinue', operationId: v.operationId };
87
+ }
88
+ case 'OperationInaccessible': {
89
+ const v = event.value;
90
+ return { event: 'operationInaccessible', operationId: v.operationId };
91
+ }
92
+ case 'OperationError': {
93
+ const v = event.value;
94
+ return { event: 'operationError', operationId: v.operationId, error: v.error };
95
+ }
96
+ case 'Stop':
97
+ return { event: 'stop' };
98
+ default:
99
+ return { event: 'stop' };
100
+ }
101
+ }
102
+ function convertRuntimeToJsonRpc(runtime) {
103
+ if (!runtime || typeof runtime !== 'object')
104
+ return null;
105
+ const rt = runtime;
106
+ if (rt.tag === 'Valid') {
107
+ const spec = rt.value;
108
+ const apisObj = {};
109
+ for (const [name, ver] of spec.apis) {
110
+ apisObj[name] = ver;
111
+ }
112
+ return {
113
+ type: 'valid',
114
+ spec: {
115
+ specName: spec.specName,
116
+ implName: spec.implName,
117
+ specVersion: spec.specVersion,
118
+ implVersion: spec.implVersion,
119
+ transactionVersion: spec.transactionVersion,
120
+ apis: apisObj,
121
+ },
122
+ };
123
+ }
124
+ if (rt.tag === 'Invalid') {
125
+ const v = rt.value;
126
+ return { type: 'invalid', error: v.error };
127
+ }
128
+ return null;
129
+ }
130
+ function convertStorageTypeToTyped(type) {
131
+ const map = {
132
+ value: 'Value',
133
+ hash: 'Hash',
134
+ closestDescendantMerkleValue: 'ClosestDescendantMerkleValue',
135
+ descendantsValues: 'DescendantsValues',
136
+ descendantsHashes: 'DescendantsHashes',
137
+ };
138
+ return map[type] ?? 'Value';
139
+ }
140
+ function convertOperationResultToJsonRpc(result) {
141
+ if (result.tag === 'Started') {
142
+ const v = result.value;
143
+ return { result: 'started', operationId: v.operationId };
144
+ }
145
+ return { result: 'limitReached' };
146
+ }
147
+ function handleMessage(message) {
148
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
149
+ const id = message.id;
150
+ const { method, params } = message;
151
+ switch (method) {
152
+ case 'chainHead_v1_follow': {
153
+ const [withRuntime] = params;
154
+ const syntheticSubId = getNextSubId();
155
+ const subscription = hostApi.chainHeadFollowSubscribe(enumValue(version, { genesisHash, withRuntime }), payload => {
156
+ if (payload.tag !== version)
157
+ return;
158
+ const typed = payload.value;
159
+ // On Stop, release the host-api subscription BEFORE forwarding
160
+ // the event. The consumer's substrate-client refollows
161
+ // synchronously inside the event handler; transport.subscribe
162
+ // dedupes by (method, payload-hash), so unless this dead
163
+ // subscription is gone first, the refollow's subscribe shares
164
+ // it and never reaches the host — events stop flowing.
165
+ if (typed.tag === 'Stop' && activeFollows.delete(syntheticSubId)) {
166
+ subscription.unsubscribe();
167
+ }
168
+ sendFollowEvent(syntheticSubId, convertTypedEventToJsonRpc(typed));
169
+ });
170
+ activeFollows.set(syntheticSubId, { syntheticSubId, subscription, genesisHash });
171
+ sendJsonRpcResponse(id, syntheticSubId);
172
+ break;
173
+ }
174
+ case 'chainHead_v1_unfollow': {
175
+ const [followSubId] = params;
176
+ const follow = activeFollows.get(followSubId);
177
+ if (follow) {
178
+ follow.subscription.unsubscribe();
179
+ activeFollows.delete(followSubId);
180
+ }
181
+ sendJsonRpcResponse(id, null);
182
+ break;
183
+ }
184
+ case 'chainHead_v1_header': {
185
+ const [followSubId, hash] = params;
186
+ hostApi.chainHeadHeader(enumValue(version, { genesisHash, followSubscriptionId: followSubId, hash })).match(result => sendJsonRpcResponse(id, result.value), error => sendJsonRpcError(id, -32603, error.value.payload.reason));
187
+ break;
188
+ }
189
+ case 'chainHead_v1_body': {
190
+ const [followSubId, hash] = params;
191
+ hostApi.chainHeadBody(enumValue(version, { genesisHash, followSubscriptionId: followSubId, hash })).match(result => sendJsonRpcResponse(id, convertOperationResultToJsonRpc(result.value)), error => sendJsonRpcError(id, -32603, error.value.payload.reason));
192
+ break;
193
+ }
194
+ case 'chainHead_v1_storage': {
195
+ const [followSubId, hash, items, childTrie] = params;
196
+ const typedItems = items.map(item => ({
197
+ key: item.key,
198
+ type: convertStorageTypeToTyped(item.type),
199
+ }));
200
+ hostApi
201
+ .chainHeadStorage(enumValue(version, {
202
+ genesisHash,
203
+ followSubscriptionId: followSubId,
204
+ hash,
205
+ items: typedItems,
206
+ childTrie,
207
+ }))
208
+ .match(result => sendJsonRpcResponse(id, convertOperationResultToJsonRpc(result.value)), error => sendJsonRpcError(id, -32603, error.value.payload.reason));
209
+ break;
210
+ }
211
+ case 'chainHead_v1_call': {
212
+ const [followSubId, hash, fn, callParameters] = params;
213
+ hostApi
214
+ .chainHeadCall(enumValue(version, {
215
+ genesisHash,
216
+ followSubscriptionId: followSubId,
217
+ hash,
218
+ function: fn,
219
+ callParameters,
220
+ }))
221
+ .match(result => sendJsonRpcResponse(id, convertOperationResultToJsonRpc(result.value)), error => sendJsonRpcError(id, -32603, error.value.payload.reason));
222
+ break;
223
+ }
224
+ case 'chainHead_v1_unpin': {
225
+ const [followSubId, hashOrHashes] = params;
226
+ const hashes = Array.isArray(hashOrHashes) ? hashOrHashes : [hashOrHashes];
227
+ hostApi.chainHeadUnpin(enumValue(version, { genesisHash, followSubscriptionId: followSubId, hashes })).match(() => sendJsonRpcResponse(id, null), error => sendJsonRpcError(id, -32603, error.value.payload.reason));
228
+ break;
229
+ }
230
+ case 'chainHead_v1_continue': {
231
+ const [followSubId, operationId] = params;
232
+ hostApi
233
+ .chainHeadContinue(enumValue(version, { genesisHash, followSubscriptionId: followSubId, operationId }))
234
+ .match(() => sendJsonRpcResponse(id, null), error => sendJsonRpcError(id, -32603, error.value.payload.reason));
235
+ break;
236
+ }
237
+ case 'chainHead_v1_stopOperation': {
238
+ const [followSubId, operationId] = params;
239
+ hostApi
240
+ .chainHeadStopOperation(enumValue(version, { genesisHash, followSubscriptionId: followSubId, operationId }))
241
+ .match(() => sendJsonRpcResponse(id, null), error => sendJsonRpcError(id, -32603, error.value.payload.reason));
242
+ break;
243
+ }
244
+ case 'chainSpec_v1_genesisHash': {
245
+ hostApi.chainSpecGenesisHash(enumValue(version, genesisHash)).match(result => sendJsonRpcResponse(id, result.value), error => sendJsonRpcError(id, -32603, error.value.payload.reason));
246
+ break;
247
+ }
248
+ case 'chainSpec_v1_chainName': {
249
+ hostApi.chainSpecChainName(enumValue(version, genesisHash)).match(result => sendJsonRpcResponse(id, result.value), error => sendJsonRpcError(id, -32603, error.value.payload.reason));
250
+ break;
251
+ }
252
+ case 'chainSpec_v1_properties': {
253
+ hostApi.chainSpecProperties(enumValue(version, genesisHash)).match(result => {
254
+ try {
255
+ sendJsonRpcResponse(id, JSON.parse(result.value));
256
+ }
257
+ catch {
258
+ sendJsonRpcResponse(id, result.value);
259
+ }
260
+ }, error => sendJsonRpcError(id, -32603, error.value.payload.reason));
261
+ break;
262
+ }
263
+ case 'transaction_v1_broadcast': {
264
+ const [transaction] = params;
265
+ hostApi.chainTransactionBroadcast(enumValue(version, { genesisHash, transaction })).match(result => {
266
+ if (result.value !== null) {
267
+ activeBroadcasts.add(result.value);
268
+ }
269
+ sendJsonRpcResponse(id, result.value);
270
+ }, error => sendJsonRpcError(id, -32603, error.value.payload.reason));
271
+ break;
272
+ }
273
+ case 'transaction_v1_stop': {
274
+ const [operationId] = params;
275
+ activeBroadcasts.delete(operationId);
276
+ hostApi.chainTransactionStop(enumValue(version, { genesisHash, operationId })).match(() => sendJsonRpcResponse(id, null), error => sendJsonRpcError(id, -32603, error.value.payload.reason));
277
+ break;
278
+ }
279
+ default: {
280
+ sendJsonRpcError(id, -32601, `Method "${method}" is not supported by HostAPI`);
281
+ break;
282
+ }
283
+ }
284
+ }
285
+ return {
286
+ send(message) {
287
+ handleMessage(message);
288
+ },
289
+ disconnect() {
290
+ for (const follow of activeFollows.values()) {
291
+ follow.subscription.unsubscribe();
292
+ }
293
+ activeFollows.clear();
294
+ for (const operationId of activeBroadcasts) {
295
+ hostApi.chainTransactionStop(enumValue(version, { genesisHash, operationId })).match(() => {
296
+ /* fire-and-forget on disconnect */
297
+ }, () => {
298
+ /* transport may already be torn down */
299
+ });
300
+ }
301
+ activeBroadcasts.clear();
302
+ },
303
+ };
304
+ };
305
+ function checkIfReady() {
306
+ return transport.isReady().then(ready => {
307
+ if (!ready)
308
+ return false;
309
+ return transport
310
+ .request('host_feature_supported', enumValue('v1', enumValue('Chain', genesisHash)))
311
+ .then(payload => {
312
+ switch (payload.tag) {
313
+ case 'v1': {
314
+ return unwrapResultOrThrow(payload.value, e => new Error(e.payload.reason));
315
+ }
316
+ default:
317
+ throw new Error(`Unknown message version ${payload.tag}`);
318
+ }
319
+ })
320
+ .catch(e => {
321
+ transport.provider.logger.error('Error checking chain support', e);
322
+ return false;
323
+ });
324
+ });
325
+ }
326
+ return getSyncProvider(onResult => {
327
+ checkIfReady().then(ready => {
328
+ if (ready) {
329
+ onResult((onMessage, _onHalt) => typedProvider(onMessage));
330
+ }
331
+ else if (__fallback) {
332
+ onResult((onMessage, _onHalt) => __fallback(onMessage));
333
+ }
334
+ else {
335
+ onResult((_onMessage, _onHalt) => ({
336
+ send() {
337
+ transport.provider.logger.error(`Provider for chain ${genesisHash} was not started because Host doesn't support it`);
338
+ },
339
+ disconnect() {
340
+ /* empty */
341
+ },
342
+ }));
343
+ }
344
+ });
345
+ return () => {
346
+ /* empty */
347
+ };
348
+ });
349
+ }