@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.
Files changed (75) hide show
  1. package/dist/umd.js +440 -71
  2. package/dist/umd.min.js +14 -14
  3. package/lib/Executable.cjs +13 -3
  4. package/lib/Executable.js +1 -1
  5. package/lib/Executable.js.map +1 -1
  6. package/lib/channel/Channel.cjs +25 -1
  7. package/lib/channel/Channel.d.ts +20 -0
  8. package/lib/channel/Channel.js +1 -1
  9. package/lib/channel/Channel.js.map +1 -1
  10. package/lib/channel/NativeChannel.cjs +96 -2
  11. package/lib/channel/NativeChannel.d.ts +21 -1
  12. package/lib/channel/NativeChannel.js +1 -1
  13. package/lib/channel/NativeChannel.js.map +1 -1
  14. package/lib/channel/NodeChannel.cjs +4 -5
  15. package/lib/channel/NodeChannel.d.ts +2 -3
  16. package/lib/channel/NodeChannel.js +1 -1
  17. package/lib/channel/NodeChannel.js.map +1 -1
  18. package/lib/channel/WebChannel.cjs +163 -4
  19. package/lib/channel/WebChannel.d.ts +51 -1
  20. package/lib/channel/WebChannel.js +1 -1
  21. package/lib/channel/WebChannel.js.map +1 -1
  22. package/lib/client/Client.cjs +62 -5
  23. package/lib/client/Client.d.ts +26 -3
  24. package/lib/client/Client.js +1 -1
  25. package/lib/client/Client.js.map +1 -1
  26. package/lib/client/NativeClient.cjs +1 -1
  27. package/lib/client/NativeClient.js +1 -1
  28. package/lib/client/NativeClient.js.map +1 -1
  29. package/lib/client/NodeClient.cjs +7 -6
  30. package/lib/client/NodeClient.d.ts +3 -3
  31. package/lib/client/NodeClient.js +1 -1
  32. package/lib/client/NodeClient.js.map +1 -1
  33. package/lib/client/WebClient.cjs +61 -27
  34. package/lib/client/WebClient.d.ts +6 -0
  35. package/lib/client/WebClient.js +1 -1
  36. package/lib/client/WebClient.js.map +1 -1
  37. package/lib/client/addressbooks/mainnet.cjs +1 -1
  38. package/lib/client/addressbooks/mainnet.d.ts +1 -1
  39. package/lib/client/addressbooks/mainnet.js +1 -1
  40. package/lib/client/addressbooks/mainnet.js.map +1 -1
  41. package/lib/client/addressbooks/previewnet.cjs +1 -1
  42. package/lib/client/addressbooks/previewnet.d.ts +1 -1
  43. package/lib/client/addressbooks/previewnet.js +1 -1
  44. package/lib/client/addressbooks/previewnet.js.map +1 -1
  45. package/lib/client/addressbooks/testnet.cjs +1 -1
  46. package/lib/client/addressbooks/testnet.d.ts +1 -1
  47. package/lib/client/addressbooks/testnet.js +1 -1
  48. package/lib/client/addressbooks/testnet.js.map +1 -1
  49. package/lib/constants/ClientConstants.cjs +18 -2
  50. package/lib/constants/ClientConstants.d.ts +15 -0
  51. package/lib/constants/ClientConstants.js +1 -1
  52. package/lib/constants/ClientConstants.js.map +1 -1
  53. package/lib/network/AddressBookQuery.cjs +0 -4
  54. package/lib/network/AddressBookQuery.js +1 -1
  55. package/lib/network/AddressBookQuery.js.map +1 -1
  56. package/lib/network/AddressBookQueryWeb.cjs +1 -5
  57. package/lib/network/AddressBookQueryWeb.js +1 -1
  58. package/lib/network/AddressBookQueryWeb.js.map +1 -1
  59. package/lib/version.js +1 -1
  60. package/package.json +9 -9
  61. package/src/Executable.js +18 -2
  62. package/src/channel/Channel.js +25 -1
  63. package/src/channel/NativeChannel.js +111 -2
  64. package/src/channel/NodeChannel.js +4 -7
  65. package/src/channel/WebChannel.js +189 -9
  66. package/src/client/Client.js +79 -5
  67. package/src/client/NativeClient.js +1 -1
  68. package/src/client/NodeClient.js +7 -6
  69. package/src/client/WebClient.js +77 -31
  70. package/src/client/addressbooks/mainnet.js +1 -1
  71. package/src/client/addressbooks/previewnet.js +1 -1
  72. package/src/client/addressbooks/testnet.js +1 -1
  73. package/src/constants/ClientConstants.js +17 -1
  74. package/src/network/AddressBookQuery.js +0 -7
  75. 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.75.0";export{o as SDK_NAME,s as SDK_VERSION};
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.75.0",
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.13.0",
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": "9.6.0",
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": "5.1.1",
98
- "@rollup/plugin-commonjs": "28.0.3",
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": "141.0.0",
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": "60.6.0",
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.51.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.3.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 est, we should race it, otherwise the only thing in the
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(new Error("grpc deadline exceeded")),
718
+ reject(
719
+ new GrpcServiceError(
720
+ GrpcStatus.DeadlineExceeded,
721
+ ),
722
+ ),
707
723
  /** @type {number=} */ (this._grpcDeadline),
708
724
  ),
709
725
  ),
@@ -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=} maxExecutionTime
17
+ * @param {number=} grpcDeadline
18
18
  */
19
- constructor(address, maxExecutionTime) {
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.maxExecutionTime
150
- ? this.maxExecutionTime
151
- : 10000;
148
+ const milliseconds = this.grpcDeadline;
152
149
  deadline.setMilliseconds(
153
150
  deadline.getMilliseconds() + milliseconds,
154
151
  );