@shapeshiftoss/unchained-client 9.0.3 → 9.2.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.
Files changed (67) hide show
  1. package/dist/evm/avalanche/index.d.ts +2 -0
  2. package/dist/{ethereum → evm/avalanche}/index.js +1 -2
  3. package/dist/evm/avalanche/parser/index.d.ts +5 -0
  4. package/dist/evm/avalanche/parser/index.js +16 -0
  5. package/dist/evm/ethereum/index.d.ts +2 -0
  6. package/dist/evm/ethereum/index.js +18 -0
  7. package/dist/{ethereum → evm/ethereum}/parser/abi/foxyStaking.d.ts +0 -0
  8. package/dist/{ethereum → evm/ethereum}/parser/abi/foxyStaking.js +0 -0
  9. package/dist/{ethereum → evm/ethereum}/parser/abi/shapeShiftRouter.d.ts +0 -0
  10. package/dist/{ethereum → evm/ethereum}/parser/abi/shapeShiftRouter.js +0 -0
  11. package/dist/{ethereum → evm/ethereum}/parser/abi/thor.d.ts +0 -0
  12. package/dist/{ethereum → evm/ethereum}/parser/abi/thor.js +0 -0
  13. package/dist/{ethereum → evm/ethereum}/parser/abi/uniV2.d.ts +0 -0
  14. package/dist/{ethereum → evm/ethereum}/parser/abi/uniV2.js +0 -0
  15. package/dist/{ethereum → evm/ethereum}/parser/abi/uniV2StakingRewards.d.ts +0 -0
  16. package/dist/{ethereum → evm/ethereum}/parser/abi/uniV2StakingRewards.js +0 -0
  17. package/dist/{ethereum → evm/ethereum}/parser/abi/weth.d.ts +0 -0
  18. package/dist/{ethereum → evm/ethereum}/parser/abi/weth.js +0 -0
  19. package/dist/{ethereum → evm/ethereum}/parser/abi/yearnVault.d.ts +0 -0
  20. package/dist/{ethereum → evm/ethereum}/parser/abi/yearnVault.js +0 -0
  21. package/dist/{ethereum → evm/ethereum}/parser/constants.d.ts +0 -0
  22. package/dist/{ethereum → evm/ethereum}/parser/constants.js +0 -0
  23. package/dist/{ethereum → evm/ethereum}/parser/foxy.d.ts +3 -3
  24. package/dist/{ethereum → evm/ethereum}/parser/foxy.js +4 -4
  25. package/dist/evm/ethereum/parser/index.d.ts +5 -0
  26. package/dist/evm/ethereum/parser/index.js +55 -0
  27. package/dist/{ethereum → evm/ethereum}/parser/thor.d.ts +3 -3
  28. package/dist/{ethereum → evm/ethereum}/parser/thor.js +4 -4
  29. package/dist/{ethereum → evm/ethereum}/parser/uniV2.d.ts +3 -3
  30. package/dist/{ethereum → evm/ethereum}/parser/uniV2.js +8 -8
  31. package/dist/{ethereum → evm/ethereum}/parser/weth.d.ts +3 -3
  32. package/dist/{ethereum → evm/ethereum}/parser/weth.js +4 -4
  33. package/dist/{ethereum → evm/ethereum}/parser/yearn.d.ts +3 -3
  34. package/dist/{ethereum → evm/ethereum}/parser/yearn.js +3 -3
  35. package/dist/evm/ethereum/parser/zrx.d.ts +5 -0
  36. package/dist/{ethereum → evm/ethereum}/parser/zrx.js +3 -3
  37. package/dist/evm/index.d.ts +2 -0
  38. package/dist/evm/index.js +28 -0
  39. package/dist/{ethereum → evm}/parser/abi/erc20.d.ts +0 -0
  40. package/dist/{ethereum → evm}/parser/abi/erc20.js +0 -0
  41. package/dist/{ethereum → evm}/parser/erc20.d.ts +3 -4
  42. package/dist/{ethereum → evm}/parser/erc20.js +1 -1
  43. package/dist/evm/parser/index.d.ts +26 -0
  44. package/dist/{ethereum → evm}/parser/index.js +29 -39
  45. package/dist/{ethereum → evm/parser}/types.d.ts +6 -4
  46. package/dist/{ethereum → evm/parser}/types.js +0 -0
  47. package/dist/evm/parser/utils.d.ts +4 -0
  48. package/dist/{ethereum → evm}/parser/utils.js +0 -0
  49. package/dist/generated/avalanche/api.d.ts +766 -0
  50. package/dist/generated/avalanche/api.js +490 -0
  51. package/dist/generated/avalanche/base.d.ts +55 -0
  52. package/dist/generated/avalanche/base.js +64 -0
  53. package/dist/generated/avalanche/common.d.ts +65 -0
  54. package/dist/generated/avalanche/common.js +137 -0
  55. package/dist/generated/avalanche/configuration.d.ts +83 -0
  56. package/dist/generated/avalanche/configuration.js +42 -0
  57. package/dist/generated/avalanche/index.d.ts +13 -0
  58. package/dist/generated/avalanche/index.js +31 -0
  59. package/dist/index.d.ts +3 -3
  60. package/dist/index.js +7 -7
  61. package/dist/websocket.d.ts +11 -3
  62. package/dist/websocket.js +124 -97
  63. package/package.json +1 -1
  64. package/dist/ethereum/index.d.ts +0 -3
  65. package/dist/ethereum/parser/index.d.ts +0 -23
  66. package/dist/ethereum/parser/utils.d.ts +0 -3
  67. package/dist/ethereum/parser/zrx.d.ts +0 -5
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /**
5
+ * @shapeshiftoss/avalanche-api
6
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
7
+ *
8
+ * The version of the OpenAPI document: 7.0.0
9
+ *
10
+ *
11
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
12
+ * https://openapi-generator.tech
13
+ * Do not edit the class manually.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.createRequestFunction = exports.toPathString = exports.serializeDataIfNeeded = exports.setSearchParams = exports.setOAuthToObject = exports.setBearerAuthToObject = exports.setBasicAuthToObject = exports.setApiKeyToObject = exports.assertParamExists = exports.DUMMY_BASE_URL = void 0;
17
+ const base_1 = require("./base");
18
+ /**
19
+ *
20
+ * @export
21
+ */
22
+ exports.DUMMY_BASE_URL = 'https://example.com';
23
+ /**
24
+ *
25
+ * @throws {RequiredError}
26
+ * @export
27
+ */
28
+ const assertParamExists = function (functionName, paramName, paramValue) {
29
+ if (paramValue === null || paramValue === undefined) {
30
+ throw new base_1.RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
31
+ }
32
+ };
33
+ exports.assertParamExists = assertParamExists;
34
+ /**
35
+ *
36
+ * @export
37
+ */
38
+ const setApiKeyToObject = async function (object, keyParamName, configuration) {
39
+ if (configuration && configuration.apiKey) {
40
+ const localVarApiKeyValue = typeof configuration.apiKey === 'function'
41
+ ? await configuration.apiKey(keyParamName)
42
+ : await configuration.apiKey;
43
+ object[keyParamName] = localVarApiKeyValue;
44
+ }
45
+ };
46
+ exports.setApiKeyToObject = setApiKeyToObject;
47
+ /**
48
+ *
49
+ * @export
50
+ */
51
+ const setBasicAuthToObject = function (object, configuration) {
52
+ if (configuration && (configuration.username || configuration.password)) {
53
+ object["auth"] = { username: configuration.username, password: configuration.password };
54
+ }
55
+ };
56
+ exports.setBasicAuthToObject = setBasicAuthToObject;
57
+ /**
58
+ *
59
+ * @export
60
+ */
61
+ const setBearerAuthToObject = async function (object, configuration) {
62
+ if (configuration && configuration.accessToken) {
63
+ const accessToken = typeof configuration.accessToken === 'function'
64
+ ? await configuration.accessToken()
65
+ : await configuration.accessToken;
66
+ object["Authorization"] = "Bearer " + accessToken;
67
+ }
68
+ };
69
+ exports.setBearerAuthToObject = setBearerAuthToObject;
70
+ /**
71
+ *
72
+ * @export
73
+ */
74
+ const setOAuthToObject = async function (object, name, scopes, configuration) {
75
+ if (configuration && configuration.accessToken) {
76
+ const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
77
+ ? await configuration.accessToken(name, scopes)
78
+ : await configuration.accessToken;
79
+ object["Authorization"] = "Bearer " + localVarAccessTokenValue;
80
+ }
81
+ };
82
+ exports.setOAuthToObject = setOAuthToObject;
83
+ /**
84
+ *
85
+ * @export
86
+ */
87
+ const setSearchParams = function (url, ...objects) {
88
+ const searchParams = new URLSearchParams(url.search);
89
+ for (const object of objects) {
90
+ for (const key in object) {
91
+ if (Array.isArray(object[key])) {
92
+ searchParams.delete(key);
93
+ for (const item of object[key]) {
94
+ searchParams.append(key, item);
95
+ }
96
+ }
97
+ else {
98
+ searchParams.set(key, object[key]);
99
+ }
100
+ }
101
+ }
102
+ url.search = searchParams.toString();
103
+ };
104
+ exports.setSearchParams = setSearchParams;
105
+ /**
106
+ *
107
+ * @export
108
+ */
109
+ const serializeDataIfNeeded = function (value, requestOptions, configuration) {
110
+ const nonString = typeof value !== 'string';
111
+ const needsSerialization = nonString && configuration && configuration.isJsonMime
112
+ ? configuration.isJsonMime(requestOptions.headers['Content-Type'])
113
+ : nonString;
114
+ return needsSerialization
115
+ ? JSON.stringify(value !== undefined ? value : {})
116
+ : (value || "");
117
+ };
118
+ exports.serializeDataIfNeeded = serializeDataIfNeeded;
119
+ /**
120
+ *
121
+ * @export
122
+ */
123
+ const toPathString = function (url) {
124
+ return url.pathname + url.search + url.hash;
125
+ };
126
+ exports.toPathString = toPathString;
127
+ /**
128
+ *
129
+ * @export
130
+ */
131
+ const createRequestFunction = function (axiosArgs, globalAxios, BASE_PATH, configuration) {
132
+ return (axios = globalAxios, basePath = BASE_PATH) => {
133
+ const axiosRequestArgs = { ...axiosArgs.options, url: ((configuration === null || configuration === void 0 ? void 0 : configuration.basePath) || basePath) + axiosArgs.url };
134
+ return axios.request(axiosRequestArgs);
135
+ };
136
+ };
137
+ exports.createRequestFunction = createRequestFunction;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @shapeshiftoss/avalanche-api
3
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
4
+ *
5
+ * The version of the OpenAPI document: 7.0.0
6
+ *
7
+ *
8
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
9
+ * https://openapi-generator.tech
10
+ * Do not edit the class manually.
11
+ */
12
+ export interface ConfigurationParameters {
13
+ apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
14
+ username?: string;
15
+ password?: string;
16
+ accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
17
+ basePath?: string;
18
+ baseOptions?: any;
19
+ formDataCtor?: new () => any;
20
+ }
21
+ export declare class Configuration {
22
+ /**
23
+ * parameter for apiKey security
24
+ * @param name security name
25
+ * @memberof Configuration
26
+ */
27
+ apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
28
+ /**
29
+ * parameter for basic security
30
+ *
31
+ * @type {string}
32
+ * @memberof Configuration
33
+ */
34
+ username?: string;
35
+ /**
36
+ * parameter for basic security
37
+ *
38
+ * @type {string}
39
+ * @memberof Configuration
40
+ */
41
+ password?: string;
42
+ /**
43
+ * parameter for oauth2 security
44
+ * @param name security name
45
+ * @param scopes oauth2 scope
46
+ * @memberof Configuration
47
+ */
48
+ accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
49
+ /**
50
+ * override base path
51
+ *
52
+ * @type {string}
53
+ * @memberof Configuration
54
+ */
55
+ basePath?: string;
56
+ /**
57
+ * base options for axios calls
58
+ *
59
+ * @type {any}
60
+ * @memberof Configuration
61
+ */
62
+ baseOptions?: any;
63
+ /**
64
+ * The FormData constructor that will be used to create multipart form data
65
+ * requests. You can inject this here so that execution environments that
66
+ * do not support the FormData class can still run the generated client.
67
+ *
68
+ * @type {new () => FormData}
69
+ */
70
+ formDataCtor?: new () => any;
71
+ constructor(param?: ConfigurationParameters);
72
+ /**
73
+ * Check if the given MIME is a JSON MIME.
74
+ * JSON MIME examples:
75
+ * application/json
76
+ * application/json; charset=UTF8
77
+ * APPLICATION/JSON
78
+ * application/vnd.company+json
79
+ * @param mime - MIME (Multipurpose Internet Mail Extensions)
80
+ * @return True if the given MIME is JSON, false otherwise.
81
+ */
82
+ isJsonMime(mime: string): boolean;
83
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /**
5
+ * @shapeshiftoss/avalanche-api
6
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
7
+ *
8
+ * The version of the OpenAPI document: 7.0.0
9
+ *
10
+ *
11
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
12
+ * https://openapi-generator.tech
13
+ * Do not edit the class manually.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.Configuration = void 0;
17
+ class Configuration {
18
+ constructor(param = {}) {
19
+ this.apiKey = param.apiKey;
20
+ this.username = param.username;
21
+ this.password = param.password;
22
+ this.accessToken = param.accessToken;
23
+ this.basePath = param.basePath;
24
+ this.baseOptions = param.baseOptions;
25
+ this.formDataCtor = param.formDataCtor;
26
+ }
27
+ /**
28
+ * Check if the given MIME is a JSON MIME.
29
+ * JSON MIME examples:
30
+ * application/json
31
+ * application/json; charset=UTF8
32
+ * APPLICATION/JSON
33
+ * application/vnd.company+json
34
+ * @param mime - MIME (Multipurpose Internet Mail Extensions)
35
+ * @return True if the given MIME is JSON, false otherwise.
36
+ */
37
+ isJsonMime(mime) {
38
+ const jsonMime = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
39
+ return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
40
+ }
41
+ }
42
+ exports.Configuration = Configuration;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @shapeshiftoss/avalanche-api
3
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
4
+ *
5
+ * The version of the OpenAPI document: 7.0.0
6
+ *
7
+ *
8
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
9
+ * https://openapi-generator.tech
10
+ * Do not edit the class manually.
11
+ */
12
+ export * from "./api";
13
+ export * from "./configuration";
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /**
5
+ * @shapeshiftoss/avalanche-api
6
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
7
+ *
8
+ * The version of the OpenAPI document: 7.0.0
9
+ *
10
+ *
11
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
12
+ * https://openapi-generator.tech
13
+ * Do not edit the class manually.
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
27
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ __exportStar(require("./api"), exports);
31
+ __exportStar(require("./configuration"), exports);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
+ export * from './types';
2
+ export * from './evm';
3
+ export * as ws from './websocket';
1
4
  export * as bitcoin from './bitcoin';
2
- export * as ethereum from './ethereum';
3
5
  export * as cosmos from './cosmos';
4
6
  export * as osmosis from './osmosis';
5
- export * as ws from './websocket';
6
- export * from './types';
package/dist/index.js CHANGED
@@ -15,6 +15,9 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
+ };
18
21
  var __importStar = (this && this.__importStar) || function (mod) {
19
22
  if (mod && mod.__esModule) return mod;
20
23
  var result = {};
@@ -22,14 +25,11 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
25
  __setModuleDefault(result, mod);
23
26
  return result;
24
27
  };
25
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
26
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
27
- };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.ws = exports.osmosis = exports.cosmos = exports.ethereum = exports.bitcoin = void 0;
29
+ exports.osmosis = exports.cosmos = exports.bitcoin = exports.ws = void 0;
30
+ __exportStar(require("./types"), exports);
31
+ __exportStar(require("./evm"), exports);
32
+ exports.ws = __importStar(require("./websocket"));
30
33
  exports.bitcoin = __importStar(require("./bitcoin"));
31
- exports.ethereum = __importStar(require("./ethereum"));
32
34
  exports.cosmos = __importStar(require("./cosmos"));
33
35
  exports.osmosis = __importStar(require("./osmosis"));
34
- exports.ws = __importStar(require("./websocket"));
35
- __exportStar(require("./types"), exports);
@@ -2,9 +2,9 @@
2
2
  import type { ErrorResponse, Topics, TxsTopicData } from '@shapeshiftoss/common-api';
3
3
  import WebSocket from 'isomorphic-ws';
4
4
  export interface Connection {
5
- ws: WebSocket;
5
+ ws?: WebSocket;
6
6
  pingTimeout?: NodeJS.Timeout;
7
- interval: NodeJS.Timeout;
7
+ interval?: NodeJS.Timeout;
8
8
  }
9
9
  export interface TransactionMessage<T> {
10
10
  address: string;
@@ -12,7 +12,7 @@ export interface TransactionMessage<T> {
12
12
  subscriptionId: string;
13
13
  }
14
14
  export interface TxsParams<T> {
15
- data: TxsTopicData | undefined;
15
+ data: TxsTopicData;
16
16
  onMessage: (message: TransactionMessage<T>) => void;
17
17
  onError?: (err: ErrorResponse) => void;
18
18
  }
@@ -21,9 +21,17 @@ export declare class Client<T> {
21
21
  private readonly url;
22
22
  private readonly connections;
23
23
  private readonly pingInterval;
24
+ private readonly retryAttempts;
25
+ private retryCount;
24
26
  private txs;
25
27
  constructor(url: string);
28
+ private initialize;
29
+ private onOpen;
30
+ private onClose;
31
+ private onError;
32
+ private onMessage;
26
33
  private heartbeat;
34
+ private getDefaultSubscriptionId;
27
35
  /**
28
36
  * Subscribe to transaction history and updates for newly confirmed and pending transactions.
29
37
  *
package/dist/websocket.js CHANGED
@@ -10,25 +10,119 @@ const logger = new logger_1.Logger({
10
10
  namespace: ['unchained-client', 'websocket'],
11
11
  level: process.env.LOG_LEVEL
12
12
  });
13
+ const NORMAL_CLOSURE_CODE = 1000;
13
14
  class Client {
14
15
  constructor(url) {
15
16
  this.pingInterval = 10000;
17
+ this.retryAttempts = 5;
18
+ this.retryCount = 0;
16
19
  this.txs = {};
17
20
  this.url = url;
18
21
  this.connections = {
19
22
  txs: undefined
20
23
  };
21
24
  }
25
+ async initialize() {
26
+ const ws = new isomorphic_ws_1.default(this.url);
27
+ this.connections.txs = { ws };
28
+ // wait for connection to open with handshake timeout
29
+ await new Promise((resolve, reject) => {
30
+ setTimeout(() => reject(new Error('timeout while trying to connect')), 5000);
31
+ ws.onopen = ({ target }) => this.onOpen(target, resolve, reject);
32
+ ws.onclose = (event) => this.onClose(event, resolve, reject);
33
+ ws.onerror = (event) => this.onError(event);
34
+ ws.onmessage = (event) => this.onMessage(event);
35
+ });
36
+ }
37
+ onOpen(ws, resolve, reject) {
38
+ this.retryCount = 0;
39
+ const interval = setInterval(() => {
40
+ // browsers do not support ping/pong frame, send ping payload instead
41
+ const payload = { subscriptionId: '', method: 'ping' };
42
+ ws.send(JSON.stringify(payload));
43
+ }, this.pingInterval);
44
+ this.heartbeat('txs');
45
+ if (this.connections.txs) {
46
+ this.connections.txs.interval = interval;
47
+ }
48
+ else {
49
+ this.connections.txs = { ws, interval };
50
+ }
51
+ try {
52
+ // subscribe to all subscriptions
53
+ Object.entries(this.txs).forEach(([id, { data }]) => {
54
+ const payload = { subscriptionId: id, method: 'subscribe', data };
55
+ ws.send(JSON.stringify(payload));
56
+ });
57
+ resolve(true);
58
+ }
59
+ catch (err) {
60
+ reject(err);
61
+ }
62
+ }
63
+ onClose(event, resolve, reject) {
64
+ var _a, _b;
65
+ logger.warn({ fn: 'onClose', code: event.code, reason: event.reason, type: event.type }, 'websocket closed');
66
+ ((_a = this.connections.txs) === null || _a === void 0 ? void 0 : _a.pingTimeout) && clearTimeout(this.connections.txs.pingTimeout);
67
+ ((_b = this.connections.txs) === null || _b === void 0 ? void 0 : _b.interval) && clearInterval(this.connections.txs.interval);
68
+ // attempt reconnect logic on non standard exit codes
69
+ if (event.code !== NORMAL_CLOSURE_CODE) {
70
+ if (++this.retryCount >= this.retryAttempts) {
71
+ reject(new Error('failed to reconnect, connection closed'));
72
+ return;
73
+ }
74
+ setTimeout(() => this.initialize(), 500 * this.retryCount ** 2);
75
+ }
76
+ resolve(false);
77
+ }
78
+ onError(event) {
79
+ if (!event.message)
80
+ return;
81
+ logger.error({ fn: 'onError', err: event.error, message: event.message, type: event.type }, 'websocket error');
82
+ // send connection errors to all subscription onError handlers
83
+ Object.entries(this.txs).forEach(([id, sub]) => {
84
+ var _a;
85
+ (_a = sub.onError) === null || _a === void 0 ? void 0 : _a.call(sub, { subscriptionId: id, type: 'error', message: event.message });
86
+ });
87
+ }
88
+ onMessage(event) {
89
+ var _a, _b, _c, _d;
90
+ if (event.type !== 'message')
91
+ return;
92
+ // trigger heartbeat keep alive on pong response
93
+ if (event.data === 'pong') {
94
+ this.heartbeat('txs');
95
+ return;
96
+ }
97
+ try {
98
+ const message = JSON.parse(event.data.toString());
99
+ const subscriptionId = this.getDefaultSubscriptionId();
100
+ // narrow type to ErrorResponse if key `type` exists and forward to correct onError handler
101
+ if ('type' in message) {
102
+ return (_b = (_a = this.txs[message.subscriptionId || subscriptionId]) === null || _a === void 0 ? void 0 : _a.onError) === null || _b === void 0 ? void 0 : _b.call(_a, message);
103
+ }
104
+ // forward the transaction message to the correct onMessage handler
105
+ (_d = (_c = this.txs[message.subscriptionId || subscriptionId]) === null || _c === void 0 ? void 0 : _c.onMessage) === null || _d === void 0 ? void 0 : _d.call(_c, message);
106
+ }
107
+ catch (err) {
108
+ logger.warn(`failed to handle onmessage event: ${JSON.stringify(event)}: ${err}`);
109
+ }
110
+ }
22
111
  heartbeat(topic) {
23
112
  const connection = this.connections[topic];
24
113
  if (!connection)
25
114
  return;
26
115
  connection.pingTimeout && clearTimeout(connection.pingTimeout);
27
116
  connection.pingTimeout = setTimeout(() => {
117
+ var _a;
28
118
  logger.warn({ fn: 'pingTimeout' }, `${topic} heartbeat failed`);
29
- connection === null || connection === void 0 ? void 0 : connection.ws.close();
119
+ (_a = connection.ws) === null || _a === void 0 ? void 0 : _a.close();
30
120
  }, this.pingInterval + 5000);
31
121
  }
122
+ // for websocket connections that do not return the subscriptionId on the message, use the initial subscriptionId as the default
123
+ getDefaultSubscriptionId() {
124
+ return Object.keys(this.txs)[0];
125
+ }
32
126
  /**
33
127
  * Subscribe to transaction history and updates for newly confirmed and pending transactions.
34
128
  *
@@ -42,95 +136,20 @@ class Client {
42
136
  * @param [onError] optional handler for any error messages associated with `subscriptionId`
43
137
  */
