@hiero-ledger/sdk 2.76.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 +426 -82
  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 -13
  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 +55 -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 +17 -1
  50. package/lib/constants/ClientConstants.d.ts +14 -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 +7 -7
  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 -21
  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 +64 -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 +16 -0
  74. package/src/network/AddressBookQuery.js +0 -7
  75. package/src/network/AddressBookQueryWeb.js +1 -8
@@ -1,12 +1,32 @@
1
1
  export default class NativeChannel extends Channel {
2
2
  /**
3
3
  * @param {string} address
4
+ * @param {number=} grpcDeadline
4
5
  */
5
- constructor(address: string);
6
+ constructor(address: string, grpcDeadline?: number | undefined);
6
7
  /**
7
8
  * @type {string}
8
9
  * @private
9
10
  */
10
11
  private _address;
12
+ /**
13
+ * Flag indicating if the connection is ready (health check has passed)
14
+ * Set to true after the first successful health check
15
+ *
16
+ * @type {boolean}
17
+ * @private
18
+ */
19
+ private _isReady;
20
+ /**
21
+ * Check if the gRPC-Web proxy is reachable and healthy
22
+ * Performs a POST request and verifies the response has gRPC-Web headers,
23
+ * which indicates the proxy is running and processing gRPC requests.
24
+ * Results are cached per address for the entire lifecycle.
25
+ *
26
+ * @param {Date} deadline - Deadline for the health check
27
+ * @returns {Promise<void>}
28
+ * @private
29
+ */
30
+ private _waitForReady;
11
31
  }
12
32
  import Channel from "./Channel.js";
@@ -1,2 +1,2 @@
1
- import t,{encodeRequest as e,decodeUnaryResponse as a}from"./Channel.js";import{encode as s,decode as r}from"../encoding/base64.native.js";import o from"../http/HttpError.js";import n from"../http/HttpStatus.js";import{SDK_NAME as i,SDK_VERSION as p}from"../version.js";class c extends t{constructor(t){super(),this._address=t}close(){}_createUnaryClient(t){return async(c,d,l)=>{try{const b=s(new Uint8Array(e(d))),h=!(this._address.includes("localhost")||this._address.includes("127.0.0.1"))?`https://${this._address}`:`http://${this._address}`,f=await fetch(`${h}/proto.${t}/${c.name}`,{method:"POST",headers:{"content-type":"application/grpc-web-text","x-user-agent":`${i}/${p}`,"x-accept-content-transfer-encoding":"base64","x-grpc-web":"1"},body:b});if(!f.ok){l(new o(n._fromValue(f.status)),null)}const m=await f.blob(),u=await new Promise((t,e)=>{const a=new FileReader;a.readAsDataURL(m),a.onloadend=()=>{t(a.result)},a.onerror=e});let w;if(u.startsWith("data:application/octet-stream;base64,"))w=r(u.split("data:application/octet-stream;base64,")[1]);else{if(!u.startsWith("data:application/grpc-web+proto;base64,"))throw new Error(`Expected response data to be base64 encode with a 'data:application/octet-stream;base64,' or 'data:application/grpc-web+proto;base64,' prefix, but found: ${u}`);w=r(u.split("data:application/grpc-web+proto;base64,")[1])}l(null,a(w.buffer,w.byteOffset,w.byteLength))}catch(t){l(t,null)}}}}export{c as default};
1
+ import t,{encodeRequest as e,decodeUnaryResponse as a}from"./Channel.js";import{encode as s,decode as r}from"../encoding/base64.native.js";import o from"../http/HttpError.js";import i from"../http/HttpStatus.js";import{SDK_NAME as n,SDK_VERSION as c}from"../version.js";import p from"../grpc/GrpcServiceError.js";import d from"../grpc/GrpcStatus.js";class l extends t{constructor(t,e){super(e),this._address=t,this._isReady=!1}close(){}async _waitForReady(t){if(this._isReady)return;const e=!(this._address.includes("localhost")||this._address.includes("127.0.0.1"))?`https://${this._address}`:`http://${this._address}`,a=t.getTime()-Date.now();if(a<=0)throw new p(d.Timeout);const r=new AbortController,o=setTimeout(()=>r.abort(),a);try{const t=await fetch(e,{method:"POST",headers:{"content-type":"application/grpc-web-text","x-user-agent":`${n}/${c}`,"x-grpc-web":"1"},body:s(new Uint8Array(0)),signal:r.signal});if(clearTimeout(o),200===t.status){const e=t.headers.get("grpc-status"),a=t.headers.get("grpc-message");if(null!=e||null!=a)return void(this._isReady=!0)}throw new p(d.Unavailable)}catch(t){if(clearTimeout(o),t instanceof Error&&"AbortError"===t.name)throw new p(d.Timeout);if(t instanceof p)throw t;throw new p(d.Unavailable)}}_createUnaryClient(t){return async(d,l,h)=>{const u=new Date,w=this._grpcDeadline;u.setMilliseconds(u.getMilliseconds()+w);try{await this._waitForReady(u);const p=s(new Uint8Array(e(l))),w=!(this._address.includes("localhost")||this._address.includes("127.0.0.1"))?`https://${this._address}`:`http://${this._address}`,f=await fetch(`${w}/proto.${t}/${d.name}`,{method:"POST",headers:{"content-type":"application/grpc-web-text","x-user-agent":`${n}/${c}`,"x-accept-content-transfer-encoding":"base64","x-grpc-web":"1"},body:p});if(!f.ok){h(new o(i._fromValue(f.status)),null)}const b=await f.blob(),m=await new Promise((t,e)=>{const a=new FileReader;a.readAsDataURL(b),a.onloadend=()=>{t(a.result)},a.onerror=e});let g;if(m.startsWith("data:application/octet-stream;base64,"))g=r(m.split("data:application/octet-stream;base64,")[1]);else{if(!m.startsWith("data:application/grpc-web+proto;base64,"))throw new Error(`Expected response data to be base64 encode with a 'data:application/octet-stream;base64,' or 'data:application/grpc-web+proto;base64,' prefix, but found: ${m}`);g=r(m.split("data:application/grpc-web+proto;base64,")[1])}h(null,a(g.buffer,g.byteOffset,g.byteLength))}catch(t){if(t instanceof p)return void h(t,null);h(t,null)}}}}export{l as default};
2
2
  //# sourceMappingURL=NativeChannel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"NativeChannel.js","sources":["../../src/channel/NativeChannel.js"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport Channel, { encodeRequest, decodeUnaryResponse } from \"./Channel.js\";\nimport * as base64 from \"../encoding/base64.native.js\";\nimport HttpError from \"../http/HttpError.js\";\nimport HttpStatus from \"../http/HttpStatus.js\";\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\n\nexport default class NativeChannel extends Channel {\n /**\n * @param {string} address\n */\n constructor(address) {\n super();\n\n /**\n * @type {string}\n * @private\n */\n this._address = address;\n }\n\n /**\n * @override\n * @returns {void}\n */\n close() {\n // do nothing\n }\n\n /**\n * @override\n * @protected\n * @param {string} serviceName\n * @returns {import(\"protobufjs\").RPCImpl}\n */\n _createUnaryClient(serviceName) {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n return async (method, requestData, callback) => {\n try {\n const data = base64.encode(\n new Uint8Array(encodeRequest(requestData)),\n );\n\n const shouldUseHttps = !(\n this._address.includes(\"localhost\") ||\n this._address.includes(\"127.0.0.1\")\n );\n\n const address = shouldUseHttps\n ? `https://${this._address}`\n : `http://${this._address}`;\n // this will be executed in react native environment sho\n // fetch should be available\n //eslint-disable-next-line n/no-unsupported-features/node-builtins\n const response = await fetch(\n `${address}/proto.${serviceName}/${method.name}`,\n {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/grpc-web-text\",\n \"x-user-agent\": `${SDK_NAME}/${SDK_VERSION}`,\n \"x-accept-content-transfer-encoding\": \"base64\",\n \"x-grpc-web\": \"1\",\n },\n body: data,\n },\n );\n\n if (!response.ok) {\n const error = new HttpError(\n HttpStatus._fromValue(response.status),\n );\n callback(error, null);\n }\n\n const blob = await response.blob();\n\n /** @type {string} */\n const responseData = await new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.readAsDataURL(blob);\n reader.onloadend = () => {\n resolve(/** @type {string} */ (reader.result));\n };\n reader.onerror = reject;\n });\n\n let responseBuffer;\n if (\n responseData.startsWith(\n \"data:application/octet-stream;base64,\",\n )\n ) {\n responseBuffer = base64.decode(\n responseData.split(\n \"data:application/octet-stream;base64,\",\n )[1],\n );\n } else if (\n responseData.startsWith(\n \"data:application/grpc-web+proto;base64,\",\n )\n ) {\n responseBuffer = base64.decode(\n responseData.split(\n \"data:application/grpc-web+proto;base64,\",\n )[1],\n );\n } else {\n throw new Error(\n `Expected response data to be base64 encode with a 'data:application/octet-stream;base64,' or 'data:application/grpc-web+proto;base64,' prefix, but found: ${responseData}`,\n );\n }\n\n const unaryResponse = decodeUnaryResponse(\n // @ts-ignore\n responseBuffer.buffer,\n responseBuffer.byteOffset,\n responseBuffer.byteLength,\n );\n\n callback(null, unaryResponse);\n } catch (error) {\n callback(/** @type {Error} */ (error), null);\n }\n };\n }\n}\n"],"names":["NativeChannel","Channel","constructor","address","super","this","_address","close","_createUnaryClient","serviceName","async","method","requestData","callback","data","base64.encode","Uint8Array","encodeRequest","includes","response","fetch","name","headers","SDK_NAME","SDK_VERSION","body","ok","HttpError","HttpStatus","_fromValue","status","blob","responseData","Promise","resolve","reject","reader","FileReader","readAsDataURL","onloadend","onerror","responseBuffer","startsWith","base64.decode","split","Error","decodeUnaryResponse","buffer","byteOffset","byteLength","error"],"mappings":"8QAOe,MAAMA,UAAsBC,EAIvC,WAAAC,CAAYC,GACRC,QAMAC,KAAKC,SAAWH,CACxB,CAMI,KAAAI,GAEJ,CAQI,kBAAAC,CAAmBC,GAEf,OAAOC,MAAOC,EAAQC,EAAaC,KAC/B,IACI,MAAMC,EAAOC,EACT,IAAIC,WAAWC,EAAcL,KAQ3BT,IAJFE,KAAKC,SAASY,SAAS,cACvBb,KAAKC,SAASY,SAAS,cAIrB,WAAWb,KAAKC,WAChB,UAAUD,KAAKC,WAIfa,QAAiBC,MACnB,GAAGjB,WAAiBM,KAAeE,EAAOU,OAC1C,CACIV,OAAQ,OACRW,QAAS,CACL,eAAgB,4BAChB,eAAgB,GAAGC,KAAYC,IAC/B,qCAAsC,SACtC,aAAc,KAElBC,KAAMX,IAId,IAAKK,EAASO,GAAI,CAIdb,EAHc,IAAIc,EACdC,EAAWC,WAAWV,EAASW,SAEnB,KACpC,CAEgB,MAAMC,QAAaZ,EAASY,OAGtBC,QAAqB,IAAIC,QAAQ,CAACC,EAASC,KAC7C,MAAMC,EAAS,IAAIC,WACnBD,EAAOE,cAAcP,GACrBK,EAAOG,UAAY,KACfL,EAA+BE,EAAa,SAEhDA,EAAOI,QAAUL,IAGrB,IAAIM,EACJ,GACIT,EAAaU,WACT,yCAGJD,EAAiBE,EACbX,EAAaY,MACT,yCACF,QAEH,KACHZ,EAAaU,WACT,2CASJ,MAAM,IAAIG,MACN,6JAA6Jb,KAPjKS,EAAiBE,EACbX,EAAaY,MACT,2CACF,GAM1B,CASgB/B,EAAS,KAPaiC,EAElBL,EAAeM,OACfN,EAAeO,WACfP,EAAeQ,YAItB,CAAC,MAAOC,GACLrC,EAAQ,EAA+B,KACvD,EAEA"}
