diff --git a/__tests__/WebSocketChannel.test.ts b/__tests__/WebSocketChannel.test.ts index 0f55d4eb6..1ed1e45b9 100644 --- a/__tests__/WebSocketChannel.test.ts +++ b/__tests__/WebSocketChannel.test.ts @@ -1,5 +1,3 @@ -import { WebSocket } from 'isows'; - import { Provider, WSSubscriptions, WebSocketChannel } from '../src'; import { StarknetChainId } from '../src/global/constants'; import { getTestAccount, getTestProvider, STRKtokenAddress, TEST_WS_URL } from './config/fixtures'; diff --git a/__tests__/utils/batch.test.ts b/__tests__/utils/batch.test.ts index 7e2638f66..2d81d144a 100644 --- a/__tests__/utils/batch.test.ts +++ b/__tests__/utils/batch.test.ts @@ -1,4 +1,4 @@ -import fetch from '../../src/utils/fetch'; +import fetch from '../../src/utils/connect/fetch'; import { BatchClient } from '../../src/utils/batch'; import { createBlockForDevnet, createTestProvider } from '../config/fixtures'; import { initializeMatcher } from '../config/schema'; diff --git a/__tests__/utils/stark.browser.test.ts b/__tests__/utils/stark.browser.test.ts index 9eda21ee6..089758a2a 100644 --- a/__tests__/utils/stark.browser.test.ts +++ b/__tests__/utils/stark.browser.test.ts @@ -8,10 +8,6 @@ import * as json from '../../src/utils/json'; const { IS_BROWSER } = constants; -// isows has faulty module resolution in the browser emulation environment which prevents test execution -// it is not required for these tests so removing it with a mock circumvents the issue -jest.mock('isows', () => jest.fn()); - test('isBrowser', () => { expect(IS_BROWSER).toBe(true); }); diff --git a/package-lock.json b/package-lock.json index cfde11225..e94953b63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,13 +14,11 @@ "@scure/base": "1.2.1", "@scure/starknet": "1.1.0", "abi-wan-kanabi": "2.2.4", - "isows": "^1.0.6", "lossless-json": "^4.0.1", "pako": "^2.0.4", "starknet-types-07": "npm:@starknet-io/types-js@~0.7.10", "starknet-types-08": "npm:@starknet-io/types-js@~0.8.1", - "ts-mixer": "^6.0.3", - "ws": "^8.18.0" + "ts-mixer": "^6.0.3" }, "devDependencies": { "@babel/plugin-transform-modules-commonjs": "^7.18.2", @@ -10321,21 +10319,6 @@ "whatwg-fetch": "^3.4.1" } }, - "node_modules/isows": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", - "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, "node_modules/issue-parser": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", @@ -19744,6 +19727,7 @@ "version": "8.18.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 52f299190..ba9d009e6 100644 --- a/package.json +++ b/package.json @@ -103,13 +103,14 @@ "@scure/base": "1.2.1", "@scure/starknet": "1.1.0", "abi-wan-kanabi": "2.2.4", - "isows": "^1.0.6", "lossless-json": "^4.0.1", "pako": "^2.0.4", "starknet-types-07": "npm:@starknet-io/types-js@~0.7.10", "starknet-types-08": "npm:@starknet-io/types-js@~0.8.1", - "ts-mixer": "^6.0.3", - "ws": "^8.18.0" + "ts-mixer": "^6.0.3" + }, + "engines": { + "node": ">=22" }, "lint-staged": { "*.ts": "eslint --cache --fix", diff --git a/src/channel/rpc_0_7_1.ts b/src/channel/rpc_0_7_1.ts index 6ab3b6b82..feb65a199 100644 --- a/src/channel/rpc_0_7_1.ts +++ b/src/channel/rpc_0_7_1.ts @@ -4,7 +4,6 @@ import { SupportedRpcVersion, SYSTEM_MESSAGES, } from '../global/constants'; -import { logger } from '../global/logger'; import { AccountInvocationItem, AccountInvocations, @@ -29,7 +28,7 @@ import { CallData } from '../utils/calldata'; import { isSierra } from '../utils/contract'; import { LibraryError, RpcError } from '../utils/errors'; import { validateAndParseEthAddress } from '../utils/eth'; -import fetch from '../utils/fetch'; +import fetch from '../utils/connect/fetch'; import { getSelector, getSelectorFromName } from '../utils/hash'; import { stringify } from '../utils/json'; import { getHexStringArray, toHex, toStorageKey } from '../utils/num'; @@ -37,6 +36,8 @@ import { Block, getDefaultNodeUrl, wait } from '../utils/provider'; import { isSupportedSpecVersion, isV3Tx, isVersion } from '../utils/resolve'; import { decompressProgram, signatureToHexArray } from '../utils/stark'; import { getVersionsByType } from '../utils/transaction'; +import { logger } from '../global/logger'; +import { config } from '../global/config'; const defaultOptions = { headers: { 'Content-Type': 'application/json' }, @@ -105,7 +106,7 @@ export class RpcChannel { this.channelSpecVersion ); } - this.baseFetch = baseFetch ?? fetch; + this.baseFetch = baseFetch || config.get('fetch') || fetch; this.blockIdentifier = blockIdentifier ?? defaultOptions.blockIdentifier; this.chainId = chainId; this.headers = { ...defaultOptions.headers, ...headers }; diff --git a/src/channel/rpc_0_8_1.ts b/src/channel/rpc_0_8_1.ts index ee47075b2..ea94ab3b2 100644 --- a/src/channel/rpc_0_8_1.ts +++ b/src/channel/rpc_0_8_1.ts @@ -4,8 +4,6 @@ import { SupportedRpcVersion, SYSTEM_MESSAGES, } from '../global/constants'; -import { logger } from '../global/logger'; -import { isRPC08_ResourceBounds } from '../provider/types/spec.type'; import { AccountInvocationItem, AccountInvocations, @@ -30,7 +28,7 @@ import { CallData } from '../utils/calldata'; import { isSierra } from '../utils/contract'; import { LibraryError, RpcError } from '../utils/errors'; import { validateAndParseEthAddress } from '../utils/eth'; -import fetch from '../utils/fetch'; +import fetch from '../utils/connect/fetch'; import { getSelector, getSelectorFromName } from '../utils/hash'; import { stringify } from '../utils/json'; import { @@ -43,6 +41,9 @@ import { Block, getDefaultNodeUrl, wait } from '../utils/provider'; import { isSupportedSpecVersion, isV3Tx, isVersion } from '../utils/resolve'; import { decompressProgram, signatureToHexArray } from '../utils/stark'; import { getVersionsByType } from '../utils/transaction'; +import { logger } from '../global/logger'; +import { isRPC08_ResourceBounds } from '../provider/types/spec.type'; +import { config } from '../global/config'; // TODO: check if we can filet type before entering to this method, as so to specify here only RPC 0.8 types const defaultOptions = { @@ -112,7 +113,7 @@ export class RpcChannel { this.channelSpecVersion ); } - this.baseFetch = baseFetch ?? fetch; + this.baseFetch = baseFetch || config.get('fetch') || fetch; this.blockIdentifier = blockIdentifier ?? defaultOptions.blockIdentifier; this.chainId = chainId; this.headers = { ...defaultOptions.headers, ...headers }; diff --git a/src/channel/ws_0_8.ts b/src/channel/ws_0_8.ts index c573eef7b..f4afa609b 100644 --- a/src/channel/ws_0_8.ts +++ b/src/channel/ws_0_8.ts @@ -1,4 +1,3 @@ -import { WebSocket } from 'isows'; import type { SUBSCRIPTION_ID, SubscriptionEventsResponse, @@ -13,9 +12,11 @@ import type { import { BigNumberish, SubscriptionBlockIdentifier } from '../types'; import { JRPC } from '../types/api'; import { WebSocketEvent } from '../types/api/jsonrpc'; +import WebSocket from '../utils/connect/ws'; import { stringify } from '../utils/json'; import { bigNumberishArrayToHexadecimalStringArray, toHex } from '../utils/num'; import { Block } from '../utils/provider'; +import { config } from '../global/config'; export const WSSubscriptions = { NEW_HEADS: 'newHeads', @@ -32,9 +33,10 @@ export type WebSocketOptions = { */ nodeUrl?: string; /** - * You can provide websocket object defined by protocol standard else library will use default 'isows'/'ws' package - * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#protocols . - * https://www.rfc-editor.org/rfc/rfc6455.html#section-1 . + * This parameter should be used when working in an environment without native WebSocket support by providing + * an equivalent WebSocket object that conforms to the protocol, e.g. from the 'isows' and/or 'ws' modules + * * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#protocols . + * * https://www.rfc-editor.org/rfc/rfc6455.html#section-1 . * @default WebSocket */ websocket?: WebSocket; @@ -183,7 +185,7 @@ export class WebSocketChannel { // provided existing websocket const nodeUrl = options.nodeUrl || 'http://localhost:3000 '; // TODO: implement getDefaultNodeUrl default node when defined by providers? this.nodeUrl = options.websocket ? options.websocket.url : nodeUrl; - this.websocket = options.websocket ? options.websocket : new WebSocket(nodeUrl); + this.websocket = options.websocket || config.get('websocket') || new WebSocket(nodeUrl); this.websocket.addEventListener('open', this.onOpen.bind(this)); this.websocket.addEventListener('close', this.onCloseProxy.bind(this)); diff --git a/src/global/constants.ts b/src/global/constants.ts index e6c2d4863..d08db6f77 100644 --- a/src/global/constants.ts +++ b/src/global/constants.ts @@ -107,6 +107,8 @@ export const DEFAULT_GLOBAL_CONFIG: { rpcVersion: _SupportedRpcVersion; transactionVersion: SupportedTransactionVersion; feeMarginPercentage: FeeMarginPercentage; + fetch: any; + websocket: any; } = { legacyMode: false, rpcVersion: '0.8.1', @@ -129,6 +131,8 @@ export const DEFAULT_GLOBAL_CONFIG: { }, maxFee: 50, }, + fetch: undefined, + websocket: undefined, }; export const RPC_DEFAULT_NODES = { diff --git a/src/utils/connect/fetch.ts b/src/utils/connect/fetch.ts new file mode 100644 index 000000000..38465a108 --- /dev/null +++ b/src/utils/connect/fetch.ts @@ -0,0 +1,11 @@ +import { LibraryError } from '../errors'; + +export default (typeof fetch !== 'undefined' && fetch) || + (typeof globalThis !== 'undefined' && globalThis.fetch) || + (typeof window !== 'undefined' && window.fetch.bind(window)) || + (typeof global !== 'undefined' && global.fetch) || + ((() => { + throw new LibraryError( + "'fetch()' not detected, use the 'baseFetch' constructor parameter to set it" + ); + }) as WindowOrWorkerGlobalScope['fetch']); diff --git a/src/utils/connect/ws.ts b/src/utils/connect/ws.ts new file mode 100644 index 000000000..6e3f80477 --- /dev/null +++ b/src/utils/connect/ws.ts @@ -0,0 +1,13 @@ +import { LibraryError } from '../errors'; + +export default (typeof WebSocket !== 'undefined' && WebSocket) || + (typeof globalThis !== 'undefined' && globalThis.WebSocket) || + (typeof window !== 'undefined' && window.WebSocket.bind(window)) || + (typeof global !== 'undefined' && global.WebSocket) || + (class { + constructor() { + throw new LibraryError( + "WebSocket module not detected, use the 'websocket' constructor parameter to set a compatible connection" + ); + } + } as unknown as typeof WebSocket); diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts deleted file mode 100644 index eeecf7966..000000000 --- a/src/utils/fetch.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { IS_BROWSER } from './encode'; -import { LibraryError } from './errors'; -import { isUndefined } from './typed'; - -export default (IS_BROWSER && window.fetch.bind(window)) || // use built-in fetch in browser if available - (!isUndefined(global) && global.fetch) || // use built-in fetch in node, react-native and service worker if available - // throw with instructions when no fetch is detected - ((() => { - throw new LibraryError( - "'fetch()' not detected, use the 'baseFetch' constructor parameter to set it" - ); - }) as WindowOrWorkerGlobalScope['fetch']); diff --git a/www/docs/guides/websocket_channel.md b/www/docs/guides/websocket_channel.md index 65107ae39..f448f5584 100644 --- a/www/docs/guides/websocket_channel.md +++ b/www/docs/guides/websocket_channel.md @@ -35,6 +35,16 @@ await webSocketChannel.waitForConnection(); // ... use webSocketChannel ``` +If the environment doesn't have a detectable global `WebSocket`, an appropriate `WebSocket` implementation should be used and set with the `websocket` constructor parameter. + +```typescript +import { WebSocket } from 'ws'; + +const webSocketChannel = new WebSocketChannel({ + websocket: new WebSocket('wss://sepolia-pathfinder-rpc.server.io/rpc/v0_8'), +}); +``` + ### Usage ```typescript