quantumcoin 6.14.2 → 6.14.5

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 (317) hide show
  1. package/CHANGELOG.md +442 -442
  2. package/FUNDING.json +10 -10
  3. package/LICENSE.md +21 -21
  4. package/README.md +132 -142
  5. package/SECURITY.md +34 -34
  6. package/dist/README.md +22 -22
  7. package/dist/quantumcoin.js +1127 -1324
  8. package/dist/quantumcoin.js.map +1 -1
  9. package/dist/quantumcoin.min.js +1 -1
  10. package/dist/quantumcoin.umd.js +1128 -1327
  11. package/dist/quantumcoin.umd.js.map +1 -1
  12. package/dist/quantumcoin.umd.min.js +1 -1
  13. package/dist/wordlists-extra.js +1 -1
  14. package/dist/wordlists-extra.js.map +1 -1
  15. package/dist/wordlists-extra.min.js +1 -1
  16. package/lib.commonjs/README.md +16 -16
  17. package/lib.commonjs/_version.js +1 -1
  18. package/lib.commonjs/crypto/signature.d.ts +3 -76
  19. package/lib.commonjs/crypto/signature.d.ts.map +1 -1
  20. package/lib.commonjs/crypto/signature.js +15 -199
  21. package/lib.commonjs/crypto/signature.js.map +1 -1
  22. package/lib.commonjs/crypto/signing-key.d.ts +1 -1
  23. package/lib.commonjs/crypto/signing-key.d.ts.map +1 -1
  24. package/lib.commonjs/crypto/signing-key.js +19 -10
  25. package/lib.commonjs/crypto/signing-key.js.map +1 -1
  26. package/lib.commonjs/package.json +12 -12
  27. package/lib.commonjs/providers/provider-jsonrpc.d.ts +0 -1
  28. package/lib.commonjs/providers/provider-jsonrpc.d.ts.map +1 -1
  29. package/lib.commonjs/providers/provider-jsonrpc.js +0 -1
  30. package/lib.commonjs/providers/provider-jsonrpc.js.map +1 -1
  31. package/lib.commonjs/quantumcoin.d.ts +2 -0
  32. package/lib.commonjs/quantumcoin.d.ts.map +1 -1
  33. package/lib.commonjs/quantumcoin.js +11 -5
  34. package/lib.commonjs/quantumcoin.js.map +1 -1
  35. package/lib.commonjs/transaction/address.d.ts.map +1 -1
  36. package/lib.commonjs/transaction/address.js +8 -3
  37. package/lib.commonjs/transaction/address.js.map +1 -1
  38. package/lib.commonjs/transaction/transaction.d.ts.map +1 -1
  39. package/lib.commonjs/transaction/transaction.js +7 -40
  40. package/lib.commonjs/transaction/transaction.js.map +1 -1
  41. package/lib.commonjs/wallet/json-keystore.d.ts.map +1 -1
  42. package/lib.commonjs/wallet/json-keystore.js +7 -7
  43. package/lib.commonjs/wallet/json-keystore.js.map +1 -1
  44. package/lib.commonjs/wallet/wallet.d.ts.map +1 -1
  45. package/lib.commonjs/wallet/wallet.js +2 -2
  46. package/lib.commonjs/wallet/wallet.js.map +1 -1
  47. package/lib.esm/README.md +16 -16
  48. package/lib.esm/_version.js +1 -1
  49. package/lib.esm/crypto/signature.d.ts +3 -76
  50. package/lib.esm/crypto/signature.d.ts.map +1 -1
  51. package/lib.esm/crypto/signature.js +16 -202
  52. package/lib.esm/crypto/signature.js.map +1 -1
  53. package/lib.esm/crypto/signing-key.d.ts +1 -1
  54. package/lib.esm/crypto/signing-key.d.ts.map +1 -1
  55. package/lib.esm/crypto/signing-key.js +20 -9
  56. package/lib.esm/crypto/signing-key.js.map +1 -1
  57. package/lib.esm/package.json +12 -12
  58. package/lib.esm/providers/provider-jsonrpc.d.ts +0 -1
  59. package/lib.esm/providers/provider-jsonrpc.d.ts.map +1 -1
  60. package/lib.esm/providers/provider-jsonrpc.js +0 -1
  61. package/lib.esm/providers/provider-jsonrpc.js.map +1 -1
  62. package/lib.esm/quantumcoin.d.ts +2 -0
  63. package/lib.esm/quantumcoin.d.ts.map +1 -1
  64. package/lib.esm/quantumcoin.js +6 -0
  65. package/lib.esm/quantumcoin.js.map +1 -1
  66. package/lib.esm/transaction/address.d.ts.map +1 -1
  67. package/lib.esm/transaction/address.js +8 -2
  68. package/lib.esm/transaction/address.js.map +1 -1
  69. package/lib.esm/transaction/transaction.d.ts.map +1 -1
  70. package/lib.esm/transaction/transaction.js +7 -40
  71. package/lib.esm/transaction/transaction.js.map +1 -1
  72. package/lib.esm/wallet/json-keystore.d.ts.map +1 -1
  73. package/lib.esm/wallet/json-keystore.js +11 -5
  74. package/lib.esm/wallet/json-keystore.js.map +1 -1
  75. package/lib.esm/wallet/wallet.d.ts.map +1 -1
  76. package/lib.esm/wallet/wallet.js +3 -1
  77. package/lib.esm/wallet/wallet.js.map +1 -1
  78. package/package.json +6 -5
  79. package/rollup.config.mjs +50 -50
  80. package/src.ts/_version.ts +1 -1
  81. package/src.ts/abi/abi-coder.ts +237 -237
  82. package/src.ts/abi/bytes32.ts +45 -45
  83. package/src.ts/abi/coders/abstract-coder.ts +541 -541
  84. package/src.ts/abi/coders/address.ts +36 -36
  85. package/src.ts/abi/coders/anonymous.ts +29 -29
  86. package/src.ts/abi/coders/array.ts +199 -199
  87. package/src.ts/abi/coders/boolean.ts +27 -27
  88. package/src.ts/abi/coders/bytes.ts +43 -43
  89. package/src.ts/abi/coders/fixed-bytes.ts +37 -37
  90. package/src.ts/abi/coders/null.ts +28 -28
  91. package/src.ts/abi/coders/number.ts +63 -63
  92. package/src.ts/abi/coders/string.ts +29 -29
  93. package/src.ts/abi/coders/tuple.ts +69 -69
  94. package/src.ts/abi/fragments.ts +1617 -1617
  95. package/src.ts/abi/index.ts +41 -41
  96. package/src.ts/abi/interface.ts +1271 -1271
  97. package/src.ts/abi/typed.ts +796 -796
  98. package/src.ts/address/address.ts +148 -148
  99. package/src.ts/address/checks.ts +123 -123
  100. package/src.ts/address/contract-address.ts +80 -80
  101. package/src.ts/address/index.ts +57 -57
  102. package/src.ts/constants/addresses.ts +8 -8
  103. package/src.ts/constants/hashes.ts +7 -7
  104. package/src.ts/constants/index.ts +16 -16
  105. package/src.ts/constants/numbers.ts +35 -35
  106. package/src.ts/constants/strings.ts +16 -16
  107. package/src.ts/contract/contract.ts +1120 -1120
  108. package/src.ts/contract/factory.ts +143 -143
  109. package/src.ts/contract/index.ts +31 -31
  110. package/src.ts/contract/types.ts +236 -236
  111. package/src.ts/contract/wrappers.ts +225 -225
  112. package/src.ts/crypto/crypto-browser.ts +64 -64
  113. package/src.ts/crypto/crypto.ts +4 -4
  114. package/src.ts/crypto/hmac.ts +51 -51
  115. package/src.ts/crypto/index.ts +59 -59
  116. package/src.ts/crypto/keccak.ts +54 -54
  117. package/src.ts/crypto/pbkdf2.ts +55 -55
  118. package/src.ts/crypto/random.ts +36 -36
  119. package/src.ts/crypto/ripemd160.ts +43 -43
  120. package/src.ts/crypto/scrypt.ts +114 -114
  121. package/src.ts/crypto/sha2.ts +78 -78
  122. package/src.ts/crypto/signature.ts +145 -349
  123. package/src.ts/crypto/signing-key.ts +126 -118
  124. package/src.ts/hash/authorization.ts +38 -38
  125. package/src.ts/hash/id.ts +17 -17
  126. package/src.ts/hash/index.ts +18 -18
  127. package/src.ts/hash/message.ts +51 -51
  128. package/src.ts/hash/namehash.ts +101 -101
  129. package/src.ts/hash/solidity.ts +117 -117
  130. package/src.ts/hash/typed-data.ts +658 -658
  131. package/src.ts/index.ts +12 -12
  132. package/src.ts/providers/abstract-provider.ts +1761 -1761
  133. package/src.ts/providers/abstract-signer.ts +314 -314
  134. package/src.ts/providers/community.ts +49 -49
  135. package/src.ts/providers/contracts.ts +42 -42
  136. package/src.ts/providers/default-provider.ts +96 -96
  137. package/src.ts/providers/ens-resolver.ts +606 -606
  138. package/src.ts/providers/format.ts +320 -320
  139. package/src.ts/providers/formatting.ts +418 -418
  140. package/src.ts/providers/index.ts +125 -125
  141. package/src.ts/providers/network.ts +327 -327
  142. package/src.ts/providers/pagination.ts +8 -8
  143. package/src.ts/providers/plugin-fallback.ts +35 -35
  144. package/src.ts/providers/plugins-network.ts +281 -281
  145. package/src.ts/providers/provider-browser.ts +334 -334
  146. package/src.ts/providers/provider-fallback.ts +801 -801
  147. package/src.ts/providers/provider-ipcsocket-browser.ts +3 -3
  148. package/src.ts/providers/provider-ipcsocket.ts +81 -81
  149. package/src.ts/providers/provider-jsonrpc.ts +1334 -1335
  150. package/src.ts/providers/provider-socket.ts +352 -352
  151. package/src.ts/providers/provider-websocket.ts +103 -103
  152. package/src.ts/providers/provider.ts +2136 -2136
  153. package/src.ts/providers/signer-noncemanager.ts +98 -98
  154. package/src.ts/providers/signer.ts +166 -166
  155. package/src.ts/providers/subscriber-connection.ts +74 -74
  156. package/src.ts/providers/subscriber-filterid.ts +199 -199
  157. package/src.ts/providers/subscriber-polling.ts +321 -321
  158. package/src.ts/providers/ws-browser.ts +11 -11
  159. package/src.ts/providers/ws.ts +3 -3
  160. package/src.ts/quantumcoin.ts +219 -211
  161. package/src.ts/thirdparty.d.ts +16 -16
  162. package/src.ts/transaction/accesslist.ts +43 -43
  163. package/src.ts/transaction/address.ts +35 -31
  164. package/src.ts/transaction/authorization.ts +14 -14
  165. package/src.ts/transaction/index.ts +51 -51
  166. package/src.ts/transaction/transaction.ts +1349 -1379
  167. package/src.ts/utils/base58.ts +73 -73
  168. package/src.ts/utils/base64-browser.ts +25 -25
  169. package/src.ts/utils/base64.ts +56 -56
  170. package/src.ts/utils/data.ts +199 -199
  171. package/src.ts/utils/errors.ts +793 -793
  172. package/src.ts/utils/events.ts +105 -105
  173. package/src.ts/utils/fetch.ts +970 -970
  174. package/src.ts/utils/fixednumber.ts +643 -643
  175. package/src.ts/utils/geturl-browser.ts +81 -81
  176. package/src.ts/utils/geturl.ts +134 -134
  177. package/src.ts/utils/index.ts +95 -95
  178. package/src.ts/utils/maths.ts +240 -240
  179. package/src.ts/utils/properties.ts +60 -60
  180. package/src.ts/utils/rlp-decode.ts +104 -104
  181. package/src.ts/utils/rlp-encode.ts +64 -64
  182. package/src.ts/utils/rlp.ts +20 -20
  183. package/src.ts/utils/units.ts +91 -91
  184. package/src.ts/utils/utf8.ts +325 -325
  185. package/src.ts/utils/uuid.ts +36 -36
  186. package/src.ts/wallet/base-wallet.ts +160 -160
  187. package/src.ts/wallet/index.ts +32 -32
  188. package/src.ts/wallet/json-keystore.ts +108 -106
  189. package/src.ts/wallet/utils.ts +147 -147
  190. package/src.ts/wallet/wallet.ts +138 -139
  191. package/src.ts/wordlists/bit-reader.ts +35 -35
  192. package/src.ts/wordlists/decode-owl.ts +58 -58
  193. package/src.ts/wordlists/decode-owla.ts +33 -33
  194. package/src.ts/wordlists/generation/encode-latin.ts +370 -370
  195. package/src.ts/wordlists/index.ts +26 -26
  196. package/src.ts/wordlists/lang-cz.ts +33 -33
  197. package/src.ts/wordlists/lang-en.ts +33 -33
  198. package/src.ts/wordlists/lang-es.ts +35 -35
  199. package/src.ts/wordlists/lang-fr.ts +34 -34
  200. package/src.ts/wordlists/lang-it.ts +33 -33
  201. package/src.ts/wordlists/lang-ja.ts +181 -181
  202. package/src.ts/wordlists/lang-ko.ts +104 -104
  203. package/src.ts/wordlists/lang-pt.ts +34 -34
  204. package/src.ts/wordlists/lang-zh.ts +112 -112
  205. package/src.ts/wordlists/wordlist-owl.ts +77 -77
  206. package/src.ts/wordlists/wordlist-owla.ts +41 -41
  207. package/src.ts/wordlists/wordlist.ts +59 -59
  208. package/src.ts/wordlists/wordlists-browser.ts +8 -8
  209. package/src.ts/wordlists/wordlists-extra.ts +9 -9
  210. package/src.ts/wordlists/wordlists.ts +38 -38
  211. package/dist/quantumcoin.min.js'.gz' +0 -0
  212. package/dist/quantumcoin.umd.min.js'.gz' +0 -0
  213. package/dist/wordlists-extra.min.js'.gz' +0 -0
  214. package/lib.commonjs/providers/provider-alchemy.d.ts +0 -50
  215. package/lib.commonjs/providers/provider-alchemy.d.ts.map +0 -1
  216. package/lib.commonjs/providers/provider-alchemy.js +0 -151
  217. package/lib.commonjs/providers/provider-alchemy.js.map +0 -1
  218. package/lib.commonjs/providers/provider-ankr.d.ts +0 -61
  219. package/lib.commonjs/providers/provider-ankr.d.ts.map +0 -1
  220. package/lib.commonjs/providers/provider-ankr.js +0 -137
  221. package/lib.commonjs/providers/provider-ankr.js.map +0 -1
  222. package/lib.commonjs/providers/provider-blockscout.d.ts +0 -59
  223. package/lib.commonjs/providers/provider-blockscout.d.ts.map +0 -1
  224. package/lib.commonjs/providers/provider-blockscout.js +0 -145
  225. package/lib.commonjs/providers/provider-blockscout.js.map +0 -1
  226. package/lib.commonjs/providers/provider-chainstack.d.ts +0 -46
  227. package/lib.commonjs/providers/provider-chainstack.d.ts.map +0 -1
  228. package/lib.commonjs/providers/provider-chainstack.js +0 -102
  229. package/lib.commonjs/providers/provider-chainstack.js.map +0 -1
  230. package/lib.commonjs/providers/provider-cloudflare.d.ts +0 -14
  231. package/lib.commonjs/providers/provider-cloudflare.d.ts.map +0 -1
  232. package/lib.commonjs/providers/provider-cloudflare.js +0 -26
  233. package/lib.commonjs/providers/provider-cloudflare.js.map +0 -1
  234. package/lib.commonjs/providers/provider-etherscan.d.ts +0 -147
  235. package/lib.commonjs/providers/provider-etherscan.d.ts.map +0 -1
  236. package/lib.commonjs/providers/provider-etherscan.js +0 -587
  237. package/lib.commonjs/providers/provider-etherscan.js.map +0 -1
  238. package/lib.commonjs/providers/provider-infura.d.ts +0 -101
  239. package/lib.commonjs/providers/provider-infura.d.ts.map +0 -1
  240. package/lib.commonjs/providers/provider-infura.js +0 -206
  241. package/lib.commonjs/providers/provider-infura.js.map +0 -1
  242. package/lib.commonjs/providers/provider-pocket.d.ts +0 -54
  243. package/lib.commonjs/providers/provider-pocket.d.ts.map +0 -1
  244. package/lib.commonjs/providers/provider-pocket.js +0 -109
  245. package/lib.commonjs/providers/provider-pocket.js.map +0 -1
  246. package/lib.commonjs/providers/provider-quicknode.d.ts +0 -59
  247. package/lib.commonjs/providers/provider-quicknode.d.ts.map +0 -1
  248. package/lib.commonjs/providers/provider-quicknode.js +0 -163
  249. package/lib.commonjs/providers/provider-quicknode.js.map +0 -1
  250. package/lib.commonjs/wallet/hdwallet.d.ts +0 -248
  251. package/lib.commonjs/wallet/hdwallet.d.ts.map +0 -1
  252. package/lib.commonjs/wallet/hdwallet.js +0 -505
  253. package/lib.commonjs/wallet/hdwallet.js.map +0 -1
  254. package/lib.commonjs/wallet/json-crowdsale.d.ts +0 -27
  255. package/lib.commonjs/wallet/json-crowdsale.d.ts.map +0 -1
  256. package/lib.commonjs/wallet/json-crowdsale.js +0 -60
  257. package/lib.commonjs/wallet/json-crowdsale.js.map +0 -1
  258. package/lib.commonjs/wallet/mnemonic.d.ts +0 -65
  259. package/lib.commonjs/wallet/mnemonic.d.ts.map +0 -1
  260. package/lib.commonjs/wallet/mnemonic.js +0 -169
  261. package/lib.commonjs/wallet/mnemonic.js.map +0 -1
  262. package/lib.commonjs/wallet/seedwallet.d.ts +0 -4
  263. package/lib.commonjs/wallet/seedwallet.d.ts.map +0 -1
  264. package/lib.commonjs/wallet/seedwallet.js +0 -8
  265. package/lib.commonjs/wallet/seedwallet.js.map +0 -1
  266. package/lib.esm/providers/provider-alchemy.d.ts +0 -50
  267. package/lib.esm/providers/provider-alchemy.d.ts.map +0 -1
  268. package/lib.esm/providers/provider-alchemy.js +0 -147
  269. package/lib.esm/providers/provider-alchemy.js.map +0 -1
  270. package/lib.esm/providers/provider-ankr.d.ts +0 -61
  271. package/lib.esm/providers/provider-ankr.d.ts.map +0 -1
  272. package/lib.esm/providers/provider-ankr.js +0 -133
  273. package/lib.esm/providers/provider-ankr.js.map +0 -1
  274. package/lib.esm/providers/provider-blockscout.d.ts +0 -59
  275. package/lib.esm/providers/provider-blockscout.d.ts.map +0 -1
  276. package/lib.esm/providers/provider-blockscout.js +0 -141
  277. package/lib.esm/providers/provider-blockscout.js.map +0 -1
  278. package/lib.esm/providers/provider-chainstack.d.ts +0 -46
  279. package/lib.esm/providers/provider-chainstack.d.ts.map +0 -1
  280. package/lib.esm/providers/provider-chainstack.js +0 -98
  281. package/lib.esm/providers/provider-chainstack.js.map +0 -1
  282. package/lib.esm/providers/provider-cloudflare.d.ts +0 -14
  283. package/lib.esm/providers/provider-cloudflare.d.ts.map +0 -1
  284. package/lib.esm/providers/provider-cloudflare.js +0 -22
  285. package/lib.esm/providers/provider-cloudflare.js.map +0 -1
  286. package/lib.esm/providers/provider-etherscan.d.ts +0 -147
  287. package/lib.esm/providers/provider-etherscan.d.ts.map +0 -1
  288. package/lib.esm/providers/provider-etherscan.js +0 -584
  289. package/lib.esm/providers/provider-etherscan.js.map +0 -1
  290. package/lib.esm/providers/provider-infura.d.ts +0 -101
  291. package/lib.esm/providers/provider-infura.d.ts.map +0 -1
  292. package/lib.esm/providers/provider-infura.js +0 -201
  293. package/lib.esm/providers/provider-infura.js.map +0 -1
  294. package/lib.esm/providers/provider-pocket.d.ts +0 -54
  295. package/lib.esm/providers/provider-pocket.d.ts.map +0 -1
  296. package/lib.esm/providers/provider-pocket.js +0 -105
  297. package/lib.esm/providers/provider-pocket.js.map +0 -1
  298. package/lib.esm/providers/provider-quicknode.d.ts +0 -59
  299. package/lib.esm/providers/provider-quicknode.d.ts.map +0 -1
  300. package/lib.esm/providers/provider-quicknode.js +0 -159
  301. package/lib.esm/providers/provider-quicknode.js.map +0 -1
  302. package/lib.esm/wallet/hdwallet.d.ts +0 -248
  303. package/lib.esm/wallet/hdwallet.d.ts.map +0 -1
  304. package/lib.esm/wallet/hdwallet.js +0 -498
  305. package/lib.esm/wallet/hdwallet.js.map +0 -1
  306. package/lib.esm/wallet/json-crowdsale.d.ts +0 -27
  307. package/lib.esm/wallet/json-crowdsale.d.ts.map +0 -1
  308. package/lib.esm/wallet/json-crowdsale.js +0 -55
  309. package/lib.esm/wallet/json-crowdsale.js.map +0 -1
  310. package/lib.esm/wallet/mnemonic.d.ts +0 -65
  311. package/lib.esm/wallet/mnemonic.d.ts.map +0 -1
  312. package/lib.esm/wallet/mnemonic.js +0 -165
  313. package/lib.esm/wallet/mnemonic.js.map +0 -1
  314. package/lib.esm/wallet/seedwallet.d.ts +0 -4
  315. package/lib.esm/wallet/seedwallet.d.ts.map +0 -1
  316. package/lib.esm/wallet/seedwallet.js +0 -4
  317. package/lib.esm/wallet/seedwallet.js.map +0 -1