1
+ {"version":3,"file":"NativeChannel.js","sources":["../../src/channel/NativeChannel.js"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport Channel, { encodeRequest, decodeUnaryResponse } from \"./Channel.js\";\nimport * as base64 from \"../encoding/base64.native.js\";\nimport HttpError from \"../http/HttpError.js\";\nimport HttpStatus from \"../http/HttpStatus.js\";\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\nimport GrpcServiceError from \"../grpc/GrpcServiceError.js\";\nimport GrpcStatus from \"../grpc/GrpcStatus.js\";\n\nexport default class NativeChannel extends Channel {\n /**\n * @param {string} address\n * @param {number=} grpcDeadline\n */\n constructor(address, grpcDeadline) {\n super(grpcDeadline);\n\n /**\n * @type {string}\n * @private\n */\n this._address = address;\n\n /**\n * Flag indicating if the connection is ready (health check has passed)\n * Set to true after the first successful health check\n *\n * @type {boolean}\n * @private\n */\n this._isReady = false;\n }\n\n /**\n * @override\n * @returns {void}\n */\n close() {\n // do nothing\n }\n\n /**\n * Check if the gRPC-Web proxy is reachable and healthy\n * Performs a POST request and verifies the response has gRPC-Web headers,\n * which indicates the proxy is running and processing gRPC requests.\n * Results are cached per address for the entire lifecycle.\n *\n * @param {Date} deadline - Deadline for the health check\n * @returns {Promise<void>}\n * @private\n */\n async _waitForReady(deadline) {\n // Check if we've already validated this address\n if (this._isReady) {\n return; // Health check already passed for this address\n }\n\n const shouldUseHttps = !(\n this._address.includes(\"localhost\") ||\n this._address.includes(\"127.0.0.1\")\n );\n\n const address = shouldUseHttps\n ? `https://${this._address}`\n : `http://${this._address}`;\n\n // Calculate remaining time until deadline\n const timeoutMs = deadline.getTime() - Date.now();\n if (timeoutMs <= 0) {\n throw new GrpcServiceError(GrpcStatus.Timeout);\n }\n\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);\n\n try {\n // Make a POST request to verify the gRPC-Web proxy is running\n // We use a minimal gRPC-Web compatible request\n //eslint-disable-next-line n/no-unsupported-features/node-builtins\n const response = await fetch(address, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/grpc-web-text\",\n \"x-user-agent\": `${SDK_NAME}/${SDK_VERSION}`,\n \"x-grpc-web\": \"1\",\n },\n body: base64.encode(new Uint8Array(0)), // Empty body for health check\n signal: abortController.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Check if response is successful (200) and has gRPC headers\n if (response.status === 200) {\n const grpcStatus = response.headers.get(\"grpc-status\");\n const grpcMessage = response.headers.get(\"grpc-message\");\n\n // If gRPC headers exist, the proxy is running and processing requests\n if (grpcStatus != null || grpcMessage != null) {\n // Mark this connection as ready\n this._isReady = true;\n return; // Healthy - gRPC-Web proxy is responding\n }\n }\n\n // If we get here, either status isn't 200 or no gRPC headers present\n // This means the proxy might not be configured correctly or not running\n throw new GrpcServiceError(GrpcStatus.Unavailable);\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new GrpcServiceError(GrpcStatus.Timeout);\n }\n\n if (error instanceof GrpcServiceError) {\n throw error;\n }\n\n // Network error - server is not reachable\n throw new GrpcServiceError(GrpcStatus.Unavailable);\n }\n }\n\n /**\n * @override\n * @protected\n * @param {string} serviceName\n * @returns {import(\"protobufjs\").RPCImpl}\n */\n _createUnaryClient(serviceName) {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n return async (method, requestData, callback) => {\n // Calculate deadline for connection check\n const deadline = new Date();\n const milliseconds = this._grpcDeadline;\n\n deadline.setMilliseconds(deadline.getMilliseconds() + milliseconds);\n\n try {\n // Wait for connection to be ready (similar to gRPC waitForReady)\n await this._waitForReady(deadline);\n\n const data = base64.encode(\n new Uint8Array(encodeRequest(requestData)),\n );\n\n const shouldUseHttps = !(\n this._address.includes(\"localhost\") ||\n this._address.includes(\"127.0.0.1\")\n );\n\n const address = shouldUseHttps\n ? `https://${this._address}`\n : `http://${this._address}`;\n // this will be executed in react native environment sho\n // fetch should be available\n //eslint-disable-next-line n/no-unsupported-features/node-builtins\n const response = await fetch(\n `${address}/proto.${serviceName}/${method.name}`,\n {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/grpc-web-text\",\n \"x-user-agent\": `${SDK_NAME}/${SDK_VERSION}`,\n \"x-accept-content-transfer-encoding\": \"base64\",\n \"x-grpc-web\": \"1\",\n },\n body: data,\n },\n );\n\n if (!response.ok) {\n const error = new HttpError(\n HttpStatus._fromValue(response.status),\n );\n callback(error, null);\n }\n\n const blob = await response.blob();\n\n /** @type {string} */\n const responseData = await new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.readAsDataURL(blob);\n reader.onloadend = () => {\n resolve(/** @type {string} */ (reader.result));\n };\n reader.onerror = reject;\n });\n\n let responseBuffer;\n if (\n responseData.startsWith(\n \"data:application/octet-stream;base64,\",\n )\n ) {\n responseBuffer = base64.decode(\n responseData.split(\n \"data:application/octet-stream;base64,\",\n )[1],\n );\n } else if (\n responseData.startsWith(\n \"data:application/grpc-web+proto;base64,\",\n )\n ) {\n responseBuffer = base64.decode(\n responseData.split(\n \"data:application/grpc-web+proto;base64,\",\n )[1],\n );\n } else {\n throw new Error(\n `Expected response data to be base64 encode with a 'data:application/octet-stream;base64,' or 'data:application/grpc-web+proto;base64,' prefix, but found: ${responseData}`,\n );\n }\n\n const unaryResponse = decodeUnaryResponse(\n // @ts-ignore\n responseBuffer.buffer,\n responseBuffer.byteOffset,\n responseBuffer.byteLength,\n );\n\n callback(null, unaryResponse);\n } catch (error) {\n if (error instanceof GrpcServiceError) {\n callback(error, null);\n return;\n }\n\n callback(/** @type {Error} */ (error), null);\n }\n };\n }\n}\n"],"names":["NativeChannel","Channel","constructor","address","grpcDeadline","super","this","_address","_isReady","close","_waitForReady","deadline","includes","timeoutMs","getTime","Date","now","GrpcServiceError","GrpcStatus","Timeout","abortController","AbortController","timeoutId","setTimeout","abort","response","fetch","method","headers","SDK_NAME","SDK_VERSION","body","base64.encode","Uint8Array","signal","clearTimeout","status","grpcStatus","get","grpcMessage","Unavailable","error","Error","name","_createUnaryClient","serviceName","async","requestData","callback","milliseconds","_grpcDeadline","setMilliseconds","getMilliseconds","data","encodeRequest","ok","HttpError","HttpStatus","_fromValue","blob","responseData","Promise","resolve","reject","reader","FileReader","readAsDataURL","onloadend","onerror","responseBuffer","startsWith","base64.decode","split","decodeUnaryResponse","buffer","byteOffset","byteLength"],"mappings":"8VASe,MAAMA,UAAsBC,EAKvC,WAAAC,CAAYC,EAASC,GACjBC,MAAMD,GAMNE,KAAKC,SAAWJ,EAShBG,KAAKE,UAAW,CACxB,CAMI,KAAAC,GAEJ,CAYI,mBAAMC,CAAcC,GAEhB,GAAIL,KAAKE,SACL,OAGJ,MAKML,IAJFG,KAAKC,SAASK,SAAS,cACvBN,KAAKC,SAASK,SAAS,cAIrB,WAAWN,KAAKC,WAChB,UAAUD,KAAKC,WAGfM,EAAYF,EAASG,UAAYC,KAAKC,MAC5C,GAAIH,GAAa,EACb,MAAM,IAAII,EAAiBC,EAAWC,SAG1C,MAAMC,EAAkB,IAAIC,gBACtBC,EAAYC,WAAW,IAAMH,EAAgBI,QAASX,GAE5D,IAII,MAAMY,QAAiBC,MAAMvB,EAAS,CAClCwB,OAAQ,OACRC,QAAS,CACL,eAAgB,4BAChB,eAAgB,GAAGC,KAAYC,IAC/B,aAAc,KAElBC,KAAMC,EAAc,IAAIC,WAAW,IACnCC,OAAQd,EAAgBc,SAM5B,GAHAC,aAAab,GAGW,MAApBG,EAASW,OAAgB,CACzB,MAAMC,EAAaZ,EAASG,QAAQU,IAAI,eAClCC,EAAcd,EAASG,QAAQU,IAAI,gBAGzC,GAAkB,MAAdD,GAAqC,MAAfE,EAGtB,YADAjC,KAAKE,UAAW,EAGpC,CAIY,MAAM,IAAIS,EAAiBC,EAAWsB,YACzC,CAAC,MAAOC,GAGL,GAFAN,aAAab,GAETmB,aAAiBC,OAAwB,eAAfD,EAAME,KAChC,MAAM,IAAI1B,EAAiBC,EAAWC,SAG1C,GAAIsB,aAAiBxB,EACjB,MAAMwB,EAIV,MAAM,IAAIxB,EAAiBC,EAAWsB,YAClD,CACA,CAQI,kBAAAI,CAAmBC,GAEf,OAAOC,MAAOnB,EAAQoB,EAAaC,KAE/B,MAAMrC,EAAW,IAAII,KACfkC,EAAe3C,KAAK4C,cAE1BvC,EAASwC,gBAAgBxC,EAASyC,kBAAoBH,GAEtD,UAEU3C,KAAKI,cAAcC,GAEzB,MAAM0C,EAAOrB,EACT,IAAIC,WAAWqB,EAAcP,KAQ3B5C,IAJFG,KAAKC,SAASK,SAAS,cACvBN,KAAKC,SAASK,SAAS,cAIrB,WAAWN,KAAKC,WAChB,UAAUD,KAAKC,WAIfkB,QAAiBC,MACnB,GAAGvB,WAAiB0C,KAAelB,EAAOgB,OAC1C,CACIhB,OAAQ,OACRC,QAAS,CACL,eAAgB,4BAChB,eAAgB,GAAGC,KAAYC,IAC/B,qCAAsC,SACtC,aAAc,KAElBC,KAAMsB,IAId,IAAK5B,EAAS8B,GAAI,CAIdP,EAHc,IAAIQ,EACdC,EAAWC,WAAWjC,EAASW,SAEnB,KACpC,CAEgB,MAAMuB,QAAalC,EAASkC,OAGtBC,QAAqB,IAAIC,QAAQ,CAACC,EAASC,KAC7C,MAAMC,EAAS,IAAIC,WACnBD,EAAOE,cAAcP,GACrBK,EAAOG,UAAY,KACfL,EAA+BE,EAAa,SAEhDA,EAAOI,QAAUL,IAGrB,IAAIM,EACJ,GACIT,EAAaU,WACT,yCAGJD,EAAiBE,EACbX,EAAaY,MACT,yCACF,QAEH,KACHZ,EAAaU,WACT,2CASJ,MAAM,IAAI5B,MACN,6JAA6JkB,KAPjKS,EAAiBE,EACbX,EAAaY,MACT,2CACF,GAM1B,CASgBxB,EAAS,KAPayB,EAElBJ,EAAeK,OACfL,EAAeM,WACfN,EAAeO,YAItB,CAAC,MAAOnC,GACL,GAAIA,aAAiBxB,EAEjB,YADA+B,EAASP,EAAO,MAIpBO,EAAQ,EAA+B,KACvD,EAEA"}
@@ -20,15 +20,14 @@ class NodeChannel extends _Channel.default {
20
20
  /**
21
21
  * @internal
22
22
  * @param {string} address
23
- * @param {number=} maxExecutionTime
23
+ * @param {number=} grpcDeadline
24
24
  */
25
- constructor(address, maxExecutionTime) {
26
- super();
25
+ constructor(address, grpcDeadline) {
26
+ super(grpcDeadline);
27
27
 
28
28
  /** @type {Client | null} */
29
29
  this._client = null;
30
30
  this.address = address;
31
- this.maxExecutionTime = maxExecutionTime;
32
31
  const {
33
32
  ip,
34
33
  port
@@ -144,7 +143,7 @@ class NodeChannel extends _Channel.default {
144
143
  return (method, requestData, callback) => {
145
144
  this._initializeClient().then(() => {
146
145
  const deadline = new Date();
147
- const milliseconds = this.maxExecutionTime ? this.maxExecutionTime : 10000;
146
+ const milliseconds = this.grpcDeadline;
148
147
  deadline.setMilliseconds(deadline.getMilliseconds() + milliseconds);
149
148
  this._client?.waitForReady(deadline, err => {
150
149
  if (err) {
@@ -2,13 +2,12 @@ export default class NodeChannel extends Channel {
2
2
  /**
3
3
  * @internal
4
4
  * @param {string} address
5
- * @param {number=} maxExecutionTime
5
+ * @param {number=} grpcDeadline
6
6
  */
7
- constructor(address: string, maxExecutionTime?: number | undefined);
7
+ constructor(address: string, grpcDeadline?: number | undefined);
8
8
  /** @type {Client | null} */
9
9
  _client: Client | null;
10
10
  address: string;
11
- maxExecutionTime: number | undefined;
12
11
  nodeIp: string;
13
12
  nodePort: string;
14
13
  /**
@@ -1,2 +1,2 @@
1
- import e from"tls";import{credentials as t,Client as r,Metadata as i}from"@grpc/grpc-js";import s from"./Channel.js";import n from"../grpc/GrpcServiceError.js";import o from"../grpc/GrpcStatus.js";import{ALL_NETWORK_IPS as c}from"../constants/ClientConstants.js";import{SDK_NAME as a,SDK_VERSION as l}from"../version.js";const d={};class p extends s{constructor(e,t){super(),this._client=null,this.address=e,this.maxExecutionTime=t;const{ip:r,port:i}=this.parseAddress(e);this.nodeIp=r,this.nodePort=i}bytesToPem(e){const t=e.toString("base64");return`-----BEGIN CERTIFICATE-----\n${t.match(/.{1,64}/g)?.join("\n")||""}\n-----END CERTIFICATE-----`}parseAddress(e){const[t,r]=e.split(":");if(!t||!r)throw new Error("Invalid address format. Expected format: 'IP:Port'");return{ip:t,port:r}}async _retrieveCertificate(){return new Promise((t,r)=>{const i=e.connect({host:this.nodeIp,port:Number(this.nodePort),rejectUnauthorized:!1},()=>{try{const e=i.getPeerCertificate();e&&e.raw?t(this.bytesToPem(e.raw)):r(new Error("No certificate retrieved."))}catch(e){r(e)}finally{i.end()}});i.on("error",r)})}async _initializeClient(){if(d[this.address])return void(this._client=d[this.address]);let e;if("50212"===this.nodePort){const r=Buffer.from(await this._retrieveCertificate());e=t.createSsl(r)}else e=t.createInsecure();this._client=new r(this.address,e,{"grpc.ssl_target_name_override":"127.0.0.1","grpc.default_authority":"127.0.0.1","grpc.http_connect_creds":"0","grpc.keepalive_time_ms":1e5,"grpc.keepalive_timeout_ms":1e4,"grpc.keepalive_permit_without_calls":1,"grpc.enable_retries":1}),d[this.address]=this._client}close(){this._client&&(this._client.close(),delete d[this.address])}_createUnaryClient(e){return(t,r,s)=>{this._initializeClient().then(()=>{const d=new Date,p=this.maxExecutionTime?this.maxExecutionTime:1e4;d.setMilliseconds(d.getMilliseconds()+p),this._client?.waitForReady(d,d=>{if(d)s(new n(o.Timeout,c[`${this.nodeIp}:`]));else{const n=new i;n.set("x-user-agent",`${a}/${l}`),this._client?.makeUnaryRequest(`/proto.${e}/${t.name}`,e=>e,e=>e,Buffer.from(r),n,(e,t)=>{s(e,t)})}})}).catch(e=>{e instanceof Error?s(e):s(new Error("An unexpected error occurred"))})}}}export{p as default};
1
+ import e from"tls";import{credentials as t,Client as r,Metadata as s}from"@grpc/grpc-js";import i from"./Channel.js";import n from"../grpc/GrpcServiceError.js";import o from"../grpc/GrpcStatus.js";import{ALL_NETWORK_IPS as c}from"../constants/ClientConstants.js";import{SDK_NAME as a,SDK_VERSION as l}from"../version.js";const d={};class p extends i{constructor(e,t){super(t),this._client=null,this.address=e;const{ip:r,port:s}=this.parseAddress(e);this.nodeIp=r,this.nodePort=s}bytesToPem(e){const t=e.toString("base64");return`-----BEGIN CERTIFICATE-----\n${t.match(/.{1,64}/g)?.join("\n")||""}\n-----END CERTIFICATE-----`}parseAddress(e){const[t,r]=e.split(":");if(!t||!r)throw new Error("Invalid address format. Expected format: 'IP:Port'");return{ip:t,port:r}}async _retrieveCertificate(){return new Promise((t,r)=>{const s=e.connect({host:this.nodeIp,port:Number(this.nodePort),rejectUnauthorized:!1},()=>{try{const e=s.getPeerCertificate();e&&e.raw?t(this.bytesToPem(e.raw)):r(new Error("No certificate retrieved."))}catch(e){r(e)}finally{s.end()}});s.on("error",r)})}async _initializeClient(){if(d[this.address])return void(this._client=d[this.address]);let e;if("50212"===this.nodePort){const r=Buffer.from(await this._retrieveCertificate());e=t.createSsl(r)}else e=t.createInsecure();this._client=new r(this.address,e,{"grpc.ssl_target_name_override":"127.0.0.1","grpc.default_authority":"127.0.0.1","grpc.http_connect_creds":"0","grpc.keepalive_time_ms":1e5,"grpc.keepalive_timeout_ms":1e4,"grpc.keepalive_permit_without_calls":1,"grpc.enable_retries":1}),d[this.address]=this._client}close(){this._client&&(this._client.close(),delete d[this.address])}_createUnaryClient(e){return(t,r,i)=>{this._initializeClient().then(()=>{const d=new Date,p=this.grpcDeadline;d.setMilliseconds(d.getMilliseconds()+p),this._client?.waitForReady(d,d=>{if(d)i(new n(o.Timeout,c[`${this.nodeIp}:`]));else{const n=new s;n.set("x-user-agent",`${a}/${l}`),this._client?.makeUnaryRequest(`/proto.${e}/${t.name}`,e=>e,e=>e,Buffer.from(r),n,(e,t)=>{i(e,t)})}})}).catch(e=>{e instanceof Error?i(e):i(new Error("An unexpected error occurred"))})}}}export{p as default};
2
2
  //# sourceMappingURL=NodeChannel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"NodeChannel.js","sources":["../../src/channel/NodeChannel.js"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport tls from \"tls\";\nimport { Client, credentials, Metadata } from \"@grpc/grpc-js\";\nimport Channel from \"./Channel.js\";\nimport GrpcServicesError from \"../grpc/GrpcServiceError.js\";\nimport GrpcStatus from \"../grpc/GrpcStatus.js\";\nimport { ALL_NETWORK_IPS } from \"../constants/ClientConstants.js\";\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\n\n/** @type {{ [key: string]: Client }} */\nconst clientCache = {};\n\nexport default class NodeChannel extends Channel {\n /**\n * @internal\n * @param {string} address\n * @param {number=} maxExecutionTime\n */\n constructor(address, maxExecutionTime) {\n super();\n\n /** @type {Client | null} */\n this._client = null;\n\n this.address = address;\n this.maxExecutionTime = maxExecutionTime;\n\n const { ip, port } = this.parseAddress(address);\n this.nodeIp = ip;\n this.nodePort = port;\n }\n\n /**\n * Convert certificate bytes to PEM format\n * @param {Buffer} certBytes\n * @returns {string}\n */\n bytesToPem(certBytes) {\n const base64Cert = certBytes.toString(\"base64\");\n const lines = base64Cert.match(/.{1,64}/g)?.join(\"\\n\") || \"\";\n return `-----BEGIN CERTIFICATE-----\\n${lines}\\n-----END CERTIFICATE-----`;\n }\n\n /**\n * Validates and parses an address in the \"IP:Port\" format.\n * @param {string} address\n * @returns {{ ip: string, port: string }}\n */\n parseAddress(address) {\n const [ip, port] = address.split(\":\");\n if (!ip || !port) {\n throw new Error(\n \"Invalid address format. Expected format: 'IP:Port'\",\n );\n }\n return { ip, port };\n }\n\n /**\n * Retrieve the server's certificate dynamically.\n * @returns {Promise<string>}\n */\n async _retrieveCertificate() {\n return new Promise((resolve, reject) => {\n const socket = tls.connect(\n {\n host: this.nodeIp,\n port: Number(this.nodePort),\n rejectUnauthorized: false,\n },\n () => {\n try {\n const cert = socket.getPeerCertificate();\n\n if (cert && cert.raw) {\n resolve(this.bytesToPem(cert.raw));\n } else {\n reject(new Error(\"No certificate retrieved.\"));\n }\n } catch (err) {\n reject(err);\n } finally {\n socket.end();\n }\n },\n );\n\n socket.on(\"error\", reject);\n });\n }\n\n /**\n * Initialize the gRPC client\n * @returns {Promise<void>}\n */\n async _initializeClient() {\n if (clientCache[this.address]) {\n this._client = clientCache[this.address];\n return;\n }\n\n let security;\n const options = {\n \"grpc.ssl_target_name_override\": \"127.0.0.1\",\n \"grpc.default_authority\": \"127.0.0.1\",\n \"grpc.http_connect_creds\": \"0\",\n \"grpc.keepalive_time_ms\": 100000,\n \"grpc.keepalive_timeout_ms\": 10000,\n \"grpc.keepalive_permit_without_calls\": 1,\n \"grpc.enable_retries\": 1,\n };\n\n // If the port is 50212, use TLS\n if (this.nodePort === \"50212\") {\n const certificate = Buffer.from(await this._retrieveCertificate());\n\n security = credentials.createSsl(certificate);\n } else {\n security = credentials.createInsecure();\n }\n\n this._client = new Client(this.address, security, options);\n\n clientCache[this.address] = this._client;\n }\n\n /**\n * @override\n * @returns {void}\n */\n close() {\n if (this._client) {\n this._client.close();\n delete clientCache[this.address];\n }\n }\n\n /**\n * @override\n * @protected\n * @param {string} serviceName\n * @returns {import(\"protobufjs\").RPCImpl}\n */\n _createUnaryClient(serviceName) {\n return (method, requestData, callback) => {\n this._initializeClient()\n .then(() => {\n const deadline = new Date();\n const milliseconds = this.maxExecutionTime\n ? this.maxExecutionTime\n : 10000;\n deadline.setMilliseconds(\n deadline.getMilliseconds() + milliseconds,\n );\n\n this._client?.waitForReady(deadline, (err) => {\n if (err) {\n callback(\n new GrpcServicesError(\n GrpcStatus.Timeout,\n // Added colons to the IP address to resolve a SonarCloud IP issue.\n ALL_NETWORK_IPS[`${this.nodeIp}:`],\n ),\n );\n } else {\n // Create metadata with user agent\n const metadata = new Metadata();\n\n metadata.set(\n \"x-user-agent\",\n `${SDK_NAME}/${SDK_VERSION}`,\n );\n\n this._client?.makeUnaryRequest(\n `/proto.${serviceName}/${method.name}`,\n (value) => value,\n (value) => value,\n Buffer.from(requestData),\n metadata,\n (e, r) => {\n callback(e, r);\n },\n );\n }\n });\n })\n .catch((err) => {\n if (err instanceof Error) {\n callback(err);\n } else {\n callback(new Error(\"An unexpected error occurred\"));\n }\n });\n };\n }\n}\n"],"names":["clientCache","NodeChannel","Channel","constructor","address","maxExecutionTime","super","this","_client","ip","port","parseAddress","nodeIp","nodePort","bytesToPem","certBytes","base64Cert","toString","match","join","split","Error","_retrieveCertificate","Promise","resolve","reject","socket","tls","connect","host","Number","rejectUnauthorized","cert","getPeerCertificate","raw","err","end","on","_initializeClient","security","certificate","Buffer","from","credentials","createSsl","createInsecure","Client","close","_createUnaryClient","serviceName","method","requestData","callback","then","deadline","Date","milliseconds","setMilliseconds","getMilliseconds","waitForReady","GrpcServicesError","GrpcStatus","Timeout","ALL_NETWORK_IPS","metadata","Metadata","set","SDK_NAME","SDK_VERSION","makeUnaryRequest","name","value","e","r","catch"],"mappings":"iUAUA,MAAMA,EAAc,CAAE,EAEP,MAAMC,UAAoBC,EAMrC,WAAAC,CAAYC,EAASC,GACjBC,QAGAC,KAAKC,QAAU,KAEfD,KAAKH,QAAUA,EACfG,KAAKF,iBAAmBA,EAExB,MAAMI,GAAEA,EAAEC,KAAEA,GAASH,KAAKI,aAAaP,GACvCG,KAAKK,OAASH,EACdF,KAAKM,SAAWH,CACxB,CAOI,UAAAI,CAAWC,GACP,MAAMC,EAAaD,EAAUE,SAAS,UAEtC,MAAO,gCADOD,EAAWE,MAAM,aAAaC,KAAK,OAAS,+BAElE,CAOI,YAAAR,CAAaP,GACT,MAAOK,EAAIC,GAAQN,EAAQgB,MAAM,KACjC,IAAKX,IAAOC,EACR,MAAM,IAAIW,MACN,sDAGR,MAAO,CAAEZ,KAAIC,OACrB,CAMI,0BAAMY,GACF,OAAO,IAAIC,QAAQ,CAACC,EAASC,KACzB,MAAMC,EAASC,EAAIC,QACf,CACIC,KAAMtB,KAAKK,OACXF,KAAMoB,OAAOvB,KAAKM,UAClBkB,oBAAoB,GAExB,KACI,IACI,MAAMC,EAAON,EAAOO,qBAEhBD,GAAQA,EAAKE,IACbV,EAAQjB,KAAKO,WAAWkB,EAAKE,MAE7BT,EAAO,IAAIJ,MAAM,6BAExB,CAAC,MAAOc,GACLV,EAAOU,EAC/B,CAA8B,QACNT,EAAOU,KAC/B,IAIYV,EAAOW,GAAG,QAASZ,IAE/B,CAMI,uBAAMa,GACF,GAAItC,EAAYO,KAAKH,SAEjB,YADAG,KAAKC,QAAUR,EAAYO,KAAKH,UAIpC,IAAImC,EAYJ,GAAsB,UAAlBhC,KAAKM,SAAsB,CAC3B,MAAM2B,EAAcC,OAAOC,WAAWnC,KAAKe,wBAE3CiB,EAAWI,EAAYC,UAAUJ,EAC7C,MACYD,EAAWI,EAAYE,iBAG3BtC,KAAKC,QAAU,IAAIsC,EAAOvC,KAAKH,QAASmC,EAnBxB,CACZ,gCAAiC,YACjC,yBAA0B,YAC1B,0BAA2B,IAC3B,yBAA0B,IAC1B,4BAA6B,IAC7B,sCAAuC,EACvC,sBAAuB,IAc3BvC,EAAYO,KAAKH,SAAWG,KAAKC,OACzC,CAMI,KAAAuC,GACQxC,KAAKC,UACLD,KAAKC,QAAQuC,eACN/C,EAAYO,KAAKH,SAEpC,CAQI,kBAAA4C,CAAmBC,GACf,MAAO,CAACC,EAAQC,EAAaC,KACzB7C,KAAK+B,oBACAe,KAAK,KACF,MAAMC,EAAW,IAAIC,KACfC,EAAejD,KAAKF,iBACpBE,KAAKF,iBACL,IACNiD,EAASG,gBACLH,EAASI,kBAAoBF,GAGjCjD,KAAKC,SAASmD,aAAaL,EAAWnB,IAClC,GAAIA,EACAiB,EACI,IAAIQ,EACAC,EAAWC,QAEXC,EAAgB,GAAGxD,KAAKK,iBAG7B,CAEH,MAAMoD,EAAW,IAAIC,EAErBD,EAASE,IACL,eACA,GAAGC,KAAYC,KAGnB7D,KAAKC,SAAS6D,iBACV,UAAUpB,KAAeC,EAAOoB,OAC/BC,GAAUA,EACVA,GAAUA,EACX9B,OAAOC,KAAKS,GACZa,EACA,CAACQ,EAAGC,KACArB,EAASoB,EAAGC,IAGhD,MAGiBC,MAAOvC,IACAA,aAAed,MACf+B,EAASjB,GAETiB,EAAS,IAAI/B,MAAM,mCAI3C"}
1
+ {"version":3,"file":"NodeChannel.js","sources":["../../src/channel/NodeChannel.js"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport tls from \"tls\";\nimport { Client, credentials, Metadata } from \"@grpc/grpc-js\";\nimport Channel from \"./Channel.js\";\nimport GrpcServicesError from \"../grpc/GrpcServiceError.js\";\nimport GrpcStatus from \"../grpc/GrpcStatus.js\";\nimport { ALL_NETWORK_IPS } from \"../constants/ClientConstants.js\";\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\n\n/** @type {{ [key: string]: Client }} */\nconst clientCache = {};\n\nexport default class NodeChannel extends Channel {\n /**\n * @internal\n * @param {string} address\n * @param {number=} grpcDeadline\n */\n constructor(address, grpcDeadline) {\n super(grpcDeadline);\n\n /** @type {Client | null} */\n this._client = null;\n\n this.address = address;\n\n const { ip, port } = this.parseAddress(address);\n this.nodeIp = ip;\n this.nodePort = port;\n }\n\n /**\n * Convert certificate bytes to PEM format\n * @param {Buffer} certBytes\n * @returns {string}\n */\n bytesToPem(certBytes) {\n const base64Cert = certBytes.toString(\"base64\");\n const lines = base64Cert.match(/.{1,64}/g)?.join(\"\\n\") || \"\";\n return `-----BEGIN CERTIFICATE-----\\n${lines}\\n-----END CERTIFICATE-----`;\n }\n\n /**\n * Validates and parses an address in the \"IP:Port\" format.\n * @param {string} address\n * @returns {{ ip: string, port: string }}\n */\n parseAddress(address) {\n const [ip, port] = address.split(\":\");\n if (!ip || !port) {\n throw new Error(\n \"Invalid address format. Expected format: 'IP:Port'\",\n );\n }\n return { ip, port };\n }\n\n /**\n * Retrieve the server's certificate dynamically.\n * @returns {Promise<string>}\n */\n async _retrieveCertificate() {\n return new Promise((resolve, reject) => {\n const socket = tls.connect(\n {\n host: this.nodeIp,\n port: Number(this.nodePort),\n rejectUnauthorized: false,\n },\n () => {\n try {\n const cert = socket.getPeerCertificate();\n\n if (cert && cert.raw) {\n resolve(this.bytesToPem(cert.raw));\n } else {\n reject(new Error(\"No certificate retrieved.\"));\n }\n } catch (err) {\n reject(err);\n } finally {\n socket.end();\n }\n },\n );\n\n socket.on(\"error\", reject);\n });\n }\n\n /**\n * Initialize the gRPC client\n * @returns {Promise<void>}\n */\n async _initializeClient() {\n if (clientCache[this.address]) {\n this._client = clientCache[this.address];\n return;\n }\n\n let security;\n const options = {\n \"grpc.ssl_target_name_override\": \"127.0.0.1\",\n \"grpc.default_authority\": \"127.0.0.1\",\n \"grpc.http_connect_creds\": \"0\",\n \"grpc.keepalive_time_ms\": 100000,\n \"grpc.keepalive_timeout_ms\": 10000,\n \"grpc.keepalive_permit_without_calls\": 1,\n \"grpc.enable_retries\": 1,\n };\n\n // If the port is 50212, use TLS\n if (this.nodePort === \"50212\") {\n const certificate = Buffer.from(await this._retrieveCertificate());\n\n security = credentials.createSsl(certificate);\n } else {\n security = credentials.createInsecure();\n }\n\n this._client = new Client(this.address, security, options);\n\n clientCache[this.address] = this._client;\n }\n\n /**\n * @override\n * @returns {void}\n */\n close() {\n if (this._client) {\n this._client.close();\n delete clientCache[this.address];\n }\n }\n\n /**\n * @override\n * @protected\n * @param {string} serviceName\n * @returns {import(\"protobufjs\").RPCImpl}\n */\n _createUnaryClient(serviceName) {\n return (method, requestData, callback) => {\n this._initializeClient()\n .then(() => {\n const deadline = new Date();\n const milliseconds = this.grpcDeadline;\n deadline.setMilliseconds(\n deadline.getMilliseconds() + milliseconds,\n );\n\n this._client?.waitForReady(deadline, (err) => {\n if (err) {\n callback(\n new GrpcServicesError(\n GrpcStatus.Timeout,\n // Added colons to the IP address to resolve a SonarCloud IP issue.\n ALL_NETWORK_IPS[`${this.nodeIp}:`],\n ),\n );\n } else {\n // Create metadata with user agent\n const metadata = new Metadata();\n\n metadata.set(\n \"x-user-agent\",\n `${SDK_NAME}/${SDK_VERSION}`,\n );\n\n this._client?.makeUnaryRequest(\n `/proto.${serviceName}/${method.name}`,\n (value) => value,\n (value) => value,\n Buffer.from(requestData),\n metadata,\n (e, r) => {\n callback(e, r);\n },\n );\n }\n });\n })\n .catch((err) => {\n if (err instanceof Error) {\n callback(err);\n } else {\n callback(new Error(\"An unexpected error occurred\"));\n }\n });\n };\n }\n}\n"],"names":["clientCache","NodeChannel","Channel","constructor","address","grpcDeadline","super","this","_client","ip","port","parseAddress","nodeIp","nodePort","bytesToPem","certBytes","base64Cert","toString","match","join","split","Error","_retrieveCertificate","Promise","resolve","reject","socket","tls","connect","host","Number","rejectUnauthorized","cert","getPeerCertificate","raw","err","end","on","_initializeClient","security","certificate","Buffer","from","credentials","createSsl","createInsecure","Client","close","_createUnaryClient","serviceName","method","requestData","callback","then","deadline","Date","milliseconds","setMilliseconds","getMilliseconds","waitForReady","GrpcServicesError","GrpcStatus","Timeout","ALL_NETWORK_IPS","metadata","Metadata","set","SDK_NAME","SDK_VERSION","makeUnaryRequest","name","value","e","r","catch"],"mappings":"iUAUA,MAAMA,EAAc,CAAE,EAEP,MAAMC,UAAoBC,EAMrC,WAAAC,CAAYC,EAASC,GACjBC,MAAMD,GAGNE,KAAKC,QAAU,KAEfD,KAAKH,QAAUA,EAEf,MAAMK,GAAEA,EAAEC,KAAEA,GAASH,KAAKI,aAAaP,GACvCG,KAAKK,OAASH,EACdF,KAAKM,SAAWH,CACxB,CAOI,UAAAI,CAAWC,GACP,MAAMC,EAAaD,EAAUE,SAAS,UAEtC,MAAO,gCADOD,EAAWE,MAAM,aAAaC,KAAK,OAAS,+BAElE,CAOI,YAAAR,CAAaP,GACT,MAAOK,EAAIC,GAAQN,EAAQgB,MAAM,KACjC,IAAKX,IAAOC,EACR,MAAM,IAAIW,MACN,sDAGR,MAAO,CAAEZ,KAAIC,OACrB,CAMI,0BAAMY,GACF,OAAO,IAAIC,QAAQ,CAACC,EAASC,KACzB,MAAMC,EAASC,EAAIC,QACf,CACIC,KAAMtB,KAAKK,OACXF,KAAMoB,OAAOvB,KAAKM,UAClBkB,oBAAoB,GAExB,KACI,IACI,MAAMC,EAAON,EAAOO,qBAEhBD,GAAQA,EAAKE,IACbV,EAAQjB,KAAKO,WAAWkB,EAAKE,MAE7BT,EAAO,IAAIJ,MAAM,6BAExB,CAAC,MAAOc,GACLV,EAAOU,EAC/B,CAA8B,QACNT,EAAOU,KAC/B,IAIYV,EAAOW,GAAG,QAASZ,IAE/B,CAMI,uBAAMa,GACF,GAAItC,EAAYO,KAAKH,SAEjB,YADAG,KAAKC,QAAUR,EAAYO,KAAKH,UAIpC,IAAImC,EAYJ,GAAsB,UAAlBhC,KAAKM,SAAsB,CAC3B,MAAM2B,EAAcC,OAAOC,WAAWnC,KAAKe,wBAE3CiB,EAAWI,EAAYC,UAAUJ,EAC7C,MACYD,EAAWI,EAAYE,iBAG3BtC,KAAKC,QAAU,IAAIsC,EAAOvC,KAAKH,QAASmC,EAnBxB,CACZ,gCAAiC,YACjC,yBAA0B,YAC1B,0BAA2B,IAC3B,yBAA0B,IAC1B,4BAA6B,IAC7B,sCAAuC,EACvC,sBAAuB,IAc3BvC,EAAYO,KAAKH,SAAWG,KAAKC,OACzC,CAMI,KAAAuC,GACQxC,KAAKC,UACLD,KAAKC,QAAQuC,eACN/C,EAAYO,KAAKH,SAEpC,CAQI,kBAAA4C,CAAmBC,GACf,MAAO,CAACC,EAAQC,EAAaC,KACzB7C,KAAK+B,oBACAe,KAAK,KACF,MAAMC,EAAW,IAAIC,KACfC,EAAejD,KAAKF,aAC1BiD,EAASG,gBACLH,EAASI,kBAAoBF,GAGjCjD,KAAKC,SAASmD,aAAaL,EAAWnB,IAClC,GAAIA,EACAiB,EACI,IAAIQ,EACAC,EAAWC,QAEXC,EAAgB,GAAGxD,KAAKK,iBAG7B,CAEH,MAAMoD,EAAW,IAAIC,EAErBD,EAASE,IACL,eACA,GAAGC,KAAYC,KAGnB7D,KAAKC,SAAS6D,iBACV,UAAUpB,KAAeC,EAAOoB,OAC/BC,GAAUA,EACVA,GAAUA,EACX9B,OAAOC,KAAKS,GACZa,EACA,CAACQ,EAAGC,KACArB,EAASoB,EAAGC,IAGhD,MAGiBC,MAAOvC,IACAA,aAAed,MACf+B,EAASjB,GAETiB,EAAS,IAAI/B,MAAM,mCAI3C"}
@@ -18,15 +18,161 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
18
18
  class WebChannel extends _Channel.default {
19
19
  /**
20
20
  * @param {string} address
21
+ * @param {number=} grpcDeadline
21
22
  */
22
- constructor(address) {
23
- super();
23
+ constructor(address, grpcDeadline) {
24
+ super(grpcDeadline);
24
25
 
25
26
  /**
26
27
  * @type {string}
27
28
  * @private
28
29
  */
29
30
  this._address = address;
31
+
32
+ // Set the gRPC deadline using the base class method
33
+
34
+ /**
35
+ * Flag indicating if the connection is ready (health check has passed)
36
+ * Set to true after the first successful health check
37
+ *
38
+ * @type {boolean}
39
+ * @private
40
+ */
41
+ this._isReady = false;
42
+
43
+ /**
44
+ * Promise that resolves when the health check is complete
45
+ * Used to prevent multiple concurrent health checks
46
+ *
47
+ * @type {Promise<void>|null}
48
+ * @private
49
+ */
50
+ this._healthCheckPromise = null;
51
+ }
52
+
53
+ /**
54
+ * Determines whether to use HTTPS based on the address
55
+ * @param {string} address - The address to check
56
+ * @returns {boolean} - True if HTTPS should be used, false for HTTP
57
+ * @private
58
+ */
59
+ _shouldUseHttps(address) {
60
+ return !(address.includes("localhost") || address.includes("127.0.0.1"));
61
+ }
62
+
63
+ /**
64
+ * Builds the full URL with appropriate scheme (http/https)
65
+ * @param {string} address - The base address
66
+ * @returns {string} - The full URL with scheme
67
+ * @private
68
+ */
69
+ _buildUrl(address) {
70
+ // Check if address already contains a scheme
71
+ const hasScheme = address.startsWith("http://") || address.startsWith("https://");
72
+ if (hasScheme) {
73
+ // Use the address as-is if it already has a scheme
74
+ return address;
75
+ } else {
76
+ // Only prepend scheme if none exists
77
+ const shouldUseHttps = this._shouldUseHttps(address);
78
+ return shouldUseHttps ? `https://${address}` : `http://${address}`;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Check if the gRPC-Web proxy is reachable and healthy
84
+ * Performs a POST request and verifies the response has gRPC-Web headers,
85
+ * which indicates the proxy is running and processing gRPC requests.
86
+ * Results are cached per address for the entire lifecycle.
87
+ * Uses promise-based synchronization to prevent multiple concurrent health checks.
88
+ *
89
+ * @param {Date} deadline - Deadline for the health check
90
+ * @returns {Promise<void>}
91
+ * @private
92
+ */
93
+ async _waitForReady(deadline) {
94
+ // Check if we've already validated this address
95
+ if (this._isReady) {
96
+ return; // Health check already passed for this address
97
+ }
98
+
99
+ // If a health check is already in progress, wait for it to complete
100
+ if (this._healthCheckPromise) {
101
+ return this._healthCheckPromise;
102
+ }
103
+
104
+ // Start a new health check and store the promise
105
+ this._healthCheckPromise = this._performHealthCheck(deadline);
106
+ try {
107
+ await this._healthCheckPromise;
108
+ } finally {
109
+ // Clear the promise when done (success or failure)
110
+ this._healthCheckPromise = null;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Performs the actual health check request
116
+ * @param {Date} deadline - Deadline for the health check
117
+ * @returns {Promise<void>}
118
+ * @private
119
+ */
120
+ async _performHealthCheck(deadline) {
121
+ const address = this._buildUrl(this._address);
122
+
123
+ // Calculate remaining time until deadline
124
+ const timeoutMs = deadline.getTime() - Date.now();
125
+ if (timeoutMs <= 0) {
126
+ throw new _GrpcServiceError.default(_GrpcStatus.default.Timeout, _ClientConstants.ALL_WEB_NETWORK_NODES?.[this._address]?.toString());
127
+ }
128
+ const abortController = new AbortController();
129
+ const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
130
+ try {
131
+ // Make a POST request to verify the gRPC-Web proxy is running
132
+ // We use a minimal gRPC-Web compatible request
133
+ //eslint-disable-next-line n/no-unsupported-features/node-builtins
134
+ const response = await fetch(address, {
135
+ method: "POST",
136
+ headers: {
137
+ "content-type": "application/grpc-web+proto",
138
+ "x-user-agent": `${_version.SDK_NAME}/${_version.SDK_VERSION}`,
139
+ "x-grpc-web": "1"
140
+ },
141
+ body: new Uint8Array(0),
142
+ // Empty body for health check
143
+ signal: abortController.signal
144
+ });
145
+ clearTimeout(timeoutId);
146
+
147
+ // Check if response is successful (200) or indicates a redirect (3xx)
148
+ // 3xx status codes indicate the resource has moved, which is valid for proxies
149
+ if (response.ok || response.status >= 300 && response.status < 400) {
150
+ const grpcStatus = response.headers.get("grpc-status");
151
+ const grpcMessage = response.headers.get("grpc-message");
152
+
153
+ // If gRPC headers exist, the proxy is running and processing requests
154
+ if (grpcStatus != null || grpcMessage != null) {
155
+ // Mark this connection as ready
156
+ this._isReady = true;
157
+ return; // Healthy - gRPC-Web proxy is responding
158
+ }
159
+ }
160
+
161
+ // If we get here, either status isn't 200/3xx or no gRPC headers present
162
+ // This means the proxy might not be configured correctly or not running
163
+ throw new _GrpcServiceError.default(_GrpcStatus.default.Unavailable, _ClientConstants.ALL_WEB_NETWORK_NODES?.[this._address]?.toString());
164
+ } catch (error) {
165
+ clearTimeout(timeoutId);
166
+ if (error instanceof Error && error.name === "AbortError") {
167
+ throw new _GrpcServiceError.default(_GrpcStatus.default.Timeout, _ClientConstants.ALL_WEB_NETWORK_NODES?.[this._address]?.toString());
168
+ }
169
+ if (error instanceof _GrpcServiceError.default) {
170
+ throw error;
171
+ }
172
+
173
+ // Network error - server is not reachable
174
+ throw new _GrpcServiceError.default(_GrpcStatus.default.Unavailable, _ClientConstants.ALL_WEB_NETWORK_NODES?.[this._address]?.toString());
175
+ }
30
176
  }
31
177
 
32
178
  /**
@@ -46,18 +192,16 @@ class WebChannel extends _Channel.default {
46
192
  _createUnaryClient(serviceName) {
47
193
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
48
194
  return async (method, requestData, callback) => {
195
+ // Calculate deadline for connection check
196
+ const deadline = new Date();
197
+ const milliseconds = this._grpcDeadline;
198
+ deadline.setMilliseconds(deadline.getMilliseconds() + milliseconds);
49
199
  try {
50
- // Check if address already contains a scheme
51
- const hasScheme = this._address.startsWith("http://") || this._address.startsWith("https://");
52
- let address;
53
- if (hasScheme) {
54
- // Use the address as-is if it already has a scheme
55
- address = this._address;
56
- } else {
57
- // Only prepend scheme if none exists
58
- const shouldUseHttps = !(this._address.includes("localhost") || this._address.includes("127.0.0.1"));
59
- address = shouldUseHttps ? `https://${this._address}` : `http://${this._address}`;
60
- }
200
+ // Wait for connection to be ready (similar to gRPC waitForReady)
201
+ await this._waitForReady(deadline);
202
+
203
+ // Build the full URL with appropriate scheme
204
+ const address = this._buildUrl(this._address);
61
205
  // this will be executed in a browser environment so eslint is
62
206
  // disabled for the fetch call
63
207
  //eslint-disable-next-line n/no-unsupported-features/node-builtins
@@ -73,6 +217,7 @@ class WebChannel extends _Channel.default {
73
217
  if (!response.ok) {
74
218
  const error = new _HttpError.default(_HttpStatus.default._fromValue(response.status));
75
219
  callback(error, null);
220
+ return;
76
221
  }
77
222
 
78
223
  // Check headers for gRPC errors
@@ -82,11 +227,16 @@ class WebChannel extends _Channel.default {
82
227
  const error = new _GrpcServiceError.default(_GrpcStatus.default._fromValue(parseInt(grpcStatus)), _ClientConstants.ALL_WEB_NETWORK_NODES?.[this._address]?.toString());
83
228
  error.message = grpcMessage;
84
229
  callback(error, null);
230
+ return;
85
231
  }
86
232
  const responseBuffer = await response.arrayBuffer();
87
233
  const unaryResponse = (0, _Channel.decodeUnaryResponse)(responseBuffer);
88
234
  callback(null, unaryResponse);
89
235
  } catch (error) {
236
+ if (error instanceof _GrpcServiceError.default) {
237
+ callback(error, null);
238
+ return;
239
+ }
90
240
  const err = new _GrpcServiceError.default(
91
241
  // retry on grpc web errors
92
242
  _GrpcStatus.default._fromValue(18), _ClientConstants.ALL_WEB_NETWORK_NODES?.[this._address]?.toString());
@@ -1,12 +1,62 @@
1
1
  export default class WebChannel extends Channel {
2
2
  /**
3
3
  * @param {string} address
4
+ * @param {number=} grpcDeadline
4
5
  */
5
- constructor(address: string);
6
+ constructor(address: string, grpcDeadline?: number | undefined);
6
7
  /**
7
8
  * @type {string}
8
9
  * @private
9
10
  */
10
11
  private _address;
12
+ /**
13
+ * Flag indicating if the connection is ready (health check has passed)
14
+ * Set to true after the first successful health check
15
+ *
16
+ * @type {boolean}
17
+ * @private
18
+ */
19
+ private _isReady;
20
+ /**
21
+ * Promise that resolves when the health check is complete
22
+ * Used to prevent multiple concurrent health checks
23
+ *
24
+ * @type {Promise<void>|null}
25
+ * @private
26
+ */
27
+ private _healthCheckPromise;
28
+ /**
29
+ * Determines whether to use HTTPS based on the address
30
+ * @param {string} address - The address to check
31
+ * @returns {boolean} - True if HTTPS should be used, false for HTTP
32
+ * @private
33
+ */
34
+ private _shouldUseHttps;
35
+ /**
36
+ * Builds the full URL with appropriate scheme (http/https)
37
+ * @param {string} address - The base address
38
+ * @returns {string} - The full URL with scheme
39
+ * @private
40
+ */
41
+ private _buildUrl;
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
+ * Uses promise-based synchronization to prevent multiple concurrent health checks.
48
+ *
49
+ * @param {Date} deadline - Deadline for the health check
50
+ * @returns {Promise<void>}
51
+ * @private
52
+ */
53
+ private _waitForReady;
54
+ /**
55
+ * Performs the actual health check request
56
+ * @param {Date} deadline - Deadline for the health check
57
+ * @returns {Promise<void>}
58
+ * @private
59
+ */
60
+ private _performHealthCheck;
11
61
  }
12
62
  import Channel from "./Channel.js";
@@ -1,2 +1,2 @@
1
- import{ALL_WEB_NETWORK_NODES as t}from"../constants/ClientConstants.js";import s from"../grpc/GrpcServiceError.js";import r from"../grpc/GrpcStatus.js";import e from"../http/HttpError.js";import a from"../http/HttpStatus.js";import{SDK_NAME as o,SDK_VERSION as n}from"../version.js";import i,{encodeRequest as p,decodeUnaryResponse as d}from"./Channel.js";class c extends i{constructor(t){super(),this._address=t}close(){}_createUnaryClient(i){return async(c,l,h)=>{try{let m;if(this._address.startsWith("http://")||this._address.startsWith("https://"))m=this._address;else{m=!(this._address.includes("localhost")||this._address.includes("127.0.0.1"))?`https://${this._address}`:`http://${this._address}`}const u=await fetch(`${m}/proto.${i}/${c.name}`,{method:"POST",headers:{"content-type":"application/grpc-web+proto","x-user-agent":`${o}/${n}`,"x-grpc-web":"1"},body:p(l)});if(!u.ok){h(new e(a._fromValue(u.status)),null)}const f=u.headers.get("grpc-status"),_=u.headers.get("grpc-message");if(null!=f&&null!=_){const e=new s(r._fromValue(parseInt(f)),t?.[this._address]?.toString());e.message=_,h(e,null)}const g=await u.arrayBuffer();h(null,d(g))}catch(e){h(new s(r._fromValue(18),t?.[this._address]?.toString()),null)}}}}export{c as default};
1
+ import{ALL_WEB_NETWORK_NODES as t}from"../constants/ClientConstants.js";import e from"../grpc/GrpcServiceError.js";import s from"../grpc/GrpcStatus.js";import r from"../http/HttpError.js";import i from"../http/HttpStatus.js";import{SDK_NAME as a,SDK_VERSION as o}from"../version.js";import n,{encodeRequest as h,decodeUnaryResponse as l}from"./Channel.js";class c extends n{constructor(t,e){super(e),this._address=t,this._isReady=!1,this._healthCheckPromise=null}_shouldUseHttps(t){return!(t.includes("localhost")||t.includes("127.0.0.1"))}_buildUrl(t){if(t.startsWith("http://")||t.startsWith("https://"))return t;return this._shouldUseHttps(t)?`https://${t}`:`http://${t}`}async _waitForReady(t){if(!this._isReady){if(this._healthCheckPromise)return this._healthCheckPromise;this._healthCheckPromise=this._performHealthCheck(t);try{await this._healthCheckPromise}finally{this._healthCheckPromise=null}}}async _performHealthCheck(r){const i=this._buildUrl(this._address),n=r.getTime()-Date.now();if(n<=0)throw new e(s.Timeout,t?.[this._address]?.toString());const h=new AbortController,l=setTimeout(()=>h.abort(),n);try{const r=await fetch(i,{method:"POST",headers:{"content-type":"application/grpc-web+proto","x-user-agent":`${a}/${o}`,"x-grpc-web":"1"},body:new Uint8Array(0),signal:h.signal});if(clearTimeout(l),r.ok||r.status>=300&&r.status<400){const t=r.headers.get("grpc-status"),e=r.headers.get("grpc-message");if(null!=t||null!=e)return void(this._isReady=!0)}throw new e(s.Unavailable,t?.[this._address]?.toString())}catch(r){if(clearTimeout(l),r instanceof Error&&"AbortError"===r.name)throw new e(s.Timeout,t?.[this._address]?.toString());if(r instanceof e)throw r;throw new e(s.Unavailable,t?.[this._address]?.toString())}}close(){}_createUnaryClient(n){return async(c,d,u)=>{const p=new Date,m=this._grpcDeadline;p.setMilliseconds(p.getMilliseconds()+m);try{await this._waitForReady(p);const m=this._buildUrl(this._address),f=await fetch(`${m}/proto.${n}/${c.name}`,{method:"POST",headers:{"content-type":"application/grpc-web+proto","x-user-agent":`${a}/${o}`,"x-grpc-web":"1"},body:h(d)});if(!f.ok){return void u(new r(i._fromValue(f.status)),null)}const _=f.headers.get("grpc-status"),g=f.headers.get("grpc-message");if(null!=_&&null!=g){const r=new e(s._fromValue(parseInt(_)),t?.[this._address]?.toString());return r.message=g,void u(r,null)}const w=await f.arrayBuffer();u(null,l(w))}catch(r){if(r instanceof e)return void u(r,null);u(new e(s._fromValue(18),t?.[this._address]?.toString()),null)}}}}export{c as default};
2
2
  //# sourceMappingURL=WebChannel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WebChannel.js","sources":["../../src/channel/WebChannel.js"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { ALL_WEB_NETWORK_NODES } from \"../constants/ClientConstants.js\";\nimport GrpcServiceError from \"../grpc/GrpcServiceError.js\";\nimport GrpcStatus from \"../grpc/GrpcStatus.js\";\nimport HttpError from \"../http/HttpError.js\";\nimport HttpStatus from \"../http/HttpStatus.js\";\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\nimport Channel, { encodeRequest, decodeUnaryResponse } from \"./Channel.js\";\n\nexport default class WebChannel extends Channel {\n /**\n * @param {string} address\n */\n constructor(address) {\n super();\n\n /**\n * @type {string}\n * @private\n */\n this._address = address;\n }\n\n /**\n * @override\n * @returns {void}\n */\n close() {\n // do nothing\n }\n\n /**\n * @override\n * @protected\n * @param {string} serviceName\n * @returns {import(\"protobufjs\").RPCImpl}\n */\n _createUnaryClient(serviceName) {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n return async (method, requestData, callback) => {\n try {\n // Check if address already contains a scheme\n const hasScheme =\n this._address.startsWith(\"http://\") ||\n this._address.startsWith(\"https://\");\n\n let address;\n if (hasScheme) {\n // Use the address as-is if it already has a scheme\n address = this._address;\n } else {\n // Only prepend scheme if none exists\n const shouldUseHttps = !(\n this._address.includes(\"localhost\") ||\n this._address.includes(\"127.0.0.1\")\n );\n\n address = shouldUseHttps\n ? `https://${this._address}`\n : `http://${this._address}`;\n }\n // this will be executed in a browser environment so eslint is\n // disabled for the fetch call\n //eslint-disable-next-line n/no-unsupported-features/node-builtins\n const response = await fetch(\n `${address}/proto.${serviceName}/${method.name}`,\n {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/grpc-web+proto\",\n \"x-user-agent\": `${SDK_NAME}/${SDK_VERSION}`,\n \"x-grpc-web\": \"1\",\n },\n body: encodeRequest(requestData),\n },\n );\n\n if (!response.ok) {\n const error = new HttpError(\n HttpStatus._fromValue(response.status),\n );\n callback(error, null);\n }\n\n // Check headers for gRPC errors\n const grpcStatus = response.headers.get(\"grpc-status\");\n const grpcMessage = response.headers.get(\"grpc-message\");\n\n if (grpcStatus != null && grpcMessage != null) {\n const error = new GrpcServiceError(\n GrpcStatus._fromValue(parseInt(grpcStatus)),\n ALL_WEB_NETWORK_NODES?.[this._address]?.toString(),\n );\n error.message = grpcMessage;\n callback(error, null);\n }\n\n const responseBuffer = await response.arrayBuffer();\n const unaryResponse = decodeUnaryResponse(responseBuffer);\n\n callback(null, unaryResponse);\n } catch (error) {\n const err = new GrpcServiceError(\n // retry on grpc web errors\n GrpcStatus._fromValue(18),\n ALL_WEB_NETWORK_NODES?.[this._address]?.toString(),\n );\n callback(err, null);\n }\n };\n }\n}\n"],"names":["WebChannel","Channel","constructor","address","super","this","_address","close","_createUnaryClient","serviceName","async","method","requestData","callback","startsWith","includes","response","fetch","name","headers","SDK_NAME","SDK_VERSION","body","encodeRequest","ok","HttpError","HttpStatus","_fromValue","status","grpcStatus","get","grpcMessage","error","GrpcServiceError","GrpcStatus","parseInt","ALL_WEB_NETWORK_NODES","toString","message","responseBuffer","arrayBuffer","decodeUnaryResponse"],"mappings":"oWASe,MAAMA,UAAmBC,EAIpC,WAAAC,CAAYC,GACRC,QAMAC,KAAKC,SAAWH,CACxB,CAMI,KAAAI,GAEJ,CAQI,kBAAAC,CAAmBC,GAEf,OAAOC,MAAOC,EAAQC,EAAaC,KAC/B,IAMI,IAAIV,EACJ,GAJIE,KAAKC,SAASQ,WAAW,YACzBT,KAAKC,SAASQ,WAAW,YAKzBX,EAAUE,KAAKC,aACZ,CAOHH,IAJIE,KAAKC,SAASS,SAAS,cACvBV,KAAKC,SAASS,SAAS,cAIrB,WAAWV,KAAKC,WAChB,UAAUD,KAAKC,UACzC,CAIgB,MAAMU,QAAiBC,MACnB,GAAGd,WAAiBM,KAAeE,EAAOO,OAC1C,CACIP,OAAQ,OACRQ,QAAS,CACL,eAAgB,6BAChB,eAAgB,GAAGC,KAAYC,IAC/B,aAAc,KAElBC,KAAMC,EAAcX,KAI5B,IAAKI,EAASQ,GAAI,CAIdX,EAHc,IAAIY,EACdC,EAAWC,WAAWX,EAASY,SAEnB,KACpC,CAGgB,MAAMC,EAAab,EAASG,QAAQW,IAAI,eAClCC,EAAcf,EAASG,QAAQW,IAAI,gBAEzC,GAAkB,MAAdD,GAAqC,MAAfE,EAAqB,CAC3C,MAAMC,EAAQ,IAAIC,EACdC,EAAWP,WAAWQ,SAASN,IAC/BO,IAAwB/B,KAAKC,WAAW+B,YAE5CL,EAAMM,QAAUP,EAChBlB,EAASmB,EAAO,KACpC,CAEgB,MAAMO,QAAuBvB,EAASwB,cAGtC3B,EAAS,KAFa4B,EAAoBF,GAG7C,CAAC,MAAOP,GAMLnB,EALY,IAAIoB,EAEZC,EAAWP,WAAW,IACtBS,IAAwB/B,KAAKC,WAAW+B,YAE9B,KAC9B,EAEA"}
1
+ {"version":3,"file":"WebChannel.js","sources":["../../src/channel/WebChannel.js"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { ALL_WEB_NETWORK_NODES } from \"../constants/ClientConstants.js\";\nimport GrpcServiceError from \"../grpc/GrpcServiceError.js\";\nimport GrpcStatus from \"../grpc/GrpcStatus.js\";\nimport HttpError from \"../http/HttpError.js\";\nimport HttpStatus from \"../http/HttpStatus.js\";\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\nimport Channel, { encodeRequest, decodeUnaryResponse } from \"./Channel.js\";\n\nexport default class WebChannel extends Channel {\n /**\n * @param {string} address\n * @param {number=} grpcDeadline\n */\n constructor(address, grpcDeadline) {\n super(grpcDeadline);\n\n /**\n * @type {string}\n * @private\n */\n this._address = address;\n\n // Set the gRPC deadline using the base class method\n\n /**\n * Flag indicating if the connection is ready (health check has passed)\n * Set to true after the first successful health check\n *\n * @type {boolean}\n * @private\n */\n this._isReady = false;\n\n /**\n * Promise that resolves when the health check is complete\n * Used to prevent multiple concurrent health checks\n *\n * @type {Promise<void>|null}\n * @private\n */\n this._healthCheckPromise = null;\n }\n\n /**\n * Determines whether to use HTTPS based on the address\n * @param {string} address - The address to check\n * @returns {boolean} - True if HTTPS should be used, false for HTTP\n * @private\n */\n _shouldUseHttps(address) {\n return !(\n address.includes(\"localhost\") || address.includes(\"127.0.0.1\")\n );\n }\n\n /**\n * Builds the full URL with appropriate scheme (http/https)\n * @param {string} address - The base address\n * @returns {string} - The full URL with scheme\n * @private\n */\n _buildUrl(address) {\n // Check if address already contains a scheme\n const hasScheme =\n address.startsWith(\"http://\") || address.startsWith(\"https://\");\n\n if (hasScheme) {\n // Use the address as-is if it already has a scheme\n return address;\n } else {\n // Only prepend scheme if none exists\n const shouldUseHttps = this._shouldUseHttps(address);\n return shouldUseHttps ? `https://${address}` : `http://${address}`;\n }\n }\n\n /**\n * Check if the gRPC-Web proxy is reachable and healthy\n * Performs a POST request and verifies the response has gRPC-Web headers,\n * which indicates the proxy is running and processing gRPC requests.\n * Results are cached per address for the entire lifecycle.\n * Uses promise-based synchronization to prevent multiple concurrent health checks.\n *\n * @param {Date} deadline - Deadline for the health check\n * @returns {Promise<void>}\n * @private\n */\n async _waitForReady(deadline) {\n // Check if we've already validated this address\n if (this._isReady) {\n return; // Health check already passed for this address\n }\n\n // If a health check is already in progress, wait for it to complete\n if (this._healthCheckPromise) {\n return this._healthCheckPromise;\n }\n\n // Start a new health check and store the promise\n this._healthCheckPromise = this._performHealthCheck(deadline);\n\n try {\n await this._healthCheckPromise;\n } finally {\n // Clear the promise when done (success or failure)\n this._healthCheckPromise = null;\n }\n }\n\n /**\n * Performs the actual health check request\n * @param {Date} deadline - Deadline for the health check\n * @returns {Promise<void>}\n * @private\n */\n async _performHealthCheck(deadline) {\n const address = this._buildUrl(this._address);\n\n // Calculate remaining time until deadline\n const timeoutMs = deadline.getTime() - Date.now();\n if (timeoutMs <= 0) {\n throw new GrpcServiceError(\n GrpcStatus.Timeout,\n ALL_WEB_NETWORK_NODES?.[this._address]?.toString(),\n );\n }\n\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);\n\n try {\n // Make a POST request to verify the gRPC-Web proxy is running\n // We use a minimal gRPC-Web compatible request\n //eslint-disable-next-line n/no-unsupported-features/node-builtins\n const response = await fetch(address, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/grpc-web+proto\",\n \"x-user-agent\": `${SDK_NAME}/${SDK_VERSION}`,\n \"x-grpc-web\": \"1\",\n },\n body: new Uint8Array(0), // Empty body for health check\n signal: abortController.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Check if response is successful (200) or indicates a redirect (3xx)\n // 3xx status codes indicate the resource has moved, which is valid for proxies\n if (\n response.ok ||\n (response.status >= 300 && response.status < 400)\n ) {\n const grpcStatus = response.headers.get(\"grpc-status\");\n const grpcMessage = response.headers.get(\"grpc-message\");\n\n // If gRPC headers exist, the proxy is running and processing requests\n if (grpcStatus != null || grpcMessage != null) {\n // Mark this connection as ready\n this._isReady = true;\n return; // Healthy - gRPC-Web proxy is responding\n }\n }\n\n // If we get here, either status isn't 200/3xx or no gRPC headers present\n // This means the proxy might not be configured correctly or not running\n throw new GrpcServiceError(\n GrpcStatus.Unavailable,\n ALL_WEB_NETWORK_NODES?.[this._address]?.toString(),\n );\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new GrpcServiceError(\n GrpcStatus.Timeout,\n ALL_WEB_NETWORK_NODES?.[this._address]?.toString(),\n );\n }\n\n if (error instanceof GrpcServiceError) {\n throw error;\n }\n\n // Network error - server is not reachable\n throw new GrpcServiceError(\n GrpcStatus.Unavailable,\n ALL_WEB_NETWORK_NODES?.[this._address]?.toString(),\n );\n }\n }\n\n /**\n * @override\n * @returns {void}\n */\n close() {\n // do nothing\n }\n\n /**\n * @override\n * @protected\n * @param {string} serviceName\n * @returns {import(\"protobufjs\").RPCImpl}\n */\n _createUnaryClient(serviceName) {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n return async (method, requestData, callback) => {\n // Calculate deadline for connection check\n const deadline = new Date();\n const milliseconds = this._grpcDeadline;\n\n deadline.setMilliseconds(deadline.getMilliseconds() + milliseconds);\n\n try {\n // Wait for connection to be ready (similar to gRPC waitForReady)\n await this._waitForReady(deadline);\n\n // Build the full URL with appropriate scheme\n const address = this._buildUrl(this._address);\n // this will be executed in a browser environment so eslint is\n // disabled for the fetch call\n //eslint-disable-next-line n/no-unsupported-features/node-builtins\n const response = await fetch(\n `${address}/proto.${serviceName}/${method.name}`,\n {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/grpc-web+proto\",\n \"x-user-agent\": `${SDK_NAME}/${SDK_VERSION}`,\n \"x-grpc-web\": \"1\",\n },\n body: encodeRequest(requestData),\n },\n );\n\n if (!response.ok) {\n const error = new HttpError(\n HttpStatus._fromValue(response.status),\n );\n callback(error, null);\n return;\n }\n\n // Check headers for gRPC errors\n const grpcStatus = response.headers.get(\"grpc-status\");\n const grpcMessage = response.headers.get(\"grpc-message\");\n\n if (grpcStatus != null && grpcMessage != null) {\n const error = new GrpcServiceError(\n GrpcStatus._fromValue(parseInt(grpcStatus)),\n ALL_WEB_NETWORK_NODES?.[this._address]?.toString(),\n );\n error.message = grpcMessage;\n callback(error, null);\n return;\n }\n\n const responseBuffer = await response.arrayBuffer();\n const unaryResponse = decodeUnaryResponse(responseBuffer);\n\n callback(null, unaryResponse);\n } catch (error) {\n if (error instanceof GrpcServiceError) {\n callback(error, null);\n return;\n }\n\n const err = new GrpcServiceError(\n // retry on grpc web errors\n GrpcStatus._fromValue(18),\n ALL_WEB_NETWORK_NODES?.[this._address]?.toString(),\n );\n callback(err, null);\n }\n };\n }\n}\n"],"names":["WebChannel","Channel","constructor","address","grpcDeadline","super","this","_address","_isReady","_healthCheckPromise","_shouldUseHttps","includes","_buildUrl","startsWith","_waitForReady","deadline","_performHealthCheck","timeoutMs","getTime","Date","now","GrpcServiceError","GrpcStatus","Timeout","ALL_WEB_NETWORK_NODES","toString","abortController","AbortController","timeoutId","setTimeout","abort","response","fetch","method","headers","SDK_NAME","SDK_VERSION","body","Uint8Array","signal","clearTimeout","ok","status","grpcStatus","get","grpcMessage","Unavailable","error","Error","name","close","_createUnaryClient","serviceName","async","requestData","callback","milliseconds","_grpcDeadline","setMilliseconds","getMilliseconds","encodeRequest","HttpError","HttpStatus","_fromValue","parseInt","message","responseBuffer","arrayBuffer","decodeUnaryResponse"],"mappings":"oWASe,MAAMA,UAAmBC,EAKpC,WAAAC,CAAYC,EAASC,GACjBC,MAAMD,GAMNE,KAAKC,SAAWJ,EAWhBG,KAAKE,UAAW,EAShBF,KAAKG,oBAAsB,IACnC,CAQI,eAAAC,CAAgBP,GACZ,QACIA,EAAQQ,SAAS,cAAgBR,EAAQQ,SAAS,aAE9D,CAQI,SAAAC,CAAUT,GAKN,GAFIA,EAAQU,WAAW,YAAcV,EAAQU,WAAW,YAIpD,OAAOV,EAIP,OADuBG,KAAKI,gBAAgBP,GACpB,WAAWA,IAAY,UAAUA,GAErE,CAaI,mBAAMW,CAAcC,GAEhB,IAAIT,KAAKE,SAAT,CAKA,GAAIF,KAAKG,oBACL,OAAOH,KAAKG,oBAIhBH,KAAKG,oBAAsBH,KAAKU,oBAAoBD,GAEpD,UACUT,KAAKG,mBACvB,CAAkB,QAENH,KAAKG,oBAAsB,IACvC,CAfA,CAgBA,CAQI,yBAAMO,CAAoBD,GACtB,MAAMZ,EAAUG,KAAKM,UAAUN,KAAKC,UAG9BU,EAAYF,EAASG,UAAYC,KAAKC,MAC5C,GAAIH,GAAa,EACb,MAAM,IAAII,EACNC,EAAWC,QACXC,IAAwBlB,KAAKC,WAAWkB,YAIhD,MAAMC,EAAkB,IAAIC,gBACtBC,EAAYC,WAAW,IAAMH,EAAgBI,QAASb,GAE5D,IAII,MAAMc,QAAiBC,MAAM7B,EAAS,CAClC8B,OAAQ,OACRC,QAAS,CACL,eAAgB,6BAChB,eAAgB,GAAGC,KAAYC,IAC/B,aAAc,KAElBC,KAAM,IAAIC,WAAW,GACrBC,OAAQb,EAAgBa,SAO5B,GAJAC,aAAaZ,GAKTG,EAASU,IACRV,EAASW,QAAU,KAAOX,EAASW,OAAS,IAC/C,CACE,MAAMC,EAAaZ,EAASG,QAAQU,IAAI,eAClCC,EAAcd,EAASG,QAAQU,IAAI,gBAGzC,GAAkB,MAAdD,GAAqC,MAAfE,EAGtB,YADAvC,KAAKE,UAAW,EAGpC,CAIY,MAAM,IAAIa,EACNC,EAAWwB,YACXtB,IAAwBlB,KAAKC,WAAWkB,WAE/C,CAAC,MAAOsB,GAGL,GAFAP,aAAaZ,GAETmB,aAAiBC,OAAwB,eAAfD,EAAME,KAChC,MAAM,IAAI5B,EACNC,EAAWC,QACXC,IAAwBlB,KAAKC,WAAWkB,YAIhD,GAAIsB,aAAiB1B,EACjB,MAAM0B,EAIV,MAAM,IAAI1B,EACNC,EAAWwB,YACXtB,IAAwBlB,KAAKC,WAAWkB,WAExD,CACA,CAMI,KAAAyB,GAEJ,CAQI,kBAAAC,CAAmBC,GAEf,OAAOC,MAAOpB,EAAQqB,EAAaC,KAE/B,MAAMxC,EAAW,IAAII,KACfqC,EAAelD,KAAKmD,cAE1B1C,EAAS2C,gBAAgB3C,EAAS4C,kBAAoBH,GAEtD,UAEUlD,KAAKQ,cAAcC,GAGzB,MAAMZ,EAAUG,KAAKM,UAAUN,KAAKC,UAI9BwB,QAAiBC,MACnB,GAAG7B,WAAiBiD,KAAenB,EAAOgB,OAC1C,CACIhB,OAAQ,OACRC,QAAS,CACL,eAAgB,6BAChB,eAAgB,GAAGC,KAAYC,IAC/B,aAAc,KAElBC,KAAMuB,EAAcN,KAI5B,IAAKvB,EAASU,GAAI,CAKd,YADAc,EAHc,IAAIM,EACdC,EAAWC,WAAWhC,EAASW,SAEnB,KAEpC,CAGgB,MAAMC,EAAaZ,EAASG,QAAQU,IAAI,eAClCC,EAAcd,EAASG,QAAQU,IAAI,gBAEzC,GAAkB,MAAdD,GAAqC,MAAfE,EAAqB,CAC3C,MAAME,EAAQ,IAAI1B,EACdC,EAAWyC,WAAWC,SAASrB,IAC/BnB,IAAwBlB,KAAKC,WAAWkB,YAI5C,OAFAsB,EAAMkB,QAAUpB,OAChBU,EAASR,EAAO,KAEpC,CAEgB,MAAMmB,QAAuBnC,EAASoC,cAGtCZ,EAAS,KAFaa,EAAoBF,GAG7C,CAAC,MAAOnB,GACL,GAAIA,aAAiB1B,EAEjB,YADAkC,EAASR,EAAO,MASpBQ,EALY,IAAIlC,EAEZC,EAAWyC,WAAW,IACtBvC,IAAwBlB,KAAKC,WAAWkB,YAE9B,KAC9B,EAEA"}