44
138
  async subscribeTxs(subscriptionId, data, onMessage, onError) {
45
- // keep track of the onMessage and onError handlers associated with each subscriptionId
46
- this.txs[subscriptionId] = { onMessage, onError, data: undefined };
47
- if (this.connections.txs) {
48
- if (this.connections.txs.ws.readyState === 1) {
49
- // subscribe if connection exists and is ready
50
- this.connections.txs.ws.send(JSON.stringify({ subscriptionId, method: 'subscribe', data }));
51
- }
52
- else {
53
- // queue up unique addresses for subscriptions if connection exists, but is not ready yet
54
- const txsData = this.txs[subscriptionId].data;
55
- this.txs[subscriptionId].data = txsData
56
- ? { ...txsData, addresses: [...new Set(...txsData.addresses, ...data.addresses)] }
57
- : data;
58
- }
59
- return;
139
+ var _a, _b, _c, _d;
140
+ const addresses = [
141
+ ...new Set([...((_c = (_b = (_a = this.txs[subscriptionId]) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.addresses) !== null && _c !== void 0 ? _c : []), ...data.addresses])
142
+ ];
143
+ // keep track of all subscribed addresses and onMessage/onError handlers associated with each subscriptionId
144
+ this.txs[subscriptionId] = { onMessage, onError, data: { topic: 'txs', addresses } };
145
+ // initialize websocket connection if one does not already exist
146
+ if (!((_d = this.connections.txs) === null || _d === void 0 ? void 0 : _d.ws)) {
147
+ return this.initialize();
148
+ }
149
+ // subscribe if connection exists and is ready
150
+ if (this.connections.txs.ws.readyState === isomorphic_ws_1.default.OPEN) {
151
+ this.connections.txs.ws.send(JSON.stringify({ subscriptionId, method: 'subscribe', data }));
60
152
  }
61
- const ws = new isomorphic_ws_1.default(this.url);
62
- const interval = setInterval(() => {
63
- // browsers do not support ping/pong frame, send ping payload instead
64
- const payload = { subscriptionId: '', method: 'ping' };
65
- ws.send(JSON.stringify(payload));
66
- }, this.pingInterval);
67
- this.connections.txs = { ws, interval };
68
- // send connection errors to all subscription onError handlers
69
- ws.onerror = (event) => {
70
- Object.entries(this.txs).forEach(([id, sub]) => {
71
- sub.onError && sub.onError({ subscriptionId: id, type: 'error', message: event.message });
72
- });
73
- };
74
- // clear heartbeat timeout on close
75
- ws.onclose = () => {
76
- var _a, _b;
77
- ((_a = this.connections.txs) === null || _a === void 0 ? void 0 : _a.pingTimeout) && clearTimeout(this.connections.txs.pingTimeout);
78
- ((_b = this.connections.txs) === null || _b === void 0 ? void 0 : _b.interval) && clearInterval(this.connections.txs.interval);
79
- };
80
- ws.onmessage = (event) => {
81
- var _a, _b;
82
- if (!event)
83
- return;
84
- if (event.type !== 'message')
85
- return;
86
- if (event.data === 'pong') {
87
- // trigger heartbeat keep alive on pong response
88
- this.heartbeat('txs');
89
- return;
90
- }
91
- try {
92
- const message = JSON.parse(event.data.toString());
93
- // narrow type to ErrorResponse if key `type` exists and forward to correct onError handler
94
- if ('type' in message) {
95
- const onErrorHandler = (_a = this.txs[message.subscriptionId || subscriptionId]) === null || _a === void 0 ? void 0 : _a.onError;
96
- onErrorHandler && onErrorHandler(message);
97
- return;
98
- }
99
- // forward the transaction message to the correct onMessage handler
100
- const onMessageHandler = (_b = this.txs[message.subscriptionId || subscriptionId]) === null || _b === void 0 ? void 0 : _b.onMessage;
101
- onMessageHandler && onMessageHandler(message);
102
- }
103
- catch (err) {
104
- logger.warn(`failed to handle onmessage event: ${JSON.stringify(event)}: ${err}`);
105
- }
106
- };
107
- // wait for the connection to open
108
- const openConnection = new Promise((resolve, reject) => {
109
- ws.onopen = () => {
110
- this.heartbeat('txs');
111
- try {
112
- // subscribe to all queued subscriptions
113
- Object.values(this.txs).forEach((tx) => {
114
- if (!tx.data)
115
- return;
116
- const payload = { subscriptionId, method: 'subscribe', data: tx.data };
117
- ws.send(JSON.stringify(payload));
118
- delete this.txs[subscriptionId].data;
119
- });
120
- // subscribe to initial subscription
121
- const payload = { subscriptionId, method: 'subscribe', data };
122
- ws.send(JSON.stringify(payload));
123
- resolve(true);
124
- }
125
- catch (err) {
126
- reject(err);
127
- }
128
- };
129
- });
130
- const connectionTimeout = new Promise((_, reject) => {
131
- setTimeout(() => reject(new Error('timeout while trying to connect')), 5000);
132
- });
133
- await Promise.race([openConnection, connectionTimeout]);
134
153
  }
135
154
  /**
136
155
  * Unsubscribe from transaction history and updates for newly confirmed and pending transactions.
@@ -146,19 +165,27 @@ class Client {
146
165
  * @param data.addresses list of addresses to unsubscribe from
147
166
  */
148
167
  unsubscribeTxs(subscriptionId, data) {
149
- var _a, _b, _c;
150
- if (((_a = this.connections.txs) === null || _a === void 0 ? void 0 : _a.ws.readyState) !== isomorphic_ws_1.default.OPEN)
168
+ var _a, _b, _c, _d, _e;
169
+ if (((_b = (_a = this.connections.txs) === null || _a === void 0 ? void 0 : _a.ws) === null || _b === void 0 ? void 0 : _b.readyState) !== isomorphic_ws_1.default.OPEN)
151
170
  return;
152
171
  if (!subscriptionId)
153
172
  this.txs = {};
154
- if (subscriptionId && !(data === null || data === void 0 ? void 0 : data.addresses.length))
155
- delete this.txs[subscriptionId];
173
+ if (subscriptionId && this.txs[subscriptionId]) {
174
+ // unsubscribe addresses from the current subscribed address set if addresses provided
175
+ if ((_c = data === null || data === void 0 ? void 0 : data.addresses) === null || _c === void 0 ? void 0 : _c.length) {
176
+ this.txs[subscriptionId].data.addresses = this.txs[subscriptionId].data.addresses.filter((address) => !data.addresses.includes(address));
177
+ }
178
+ else {
179
+ // delete subscription if no addresses provided
180
+ delete this.txs[subscriptionId];
181
+ }
182
+ }
156
183
  const payload = {
157
184
  subscriptionId: subscriptionId !== null && subscriptionId !== void 0 ? subscriptionId : '',
158
185
  method: 'unsubscribe',
159
- data: { topic: 'txs', addresses: (_b = data === null || data === void 0 ? void 0 : data.addresses) !== null && _b !== void 0 ? _b : [] }
186
+ data: { topic: 'txs', addresses: (_d = data === null || data === void 0 ? void 0 : data.addresses) !== null && _d !== void 0 ? _d : [] }
160
187
  };
161
- (_c = this.connections.txs) === null || _c === void 0 ? void 0 : _c.ws.send(JSON.stringify(payload));
188
+ (_e = this.connections.txs) === null || _e === void 0 ? void 0 : _e.ws.send(JSON.stringify(payload));
162
189
  }
163
190
  /**
164
191
  * Close and unsubscribe from any subscriptions
@@ -169,9 +196,9 @@ class Client {
169
196
  */
170
197
  close(topic) {
171
198
  const closeTxs = () => {
172
- var _a;
199
+ var _a, _b;
173
200
  this.unsubscribeTxs();
174
- (_a = this.connections.txs) === null || _a === void 0 ? void 0 : _a.ws.close();
201
+ (_b = (_a = this.connections.txs) === null || _a === void 0 ? void 0 : _a.ws) === null || _b === void 0 ? void 0 : _b.close(NORMAL_CLOSURE_CODE);
175
202
  };
176
203
  // close all connections if no topic is provided
177
204
  if (!topic) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshiftoss/unchained-client",
3
- "version": "9.0.3",
3
+ "version": "9.2.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "source": "src/index.ts",