@hiero-ledger/sdk 2.75.0 → 2.77.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.
- package/dist/umd.js +440 -71
- package/dist/umd.min.js +14 -14
- package/lib/Executable.cjs +13 -3
- package/lib/Executable.js +1 -1
- package/lib/Executable.js.map +1 -1
- package/lib/channel/Channel.cjs +25 -1
- package/lib/channel/Channel.d.ts +20 -0
- package/lib/channel/Channel.js +1 -1
- package/lib/channel/Channel.js.map +1 -1
- package/lib/channel/NativeChannel.cjs +96 -2
- package/lib/channel/NativeChannel.d.ts +21 -1
- package/lib/channel/NativeChannel.js +1 -1
- package/lib/channel/NativeChannel.js.map +1 -1
- package/lib/channel/NodeChannel.cjs +4 -5
- package/lib/channel/NodeChannel.d.ts +2 -3
- package/lib/channel/NodeChannel.js +1 -1
- package/lib/channel/NodeChannel.js.map +1 -1
- package/lib/channel/WebChannel.cjs +163 -4
- package/lib/channel/WebChannel.d.ts +51 -1
- package/lib/channel/WebChannel.js +1 -1
- package/lib/channel/WebChannel.js.map +1 -1
- package/lib/client/Client.cjs +62 -5
- package/lib/client/Client.d.ts +26 -3
- package/lib/client/Client.js +1 -1
- package/lib/client/Client.js.map +1 -1
- package/lib/client/NativeClient.cjs +1 -1
- package/lib/client/NativeClient.js +1 -1
- package/lib/client/NativeClient.js.map +1 -1
- package/lib/client/NodeClient.cjs +7 -6
- package/lib/client/NodeClient.d.ts +3 -3
- package/lib/client/NodeClient.js +1 -1
- package/lib/client/NodeClient.js.map +1 -1
- package/lib/client/WebClient.cjs +61 -27
- package/lib/client/WebClient.d.ts +6 -0
- package/lib/client/WebClient.js +1 -1
- package/lib/client/WebClient.js.map +1 -1
- package/lib/client/addressbooks/mainnet.cjs +1 -1
- package/lib/client/addressbooks/mainnet.d.ts +1 -1
- package/lib/client/addressbooks/mainnet.js +1 -1
- package/lib/client/addressbooks/mainnet.js.map +1 -1
- package/lib/client/addressbooks/previewnet.cjs +1 -1
- package/lib/client/addressbooks/previewnet.d.ts +1 -1
- package/lib/client/addressbooks/previewnet.js +1 -1
- package/lib/client/addressbooks/previewnet.js.map +1 -1
- package/lib/client/addressbooks/testnet.cjs +1 -1
- package/lib/client/addressbooks/testnet.d.ts +1 -1
- package/lib/client/addressbooks/testnet.js +1 -1
- package/lib/client/addressbooks/testnet.js.map +1 -1
- package/lib/constants/ClientConstants.cjs +18 -2
- package/lib/constants/ClientConstants.d.ts +15 -0
- package/lib/constants/ClientConstants.js +1 -1
- package/lib/constants/ClientConstants.js.map +1 -1
- package/lib/network/AddressBookQuery.cjs +0 -4
- package/lib/network/AddressBookQuery.js +1 -1
- package/lib/network/AddressBookQuery.js.map +1 -1
- package/lib/network/AddressBookQueryWeb.cjs +1 -5
- package/lib/network/AddressBookQueryWeb.js +1 -1
- package/lib/network/AddressBookQueryWeb.js.map +1 -1
- package/lib/version.js +1 -1
- package/package.json +9 -9
- package/src/Executable.js +18 -2
- package/src/channel/Channel.js +25 -1
- package/src/channel/NativeChannel.js +111 -2
- package/src/channel/NodeChannel.js +4 -7
- package/src/channel/WebChannel.js +189 -9
- package/src/client/Client.js +79 -5
- package/src/client/NativeClient.js +1 -1
- package/src/client/NodeClient.js +7 -6
- package/src/client/WebClient.js +77 -31
- package/src/client/addressbooks/mainnet.js +1 -1
- package/src/client/addressbooks/previewnet.js +1 -1
- package/src/client/addressbooks/testnet.js +1 -1
- package/src/constants/ClientConstants.js +17 -1
- package/src/network/AddressBookQuery.js +0 -7
- package/src/network/AddressBookQueryWeb.js +1 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddressBookQueryWeb.js","sources":["../../src/network/AddressBookQueryWeb.js"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\nimport Query from \"../query/Query.js\";\nimport NodeAddressBook from \"../address_book/NodeAddressBook.js\";\nimport FileId from \"../file/FileId.js\";\nimport { RST_STREAM } from \"../Executable.js\";\nimport NodeAddress from \"../address_book/NodeAddress.js\";\nimport {\n MAINNET,\n WEB_TESTNET,\n WEB_PREVIEWNET,\n} from \"../constants/ClientConstants.js\";\n\n/**\n * @typedef {import(\"../channel/Channel.js\").default} Channel\n * @typedef {import(\"../channel/MirrorChannel.js\").default} MirrorChannel\n * @typedef {import(\"../channel/MirrorChannel.js\").MirrorError} MirrorError\n */\n\n/**\n * @template {Channel} ChannelT\n * @typedef {import(\"../client/Client.js\").default<ChannelT, MirrorChannel>} Client<ChannelT, MirrorChannel>\n */\n\n/**\n * @typedef {object} EndpointWebResponse\n * @property {string} domain_name\n * @property {string} ip_address_v4\n * @property {number} port\n */\n\n/**\n * @typedef {object} AddressBookQueryWebResponse\n * @property {Array<{\n * admin_key: {\n * key: string,\n * _type: string,\n * },\n * decline_reward: boolean,\n * grpc_proxy_endpoint: EndpointWebResponse,\n * file_id: string,\n * memo: string,\n * public_key: string,\n * node_id: number,\n * node_account_id: string,\n * node_cert_hash: string,\n * address: string,\n * service_endpoints: EndpointWebResponse[],\n * description: string,\n * stake: number\n * }>} nodes\n * @property {?{next: ?string}} links - Links object containing pagination information\n */\n\n/**\n * Default page size limit for optimal pagination performance\n * @constant {number}\n */\nconst DEFAULT_PAGE_SIZE = 25;\n\n/**\n * Web-compatible query to get a list of Hedera network node addresses from a mirror node.\n * Uses fetch API instead of gRPC for web environments.\n *\n * This query can be used to retrieve node addresses either from a specific file ID\n * or from the most recent address book if no file ID is specified. The response\n * contains node metadata including IP addresses and ports for both node and mirror\n * node services.\n * @augments {Query<NodeAddressBook>}\n */\nexport default class AddressBookQueryWeb extends Query {\n /**\n * @param {object} props\n * @param {FileId | string} [props.fileId]\n * @param {number} [props.limit] - Page size limit (defaults to 25 for optimal performance)\n */\n constructor(props = {}) {\n super();\n\n /**\n * @private\n * @type {?FileId}\n */\n this._fileId = null;\n if (props.fileId != null) {\n this.setFileId(props.fileId);\n }\n\n /**\n * Page limit for the query\n * @private\n * @type {?number}\n */\n this._limit = null;\n if (props.limit != null) {\n this.setLimit(props.limit);\n }\n\n /**\n * @private\n * @type {(error: MirrorError | Error | null) => boolean}\n */\n this._retryHandler = (error) => {\n if (error != null) {\n if (error instanceof Error) {\n // Retry on all errors which are not `MirrorError` because they're\n // likely lower level HTTP errors\n return true;\n } else {\n // Retry on `NOT_FOUND`, `RESOURCE_EXHAUSTED`, `UNAVAILABLE`, and conditionally on `INTERNAL`\n // if the message matches the right regex.\n switch (error.code) {\n // INTERNAL\n // eslint-disable-next-line no-fallthrough\n case 13:\n return RST_STREAM.test(error.details.toString());\n // NOT_FOUND\n // eslint-disable-next-line no-fallthrough\n case 5:\n // RESOURCE_EXHAUSTED\n // eslint-disable-next-line no-fallthrough\n case 8:\n // UNAVAILABLE\n // eslint-disable-next-line no-fallthrough\n case 14:\n case 17:\n return true;\n default:\n return false;\n }\n }\n }\n\n return false;\n };\n\n /** @type {NodeAddress[]} */\n this._addresses = [];\n }\n\n /**\n * @returns {?FileId}\n */\n get fileId() {\n return this._fileId;\n }\n\n /**\n * @param {FileId | string} fileId\n * @returns {AddressBookQueryWeb}\n */\n setFileId(fileId) {\n this._fileId =\n typeof fileId === \"string\"\n ? FileId.fromString(fileId)\n : fileId.clone();\n\n return this;\n }\n\n /**\n * Page limit for the query\n * @returns {?number}\n */\n get limit() {\n return this._limit;\n }\n\n /**\n * Set the page limit for the query\n * @param {number} limit\n * @returns {AddressBookQueryWeb}\n */\n setLimit(limit) {\n this._limit = limit;\n\n return this;\n }\n\n /**\n * @param {number} attempts\n * @returns {this}\n */\n setMaxAttempts(attempts) {\n this._maxAttempts = attempts;\n return this;\n }\n\n /**\n * @param {number} backoff\n * @returns {this}\n */\n setMaxBackoff(backoff) {\n this._maxBackoff = backoff;\n return this;\n }\n\n /**\n * @param {Client<Channel>} client\n * @param {number=} requestTimeout\n * @returns {Promise<NodeAddressBook>}\n */\n execute(client, requestTimeout) {\n // Extra validation when initializing the client with only a mirror network\n if (client._network._network.size === 0 && !client._timer) {\n throw new Error(\n \"The client's network update period is required. Please set it using the setNetworkUpdatePeriod method.\",\n );\n }\n\n return new Promise((resolve, reject) => {\n void this._makeFetchRequest(\n client,\n resolve,\n reject,\n requestTimeout,\n );\n });\n }\n\n /**\n * @private\n * @param {Client<Channel>} client\n * @param {(value: NodeAddressBook) => void} resolve\n * @param {(error: Error) => void} reject\n * @param {number=} requestTimeout\n */\n async _makeFetchRequest(client, resolve, reject, requestTimeout) {\n const { port, address } =\n client._mirrorNetwork.getNextMirrorNode().address;\n\n let baseUrl = `${\n address.includes(\"127.0.0.1\") || address.includes(\"localhost\")\n ? \"http\"\n : \"https\"\n }://${address}`;\n\n if (port) {\n baseUrl = `${baseUrl}:${port}`;\n }\n\n // Initialize aggregated results\n this._addresses = [];\n let nextUrl = null;\n let isLastPage = false;\n\n // Build initial URL\n const initialUrl = new URL(`${baseUrl}/api/v1/network/nodes`);\n if (this._fileId != null) {\n initialUrl.searchParams.append(\"file.id\", this._fileId.toString());\n }\n\n // Use the specified limit, or default to DEFAULT_PAGE_SIZE for optimal pagination performance\n const effectiveLimit =\n this._limit != null ? this._limit : DEFAULT_PAGE_SIZE;\n initialUrl.searchParams.append(\"limit\", effectiveLimit.toString());\n\n // Fetch all pages\n while (!isLastPage) {\n const currentUrl = nextUrl ? new URL(nextUrl, baseUrl) : initialUrl;\n\n for (let attempt = 0; attempt <= this._maxAttempts; attempt++) {\n try {\n // eslint-disable-next-line n/no-unsupported-features/node-builtins\n const response = await fetch(currentUrl.toString(), {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n },\n signal: requestTimeout\n ? AbortSignal.timeout(requestTimeout)\n : undefined,\n });\n\n if (!response.ok) {\n throw new Error(\n `HTTP error! status: ${response.status}`,\n );\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const data = /** @type {AddressBookQueryWebResponse} */ (\n await response.json()\n );\n\n const nodes = data.nodes || [];\n\n // Aggregate nodes from this page\n const pageNodes = nodes.map((node) =>\n NodeAddress.fromJSON({\n nodeId: node.node_id.toString(),\n accountId: node.node_account_id,\n addresses:\n this._handleAddressesFromGrpcProxyEndpoint(\n node,\n client,\n ),\n certHash: node.node_cert_hash,\n publicKey: node.public_key,\n description: node.description,\n stake: node.stake.toString(),\n }),\n );\n\n this._addresses.push(...pageNodes);\n nextUrl = data.links?.next || null;\n\n // If no more pages, set flag to exit loop\n if (!nextUrl) {\n isLastPage = true;\n }\n\n // Move to next page\n break;\n } catch (error) {\n console.error(\"Error in _makeFetchRequest:\", error);\n const message =\n error instanceof Error ? error.message : String(error);\n\n // Check if we should retry\n if (\n attempt < this._maxAttempts &&\n !client.isClientShutDown &&\n this._retryHandler(\n /** @type {MirrorError | Error | null} */ (error),\n )\n ) {\n const delay = Math.min(\n 250 * 2 ** attempt,\n this._maxBackoff,\n );\n\n if (this._logger) {\n this._logger.debug(\n `Error getting nodes from mirror for file ${\n this._fileId != null\n ? this._fileId.toString()\n : \"UNKNOWN\"\n } during attempt ${\n attempt + 1\n }. Waiting ${delay} ms before next attempt: ${message}`,\n );\n }\n\n // Wait before next attempt\n // eslint-disable-next-line ie11/no-loop-func\n await new Promise((resolve) =>\n setTimeout(resolve, delay),\n );\n continue;\n }\n\n // If we shouldn't retry or have exhausted attempts, reject\n const maxAttemptsReached = attempt >= this._maxAttempts;\n const errorMessage = maxAttemptsReached\n ? `Failed to query address book after ${\n this._maxAttempts + 1\n } attempts. Last error: ${message}`\n : `Failed to query address book: ${message}`;\n reject(new Error(errorMessage));\n return;\n }\n }\n }\n\n // Return the aggregated results\n const addressBook = new NodeAddressBook({\n nodeAddresses: this._addresses,\n });\n resolve(addressBook);\n }\n\n /**\n * Handles the grpc_proxy_endpoint fallback logic for a node.\n * @param {AddressBookQueryWebResponse['nodes'][number]} node - The node object from the mirror node response.\n * @param {Client<Channel>} client - The client instance.\n * @returns {Array<{address: string, port: string}>}\n */\n _handleAddressesFromGrpcProxyEndpoint(node, client) {\n const grpcProxyEndpoint = node.grpc_proxy_endpoint;\n\n if (\n grpcProxyEndpoint &&\n grpcProxyEndpoint.domain_name &&\n grpcProxyEndpoint.port\n ) {\n return [\n {\n address: grpcProxyEndpoint.domain_name,\n port: grpcProxyEndpoint.port.toString(),\n },\n ];\n }\n\n let networkConstant;\n const ledgerId = client._network.ledgerId;\n\n if (ledgerId && ledgerId.isMainnet()) {\n networkConstant = MAINNET;\n } else if (ledgerId && ledgerId.isTestnet()) {\n networkConstant = WEB_TESTNET;\n } else if (ledgerId && ledgerId.isPreviewnet()) {\n networkConstant = WEB_PREVIEWNET;\n } else {\n return [];\n }\n\n const nodeAccountId = node.node_account_id;\n\n for (const [address, accountIdObj] of Object.entries(networkConstant)) {\n if (accountIdObj.toString() === nodeAccountId) {\n const [domain_name, port] = address.split(\":\");\n\n return [\n {\n address: domain_name,\n port,\n },\n ];\n }\n }\n\n return [];\n }\n}\n"],"names":["AddressBookQueryWeb","Query","constructor","props","super","this","_fileId","fileId","setFileId","_limit","limit","setLimit","_retryHandler","error","Error","code","RST_STREAM","test","details","toString","_addresses","FileId","fromString","clone","setMaxAttempts","attempts","_maxAttempts","setMaxBackoff","backoff","_maxBackoff","execute","client","requestTimeout","_network","size","_timer","Promise","resolve","reject","_makeFetchRequest","port","address","_mirrorNetwork","getNextMirrorNode","baseUrl","includes","nextUrl","isLastPage","initialUrl","URL","searchParams","append","effectiveLimit","currentUrl","attempt","response","fetch","method","headers","Accept","signal","AbortSignal","timeout","undefined","ok","status","data","json","pageNodes","nodes","map","node","NodeAddress","fromJSON","nodeId","node_id","accountId","node_account_id","addresses","_handleAddressesFromGrpcProxyEndpoint","certHash","node_cert_hash","publicKey","public_key","description","stake","push","links","next","console","message","String","isClientShutDown","delay","Math","min","_logger","debug","setTimeout","errorMessage","NodeAddressBook","nodeAddresses","grpcProxyEndpoint","grpc_proxy_endpoint","domain_name","networkConstant","ledgerId","isMainnet","MAINNET","isTestnet","WEB_TESTNET","isPreviewnet","WEB_PREVIEWNET","nodeAccountId","accountIdObj","Object","entries","split"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AddressBookQueryWeb.js","sources":["../../src/network/AddressBookQueryWeb.js"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\nimport Query from \"../query/Query.js\";\nimport NodeAddressBook from \"../address_book/NodeAddressBook.js\";\nimport FileId from \"../file/FileId.js\";\nimport { RST_STREAM } from \"../Executable.js\";\nimport NodeAddress from \"../address_book/NodeAddress.js\";\nimport {\n MAINNET,\n WEB_TESTNET,\n WEB_PREVIEWNET,\n} from \"../constants/ClientConstants.js\";\n\n/**\n * @typedef {import(\"../channel/Channel.js\").default} Channel\n * @typedef {import(\"../channel/MirrorChannel.js\").default} MirrorChannel\n * @typedef {import(\"../channel/MirrorChannel.js\").MirrorError} MirrorError\n */\n\n/**\n * @template {Channel} ChannelT\n * @typedef {import(\"../client/Client.js\").default<ChannelT, MirrorChannel>} Client<ChannelT, MirrorChannel>\n */\n\n/**\n * @typedef {object} EndpointWebResponse\n * @property {string} domain_name\n * @property {string} ip_address_v4\n * @property {number} port\n */\n\n/**\n * @typedef {object} AddressBookQueryWebResponse\n * @property {Array<{\n * admin_key: {\n * key: string,\n * _type: string,\n * },\n * decline_reward: boolean,\n * grpc_proxy_endpoint: EndpointWebResponse,\n * file_id: string,\n * memo: string,\n * public_key: string,\n * node_id: number,\n * node_account_id: string,\n * node_cert_hash: string,\n * address: string,\n * service_endpoints: EndpointWebResponse[],\n * description: string,\n * stake: number\n * }>} nodes\n * @property {?{next: ?string}} links - Links object containing pagination information\n */\n\n/**\n * Default page size limit for optimal pagination performance\n * @constant {number}\n */\nconst DEFAULT_PAGE_SIZE = 25;\n\n/**\n * Web-compatible query to get a list of Hedera network node addresses from a mirror node.\n * Uses fetch API instead of gRPC for web environments.\n *\n * This query can be used to retrieve node addresses either from a specific file ID\n * or from the most recent address book if no file ID is specified. The response\n * contains node metadata including IP addresses and ports for both node and mirror\n * node services.\n * @augments {Query<NodeAddressBook>}\n */\nexport default class AddressBookQueryWeb extends Query {\n /**\n * @param {object} props\n * @param {FileId | string} [props.fileId]\n * @param {number} [props.limit] - Page size limit (defaults to 25 for optimal performance)\n */\n constructor(props = {}) {\n super();\n\n /**\n * @private\n * @type {?FileId}\n */\n this._fileId = null;\n if (props.fileId != null) {\n this.setFileId(props.fileId);\n }\n\n /**\n * Page limit for the query\n * @private\n * @type {?number}\n */\n this._limit = null;\n if (props.limit != null) {\n this.setLimit(props.limit);\n }\n\n /**\n * @private\n * @type {(error: MirrorError | Error | null) => boolean}\n */\n this._retryHandler = (error) => {\n if (error != null) {\n if (error instanceof Error) {\n // Retry on all errors which are not `MirrorError` because they're\n // likely lower level HTTP errors\n return true;\n } else {\n // Retry on `NOT_FOUND`, `RESOURCE_EXHAUSTED`, `UNAVAILABLE`, and conditionally on `INTERNAL`\n // if the message matches the right regex.\n switch (error.code) {\n // INTERNAL\n // eslint-disable-next-line no-fallthrough\n case 13:\n return RST_STREAM.test(error.details.toString());\n // NOT_FOUND\n // eslint-disable-next-line no-fallthrough\n case 5:\n // RESOURCE_EXHAUSTED\n // eslint-disable-next-line no-fallthrough\n case 8:\n // UNAVAILABLE\n // eslint-disable-next-line no-fallthrough\n case 14:\n case 17:\n return true;\n default:\n return false;\n }\n }\n }\n\n return false;\n };\n\n /** @type {NodeAddress[]} */\n this._addresses = [];\n }\n\n /**\n * @returns {?FileId}\n */\n get fileId() {\n return this._fileId;\n }\n\n /**\n * @param {FileId | string} fileId\n * @returns {AddressBookQueryWeb}\n */\n setFileId(fileId) {\n this._fileId =\n typeof fileId === \"string\"\n ? FileId.fromString(fileId)\n : fileId.clone();\n\n return this;\n }\n\n /**\n * Page limit for the query\n * @returns {?number}\n */\n get limit() {\n return this._limit;\n }\n\n /**\n * Set the page limit for the query\n * @param {number} limit\n * @returns {AddressBookQueryWeb}\n */\n setLimit(limit) {\n this._limit = limit;\n\n return this;\n }\n\n /**\n * @param {number} attempts\n * @returns {this}\n */\n setMaxAttempts(attempts) {\n this._maxAttempts = attempts;\n return this;\n }\n\n /**\n * @param {number} backoff\n * @returns {this}\n */\n setMaxBackoff(backoff) {\n this._maxBackoff = backoff;\n return this;\n }\n\n /**\n * @param {Client<Channel>} client\n * @param {number=} requestTimeout\n * @returns {Promise<NodeAddressBook>}\n */\n execute(client, requestTimeout) {\n return new Promise((resolve, reject) => {\n void this._makeFetchRequest(\n client,\n resolve,\n reject,\n requestTimeout,\n );\n });\n }\n\n /**\n * @private\n * @param {Client<Channel>} client\n * @param {(value: NodeAddressBook) => void} resolve\n * @param {(error: Error) => void} reject\n * @param {number=} requestTimeout\n */\n async _makeFetchRequest(client, resolve, reject, requestTimeout) {\n const { port, address } =\n client._mirrorNetwork.getNextMirrorNode().address;\n\n let baseUrl = `${\n address.includes(\"127.0.0.1\") || address.includes(\"localhost\")\n ? \"http\"\n : \"https\"\n }://${address}`;\n\n if (port) {\n baseUrl = `${baseUrl}:${port}`;\n }\n\n // Initialize aggregated results\n this._addresses = [];\n let nextUrl = null;\n let isLastPage = false;\n\n // Build initial URL\n const initialUrl = new URL(`${baseUrl}/api/v1/network/nodes`);\n if (this._fileId != null) {\n initialUrl.searchParams.append(\"file.id\", this._fileId.toString());\n }\n\n // Use the specified limit, or default to DEFAULT_PAGE_SIZE for optimal pagination performance\n const effectiveLimit =\n this._limit != null ? this._limit : DEFAULT_PAGE_SIZE;\n initialUrl.searchParams.append(\"limit\", effectiveLimit.toString());\n\n // Fetch all pages\n while (!isLastPage) {\n const currentUrl = nextUrl ? new URL(nextUrl, baseUrl) : initialUrl;\n\n for (let attempt = 0; attempt <= this._maxAttempts; attempt++) {\n try {\n // eslint-disable-next-line n/no-unsupported-features/node-builtins\n const response = await fetch(currentUrl.toString(), {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n },\n signal: requestTimeout\n ? AbortSignal.timeout(requestTimeout)\n : undefined,\n });\n\n if (!response.ok) {\n throw new Error(\n `HTTP error! status: ${response.status}`,\n );\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const data = /** @type {AddressBookQueryWebResponse} */ (\n await response.json()\n );\n\n const nodes = data.nodes || [];\n\n // Aggregate nodes from this page\n const pageNodes = nodes.map((node) =>\n NodeAddress.fromJSON({\n nodeId: node.node_id.toString(),\n accountId: node.node_account_id,\n addresses:\n this._handleAddressesFromGrpcProxyEndpoint(\n node,\n client,\n ),\n certHash: node.node_cert_hash,\n publicKey: node.public_key,\n description: node.description,\n stake: node.stake?.toString(),\n }),\n );\n\n this._addresses.push(...pageNodes);\n nextUrl = data.links?.next || null;\n\n // If no more pages, set flag to exit loop\n if (!nextUrl) {\n isLastPage = true;\n }\n\n // Move to next page\n break;\n } catch (error) {\n console.error(\"Error in _makeFetchRequest:\", error);\n const message =\n error instanceof Error ? error.message : String(error);\n\n // Check if we should retry\n if (\n attempt < this._maxAttempts &&\n !client.isClientShutDown &&\n this._retryHandler(\n /** @type {MirrorError | Error | null} */ (error),\n )\n ) {\n const delay = Math.min(\n 250 * 2 ** attempt,\n this._maxBackoff,\n );\n\n if (this._logger) {\n this._logger.debug(\n `Error getting nodes from mirror for file ${\n this._fileId != null\n ? this._fileId.toString()\n : \"UNKNOWN\"\n } during attempt ${\n attempt + 1\n }. Waiting ${delay} ms before next attempt: ${message}`,\n );\n }\n\n // Wait before next attempt\n // eslint-disable-next-line ie11/no-loop-func\n await new Promise((resolve) =>\n setTimeout(resolve, delay),\n );\n continue;\n }\n\n // If we shouldn't retry or have exhausted attempts, reject\n const maxAttemptsReached = attempt >= this._maxAttempts;\n const errorMessage = maxAttemptsReached\n ? `Failed to query address book after ${\n this._maxAttempts + 1\n } attempts. Last error: ${message}`\n : `Failed to query address book: ${message}`;\n reject(new Error(errorMessage));\n return;\n }\n }\n }\n\n // Return the aggregated results\n const addressBook = new NodeAddressBook({\n nodeAddresses: this._addresses,\n });\n resolve(addressBook);\n }\n\n /**\n * Handles the grpc_proxy_endpoint fallback logic for a node.\n * @param {AddressBookQueryWebResponse['nodes'][number]} node - The node object from the mirror node response.\n * @param {Client<Channel>} client - The client instance.\n * @returns {Array<{address: string, port: string}>}\n */\n _handleAddressesFromGrpcProxyEndpoint(node, client) {\n const grpcProxyEndpoint = node.grpc_proxy_endpoint;\n\n if (\n grpcProxyEndpoint &&\n grpcProxyEndpoint.domain_name &&\n grpcProxyEndpoint.port\n ) {\n return [\n {\n address: grpcProxyEndpoint.domain_name,\n port: grpcProxyEndpoint.port.toString(),\n },\n ];\n }\n\n let networkConstant;\n const ledgerId = client._network.ledgerId;\n\n if (ledgerId && ledgerId.isMainnet()) {\n networkConstant = MAINNET;\n } else if (ledgerId && ledgerId.isTestnet()) {\n networkConstant = WEB_TESTNET;\n } else if (ledgerId && ledgerId.isPreviewnet()) {\n networkConstant = WEB_PREVIEWNET;\n } else {\n return [];\n }\n\n const nodeAccountId = node.node_account_id;\n\n for (const [address, accountIdObj] of Object.entries(networkConstant)) {\n if (accountIdObj.toString() === nodeAccountId) {\n const [domain_name, port] = address.split(\":\");\n\n return [\n {\n address: domain_name,\n port,\n },\n ];\n }\n }\n\n return [];\n }\n}\n"],"names":["AddressBookQueryWeb","Query","constructor","props","super","this","_fileId","fileId","setFileId","_limit","limit","setLimit","_retryHandler","error","Error","code","RST_STREAM","test","details","toString","_addresses","FileId","fromString","clone","setMaxAttempts","attempts","_maxAttempts","setMaxBackoff","backoff","_maxBackoff","execute","client","requestTimeout","Promise","resolve","reject","_makeFetchRequest","port","address","_mirrorNetwork","getNextMirrorNode","baseUrl","includes","nextUrl","isLastPage","initialUrl","URL","searchParams","append","effectiveLimit","currentUrl","attempt","response","fetch","method","headers","Accept","signal","AbortSignal","timeout","undefined","ok","status","data","json","pageNodes","nodes","map","node","NodeAddress","fromJSON","nodeId","node_id","accountId","node_account_id","addresses","_handleAddressesFromGrpcProxyEndpoint","certHash","node_cert_hash","publicKey","public_key","description","stake","push","links","next","console","message","String","isClientShutDown","delay","Math","min","_logger","debug","setTimeout","errorMessage","NodeAddressBook","nodeAddresses","grpcProxyEndpoint","grpc_proxy_endpoint","domain_name","networkConstant","ledgerId","_network","isMainnet","MAINNET","isTestnet","WEB_TESTNET","isPreviewnet","WEB_PREVIEWNET","nodeAccountId","accountIdObj","Object","entries","split"],"mappings":"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"}
|
package/lib/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const o="hiero-sdk-js",s="2.
|
|
1
|
+
const o="hiero-sdk-js",s="2.77.0";export{o as SDK_NAME,s as SDK_VERSION};
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hiero-ledger/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.77.0",
|
|
4
4
|
"description": "Hiero SDK",
|
|
5
5
|
"types": "./lib/index.d.ts",
|
|
6
6
|
"main": "./lib/index.cjs",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"@ethersproject/bytes": "5.8.0",
|
|
66
66
|
"@ethersproject/rlp": "5.8.0",
|
|
67
67
|
"@grpc/grpc-js": "1.12.6",
|
|
68
|
-
"@hashgraph/cryptography": "1.
|
|
68
|
+
"@hashgraph/cryptography": "1.15.0",
|
|
69
69
|
"@hashgraph/proto": "2.24.0",
|
|
70
70
|
"ansi-regex": "6.2.2",
|
|
71
71
|
"ansi-styles": "6.2.3",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"debug": "4.4.1",
|
|
76
76
|
"js-base64": "3.7.4",
|
|
77
77
|
"long": "5.3.1",
|
|
78
|
-
"pino": "
|
|
78
|
+
"pino": "10.1.0",
|
|
79
79
|
"pino-pretty": "13.0.0",
|
|
80
80
|
"protobufjs": "7.5.4",
|
|
81
81
|
"rfc4648": "1.5.3",
|
|
@@ -94,8 +94,8 @@
|
|
|
94
94
|
"@babel/register": "7.22.5",
|
|
95
95
|
"@grpc/proto-loader": "0.7.9",
|
|
96
96
|
"@playwright/test": "1.30.0",
|
|
97
|
-
"@rollup/plugin-alias": "
|
|
98
|
-
"@rollup/plugin-commonjs": "
|
|
97
|
+
"@rollup/plugin-alias": "6.0.0",
|
|
98
|
+
"@rollup/plugin-commonjs": "29.0.0",
|
|
99
99
|
"@rollup/plugin-json": "6.1.0",
|
|
100
100
|
"@rollup/plugin-node-resolve": "16.0.1",
|
|
101
101
|
"@rollup/plugin-replace": "6.0.2",
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"babel-plugin-dynamic-import-node": "2.3.3",
|
|
111
111
|
"babel-plugin-module-rewrite": "0.2.0",
|
|
112
112
|
"c8": "10.1.3",
|
|
113
|
-
"chromedriver": "
|
|
113
|
+
"chromedriver": "142.0.0",
|
|
114
114
|
"codecov": "3.8.3",
|
|
115
115
|
"dotenv": "17.0.1",
|
|
116
116
|
"dpdm": "3.11.0",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"eslint-plugin-deprecation": "3.0.0",
|
|
120
120
|
"eslint-plugin-ie11": "1.0.0",
|
|
121
121
|
"eslint-plugin-import": "2.28.1",
|
|
122
|
-
"eslint-plugin-jsdoc": "
|
|
122
|
+
"eslint-plugin-jsdoc": "61.1.11",
|
|
123
123
|
"eslint-plugin-n": "17.16.1",
|
|
124
124
|
"eslint-plugin-vitest": "0.3.25",
|
|
125
125
|
"geckodriver": "6.0.1",
|
|
@@ -128,13 +128,13 @@
|
|
|
128
128
|
"npm-run-all": "4.1.5",
|
|
129
129
|
"npx": "10.2.2",
|
|
130
130
|
"nyc": "17.1.0",
|
|
131
|
-
"playwright": "1.
|
|
131
|
+
"playwright": "1.55.1",
|
|
132
132
|
"prettier": "3.0.3",
|
|
133
133
|
"rollup": "4.34.8",
|
|
134
134
|
"sinon": "21.0.0",
|
|
135
135
|
"typedoc": "0.28.1",
|
|
136
136
|
"typescript": "5.7.2",
|
|
137
|
-
"vite": "6.
|
|
137
|
+
"vite": "6.4.1",
|
|
138
138
|
"vitest": "3.1.2",
|
|
139
139
|
"yalc": "1.0.0-pre.53"
|
|
140
140
|
},
|
package/src/Executable.js
CHANGED
|
@@ -437,6 +437,7 @@ export default class Executable {
|
|
|
437
437
|
if (error instanceof GrpcServiceError) {
|
|
438
438
|
return (
|
|
439
439
|
error.status._code === GrpcStatus.Timeout._code ||
|
|
440
|
+
error.status._code === GrpcStatus.DeadlineExceeded._code ||
|
|
440
441
|
error.status._code === GrpcStatus.Unavailable._code ||
|
|
441
442
|
error.status._code === GrpcStatus.ResourceExhausted._code ||
|
|
442
443
|
error.status._code === GrpcStatus.GrpcWeb._code ||
|
|
@@ -527,6 +528,11 @@ export default class Executable {
|
|
|
527
528
|
requestTimeout != null ? requestTimeout : client.requestTimeout;
|
|
528
529
|
}
|
|
529
530
|
|
|
531
|
+
// If the grpc deadline is not set on the request, use the default value from client
|
|
532
|
+
if (this._grpcDeadline == null) {
|
|
533
|
+
this._grpcDeadline = client.grpcDeadline;
|
|
534
|
+
}
|
|
535
|
+
|
|
530
536
|
// Some request need to perform additional requests before the executing
|
|
531
537
|
// such as paid queries need to fetch the cost of the query before
|
|
532
538
|
// finally executing the actual query.
|
|
@@ -644,6 +650,12 @@ export default class Executable {
|
|
|
644
650
|
}
|
|
645
651
|
|
|
646
652
|
const channel = node.getChannel();
|
|
653
|
+
|
|
654
|
+
// Set the gRPC deadline on the channel if this query has a custom deadline
|
|
655
|
+
if (this._grpcDeadline != null) {
|
|
656
|
+
channel.setGrpcDeadline(this._grpcDeadline);
|
|
657
|
+
}
|
|
658
|
+
|
|
647
659
|
const request = await this._makeRequestAsync();
|
|
648
660
|
|
|
649
661
|
let response;
|
|
@@ -694,7 +706,7 @@ export default class Executable {
|
|
|
694
706
|
// from blocking this request
|
|
695
707
|
const promises = [];
|
|
696
708
|
|
|
697
|
-
// If a grpc deadline is
|
|
709
|
+
// If a grpc deadline is set, we should race it, otherwise the only thing in the
|
|
698
710
|
// list of promises will be the execution promise.
|
|
699
711
|
if (this._grpcDeadline != null) {
|
|
700
712
|
promises.push(
|
|
@@ -703,7 +715,11 @@ export default class Executable {
|
|
|
703
715
|
setTimeout(
|
|
704
716
|
// eslint-disable-next-line ie11/no-loop-func
|
|
705
717
|
() =>
|
|
706
|
-
reject(
|
|
718
|
+
reject(
|
|
719
|
+
new GrpcServiceError(
|
|
720
|
+
GrpcStatus.DeadlineExceeded,
|
|
721
|
+
),
|
|
722
|
+
),
|
|
707
723
|
/** @type {number=} */ (this._grpcDeadline),
|
|
708
724
|
),
|
|
709
725
|
),
|
package/src/channel/Channel.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as HieroProto from "@hashgraph/proto";
|
|
4
4
|
import * as utf8 from "../encoding/utf8.js";
|
|
5
|
+
import { DEFAULT_GRPC_DEADLINE } from "../constants/ClientConstants.js";
|
|
5
6
|
|
|
6
7
|
const { proto } = HieroProto;
|
|
7
8
|
|
|
@@ -12,8 +13,9 @@ const { proto } = HieroProto;
|
|
|
12
13
|
export default class Channel {
|
|
13
14
|
/**
|
|
14
15
|
* @protected
|
|
16
|
+
* @param {number} [grpcDeadline] - The gRPC deadline in milliseconds
|
|
15
17
|
*/
|
|
16
|
-
constructor() {
|
|
18
|
+
constructor(grpcDeadline = DEFAULT_GRPC_DEADLINE) {
|
|
17
19
|
/**
|
|
18
20
|
* @protected
|
|
19
21
|
* @type {?HieroProto.proto.CryptoService}
|
|
@@ -73,6 +75,28 @@ export default class Channel {
|
|
|
73
75
|
* @type {?HieroProto.proto.AddressBookService}
|
|
74
76
|
*/
|
|
75
77
|
this._addressBook = null;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @protected
|
|
81
|
+
* @type {number}
|
|
82
|
+
*/
|
|
83
|
+
this._grpcDeadline = grpcDeadline;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Set the gRPC deadline for this channel
|
|
88
|
+
* @param {number} deadline - The deadline in milliseconds, or null to clear
|
|
89
|
+
*/
|
|
90
|
+
setGrpcDeadline(deadline) {
|
|
91
|
+
this._grpcDeadline = deadline;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get the gRPC deadline for this channel
|
|
96
|
+
* @returns {number}
|
|
97
|
+
*/
|
|
98
|
+
get grpcDeadline() {
|
|
99
|
+
return this._grpcDeadline;
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
/**
|
|
@@ -4,19 +4,31 @@ import * as base64 from "../encoding/base64.native.js";
|
|
|
4
4
|
import HttpError from "../http/HttpError.js";
|
|
5
5
|
import HttpStatus from "../http/HttpStatus.js";
|
|
6
6
|
import { SDK_NAME, SDK_VERSION } from "../version.js";
|
|
7
|
+
import GrpcServiceError from "../grpc/GrpcServiceError.js";
|
|
8
|
+
import GrpcStatus from "../grpc/GrpcStatus.js";
|
|
7
9
|
|
|
8
10
|
export default class NativeChannel extends Channel {
|
|
9
11
|
/**
|
|
10
12
|
* @param {string} address
|
|
13
|
+
* @param {number=} grpcDeadline
|
|
11
14
|
*/
|
|
12
|
-
constructor(address) {
|
|
13
|
-
super();
|
|
15
|
+
constructor(address, grpcDeadline) {
|
|
16
|
+
super(grpcDeadline);
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* @type {string}
|
|
17
20
|
* @private
|
|
18
21
|
*/
|
|
19
22
|
this._address = address;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Flag indicating if the connection is ready (health check has passed)
|
|
26
|
+
* Set to true after the first successful health check
|
|
27
|
+
*
|
|
28
|
+
* @type {boolean}
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
this._isReady = false;
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
/**
|
|
@@ -27,6 +39,89 @@ export default class NativeChannel extends Channel {
|
|
|
27
39
|
// do nothing
|
|
28
40
|
}
|
|
29
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Check if the gRPC-Web proxy is reachable and healthy
|
|
44
|
+
* Performs a POST request and verifies the response has gRPC-Web headers,
|
|
45
|
+
* which indicates the proxy is running and processing gRPC requests.
|
|
46
|
+
* Results are cached per address for the entire lifecycle.
|
|
47
|
+
*
|
|
48
|
+
* @param {Date} deadline - Deadline for the health check
|
|
49
|
+
* @returns {Promise<void>}
|
|
50
|
+
* @private
|
|
51
|
+
*/
|
|
52
|
+
async _waitForReady(deadline) {
|
|
53
|
+
// Check if we've already validated this address
|
|
54
|
+
if (this._isReady) {
|
|
55
|
+
return; // Health check already passed for this address
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const shouldUseHttps = !(
|
|
59
|
+
this._address.includes("localhost") ||
|
|
60
|
+
this._address.includes("127.0.0.1")
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const address = shouldUseHttps
|
|
64
|
+
? `https://${this._address}`
|
|
65
|
+
: `http://${this._address}`;
|
|
66
|
+
|
|
67
|
+
// Calculate remaining time until deadline
|
|
68
|
+
const timeoutMs = deadline.getTime() - Date.now();
|
|
69
|
+
if (timeoutMs <= 0) {
|
|
70
|
+
throw new GrpcServiceError(GrpcStatus.Timeout);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const abortController = new AbortController();
|
|
74
|
+
const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
// Make a POST request to verify the gRPC-Web proxy is running
|
|
78
|
+
// We use a minimal gRPC-Web compatible request
|
|
79
|
+
//eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
80
|
+
const response = await fetch(address, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: {
|
|
83
|
+
"content-type": "application/grpc-web-text",
|
|
84
|
+
"x-user-agent": `${SDK_NAME}/${SDK_VERSION}`,
|
|
85
|
+
"x-grpc-web": "1",
|
|
86
|
+
},
|
|
87
|
+
body: base64.encode(new Uint8Array(0)), // Empty body for health check
|
|
88
|
+
signal: abortController.signal,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
clearTimeout(timeoutId);
|
|
92
|
+
|
|
93
|
+
// Check if response is successful (200) and has gRPC headers
|
|
94
|
+
if (response.status === 200) {
|
|
95
|
+
const grpcStatus = response.headers.get("grpc-status");
|
|
96
|
+
const grpcMessage = response.headers.get("grpc-message");
|
|
97
|
+
|
|
98
|
+
// If gRPC headers exist, the proxy is running and processing requests
|
|
99
|
+
if (grpcStatus != null || grpcMessage != null) {
|
|
100
|
+
// Mark this connection as ready
|
|
101
|
+
this._isReady = true;
|
|
102
|
+
return; // Healthy - gRPC-Web proxy is responding
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// If we get here, either status isn't 200 or no gRPC headers present
|
|
107
|
+
// This means the proxy might not be configured correctly or not running
|
|
108
|
+
throw new GrpcServiceError(GrpcStatus.Unavailable);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
clearTimeout(timeoutId);
|
|
111
|
+
|
|
112
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
113
|
+
throw new GrpcServiceError(GrpcStatus.Timeout);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (error instanceof GrpcServiceError) {
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Network error - server is not reachable
|
|
121
|
+
throw new GrpcServiceError(GrpcStatus.Unavailable);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
30
125
|
/**
|
|
31
126
|
* @override
|
|
32
127
|
* @protected
|
|
@@ -36,7 +131,16 @@ export default class NativeChannel extends Channel {
|
|
|
36
131
|
_createUnaryClient(serviceName) {
|
|
37
132
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
38
133
|
return async (method, requestData, callback) => {
|
|
134
|
+
// Calculate deadline for connection check
|
|
135
|
+
const deadline = new Date();
|
|
136
|
+
const milliseconds = this._grpcDeadline;
|
|
137
|
+
|
|
138
|
+
deadline.setMilliseconds(deadline.getMilliseconds() + milliseconds);
|
|
139
|
+
|
|
39
140
|
try {
|
|
141
|
+
// Wait for connection to be ready (similar to gRPC waitForReady)
|
|
142
|
+
await this._waitForReady(deadline);
|
|
143
|
+
|
|
40
144
|
const data = base64.encode(
|
|
41
145
|
new Uint8Array(encodeRequest(requestData)),
|
|
42
146
|
);
|
|
@@ -121,6 +225,11 @@ export default class NativeChannel extends Channel {
|
|
|
121
225
|
|
|
122
226
|
callback(null, unaryResponse);
|
|
123
227
|
} catch (error) {
|
|
228
|
+
if (error instanceof GrpcServiceError) {
|
|
229
|
+
callback(error, null);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
124
233
|
callback(/** @type {Error} */ (error), null);
|
|
125
234
|
}
|
|
126
235
|
};
|
|
@@ -14,16 +14,15 @@ export default class NodeChannel extends Channel {
|
|
|
14
14
|
/**
|
|
15
15
|
* @internal
|
|
16
16
|
* @param {string} address
|
|
17
|
-
* @param {number=}
|
|
17
|
+
* @param {number=} grpcDeadline
|
|
18
18
|
*/
|
|
19
|
-
constructor(address,
|
|
20
|
-
super();
|
|
19
|
+
constructor(address, grpcDeadline) {
|
|
20
|
+
super(grpcDeadline);
|
|
21
21
|
|
|
22
22
|
/** @type {Client | null} */
|
|
23
23
|
this._client = null;
|
|
24
24
|
|
|
25
25
|
this.address = address;
|
|
26
|
-
this.maxExecutionTime = maxExecutionTime;
|
|
27
26
|
|
|
28
27
|
const { ip, port } = this.parseAddress(address);
|
|
29
28
|
this.nodeIp = ip;
|
|
@@ -146,9 +145,7 @@ export default class NodeChannel extends Channel {
|
|
|
146
145
|
this._initializeClient()
|
|
147
146
|
.then(() => {
|
|
148
147
|
const deadline = new Date();
|
|
149
|
-
const milliseconds = this.
|
|
150
|
-
? this.maxExecutionTime
|
|
151
|
-
: 10000;
|
|
148
|
+
const milliseconds = this.grpcDeadline;
|
|
152
149
|
deadline.setMilliseconds(
|
|
153
150
|
deadline.getMilliseconds() + milliseconds,
|
|
154
151
|
);
|