@@ -1,970 +1,970 @@
1
- /**
2
- * Fetching content from the web is environment-specific, so Ethers
3
- * provides an abstraction that each environment can implement to provide
4
- * this service.
5
- *
6
- * On [Node.js](link-node), the ``http`` and ``https`` libs are used to
7
- * create a request object, register event listeners and process data
8
- * and populate the [[FetchResponse]].
9
- *
10
- * In a browser, the [DOM fetch](link-js-fetch) is used, and the resulting
11
- * ``Promise`` is waited on to retrieve the payload.
12
- *
13
- * The [[FetchRequest]] is responsible for handling many common situations,
14
- * such as redirects, server throttling, authentication, etc.
15
- *
16
- * It also handles common gateways, such as IPFS and data URIs.
17
- *
18
- * @_section api/utils/fetching:Fetching Web Content [about-fetch]
19
- */
20
- import { decodeBase64, encodeBase64 } from "./base64.js";
21
- import { hexlify } from "./data.js";
22
- import { assert, assertArgument } from "./errors.js";
23
- import { defineProperties } from "./properties.js";
24
- import { toUtf8Bytes, toUtf8String } from "./utf8.js";
25
-
26
- import { createGetUrl } from "./geturl.js";
27
-
28
- /**
29
- * An environment's implementation of ``getUrl`` must return this type.
30
- */
31
- export type GetUrlResponse = {
32
- statusCode: number,
33
- statusMessage: string,
34
- headers: Record<string, string>,
35
- body: null | Uint8Array
36
- };
37
-
38
- /**
39
- * This can be used to control how throttling is handled in
40
- * [[FetchRequest-setThrottleParams]].
41
- */
42
- export type FetchThrottleParams = {
43
- maxAttempts?: number;
44
- slotInterval?: number;
45
- };
46
-
47
- /**
48
- * Called before any network request, allowing updated headers (e.g. Bearer tokens), etc.
49
- */
50
- export type FetchPreflightFunc = (req: FetchRequest) => Promise<FetchRequest>;
51
-
52
- /**
53
- * Called on the response, allowing client-based throttling logic or post-processing.
54
- */
55
- export type FetchProcessFunc = (req: FetchRequest, resp: FetchResponse) => Promise<FetchResponse>;
56
-
57
- /**
58
- * Called prior to each retry; return true to retry, false to abort.
59
- */
60
- export type FetchRetryFunc = (req: FetchRequest, resp: FetchResponse, attempt: number) => Promise<boolean>;
61
-
62
- /**
63
- * Called on Gateway URLs.
64
- */
65
- export type FetchGatewayFunc = (url: string, signal?: FetchCancelSignal) => Promise<FetchRequest | FetchResponse>;
66
-
67
- /**
68
- * Used to perform a fetch; use this to override the underlying network
69
- * fetch layer. In NodeJS, the default uses the "http" and "https" libraries
70
- * and in the browser ``fetch`` is used. If you wish to use Axios, this is
71
- * how you would register it.
72
- */
73
- export type FetchGetUrlFunc = (req: FetchRequest, signal?: FetchCancelSignal) => Promise<GetUrlResponse>;
74
-
75
-
76
- const MAX_ATTEMPTS = 12;
77
- const SLOT_INTERVAL = 250;
78
-
79
- // The global FetchGetUrlFunc implementation.
80
- let defaultGetUrlFunc: FetchGetUrlFunc = createGetUrl();
81
-
82
- const reData = new RegExp("^data:([^;:]*)?(;base64)?,(.*)$", "i");
83
- const reIpfs = new RegExp("^ipfs:/\/(ipfs/)?(.*)$", "i");
84
-
85
- // If locked, new Gateways cannot be added
86
- let locked = false;
87
-
88
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs
89
- async function dataGatewayFunc(url: string, signal?: FetchCancelSignal): Promise<FetchResponse> {
90
- try {
91
- const match = url.match(reData);
92
- if (!match) { throw new Error("invalid data"); }
93
- return new FetchResponse(200, "OK", {
94
- "content-type": (match[1] || "text/plain"),
95
- }, (match[2] ? decodeBase64(match[3]): unpercent(match[3])));
96
- } catch (error) {
97
- return new FetchResponse(599, "BAD REQUEST (invalid data: URI)", { }, null, new FetchRequest(url));
98
- }
99
- }
100
-
101
- /**
102
- * Returns a [[FetchGatewayFunc]] for fetching content from a standard
103
- * IPFS gateway hosted at %%baseUrl%%.
104
- */
105
- function getIpfsGatewayFunc(baseUrl: string): FetchGatewayFunc {
106
- async function gatewayIpfs(url: string, signal?: FetchCancelSignal): Promise<FetchRequest | FetchResponse> {
107
- try {
108
- const match = url.match(reIpfs);
109
- if (!match) { throw new Error("invalid link"); }
110
- return new FetchRequest(`${ baseUrl }${ match[2] }`);
111
- } catch (error) {
112
- return new FetchResponse(599, "BAD REQUEST (invalid IPFS URI)", { }, null, new FetchRequest(url));
113
- }
114
- }
115
-
116
- return gatewayIpfs;
117
- }
118
-
119
- const Gateways: Record<string, FetchGatewayFunc> = {
120
- "data": dataGatewayFunc,
121
- "ipfs": getIpfsGatewayFunc("https:/\/gateway.ipfs.io/ipfs/")
122
- };
123
-
124
- const fetchSignals: WeakMap<FetchRequest, () => void> = new WeakMap();
125
-
126
- /**
127
- * @_ignore
128
- */
129
- export class FetchCancelSignal {
130
- #listeners: Array<() => void>;
131
- #cancelled: boolean;
132
-
133
- constructor(request: FetchRequest) {
134
- this.#listeners = [ ];
135
- this.#cancelled = false;
136
-
137
- fetchSignals.set(request, () => {
138
- if (this.#cancelled) { return; }
139
- this.#cancelled = true;
140
-
141
- for (const listener of this.#listeners) {
142
- setTimeout(() => { listener(); }, 0);
143
- }
144
- this.#listeners = [ ];
145
- });
146
- }
147
-
148
- addListener(listener: () => void): void {
149
- assert(!this.#cancelled, "singal already cancelled", "UNSUPPORTED_OPERATION", {
150
- operation: "fetchCancelSignal.addCancelListener"
151
- });
152
- this.#listeners.push(listener);
153
- }
154
-
155
- get cancelled(): boolean { return this.#cancelled; }
156
-
157
- checkSignal(): void {
158
- assert(!this.cancelled, "cancelled", "CANCELLED", { });
159
- }
160
- }
161
-
162
- // Check the signal, throwing if it is cancelled
163
- function checkSignal(signal?: FetchCancelSignal): FetchCancelSignal {
164
- if (signal == null) { throw new Error("missing signal; should not happen"); }
165
- signal.checkSignal();
166
- return signal;
167
- }
168
-
169
- /**
170
- * Represents a request for a resource using a URI.
171
- *
172
- * By default, the supported schemes are ``HTTP``, ``HTTPS``, ``data:``,
173
- * and ``IPFS:``.
174
- *
175
- * Additional schemes can be added globally using [[registerGateway]].
176
- *
177
- * @example:
178
- * req = new FetchRequest("https://www.ricmoo.com")
179
- * resp = await req.send()
180
- * resp.body.length
181
- * //_result:
182
- */
183
- export class FetchRequest implements Iterable<[ key: string, value: string ]> {
184
- #allowInsecure: boolean;
185
- #gzip: boolean;
186
- #headers: Record<string, string>;
187
- #method: string;
188
- #timeout: number;
189
- #url: string;
190
-
191
- #body?: Uint8Array;
192
- #bodyType?: string;
193
- #creds?: string;
194
-
195
- // Hooks
196
- #preflight?: null | FetchPreflightFunc;
197
- #process?: null | FetchProcessFunc;
198
- #retry?: null | FetchRetryFunc;
199
-
200
- #signal?: FetchCancelSignal;
201
-
202
- #throttle: Required<FetchThrottleParams>;
203
-
204
- #getUrlFunc: null | FetchGetUrlFunc;
205
-
206
- /**
207
- * The fetch URL to request.
208
- */
209
- get url(): string { return this.#url; }
210
- set url(url: string) {
211
- this.#url = String(url);
212
- }
213
-
214
- /**
215
- * The fetch body, if any, to send as the request body. //(default: null)//
216
- *
217
- * When setting a body, the intrinsic ``Content-Type`` is automatically
218
- * set and will be used if **not overridden** by setting a custom
219
- * header.
220
- *
221
- * If %%body%% is null, the body is cleared (along with the
222
- * intrinsic ``Content-Type``).
223
- *
224
- * If %%body%% is a string, the intrinsic ``Content-Type`` is set to
225
- * ``text/plain``.
226
- *
227
- * If %%body%% is a Uint8Array, the intrinsic ``Content-Type`` is set to
228
- * ``application/octet-stream``.
229
- *
230
- * If %%body%% is any other object, the intrinsic ``Content-Type`` is
231
- * set to ``application/json``.
232
- */
233
- get body(): null | Uint8Array {
234
- if (this.#body == null) { return null; }
235
- return new Uint8Array(this.#body);
236
- }
237
- set body(body: null | string | Readonly<object> | Readonly<Uint8Array>) {
238
- if (body == null) {
239
- this.#body = undefined;
240
- this.#bodyType = undefined;
241
- } else if (typeof(body) === "string") {
242
- this.#body = toUtf8Bytes(body);
243
- this.#bodyType = "text/plain";
244
- } else if (body instanceof Uint8Array) {
245
- this.#body = body;
246
- this.#bodyType = "application/octet-stream";
247
- } else if (typeof(body) === "object") {
248
- this.#body = toUtf8Bytes(JSON.stringify(body));
249
- this.#bodyType = "application/json";
250
- } else {
251
- throw new Error("invalid body");
252
- }
253
- }
254
-
255
- /**
256
- * Returns true if the request has a body.
257
- */
258
- hasBody(): this is (FetchRequest & { body: Uint8Array }) {
259
- return (this.#body != null);
260
- }
261
-
262
- /**
263
- * The HTTP method to use when requesting the URI. If no method
264
- * has been explicitly set, then ``GET`` is used if the body is
265
- * null and ``POST`` otherwise.
266
- */
267
- get method(): string {
268
- if (this.#method) { return this.#method; }
269
- if (this.hasBody()) { return "POST"; }
270
- return "GET";
271
- }
272
- set method(method: null | string) {
273
- if (method == null) { method = ""; }
274
- this.#method = String(method).toUpperCase();
275
- }
276
-
277
- /**
278
- * The headers that will be used when requesting the URI. All
279
- * keys are lower-case.
280
- *
281
- * This object is a copy, so any changes will **NOT** be reflected
282
- * in the ``FetchRequest``.
283
- *
284
- * To set a header entry, use the ``setHeader`` method.
285
- */
286
- get headers(): Record<string, string> {
287
- const headers = Object.assign({ }, this.#headers);
288
-
289
- if (this.#creds) {
290
- headers["authorization"] = `Basic ${ encodeBase64(toUtf8Bytes(this.#creds)) }`;
291
- };
292
-
293
- if (this.allowGzip) {
294
- headers["accept-encoding"] = "gzip";
295
- }
296
-
297
- if (headers["content-type"] == null && this.#bodyType) {
298
- headers["content-type"] = this.#bodyType;
299
- }
300
- if (this.body) { headers["content-length"] = String(this.body.length); }
301
-
302
- return headers;
303
- }
304
-
305
- /**
306
- * Get the header for %%key%%, ignoring case.
307
- */
308
- getHeader(key: string): string {
309
- return this.headers[key.toLowerCase()];
310
- }
311
-
312
- /**
313
- * Set the header for %%key%% to %%value%%. All values are coerced
314
- * to a string.
315
- */
316
- setHeader(key: string, value: string | number): void {
317
- this.#headers[String(key).toLowerCase()] = String(value);
318
- }
319
-
320
- /**
321
- * Clear all headers, resetting all intrinsic headers.
322
- */
323
- clearHeaders(): void {
324
- this.#headers = { };
325
- }
326
-
327
- [Symbol.iterator](): Iterator<[ key: string, value: string ]> {
328
- const headers = this.headers;
329
- const keys = Object.keys(headers);
330
- let index = 0;
331
- return {
332
- next: () => {
333
- if (index < keys.length) {
334
- const key = keys[index++];
335
- return {
336
- value: [ key, headers[key] ], done: false
337
- }
338
- }
339
- return { value: undefined, done: true };
340
- }
341
- };
342
- }
343
-
344
- /**
345
- * The value that will be sent for the ``Authorization`` header.
346
- *
347
- * To set the credentials, use the ``setCredentials`` method.
348
- */
349
- get credentials(): null | string {
350
- return this.#creds || null;
351
- }
352
-
353
- /**
354
- * Sets an ``Authorization`` for %%username%% with %%password%%.
355
- */
356
- setCredentials(username: string, password: string): void {
357
- assertArgument(!username.match(/:/), "invalid basic authentication username", "username", "[REDACTED]");
358
- this.#creds = `${ username }:${ password }`;
359
- }
360
-
361
- /**
362
- * Enable and request gzip-encoded responses. The response will
363
- * automatically be decompressed. //(default: true)//
364
- */
365
- get allowGzip(): boolean {
366
- return this.#gzip;
367
- }
368
- set allowGzip(value: boolean) {
369
- this.#gzip = !!value;
370
- }
371
-
372
- /**
373
- * Allow ``Authentication`` credentials to be sent over insecure
374
- * channels. //(default: false)//
375
- */
376
- get allowInsecureAuthentication(): boolean {
377
- return !!this.#allowInsecure;
378
- }
379
- set allowInsecureAuthentication(value: boolean) {
380
- this.#allowInsecure = !!value;
381
- }
382
-
383
- /**
384
- * The timeout (in milliseconds) to wait for a complete response.
385
- * //(default: 5 minutes)//
386
- */
387
- get timeout(): number { return this.#timeout; }
388
- set timeout(timeout: number) {
389
- assertArgument(timeout >= 0, "timeout must be non-zero", "timeout", timeout);
390
- this.#timeout = timeout;
391
- }
392
-
393
- /**
394
- * This function is called prior to each request, for example
395
- * during a redirection or retry in case of server throttling.
396
- *
397
- * This offers an opportunity to populate headers or update
398
- * content before sending a request.
399
- */
400
- get preflightFunc(): null | FetchPreflightFunc {
401
- return this.#preflight || null;
402
- }
403
- set preflightFunc(preflight: null | FetchPreflightFunc) {
404
- this.#preflight = preflight;
405
- }
406
-
407
- /**
408
- * This function is called after each response, offering an
409
- * opportunity to provide client-level throttling or updating
410
- * response data.
411
- *
412
- * Any error thrown in this causes the ``send()`` to throw.
413
- *
414
- * To schedule a retry attempt (assuming the maximum retry limit
415
- * has not been reached), use [[response.throwThrottleError]].
416
- */
417
- get processFunc(): null | FetchProcessFunc {
418
- return this.#process || null;
419
- }
420
- set processFunc(process: null | FetchProcessFunc) {
421
- this.#process = process;
422
- }
423
-
424
- /**
425
- * This function is called on each retry attempt.
426
- */
427
- get retryFunc(): null | FetchRetryFunc {
428
- return this.#retry || null;
429
- }
430
- set retryFunc(retry: null | FetchRetryFunc) {
431
- this.#retry = retry;
432
- }
433
-
434
- /**
435
- * This function is called to fetch content from HTTP and
436
- * HTTPS URLs and is platform specific (e.g. nodejs vs
437
- * browsers).
438
- *
439
- * This is by default the currently registered global getUrl
440
- * function, which can be changed using [[registerGetUrl]].
441
- * If this has been set, setting is to ``null`` will cause
442
- * this FetchRequest (and any future clones) to revert back to
443
- * using the currently registered global getUrl function.
444
- *
445
- * Setting this is generally not necessary, but may be useful
446
- * for developers that wish to intercept requests or to
447
- * configurege a proxy or other agent.
448
- */
449
- get getUrlFunc(): FetchGetUrlFunc {
450
- return this.#getUrlFunc || defaultGetUrlFunc;
451
- }
452
- set getUrlFunc(value: null | FetchGetUrlFunc) {
453
- this.#getUrlFunc = value;
454
- }
455
-
456
- /**
457
- * Create a new FetchRequest instance with default values.
458
- *
459
- * Once created, each property may be set before issuing a
460
- * ``.send()`` to make the request.
461
- */
462
- constructor(url: string) {
463
- this.#url = String(url);
464
-
465
- this.#allowInsecure = false;
466
- this.#gzip = true;
467
- this.#headers = { };
468
- this.#method = "";
469
- this.#timeout = 300000;
470
-
471
- this.#throttle = {
472
- slotInterval: SLOT_INTERVAL,
473
- maxAttempts: MAX_ATTEMPTS
474
- };
475
-
476
- this.#getUrlFunc = null;
477
- }
478
-
479
- toString(): string {
480
- return `<FetchRequest method=${ JSON.stringify(this.method) } url=${ JSON.stringify(this.url) } headers=${ JSON.stringify(this.headers) } body=${ this.#body ? hexlify(this.#body): "null" }>`;
481
- }
482
-
483
- /**
484
- * Update the throttle parameters used to determine maximum
485
- * attempts and exponential-backoff properties.
486
- */
487
- setThrottleParams(params: FetchThrottleParams): void {
488
- if (params.slotInterval != null) {
489
- this.#throttle.slotInterval = params.slotInterval;
490
- }
491
- if (params.maxAttempts != null) {
492
- this.#throttle.maxAttempts = params.maxAttempts;
493
- }
494
- }
495
-
496
- async #send(attempt: number, expires: number, delay: number, _request: FetchRequest, _response: FetchResponse): Promise<FetchResponse> {
497
- if (attempt >= this.#throttle.maxAttempts) {
498
- return _response.makeServerError("exceeded maximum retry limit");
499
- }
500
-
501
- assert(getTime() <= expires, "timeout", "TIMEOUT", {
502
- operation: "request.send", reason: "timeout", request: _request
503
- });
504
-
505
- if (delay > 0) { await wait(delay); }
506
-
507
- let req = this.clone();
508
- const scheme = (req.url.split(":")[0] || "").toLowerCase();
509
-
510
- // Process any Gateways
511
- if (scheme in Gateways) {
512
- const result = await Gateways[scheme](req.url, checkSignal(_request.#signal));
513
- if (result instanceof FetchResponse) {
514
- let response = result;
515
-
516
- if (this.processFunc) {
517
- checkSignal(_request.#signal);
518
- try {
519
- response = await this.processFunc(req, response);
520
- } catch (error: any) {
521
-
522
- // Something went wrong during processing; throw a 5xx server error
523
- if (error.throttle == null || typeof(error.stall) !== "number") {
524
- response.makeServerError("error in post-processing function", error).assertOk();
525
- }
526
-
527
- // Ignore throttling
528
- }
529
- }
530
-
531
- return response;
532
- }
533
- req = result;
534
- }
535
-
536
- // We have a preflight function; update the request
537
- if (this.preflightFunc) { req = await this.preflightFunc(req); }
538
-
539
- const resp = await this.getUrlFunc(req, checkSignal(_request.#signal));
540
- let response = new FetchResponse(resp.statusCode, resp.statusMessage, resp.headers, resp.body, _request);
541
-
542
- if (response.statusCode === 301 || response.statusCode === 302) {
543
-
544
- // Redirect
545
- try {
546
- const location = response.headers.location || "";
547
- return req.redirect(location).#send(attempt + 1, expires, 0, _request, response);
548
- } catch (error) { }
549
-
550
- // Things won't get any better on another attempt; abort
551
- return response;
552
-
553
- } else if (response.statusCode === 429) {
554
-
555
- // Throttle
556
- if (this.retryFunc == null || (await this.retryFunc(req, response, attempt))) {
557
- const retryAfter = response.headers["retry-after"];
558
- let delay = this.#throttle.slotInterval * Math.trunc(Math.random() * Math.pow(2, attempt));
559
- if (typeof(retryAfter) === "string" && retryAfter.match(/^[1-9][0-9]*$/)) {
560
- delay = parseInt(retryAfter);
561
- }
562
- return req.clone().#send(attempt + 1, expires, delay, _request, response);
563
- }
564
- }
565
-
566
- if (this.processFunc) {
567
- checkSignal(_request.#signal);
568
- try {
569
- response = await this.processFunc(req, response);
570
- } catch (error: any) {
571
-
572
- // Something went wrong during processing; throw a 5xx server error
573
- if (error.throttle == null || typeof(error.stall) !== "number") {
574
- response.makeServerError("error in post-processing function", error).assertOk();
575
- }
576
-
577
- // Throttle
578
- let delay = this.#throttle.slotInterval * Math.trunc(Math.random() * Math.pow(2, attempt));;
579
- if (error.stall >= 0) { delay = error.stall; }
580
-
581
- return req.clone().#send(attempt + 1, expires, delay, _request, response);
582
- }
583
- }
584
-
585
- return response;
586
- }
587
-
588
- /**
589
- * Resolves to the response by sending the request.
590
- */
591
- send(): Promise<FetchResponse> {
592
- assert(this.#signal == null, "request already sent", "UNSUPPORTED_OPERATION", { operation: "fetchRequest.send" });
593
- this.#signal = new FetchCancelSignal(this);
594
- return this.#send(0, getTime() + this.timeout, 0, this, new FetchResponse(0, "", { }, null, this));
595
- }
596
-
597
- /**
598
- * Cancels the inflight response, causing a ``CANCELLED``
599
- * error to be rejected from the [[send]].
600
- */
601
- cancel(): void {
602
- assert(this.#signal != null, "request has not been sent", "UNSUPPORTED_OPERATION", { operation: "fetchRequest.cancel" });
603
- const signal = fetchSignals.get(this);
604
- if (!signal) { throw new Error("missing signal; should not happen"); }
605
- signal();
606
- }
607
-
608
- /**
609
- * Returns a new [[FetchRequest]] that represents the redirection
610
- * to %%location%%.
611
- */
612
- redirect(location: string): FetchRequest {
613
- // Redirection; for now we only support absolute locations
614
- const current = this.url.split(":")[0].toLowerCase();
615
- const target = location.split(":")[0].toLowerCase();
616
-
617
- // Don't allow redirecting:
618
- // - non-GET requests
619
- // - downgrading the security (e.g. https => http)
620
- // - to non-HTTP (or non-HTTPS) protocols [this could be relaxed?]
621
- assert(this.method === "GET" && (current !== "https" || target !== "http") && location.match(/^https?:/), `unsupported redirect`, "UNSUPPORTED_OPERATION", {
622
- operation: `redirect(${ this.method } ${ JSON.stringify(this.url) } => ${ JSON.stringify(location) })`
623
- });
624
-
625
- // Create a copy of this request, with a new URL
626
- const req = new FetchRequest(location);
627
- req.method = "GET";
628
- req.allowGzip = this.allowGzip;
629
- req.timeout = this.timeout;
630
- req.#headers = Object.assign({ }, this.#headers);
631
- if (this.#body) { req.#body = new Uint8Array(this.#body); }
632
- req.#bodyType = this.#bodyType;
633
-
634
- // Do not forward credentials unless on the same domain; only absolute
635
- //req.allowInsecure = false;
636
- // paths are currently supported; may want a way to specify to forward?
637
- //setStore(req.#props, "creds", getStore(this.#pros, "creds"));
638
-
639
- return req;
640
- }
641
-
642
- /**
643
- * Create a new copy of this request.
644
- */
645
- clone(): FetchRequest {
646
- const clone = new FetchRequest(this.url);
647
-
648
- // Preserve "default method" (i.e. null)
649
- clone.#method = this.#method;
650
-
651
- // Preserve "default body" with type, copying the Uint8Array is present
652
- if (this.#body) { clone.#body = this.#body; }
653
- clone.#bodyType = this.#bodyType;
654
-
655
- // Preserve "default headers"
656
- clone.#headers = Object.assign({ }, this.#headers);
657
-
658
- // Credentials is readonly, so we copy internally
659
- clone.#creds = this.#creds;
660
-
661
- if (this.allowGzip) { clone.allowGzip = true; }
662
-
663
- clone.timeout = this.timeout;
664
- if (this.allowInsecureAuthentication) { clone.allowInsecureAuthentication = true; }
665
-
666
- clone.#preflight = this.#preflight;
667
- clone.#process = this.#process;
668
- clone.#retry = this.#retry;
669
-
670
- clone.#throttle = Object.assign({ }, this.#throttle);
671
-
672
- clone.#getUrlFunc = this.#getUrlFunc;
673
-
674
- return clone;
675
- }
676
-
677
- /**
678
- * Locks all static configuration for gateways and FetchGetUrlFunc
679
- * registration.
680
- */
681
- static lockConfig(): void {
682
- locked = true;
683
- }
684
-
685
- /**
686
- * Get the current Gateway function for %%scheme%%.
687
- */
688
- static getGateway(scheme: string): null | FetchGatewayFunc {
689
- return Gateways[scheme.toLowerCase()] || null;
690
- }
691
-
692
- /**
693
- * Use the %%func%% when fetching URIs using %%scheme%%.
694
- *
695
- * This method affects all requests globally.
696
- *
697
- * If [[lockConfig]] has been called, no change is made and this
698
- * throws.
699
- */
700
- static registerGateway(scheme: string, func: FetchGatewayFunc): void {
701
- scheme = scheme.toLowerCase();
702
- if (scheme === "http" || scheme === "https") {
703
- throw new Error(`cannot intercept ${ scheme }; use registerGetUrl`);
704
- }
705
- if (locked) { throw new Error("gateways locked"); }
706
- Gateways[scheme] = func;
707
- }
708
-
709
- /**
710
- * Use %%getUrl%% when fetching URIs over HTTP and HTTPS requests.
711
- *
712
- * This method affects all requests globally.
713
- *
714
- * If [[lockConfig]] has been called, no change is made and this
715
- * throws.
716
- */
717
- static registerGetUrl(getUrl: FetchGetUrlFunc): void {
718
- if (locked) { throw new Error("gateways locked"); }
719
- defaultGetUrlFunc = getUrl;
720
- }
721
-
722
- /**
723
- * Creates a getUrl function that fetches content from HTTP and
724
- * HTTPS URLs.
725
- *
726
- * The available %%options%% are dependent on the platform
727
- * implementation of the default getUrl function.
728
- *
729
- * This is not generally something that is needed, but is useful
730
- * when trying to customize simple behaviour when fetching HTTP
731
- * content.
732
- */
733
- static createGetUrlFunc(options?: Record<string, any>): FetchGetUrlFunc {
734
- return createGetUrl(options);
735
- }
736
-
737
- /**
738
- * Creates a function that can "fetch" data URIs.
739
- *
740
- * Note that this is automatically done internally to support
741
- * data URIs, so it is not necessary to register it.
742
- *
743
- * This is not generally something that is needed, but may
744
- * be useful in a wrapper to perfom custom data URI functionality.
745
- */
746
- static createDataGateway(): FetchGatewayFunc {
747
- return dataGatewayFunc;
748
- }
749
-
750
- /**
751
- * Creates a function that will fetch IPFS (unvalidated) from
752
- * a custom gateway baseUrl.
753
- *
754
- * The default IPFS gateway used internally is
755
- * ``"https:/\/gateway.ipfs.io/ipfs/"``.
756
- */
757
- static createIpfsGatewayFunc(baseUrl: string): FetchGatewayFunc {
758
- return getIpfsGatewayFunc(baseUrl);
759
- }
760
- }
761
-
762
-
763
- interface ThrottleError extends Error {
764
- stall: number;
765
- throttle: true;
766
- };
767
-
768
- /**
769
- * The response for a FetchRequest.
770
- */
771
- export class FetchResponse implements Iterable<[ key: string, value: string ]> {
772
- #statusCode: number;
773
- #statusMessage: string;
774
- #headers: Record<string, string>;
775
- #body: null | Readonly<Uint8Array>;
776
- #request: null | FetchRequest;
777
-
778
- #error: { error?: Error, message: string };
779
-
780
- toString(): string {
781
- return `<FetchResponse status=${ this.statusCode } body=${ this.#body ? hexlify(this.#body): "null" }>`;
782
- }
783
-
784
- /**
785
- * The response status code.
786
- */
787
- get statusCode(): number { return this.#statusCode; }
788
-
789
- /**
790
- * The response status message.
791
- */
792
- get statusMessage(): string { return this.#statusMessage; }
793
-
794
- /**
795
- * The response headers. All keys are lower-case.
796
- */
797
- get headers(): Record<string, string> { return Object.assign({ }, this.#headers); }
798
-
799
- /**
800
- * The response body, or ``null`` if there was no body.
801
- */
802
- get body(): null | Readonly<Uint8Array> {
803
- return (this.#body == null) ? null: new Uint8Array(this.#body);
804
- }
805
-
806
- /**
807
- * The response body as a UTF-8 encoded string, or the empty
808
- * string (i.e. ``""``) if there was no body.
809
- *
810
- * An error is thrown if the body is invalid UTF-8 data.
811
- */
812
- get bodyText(): string {
813
- try {
814
- return (this.#body == null) ? "": toUtf8String(this.#body);
815
- } catch (error) {
816
- assert(false, "response body is not valid UTF-8 data", "UNSUPPORTED_OPERATION", {
817
- operation: "bodyText", info: { response: this }
818
- });
819
- }
820
- }
821
-
822
- /**
823
- * The response body, decoded as JSON.
824
- *
825
- * An error is thrown if the body is invalid JSON-encoded data
826
- * or if there was no body.
827
- */
828
- get bodyJson(): any {
829
- try {
830
- return JSON.parse(this.bodyText);
831
- } catch (error) {
832
- assert(false, "response body is not valid JSON", "UNSUPPORTED_OPERATION", {
833
- operation: "bodyJson", info: { response: this }
834
- });
835
- }
836
- }
837
-
838
- [Symbol.iterator](): Iterator<[ key: string, value: string ]> {
839
- const headers = this.headers;
840
- const keys = Object.keys(headers);
841
- let index = 0;
842
- return {
843
- next: () => {
844
- if (index < keys.length) {
845
- const key = keys[index++];
846
- return {
847
- value: [ key, headers[key] ], done: false
848
- }
849
- }
850
- return { value: undefined, done: true };
851
- }
852
- };
853
- }
854
-
855
- constructor(statusCode: number, statusMessage: string, headers: Readonly<Record<string, string>>, body: null | Uint8Array, request?: FetchRequest) {
856
- this.#statusCode = statusCode;
857
- this.#statusMessage = statusMessage;
858
- this.#headers = Object.keys(headers).reduce((accum, k) => {
859
- accum[k.toLowerCase()] = String(headers[k]);
860
- return accum;
861
- }, <Record<string, string>>{ });
862
- this.#body = ((body == null) ? null: new Uint8Array(body));
863
- this.#request = (request || null);
864
-
865
- this.#error = { message: "" };
866
- }
867
-
868
- /**
869
- * Return a Response with matching headers and body, but with
870
- * an error status code (i.e. 599) and %%message%% with an
871
- * optional %%error%%.
872
- */
873
- makeServerError(message?: string, error?: Error): FetchResponse {
874
- let statusMessage: string;
875
- if (!message) {
876
- message = `${ this.statusCode } ${ this.statusMessage }`;
877
- statusMessage = `CLIENT ESCALATED SERVER ERROR (${ message })`;
878
- } else {
879
- statusMessage = `CLIENT ESCALATED SERVER ERROR (${ this.statusCode } ${ this.statusMessage }; ${ message })`;
880
- }
881
- const response = new FetchResponse(599, statusMessage, this.headers,
882
- this.body, this.#request || undefined);
883
- response.#error = { message, error };
884
- return response;
885
- }
886
-
887
- /**
888
- * If called within a [request.processFunc](FetchRequest-processFunc)
889
- * call, causes the request to retry as if throttled for %%stall%%
890
- * milliseconds.
891
- */
892
- throwThrottleError(message?: string, stall?: number): never {
893
- if (stall == null) {
894
- stall = -1;
895
- } else {
896
- assertArgument(Number.isInteger(stall) && stall >= 0, "invalid stall timeout", "stall", stall);
897
- }
898
-
899
- const error = new Error(message || "throttling requests");
900
-
901
- defineProperties(<ThrottleError>error, { stall, throttle: true });
902
-
903
- throw error;
904
- }
905
-
906
- /**
907
- * Get the header value for %%key%%, ignoring case.
908
- */
909
- getHeader(key: string): string {
910
- return this.headers[key.toLowerCase()];
911
- }
912
-
913
- /**
914
- * Returns true if the response has a body.
915
- */
916
- hasBody(): this is (FetchResponse & { body: Uint8Array }) {
917
- return (this.#body != null);
918
- }
919
-
920
- /**
921
- * The request made for this response.
922
- */
923
- get request(): null | FetchRequest { return this.#request; }
924
-
925
- /**
926
- * Returns true if this response was a success statusCode.
927
- */
928
- ok(): boolean {
929
- return (this.#error.message === "" && this.statusCode >= 200 && this.statusCode < 300);
930
- }
931
-
932
- /**
933
- * Throws a ``SERVER_ERROR`` if this response is not ok.
934
- */
935
- assertOk(): void {
936
- if (this.ok()) { return; }
937
- let { message, error } = this.#error;
938
- if (message === "") {
939
- message = `server response ${ this.statusCode } ${ this.statusMessage }`;
940
- }
941
-
942
- let requestUrl: null | string = null;
943
- if (this.request) { requestUrl = this.request.url; }
944
-
945
- let responseBody: null | string = null;
946
- try {
947
- if (this.#body) { responseBody = toUtf8String(this.#body); }
948
- } catch (e) { }
949
-
950
- assert(false, message, "SERVER_ERROR", {
951
- request: (this.request || "unknown request"), response: this, error,
952
- info: {
953
- requestUrl, responseBody,
954
- responseStatus: `${ this.statusCode } ${ this.statusMessage }` }
955
- });
956
- }
957
- }
958
-
959
-
960
- function getTime(): number { return (new Date()).getTime(); }
961
-
962
- function unpercent(value: string): Uint8Array {
963
- return toUtf8Bytes(value.replace(/%([0-9a-f][0-9a-f])/gi, (all, code) => {
964
- return String.fromCharCode(parseInt(code, 16));
965
- }));
966
- }
967
-
968
- function wait(delay: number): Promise<void> {
969
- return new Promise((resolve) => setTimeout(resolve, delay));
970
- }
1
+ /**
2
+ * Fetching content from the web is environment-specific, so Ethers
3
+ * provides an abstraction that each environment can implement to provide
4
+ * this service.
5
+ *
6
+ * On [Node.js](link-node), the ``http`` and ``https`` libs are used to
7
+ * create a request object, register event listeners and process data
8
+ * and populate the [[FetchResponse]].
9
+ *
10
+ * In a browser, the [DOM fetch](link-js-fetch) is used, and the resulting
11
+ * ``Promise`` is waited on to retrieve the payload.
12
+ *
13
+ * The [[FetchRequest]] is responsible for handling many common situations,
14
+ * such as redirects, server throttling, authentication, etc.
15
+ *
16
+ * It also handles common gateways, such as IPFS and data URIs.
17
+ *
18
+ * @_section api/utils/fetching:Fetching Web Content [about-fetch]
19
+ */
20
+ import { decodeBase64, encodeBase64 } from "./base64.js";
21
+ import { hexlify } from "./data.js";
22
+ import { assert, assertArgument } from "./errors.js";
23
+ import { defineProperties } from "./properties.js";
24
+ import { toUtf8Bytes, toUtf8String } from "./utf8.js";
25
+
26
+ import { createGetUrl } from "./geturl.js";
27
+
28
+ /**
29
+ * An environment's implementation of ``getUrl`` must return this type.
30
+ */
31
+ export type GetUrlResponse = {
32
+ statusCode: number,
33
+ statusMessage: string,
34
+ headers: Record<string, string>,
35
+ body: null | Uint8Array
36
+ };
37
+
38
+ /**
39
+ * This can be used to control how throttling is handled in
40
+ * [[FetchRequest-setThrottleParams]].
41
+ */
42
+ export type FetchThrottleParams = {
43
+ maxAttempts?: number;
44
+ slotInterval?: number;
45
+ };
46
+
47
+ /**
48
+ * Called before any network request, allowing updated headers (e.g. Bearer tokens), etc.
49
+ */
50
+ export type FetchPreflightFunc = (req: FetchRequest) => Promise<FetchRequest>;
51
+
52
+ /**
53
+ * Called on the response, allowing client-based throttling logic or post-processing.
54
+ */
55
+ export type FetchProcessFunc = (req: FetchRequest, resp: FetchResponse) => Promise<FetchResponse>;
56
+
57
+ /**
58
+ * Called prior to each retry; return true to retry, false to abort.
59
+ */
60
+ export type FetchRetryFunc = (req: FetchRequest, resp: FetchResponse, attempt: number) => Promise<boolean>;
61
+
62
+ /**
63
+ * Called on Gateway URLs.
64
+ */
65
+ export type FetchGatewayFunc = (url: string, signal?: FetchCancelSignal) => Promise<FetchRequest | FetchResponse>;
66
+
67
+ /**
68
+ * Used to perform a fetch; use this to override the underlying network
69
+ * fetch layer. In NodeJS, the default uses the "http" and "https" libraries
70
+ * and in the browser ``fetch`` is used. If you wish to use Axios, this is
71
+ * how you would register it.
72
+ */
73
+ export type FetchGetUrlFunc = (req: FetchRequest, signal?: FetchCancelSignal) => Promise<GetUrlResponse>;
74
+
75
+
76
+ const MAX_ATTEMPTS = 12;
77
+ const SLOT_INTERVAL = 250;
78
+
79
+ // The global FetchGetUrlFunc implementation.
80
+ let defaultGetUrlFunc: FetchGetUrlFunc = createGetUrl();
81
+
82
+ const reData = new RegExp("^data:([^;:]*)?(;base64)?,(.*)$", "i");
83
+ const reIpfs = new RegExp("^ipfs:/\/(ipfs/)?(.*)$", "i");
84
+
85
+ // If locked, new Gateways cannot be added
86
+ let locked = false;
87
+
88
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs
89
+ async function dataGatewayFunc(url: string, signal?: FetchCancelSignal): Promise<FetchResponse> {
90
+ try {
91
+ const match = url.match(reData);
92
+ if (!match) { throw new Error("invalid data"); }
93
+ return new FetchResponse(200, "OK", {
94
+ "content-type": (match[1] || "text/plain"),
95
+ }, (match[2] ? decodeBase64(match[3]): unpercent(match[3])));
96
+ } catch (error) {
97
+ return new FetchResponse(599, "BAD REQUEST (invalid data: URI)", { }, null, new FetchRequest(url));
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Returns a [[FetchGatewayFunc]] for fetching content from a standard
103
+ * IPFS gateway hosted at %%baseUrl%%.
104
+ */
105
+ function getIpfsGatewayFunc(baseUrl: string): FetchGatewayFunc {
106
+ async function gatewayIpfs(url: string, signal?: FetchCancelSignal): Promise<FetchRequest | FetchResponse> {
107
+ try {
108
+ const match = url.match(reIpfs);
109
+ if (!match) { throw new Error("invalid link"); }
110
+ return new FetchRequest(`${ baseUrl }${ match[2] }`);
111
+ } catch (error) {
112
+ return new FetchResponse(599, "BAD REQUEST (invalid IPFS URI)", { }, null, new FetchRequest(url));
113
+ }
114
+ }
115
+
116
+ return gatewayIpfs;
117
+ }
118
+
119
+ const Gateways: Record<string, FetchGatewayFunc> = {
120
+ "data": dataGatewayFunc,
121
+ "ipfs": getIpfsGatewayFunc("https:/\/gateway.ipfs.io/ipfs/")
122
+ };
123
+
124
+ const fetchSignals: WeakMap<FetchRequest, () => void> = new WeakMap();
125
+
126
+ /**
127
+ * @_ignore
128
+ */
129
+ export class FetchCancelSignal {
130
+ #listeners: Array<() => void>;
131
+ #cancelled: boolean;
132
+
133
+ constructor(request: FetchRequest) {
134
+ this.#listeners = [ ];
135
+ this.#cancelled = false;
136
+
137
+ fetchSignals.set(request, () => {
138
+ if (this.#cancelled) { return; }
139
+ this.#cancelled = true;
140
+
141
+ for (const listener of this.#listeners) {
142
+ setTimeout(() => { listener(); }, 0);
143
+ }
144
+ this.#listeners = [ ];
145
+ });
146
+ }
147
+
148
+ addListener(listener: () => void): void {
149
+ assert(!this.#cancelled, "singal already cancelled", "UNSUPPORTED_OPERATION", {
150
+ operation: "fetchCancelSignal.addCancelListener"
151
+ });
152
+ this.#listeners.push(listener);
153
+ }
154
+
155
+ get cancelled(): boolean { return this.#cancelled; }
156
+
157
+ checkSignal(): void {
158
+ assert(!this.cancelled, "cancelled", "CANCELLED", { });
159
+ }
160
+ }
161
+
162
+ // Check the signal, throwing if it is cancelled
163
+ function checkSignal(signal?: FetchCancelSignal): FetchCancelSignal {
164
+ if (signal == null) { throw new Error("missing signal; should not happen"); }
165
+ signal.checkSignal();
166
+ return signal;
167
+ }
168
+
169
+ /**
170
+ * Represents a request for a resource using a URI.
171
+ *
172
+ * By default, the supported schemes are ``HTTP``, ``HTTPS``, ``data:``,
173
+ * and ``IPFS:``.
174
+ *
175
+ * Additional schemes can be added globally using [[registerGateway]].
176
+ *
177
+ * @example:
178
+ * req = new FetchRequest("https://www.ricmoo.com")
179
+ * resp = await req.send()
180
+ * resp.body.length
181
+ * //_result:
182
+ */
183
+ export class FetchRequest implements Iterable<[ key: string, value: string ]> {
184
+ #allowInsecure: boolean;
185
+ #gzip: boolean;
186
+ #headers: Record<string, string>;
187
+ #method: string;
188
+ #timeout: number;
189
+ #url: string;
190
+
191
+ #body?: Uint8Array;
192
+ #bodyType?: string;
193
+ #creds?: string;
194
+
195
+ // Hooks
196
+ #preflight?: null | FetchPreflightFunc;
197
+ #process?: null | FetchProcessFunc;
198
+ #retry?: null | FetchRetryFunc;
199
+
200
+ #signal?: FetchCancelSignal;
201
+
202
+ #throttle: Required<FetchThrottleParams>;
203
+
204
+ #getUrlFunc: null | FetchGetUrlFunc;
205
+
206
+ /**
207
+ * The fetch URL to request.
208
+ */
209
+ get url(): string { return this.#url; }
210
+ set url(url: string) {
211
+ this.#url = String(url);
212
+ }
213
+
214
+ /**
215
+ * The fetch body, if any, to send as the request body. //(default: null)//
216
+ *
217
+ * When setting a body, the intrinsic ``Content-Type`` is automatically
218
+ * set and will be used if **not overridden** by setting a custom
219
+ * header.
220
+ *
221
+ * If %%body%% is null, the body is cleared (along with the
222
+ * intrinsic ``Content-Type``).
223
+ *
224
+ * If %%body%% is a string, the intrinsic ``Content-Type`` is set to
225
+ * ``text/plain``.
226
+ *
227
+ * If %%body%% is a Uint8Array, the intrinsic ``Content-Type`` is set to
228
+ * ``application/octet-stream``.
229
+ *
230
+ * If %%body%% is any other object, the intrinsic ``Content-Type`` is
231
+ * set to ``application/json``.
232
+ */
233
+ get body(): null | Uint8Array {
234
+ if (this.#body == null) { return null; }
235
+ return new Uint8Array(this.#body);
236
+ }
237
+ set body(body: null | string | Readonly<object> | Readonly<Uint8Array>) {
238
+ if (body == null) {
239
+ this.#body = undefined;
240
+ this.#bodyType = undefined;
241
+ } else if (typeof(body) === "string") {
242
+ this.#body = toUtf8Bytes(body);
243
+ this.#bodyType = "text/plain";
244
+ } else if (body instanceof Uint8Array) {
245
+ this.#body = body;
246
+ this.#bodyType = "application/octet-stream";
247
+ } else if (typeof(body) === "object") {
248
+ this.#body = toUtf8Bytes(JSON.stringify(body));
249
+ this.#bodyType = "application/json";
250
+ } else {
251
+ throw new Error("invalid body");
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Returns true if the request has a body.
257
+ */
258
+ hasBody(): this is (FetchRequest & { body: Uint8Array }) {
259
+ return (this.#body != null);
260
+ }
261
+
262
+ /**
263
+ * The HTTP method to use when requesting the URI. If no method
264
+ * has been explicitly set, then ``GET`` is used if the body is
265
+ * null and ``POST`` otherwise.
266
+ */
267
+ get method(): string {
268
+ if (this.#method) { return this.#method; }
269
+ if (this.hasBody()) { return "POST"; }
270
+ return "GET";
271
+ }
272
+ set method(method: null | string) {
273
+ if (method == null) { method = ""; }
274
+ this.#method = String(method).toUpperCase();
275
+ }
276
+
277
+ /**
278
+ * The headers that will be used when requesting the URI. All
279
+ * keys are lower-case.
280
+ *
281
+ * This object is a copy, so any changes will **NOT** be reflected
282
+ * in the ``FetchRequest``.
283
+ *
284
+ * To set a header entry, use the ``setHeader`` method.
285
+ */
286
+ get headers(): Record<string, string> {
287
+ const headers = Object.assign({ }, this.#headers);
288
+
289
+ if (this.#creds) {
290
+ headers["authorization"] = `Basic ${ encodeBase64(toUtf8Bytes(this.#creds)) }`;
291
+ };
292
+
293
+ if (this.allowGzip) {
294
+ headers["accept-encoding"] = "gzip";
295
+ }
296
+
297
+ if (headers["content-type"] == null && this.#bodyType) {
298
+ headers["content-type"] = this.#bodyType;
299
+ }
300
+ if (this.body) { headers["content-length"] = String(this.body.length); }
301
+
302
+ return headers;
303
+ }
304
+
305
+ /**
306
+ * Get the header for %%key%%, ignoring case.
307
+ */
308
+ getHeader(key: string): string {
309
+ return this.headers[key.toLowerCase()];
310
+ }
311
+
312
+ /**
313
+ * Set the header for %%key%% to %%value%%. All values are coerced
314
+ * to a string.
315
+ */
316
+ setHeader(key: string, value: string | number): void {
317
+ this.#headers[String(key).toLowerCase()] = String(value);
318
+ }
319
+
320
+ /**
321
+ * Clear all headers, resetting all intrinsic headers.
322
+ */
323
+ clearHeaders(): void {
324
+ this.#headers = { };
325
+ }
326
+
327
+ [Symbol.iterator](): Iterator<[ key: string, value: string ]> {
328
+ const headers = this.headers;
329
+ const keys = Object.keys(headers);
330
+ let index = 0;
331
+ return {
332
+ next: () => {
333
+ if (index < keys.length) {
334
+ const key = keys[index++];
335
+ return {
336
+ value: [ key, headers[key] ], done: false
337
+ }
338
+ }
339
+ return { value: undefined, done: true };
340
+ }
341
+ };
342
+ }
343
+
344
+ /**
345
+ * The value that will be sent for the ``Authorization`` header.
346
+ *
347
+ * To set the credentials, use the ``setCredentials`` method.
348
+ */
349
+ get credentials(): null | string {
350
+ return this.#creds || null;
351
+ }
352
+
353
+ /**
354
+ * Sets an ``Authorization`` for %%username%% with %%password%%.
355
+ */
356
+ setCredentials(username: string, password: string): void {
357
+ assertArgument(!username.match(/:/), "invalid basic authentication username", "username", "[REDACTED]");
358
+ this.#creds = `${ username }:${ password }`;
359
+ }
360
+
361
+ /**
362
+ * Enable and request gzip-encoded responses. The response will
363
+ * automatically be decompressed. //(default: true)//
364
+ */
365
+ get allowGzip(): boolean {
366
+ return this.#gzip;
367
+ }
368
+ set allowGzip(value: boolean) {
369
+ this.#gzip = !!value;
370
+ }
371
+
372
+ /**
373
+ * Allow ``Authentication`` credentials to be sent over insecure
374
+ * channels. //(default: false)//
375
+ */
376
+ get allowInsecureAuthentication(): boolean {
377
+ return !!this.#allowInsecure;
378
+ }
379
+ set allowInsecureAuthentication(value: boolean) {
380
+ this.#allowInsecure = !!value;
381
+ }
382
+
383
+ /**
384
+ * The timeout (in milliseconds) to wait for a complete response.
385
+ * //(default: 5 minutes)//
386
+ */
387
+ get timeout(): number { return this.#timeout; }
388
+ set timeout(timeout: number) {
389
+ assertArgument(timeout >= 0, "timeout must be non-zero", "timeout", timeout);
390
+ this.#timeout = timeout;
391
+ }
392
+
393
+ /**
394
+ * This function is called prior to each request, for example
395
+ * during a redirection or retry in case of server throttling.
396
+ *
397
+ * This offers an opportunity to populate headers or update
398
+ * content before sending a request.
399
+ */
400
+ get preflightFunc(): null | FetchPreflightFunc {
401
+ return this.#preflight || null;
402
+ }
403
+ set preflightFunc(preflight: null | FetchPreflightFunc) {
404
+ this.#preflight = preflight;
405
+ }
406
+
407
+ /**
408
+ * This function is called after each response, offering an
409
+ * opportunity to provide client-level throttling or updating
410
+ * response data.
411
+ *
412
+ * Any error thrown in this causes the ``send()`` to throw.
413
+ *
414
+ * To schedule a retry attempt (assuming the maximum retry limit
415
+ * has not been reached), use [[response.throwThrottleError]].
416
+ */
417
+ get processFunc(): null | FetchProcessFunc {
418
+ return this.#process || null;
419
+ }
420
+ set processFunc(process: null | FetchProcessFunc) {
421
+ this.#process = process;
422
+ }
423
+
424
+ /**
425
+ * This function is called on each retry attempt.
426
+ */
427
+ get retryFunc(): null | FetchRetryFunc {
428
+ return this.#retry || null;
429
+ }
430
+ set retryFunc(retry: null | FetchRetryFunc) {
431
+ this.#retry = retry;
432
+ }
433
+
434
+ /**
435
+ * This function is called to fetch content from HTTP and
436
+ * HTTPS URLs and is platform specific (e.g. nodejs vs
437
+ * browsers).
438
+ *
439
+ * This is by default the currently registered global getUrl
440
+ * function, which can be changed using [[registerGetUrl]].
441
+ * If this has been set, setting is to ``null`` will cause
442
+ * this FetchRequest (and any future clones) to revert back to
443
+ * using the currently registered global getUrl function.
444
+ *
445
+ * Setting this is generally not necessary, but may be useful
446
+ * for developers that wish to intercept requests or to
447
+ * configurege a proxy or other agent.
448
+ */
449
+ get getUrlFunc(): FetchGetUrlFunc {
450
+ return this.#getUrlFunc || defaultGetUrlFunc;
451
+ }
452
+ set getUrlFunc(value: null | FetchGetUrlFunc) {
453
+ this.#getUrlFunc = value;
454
+ }
455
+
456
+ /**
457
+ * Create a new FetchRequest instance with default values.
458
+ *
459
+ * Once created, each property may be set before issuing a
460
+ * ``.send()`` to make the request.
461
+ */
462
+ constructor(url: string) {
463
+ this.#url = String(url);
464
+
465
+ this.#allowInsecure = false;
466
+ this.#gzip = true;
467
+ this.#headers = { };
468
+ this.#method = "";
469
+ this.#timeout = 300000;
470
+
471
+ this.#throttle = {
472
+ slotInterval: SLOT_INTERVAL,
473
+ maxAttempts: MAX_ATTEMPTS
474
+ };
475
+
476
+ this.#getUrlFunc = null;
477
+ }
478
+
479
+ toString(): string {
480
+ return `<FetchRequest method=${ JSON.stringify(this.method) } url=${ JSON.stringify(this.url) } headers=${ JSON.stringify(this.headers) } body=${ this.#body ? hexlify(this.#body): "null" }>`;
481
+ }
482
+
483
+ /**
484
+ * Update the throttle parameters used to determine maximum
485
+ * attempts and exponential-backoff properties.
486
+ */
487
+ setThrottleParams(params: FetchThrottleParams): void {
488
+ if (params.slotInterval != null) {
489
+ this.#throttle.slotInterval = params.slotInterval;
490
+ }
491
+ if (params.maxAttempts != null) {
492
+ this.#throttle.maxAttempts = params.maxAttempts;
493
+ }
494
+ }
495
+
496
+ async #send(attempt: number, expires: number, delay: number, _request: FetchRequest, _response: FetchResponse): Promise<FetchResponse> {
497
+ if (attempt >= this.#throttle.maxAttempts) {
498
+ return _response.makeServerError("exceeded maximum retry limit");
499
+ }
500
+
501
+ assert(getTime() <= expires, "timeout", "TIMEOUT", {
502
+ operation: "request.send", reason: "timeout", request: _request
503
+ });
504
+
505
+ if (delay > 0) { await wait(delay); }
506
+
507
+ let req = this.clone();
508
+ const scheme = (req.url.split(":")[0] || "").toLowerCase();
509
+
510
+ // Process any Gateways
511
+ if (scheme in Gateways) {
512
+ const result = await Gateways[scheme](req.url, checkSignal(_request.#signal));
513
+ if (result instanceof FetchResponse) {
514
+ let response = result;
515
+
516
+ if (this.processFunc) {
517
+ checkSignal(_request.#signal);
518
+ try {
519
+ response = await this.processFunc(req, response);
520
+ } catch (error: any) {
521
+
522
+ // Something went wrong during processing; throw a 5xx server error
523
+ if (error.throttle == null || typeof(error.stall) !== "number") {
524
+ response.makeServerError("error in post-processing function", error).assertOk();
525
+ }
526
+
527
+ // Ignore throttling
528
+ }
529
+ }
530
+
531
+ return response;
532
+ }
533
+ req = result;
534
+ }
535
+
536
+ // We have a preflight function; update the request
537
+ if (this.preflightFunc) { req = await this.preflightFunc(req); }
538
+
539
+ const resp = await this.getUrlFunc(req, checkSignal(_request.#signal));
540
+ let response = new FetchResponse(resp.statusCode, resp.statusMessage, resp.headers, resp.body, _request);
541
+
542
+ if (response.statusCode === 301 || response.statusCode === 302) {
543
+
544
+ // Redirect
545
+ try {
546
+ const location = response.headers.location || "";
547
+ return req.redirect(location).#send(attempt + 1, expires, 0, _request, response);
548
+ } catch (error) { }
549
+
550
+ // Things won't get any better on another attempt; abort
551
+ return response;
552
+
553
+ } else if (response.statusCode === 429) {
554
+
555
+ // Throttle
556
+ if (this.retryFunc == null || (await this.retryFunc(req, response, attempt))) {
557
+ const retryAfter = response.headers["retry-after"];
558
+ let delay = this.#throttle.slotInterval * Math.trunc(Math.random() * Math.pow(2, attempt));
559
+ if (typeof(retryAfter) === "string" && retryAfter.match(/^[1-9][0-9]*$/)) {
560
+ delay = parseInt(retryAfter);
561
+ }
562
+ return req.clone().#send(attempt + 1, expires, delay, _request, response);
563
+ }
564
+ }
565
+
566
+ if (this.processFunc) {
567
+ checkSignal(_request.#signal);
568
+ try {
569
+ response = await this.processFunc(req, response);
570
+ } catch (error: any) {
571
+
572
+ // Something went wrong during processing; throw a 5xx server error
573
+ if (error.throttle == null || typeof(error.stall) !== "number") {
574
+ response.makeServerError("error in post-processing function", error).assertOk();
575
+ }
576
+
577
+ // Throttle
578
+ let delay = this.#throttle.slotInterval * Math.trunc(Math.random() * Math.pow(2, attempt));;
579
+ if (error.stall >= 0) { delay = error.stall; }
580
+
581
+ return req.clone().#send(attempt + 1, expires, delay, _request, response);
582
+ }
583
+ }
584
+
585
+ return response;
586
+ }
587
+
588
+ /**
589
+ * Resolves to the response by sending the request.
590
+ */
591
+ send(): Promise<FetchResponse> {
592
+ assert(this.#signal == null, "request already sent", "UNSUPPORTED_OPERATION", { operation: "fetchRequest.send" });
593
+ this.#signal = new FetchCancelSignal(this);
594
+ return this.#send(0, getTime() + this.timeout, 0, this, new FetchResponse(0, "", { }, null, this));
595
+ }
596
+
597
+ /**
598
+ * Cancels the inflight response, causing a ``CANCELLED``
599
+ * error to be rejected from the [[send]].
600
+ */
601
+ cancel(): void {
602
+ assert(this.#signal != null, "request has not been sent", "UNSUPPORTED_OPERATION", { operation: "fetchRequest.cancel" });
603
+ const signal = fetchSignals.get(this);
604
+ if (!signal) { throw new Error("missing signal; should not happen"); }
605
+ signal();
606
+ }
607
+
608
+ /**
609
+ * Returns a new [[FetchRequest]] that represents the redirection
610
+ * to %%location%%.
611
+ */
612
+ redirect(location: string): FetchRequest {
613
+ // Redirection; for now we only support absolute locations
614
+ const current = this.url.split(":")[0].toLowerCase();
615
+ const target = location.split(":")[0].toLowerCase();
616
+
617
+ // Don't allow redirecting:
618
+ // - non-GET requests
619
+ // - downgrading the security (e.g. https => http)
620
+ // - to non-HTTP (or non-HTTPS) protocols [this could be relaxed?]
621
+ assert(this.method === "GET" && (current !== "https" || target !== "http") && location.match(/^https?:/), `unsupported redirect`, "UNSUPPORTED_OPERATION", {
622
+ operation: `redirect(${ this.method } ${ JSON.stringify(this.url) } => ${ JSON.stringify(location) })`
623
+ });
624
+
625
+ // Create a copy of this request, with a new URL
626
+ const req = new FetchRequest(location);
627
+ req.method = "GET";
628
+ req.allowGzip = this.allowGzip;
629
+ req.timeout = this.timeout;
630
+ req.#headers = Object.assign({ }, this.#headers);
631
+ if (this.#body) { req.#body = new Uint8Array(this.#body); }
632
+ req.#bodyType = this.#bodyType;
633
+
634
+ // Do not forward credentials unless on the same domain; only absolute
635
+ //req.allowInsecure = false;
636
+ // paths are currently supported; may want a way to specify to forward?
637
+ //setStore(req.#props, "creds", getStore(this.#pros, "creds"));
638
+
639
+ return req;
640
+ }
641
+
642
+ /**
643
+ * Create a new copy of this request.
644
+ */
645
+ clone(): FetchRequest {
646
+ const clone = new FetchRequest(this.url);
647
+
648
+ // Preserve "default method" (i.e. null)
649
+ clone.#method = this.#method;
650
+
651
+ // Preserve "default body" with type, copying the Uint8Array is present
652
+ if (this.#body) { clone.#body = this.#body; }
653
+ clone.#bodyType = this.#bodyType;
654
+
655
+ // Preserve "default headers"
656
+ clone.#headers = Object.assign({ }, this.#headers);
657
+
658
+ // Credentials is readonly, so we copy internally
659
+ clone.#creds = this.#creds;
660
+
661
+ if (this.allowGzip) { clone.allowGzip = true; }
662
+
663
+ clone.timeout = this.timeout;
664
+ if (this.allowInsecureAuthentication) { clone.allowInsecureAuthentication = true; }
665
+
666
+ clone.#preflight = this.#preflight;
667
+ clone.#process = this.#process;
668
+ clone.#retry = this.#retry;
669
+
670
+ clone.#throttle = Object.assign({ }, this.#throttle);
671
+
672
+ clone.#getUrlFunc = this.#getUrlFunc;
673
+
674
+ return clone;
675
+ }
676
+
677
+ /**
678
+ * Locks all static configuration for gateways and FetchGetUrlFunc
679
+ * registration.
680
+ */
681
+ static lockConfig(): void {
682
+ locked = true;
683
+ }
684
+
685
+ /**
686
+ * Get the current Gateway function for %%scheme%%.
687
+ */
688
+ static getGateway(scheme: string): null | FetchGatewayFunc {
689
+ return Gateways[scheme.toLowerCase()] || null;
690
+ }
691
+
692
+ /**
693
+ * Use the %%func%% when fetching URIs using %%scheme%%.
694
+ *
695
+ * This method affects all requests globally.
696
+ *
697
+ * If [[lockConfig]] has been called, no change is made and this
698
+ * throws.
699
+ */
700
+ static registerGateway(scheme: string, func: FetchGatewayFunc): void {
701
+ scheme = scheme.toLowerCase();
702
+ if (scheme === "http" || scheme === "https") {
703
+ throw new Error(`cannot intercept ${ scheme }; use registerGetUrl`);
704
+ }
705
+ if (locked) { throw new Error("gateways locked"); }
706
+ Gateways[scheme] = func;
707
+ }
708
+
709
+ /**
710
+ * Use %%getUrl%% when fetching URIs over HTTP and HTTPS requests.
711
+ *
712
+ * This method affects all requests globally.
713
+ *
714
+ * If [[lockConfig]] has been called, no change is made and this
715
+ * throws.
716
+ */
717
+ static registerGetUrl(getUrl: FetchGetUrlFunc): void {
718
+ if (locked) { throw new Error("gateways locked"); }
719
+ defaultGetUrlFunc = getUrl;
720
+ }
721
+
722
+ /**
723
+ * Creates a getUrl function that fetches content from HTTP and
724
+ * HTTPS URLs.
725
+ *
726
+ * The available %%options%% are dependent on the platform
727
+ * implementation of the default getUrl function.
728
+ *
729
+ * This is not generally something that is needed, but is useful
730
+ * when trying to customize simple behaviour when fetching HTTP
731
+ * content.
732
+ */
733
+ static createGetUrlFunc(options?: Record<string, any>): FetchGetUrlFunc {
734
+ return createGetUrl(options);
735
+ }
736
+
737
+ /**
738
+ * Creates a function that can "fetch" data URIs.
739
+ *
740
+ * Note that this is automatically done internally to support
741
+ * data URIs, so it is not necessary to register it.
742
+ *
743
+ * This is not generally something that is needed, but may
744
+ * be useful in a wrapper to perfom custom data URI functionality.
745
+ */
746
+ static createDataGateway(): FetchGatewayFunc {
747
+ return dataGatewayFunc;
748
+ }
749
+
750
+ /**
751
+ * Creates a function that will fetch IPFS (unvalidated) from
752
+ * a custom gateway baseUrl.
753
+ *
754
+ * The default IPFS gateway used internally is
755
+ * ``"https:/\/gateway.ipfs.io/ipfs/"``.
756
+ */
757
+ static createIpfsGatewayFunc(baseUrl: string): FetchGatewayFunc {
758
+ return getIpfsGatewayFunc(baseUrl);
759
+ }
760
+ }
761
+
762
+
763
+ interface ThrottleError extends Error {
764
+ stall: number;
765
+ throttle: true;
766
+ };
767
+
768
+ /**
769
+ * The response for a FetchRequest.
770
+ */
771
+ export class FetchResponse implements Iterable<[ key: string, value: string ]> {
772
+ #statusCode: number;
773
+ #statusMessage: string;
774
+ #headers: Record<string, string>;
775
+ #body: null | Readonly<Uint8Array>;
776
+ #request: null | FetchRequest;
777
+
778
+ #error: { error?: Error, message: string };
779
+
780
+ toString(): string {
781
+ return `<FetchResponse status=${ this.statusCode } body=${ this.#body ? hexlify(this.#body): "null" }>`;
782
+ }
783
+
784
+ /**
785
+ * The response status code.
786
+ */
787
+ get statusCode(): number { return this.#statusCode; }
788
+
789
+ /**
790
+ * The response status message.
791
+ */
792
+ get statusMessage(): string { return this.#statusMessage; }
793
+
794
+ /**
795
+ * The response headers. All keys are lower-case.
796
+ */
797
+ get headers(): Record<string, string> { return Object.assign({ }, this.#headers); }
798
+
799
+ /**
800
+ * The response body, or ``null`` if there was no body.
801
+ */
802
+ get body(): null | Readonly<Uint8Array> {
803
+ return (this.#body == null) ? null: new Uint8Array(this.#body);
804
+ }
805
+
806
+ /**
807
+ * The response body as a UTF-8 encoded string, or the empty
808
+ * string (i.e. ``""``) if there was no body.
809
+ *
810
+ * An error is thrown if the body is invalid UTF-8 data.
811
+ */
812
+ get bodyText(): string {
813
+ try {
814
+ return (this.#body == null) ? "": toUtf8String(this.#body);
815
+ } catch (error) {
816
+ assert(false, "response body is not valid UTF-8 data", "UNSUPPORTED_OPERATION", {
817
+ operation: "bodyText", info: { response: this }
818
+ });
819
+ }
820
+ }
821
+
822
+ /**
823
+ * The response body, decoded as JSON.
824
+ *
825
+ * An error is thrown if the body is invalid JSON-encoded data
826
+ * or if there was no body.
827
+ */
828
+ get bodyJson(): any {
829
+ try {
830
+ return JSON.parse(this.bodyText);
831
+ } catch (error) {
832
+ assert(false, "response body is not valid JSON", "UNSUPPORTED_OPERATION", {
833
+ operation: "bodyJson", info: { response: this }
834
+ });
835
+ }
836
+ }
837
+
838
+ [Symbol.iterator](): Iterator<[ key: string, value: string ]> {
839
+ const headers = this.headers;
840
+ const keys = Object.keys(headers);
841
+ let index = 0;
842
+ return {
843
+ next: () => {
844
+ if (index < keys.length) {
845
+ const key = keys[index++];
846
+ return {
847
+ value: [ key, headers[key] ], done: false
848
+ }
849
+ }
850
+ return { value: undefined, done: true };
851
+ }
852
+ };
853
+ }
854
+
855
+ constructor(statusCode: number, statusMessage: string, headers: Readonly<Record<string, string>>, body: null | Uint8Array, request?: FetchRequest) {
856
+ this.#statusCode = statusCode;
857
+ this.#statusMessage = statusMessage;
858
+ this.#headers = Object.keys(headers).reduce((accum, k) => {
859
+ accum[k.toLowerCase()] = String(headers[k]);
860
+ return accum;
861
+ }, <Record<string, string>>{ });
862
+ this.#body = ((body == null) ? null: new Uint8Array(body));
863
+ this.#request = (request || null);
864
+
865
+ this.#error = { message: "" };
866
+ }
867
+
868
+ /**
869
+ * Return a Response with matching headers and body, but with
870
+ * an error status code (i.e. 599) and %%message%% with an
871
+ * optional %%error%%.
872
+ */
873
+ makeServerError(message?: string, error?: Error): FetchResponse {
874
+ let statusMessage: string;
875
+ if (!message) {
876
+ message = `${ this.statusCode } ${ this.statusMessage }`;
877
+ statusMessage = `CLIENT ESCALATED SERVER ERROR (${ message })`;
878
+ } else {
879
+ statusMessage = `CLIENT ESCALATED SERVER ERROR (${ this.statusCode } ${ this.statusMessage }; ${ message })`;
880
+ }
881
+ const response = new FetchResponse(599, statusMessage, this.headers,
882
+ this.body, this.#request || undefined);
883
+ response.#error = { message, error };
884
+ return response;
885
+ }
886
+
887
+ /**
888
+ * If called within a [request.processFunc](FetchRequest-processFunc)
889
+ * call, causes the request to retry as if throttled for %%stall%%
890
+ * milliseconds.
891
+ */
892
+ throwThrottleError(message?: string, stall?: number): never {
893
+ if (stall == null) {
894
+ stall = -1;
895
+ } else {
896
+ assertArgument(Number.isInteger(stall) && stall >= 0, "invalid stall timeout", "stall", stall);
897
+ }
898
+
899
+ const error = new Error(message || "throttling requests");
900
+
901
+ defineProperties(<ThrottleError>error, { stall, throttle: true });
902
+
903
+ throw error;
904
+ }
905
+
906
+ /**
907
+ * Get the header value for %%key%%, ignoring case.
908
+ */
909
+ getHeader(key: string): string {
910
+ return this.headers[key.toLowerCase()];
911
+ }
912
+
913
+ /**
914
+ * Returns true if the response has a body.
915
+ */
916
+ hasBody(): this is (FetchResponse & { body: Uint8Array }) {
917
+ return (this.#body != null);
918
+ }
919
+
920
+ /**
921
+ * The request made for this response.
922
+ */
923
+ get request(): null | FetchRequest { return this.#request; }
924
+
925
+ /**
926
+ * Returns true if this response was a success statusCode.
927
+ */
928
+ ok(): boolean {
929
+ return (this.#error.message === "" && this.statusCode >= 200 && this.statusCode < 300);
930
+ }
931
+
932
+ /**
933
+ * Throws a ``SERVER_ERROR`` if this response is not ok.
934
+ */
935
+ assertOk(): void {
936
+ if (this.ok()) { return; }
937
+ let { message, error } = this.#error;
938
+ if (message === "") {
939
+ message = `server response ${ this.statusCode } ${ this.statusMessage }`;
940
+ }
941
+
942
+ let requestUrl: null | string = null;
943
+ if (this.request) { requestUrl = this.request.url; }
944
+
945
+ let responseBody: null | string = null;
946
+ try {
947
+ if (this.#body) { responseBody = toUtf8String(this.#body); }
948
+ } catch (e) { }
949
+
950
+ assert(false, message, "SERVER_ERROR", {
951
+ request: (this.request || "unknown request"), response: this, error,
952
+ info: {
953
+ requestUrl, responseBody,
954
+ responseStatus: `${ this.statusCode } ${ this.statusMessage }` }
955
+ });
956
+ }
957
+ }
958
+
959
+
960
+ function getTime(): number { return (new Date()).getTime(); }
961
+
962
+ function unpercent(value: string): Uint8Array {
963
+ return toUtf8Bytes(value.replace(/%([0-9a-f][0-9a-f])/gi, (all, code) => {
964
+ return String.fromCharCode(parseInt(code, 16));
965
+ }));
966
+ }
967
+
968
+ function wait(delay: number): Promise<void> {
969
+ return new Promise((resolve) => setTimeout(resolve, delay));
970
+ }