@backblaze-labs/b2-sdk 0.1.0 → 0.2.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 (701) hide show
  1. package/CHANGELOG.md +110 -5
  2. package/README.md +144 -33
  3. package/dist/_virtual/_b2-sdk-version-json.cjs +4 -0
  4. package/dist/_virtual/_b2-sdk-version-json.js +4 -0
  5. package/dist/auth/account-info.d.cts +32 -0
  6. package/dist/auth/account-info.d.cts.map +1 -1
  7. package/dist/auth/account-info.d.ts +32 -0
  8. package/dist/auth/account-info.d.ts.map +1 -1
  9. package/dist/auth/file.cjs +408 -207
  10. package/dist/auth/file.cjs.map +1 -1
  11. package/dist/auth/file.d.cts +77 -3
  12. package/dist/auth/file.d.cts.map +1 -1
  13. package/dist/auth/file.d.ts +77 -3
  14. package/dist/auth/file.d.ts.map +1 -1
  15. package/dist/auth/file.js +407 -207
  16. package/dist/auth/file.js.map +1 -1
  17. package/dist/auth/in-memory.cjs +212 -183
  18. package/dist/auth/in-memory.cjs.map +1 -1
  19. package/dist/auth/in-memory.d.cts +8 -0
  20. package/dist/auth/in-memory.d.cts.map +1 -1
  21. package/dist/auth/in-memory.d.ts +8 -0
  22. package/dist/auth/in-memory.d.ts.map +1 -1
  23. package/dist/auth/in-memory.js +211 -182
  24. package/dist/auth/in-memory.js.map +1 -1
  25. package/dist/auth/index.cjs +7 -9
  26. package/dist/auth/index.d.cts +3 -2
  27. package/dist/auth/index.d.cts.map +1 -1
  28. package/dist/auth/index.d.ts +3 -2
  29. package/dist/auth/index.d.ts.map +1 -1
  30. package/dist/auth/index.js +2 -8
  31. package/dist/auth/realms.cjs +80 -7
  32. package/dist/auth/realms.cjs.map +1 -1
  33. package/dist/auth/realms.d.cts +27 -4
  34. package/dist/auth/realms.d.cts.map +1 -1
  35. package/dist/auth/realms.d.ts +27 -4
  36. package/dist/auth/realms.d.ts.map +1 -1
  37. package/dist/auth/realms.js +80 -9
  38. package/dist/auth/realms.js.map +1 -1
  39. package/dist/auth/upload-url-pool.cjs +55 -51
  40. package/dist/auth/upload-url-pool.cjs.map +1 -1
  41. package/dist/auth/upload-url-pool.js +55 -51
  42. package/dist/auth/upload-url-pool.js.map +1 -1
  43. package/dist/bucket.cjs +785 -787
  44. package/dist/bucket.cjs.map +1 -1
  45. package/dist/bucket.d.cts +35 -47
  46. package/dist/bucket.d.cts.map +1 -1
  47. package/dist/bucket.d.ts +35 -47
  48. package/dist/bucket.d.ts.map +1 -1
  49. package/dist/bucket.js +780 -782
  50. package/dist/bucket.js.map +1 -1
  51. package/dist/client.cjs +246 -228
  52. package/dist/client.cjs.map +1 -1
  53. package/dist/client.d.cts +25 -17
  54. package/dist/client.d.cts.map +1 -1
  55. package/dist/client.d.ts +25 -17
  56. package/dist/client.d.ts.map +1 -1
  57. package/dist/client.js +243 -225
  58. package/dist/client.js.map +1 -1
  59. package/dist/copy/large.cjs +114 -71
  60. package/dist/copy/large.cjs.map +1 -1
  61. package/dist/copy/large.d.cts +6 -0
  62. package/dist/copy/large.d.cts.map +1 -1
  63. package/dist/copy/large.d.ts +6 -0
  64. package/dist/copy/large.d.ts.map +1 -1
  65. package/dist/copy/large.js +112 -69
  66. package/dist/copy/large.js.map +1 -1
  67. package/dist/download/checksum.cjs +89 -0
  68. package/dist/download/checksum.cjs.map +1 -0
  69. package/dist/download/checksum.d.cts +40 -0
  70. package/dist/download/checksum.d.cts.map +1 -0
  71. package/dist/download/checksum.d.ts +40 -0
  72. package/dist/download/checksum.d.ts.map +1 -0
  73. package/dist/download/checksum.js +87 -0
  74. package/dist/download/checksum.js.map +1 -0
  75. package/dist/download/parallel.cjs +214 -118
  76. package/dist/download/parallel.cjs.map +1 -1
  77. package/dist/download/parallel.d.cts +5 -1
  78. package/dist/download/parallel.d.cts.map +1 -1
  79. package/dist/download/parallel.d.ts +5 -1
  80. package/dist/download/parallel.d.ts.map +1 -1
  81. package/dist/download/parallel.js +212 -116
  82. package/dist/download/parallel.js.map +1 -1
  83. package/dist/download/single.cjs +188 -97
  84. package/dist/download/single.cjs.map +1 -1
  85. package/dist/download/single.d.cts +6 -1
  86. package/dist/download/single.d.cts.map +1 -1
  87. package/dist/download/single.d.ts +6 -1
  88. package/dist/download/single.d.ts.map +1 -1
  89. package/dist/download/single.js +186 -98
  90. package/dist/download/single.js.map +1 -1
  91. package/dist/errors/index.cjs +699 -243
  92. package/dist/errors/index.cjs.map +1 -1
  93. package/dist/errors/index.d.cts +285 -82
  94. package/dist/errors/index.d.cts.map +1 -1
  95. package/dist/errors/index.d.ts +285 -82
  96. package/dist/errors/index.d.ts.map +1 -1
  97. package/dist/errors/index.js +678 -261
  98. package/dist/errors/index.js.map +1 -1
  99. package/dist/http/retry.cjs +45 -28
  100. package/dist/http/retry.cjs.map +1 -1
  101. package/dist/http/retry.d.cts +15 -2
  102. package/dist/http/retry.d.cts.map +1 -1
  103. package/dist/http/retry.d.ts +15 -2
  104. package/dist/http/retry.d.ts.map +1 -1
  105. package/dist/http/retry.js +46 -31
  106. package/dist/http/retry.js.map +1 -1
  107. package/dist/http/transport.cjs +438 -140
  108. package/dist/http/transport.cjs.map +1 -1
  109. package/dist/http/transport.d.cts +21 -2
  110. package/dist/http/transport.d.cts.map +1 -1
  111. package/dist/http/transport.d.ts +21 -2
  112. package/dist/http/transport.d.ts.map +1 -1
  113. package/dist/http/transport.js +437 -140
  114. package/dist/http/transport.js.map +1 -1
  115. package/dist/http/url-guard.cjs +154 -89
  116. package/dist/http/url-guard.cjs.map +1 -1
  117. package/dist/http/url-guard.d.cts +25 -3
  118. package/dist/http/url-guard.d.cts.map +1 -1
  119. package/dist/http/url-guard.d.ts +25 -3
  120. package/dist/http/url-guard.d.ts.map +1 -1
  121. package/dist/http/url-guard.js +152 -90
  122. package/dist/http/url-guard.js.map +1 -1
  123. package/dist/http/user-agent.cjs +84 -44
  124. package/dist/http/user-agent.cjs.map +1 -1
  125. package/dist/http/user-agent.js +84 -46
  126. package/dist/http/user-agent.js.map +1 -1
  127. package/dist/index.cjs +101 -77
  128. package/dist/index.d.cts +27 -21
  129. package/dist/index.d.cts.map +1 -1
  130. package/dist/index.d.ts +27 -21
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +15 -71
  133. package/dist/internal/b2-naming.cjs +66 -0
  134. package/dist/internal/b2-naming.cjs.map +1 -0
  135. package/dist/internal/b2-naming.d.cts +43 -0
  136. package/dist/internal/b2-naming.d.cts.map +1 -0
  137. package/dist/internal/b2-naming.d.ts +43 -0
  138. package/dist/internal/b2-naming.d.ts.map +1 -0
  139. package/dist/internal/b2-naming.js +60 -0
  140. package/dist/internal/b2-naming.js.map +1 -0
  141. package/dist/internal/upload-retry-options.cjs +21 -0
  142. package/dist/internal/upload-retry-options.cjs.map +1 -0
  143. package/dist/internal/upload-retry-options.d.cts +12 -0
  144. package/dist/internal/upload-retry-options.d.cts.map +1 -0
  145. package/dist/internal/upload-retry-options.d.ts +12 -0
  146. package/dist/internal/upload-retry-options.d.ts.map +1 -0
  147. package/dist/internal/upload-retry-options.js +21 -0
  148. package/dist/internal/upload-retry-options.js.map +1 -0
  149. package/dist/internal/url-redaction.cjs +31 -0
  150. package/dist/internal/url-redaction.cjs.map +1 -0
  151. package/dist/internal/url-redaction.d.cts +17 -0
  152. package/dist/internal/url-redaction.d.cts.map +1 -0
  153. package/dist/internal/url-redaction.d.ts +17 -0
  154. package/dist/internal/url-redaction.d.ts.map +1 -0
  155. package/dist/internal/url-redaction.js +31 -0
  156. package/dist/internal/url-redaction.js.map +1 -0
  157. package/dist/notifications/index.cjs +4 -6
  158. package/dist/notifications/index.d.cts +1 -1
  159. package/dist/notifications/index.d.cts.map +1 -1
  160. package/dist/notifications/index.d.ts +1 -1
  161. package/dist/notifications/index.d.ts.map +1 -1
  162. package/dist/notifications/index.js +1 -6
  163. package/dist/notifications/webhook.cjs +195 -85
  164. package/dist/notifications/webhook.cjs.map +1 -1
  165. package/dist/notifications/webhook.d.cts.map +1 -1
  166. package/dist/notifications/webhook.d.ts.map +1 -1
  167. package/dist/notifications/webhook.js +195 -87
  168. package/dist/notifications/webhook.js.map +1 -1
  169. package/dist/object.cjs +253 -210
  170. package/dist/object.cjs.map +1 -1
  171. package/dist/object.d.cts +9 -55
  172. package/dist/object.d.cts.map +1 -1
  173. package/dist/object.d.ts +9 -55
  174. package/dist/object.d.ts.map +1 -1
  175. package/dist/object.js +249 -206
  176. package/dist/object.js.map +1 -1
  177. package/dist/raw/encoding.cjs +74 -35
  178. package/dist/raw/encoding.cjs.map +1 -1
  179. package/dist/raw/encoding.js +74 -38
  180. package/dist/raw/encoding.js.map +1 -1
  181. package/dist/raw/index.cjs +678 -587
  182. package/dist/raw/index.cjs.map +1 -1
  183. package/dist/raw/index.d.cts +121 -18
  184. package/dist/raw/index.d.cts.map +1 -1
  185. package/dist/raw/index.d.ts +121 -18
  186. package/dist/raw/index.d.ts.map +1 -1
  187. package/dist/raw/index.js +674 -589
  188. package/dist/raw/index.js.map +1 -1
  189. package/dist/s3/index.cjs +355 -18
  190. package/dist/s3/index.cjs.map +1 -1
  191. package/dist/s3/index.d.cts +233 -5
  192. package/dist/s3/index.d.cts.map +1 -1
  193. package/dist/s3/index.d.ts +233 -5
  194. package/dist/s3/index.d.ts.map +1 -1
  195. package/dist/s3/index.js +351 -22
  196. package/dist/s3/index.js.map +1 -1
  197. package/dist/s3/sigv4.cjs +149 -0
  198. package/dist/s3/sigv4.cjs.map +1 -0
  199. package/dist/s3/sigv4.d.cts +43 -0
  200. package/dist/s3/sigv4.d.cts.map +1 -0
  201. package/dist/s3/sigv4.d.ts +43 -0
  202. package/dist/s3/sigv4.d.ts.map +1 -0
  203. package/dist/s3/sigv4.js +148 -0
  204. package/dist/s3/sigv4.js.map +1 -0
  205. package/dist/s3/validation.cjs +69 -0
  206. package/dist/s3/validation.cjs.map +1 -0
  207. package/dist/s3/validation.d.cts +43 -0
  208. package/dist/s3/validation.d.cts.map +1 -0
  209. package/dist/s3/validation.d.ts +43 -0
  210. package/dist/s3/validation.d.ts.map +1 -0
  211. package/dist/s3/validation.js +66 -0
  212. package/dist/s3/validation.js.map +1 -0
  213. package/dist/simulator/capabilities.cjs +63 -48
  214. package/dist/simulator/capabilities.cjs.map +1 -1
  215. package/dist/simulator/capabilities.js +63 -49
  216. package/dist/simulator/capabilities.js.map +1 -1
  217. package/dist/simulator/index.cjs +2124 -1548
  218. package/dist/simulator/index.cjs.map +1 -1
  219. package/dist/simulator/index.d.cts +79 -11
  220. package/dist/simulator/index.d.cts.map +1 -1
  221. package/dist/simulator/index.d.ts +79 -11
  222. package/dist/simulator/index.d.ts.map +1 -1
  223. package/dist/simulator/index.js +2112 -1546
  224. package/dist/simulator/index.js.map +1 -1
  225. package/dist/simulator/validation.cjs +191 -153
  226. package/dist/simulator/validation.cjs.map +1 -1
  227. package/dist/simulator/validation.d.cts +3 -21
  228. package/dist/simulator/validation.d.cts.map +1 -1
  229. package/dist/simulator/validation.d.ts +3 -21
  230. package/dist/simulator/validation.d.ts.map +1 -1
  231. package/dist/simulator/validation.js +188 -162
  232. package/dist/simulator/validation.js.map +1 -1
  233. package/dist/streams/collect.cjs +74 -24
  234. package/dist/streams/collect.cjs.map +1 -1
  235. package/dist/streams/collect.d.cts +14 -1
  236. package/dist/streams/collect.d.cts.map +1 -1
  237. package/dist/streams/collect.d.ts +14 -1
  238. package/dist/streams/collect.d.ts.map +1 -1
  239. package/dist/streams/collect.js +74 -25
  240. package/dist/streams/collect.js.map +1 -1
  241. package/dist/streams/file-source.cjs +287 -0
  242. package/dist/streams/file-source.cjs.map +1 -0
  243. package/dist/streams/file-source.d.cts +96 -0
  244. package/dist/streams/file-source.d.cts.map +1 -0
  245. package/dist/streams/file-source.d.ts +96 -0
  246. package/dist/streams/file-source.d.ts.map +1 -0
  247. package/dist/streams/file-source.js +286 -0
  248. package/dist/streams/file-source.js.map +1 -0
  249. package/dist/streams/hash.cjs +207 -115
  250. package/dist/streams/hash.cjs.map +1 -1
  251. package/dist/streams/hash.d.cts +5 -4
  252. package/dist/streams/hash.d.cts.map +1 -1
  253. package/dist/streams/hash.d.ts +5 -4
  254. package/dist/streams/hash.d.ts.map +1 -1
  255. package/dist/streams/hash.js +208 -95
  256. package/dist/streams/hash.js.map +1 -1
  257. package/dist/streams/index.cjs +12 -12
  258. package/dist/streams/index.d.cts +3 -3
  259. package/dist/streams/index.d.cts.map +1 -1
  260. package/dist/streams/index.d.ts +3 -3
  261. package/dist/streams/index.d.ts.map +1 -1
  262. package/dist/streams/index.js +2 -10
  263. package/dist/streams/progress.cjs +60 -46
  264. package/dist/streams/progress.cjs.map +1 -1
  265. package/dist/streams/progress.js +60 -46
  266. package/dist/streams/progress.js.map +1 -1
  267. package/dist/streams/source.cjs +316 -146
  268. package/dist/streams/source.cjs.map +1 -1
  269. package/dist/streams/source.d.cts +56 -18
  270. package/dist/streams/source.d.cts.map +1 -1
  271. package/dist/streams/source.d.ts +56 -18
  272. package/dist/streams/source.d.ts.map +1 -1
  273. package/dist/streams/source.js +312 -149
  274. package/dist/streams/source.js.map +1 -1
  275. package/dist/sync/actions/index.cjs +236 -184
  276. package/dist/sync/actions/index.cjs.map +1 -1
  277. package/dist/sync/actions/index.d.cts +29 -21
  278. package/dist/sync/actions/index.d.cts.map +1 -1
  279. package/dist/sync/actions/index.d.ts +29 -21
  280. package/dist/sync/actions/index.d.ts.map +1 -1
  281. package/dist/sync/actions/index.js +236 -190
  282. package/dist/sync/actions/index.js.map +1 -1
  283. package/dist/sync/b2-sha1-reader.cjs +148 -0
  284. package/dist/sync/b2-sha1-reader.cjs.map +1 -0
  285. package/dist/sync/b2-sha1-reader.d.cts +58 -0
  286. package/dist/sync/b2-sha1-reader.d.cts.map +1 -0
  287. package/dist/sync/b2-sha1-reader.d.ts +58 -0
  288. package/dist/sync/b2-sha1-reader.d.ts.map +1 -0
  289. package/dist/sync/b2-sha1-reader.js +145 -0
  290. package/dist/sync/b2-sha1-reader.js.map +1 -0
  291. package/dist/sync/download-staging.cjs +265 -0
  292. package/dist/sync/download-staging.cjs.map +1 -0
  293. package/dist/sync/download-staging.d.cts +54 -0
  294. package/dist/sync/download-staging.d.cts.map +1 -0
  295. package/dist/sync/download-staging.d.ts +54 -0
  296. package/dist/sync/download-staging.d.ts.map +1 -0
  297. package/dist/sync/download-staging.js +259 -0
  298. package/dist/sync/download-staging.js.map +1 -0
  299. package/dist/sync/filesystem-errors.cjs +32 -0
  300. package/dist/sync/filesystem-errors.cjs.map +1 -0
  301. package/dist/sync/filesystem-errors.d.cts +8 -0
  302. package/dist/sync/filesystem-errors.d.cts.map +1 -0
  303. package/dist/sync/filesystem-errors.d.ts +8 -0
  304. package/dist/sync/filesystem-errors.d.ts.map +1 -0
  305. package/dist/sync/filesystem-errors.js +32 -0
  306. package/dist/sync/filesystem-errors.js.map +1 -0
  307. package/dist/sync/filters.cjs +217 -0
  308. package/dist/sync/filters.cjs.map +1 -0
  309. package/dist/sync/filters.d.cts +51 -0
  310. package/dist/sync/filters.d.cts.map +1 -0
  311. package/dist/sync/filters.d.ts +51 -0
  312. package/dist/sync/filters.d.ts.map +1 -0
  313. package/dist/sync/filters.js +213 -0
  314. package/dist/sync/filters.js.map +1 -0
  315. package/dist/sync/index.cjs +39 -22
  316. package/dist/sync/index.d.cts +14 -9
  317. package/dist/sync/index.d.cts.map +1 -1
  318. package/dist/sync/index.d.ts +14 -9
  319. package/dist/sync/index.d.ts.map +1 -1
  320. package/dist/sync/index.js +9 -20
  321. package/dist/sync/local-file-identity.cjs +61 -0
  322. package/dist/sync/local-file-identity.cjs.map +1 -0
  323. package/dist/sync/local-file-identity.d.cts +37 -0
  324. package/dist/sync/local-file-identity.d.cts.map +1 -0
  325. package/dist/sync/local-file-identity.d.ts +37 -0
  326. package/dist/sync/local-file-identity.d.ts.map +1 -0
  327. package/dist/sync/local-file-identity.js +60 -0
  328. package/dist/sync/local-file-identity.js.map +1 -0
  329. package/dist/sync/local-file-io.cjs +364 -0
  330. package/dist/sync/local-file-io.cjs.map +1 -0
  331. package/dist/sync/local-file-io.d.cts +75 -0
  332. package/dist/sync/local-file-io.d.cts.map +1 -0
  333. package/dist/sync/local-file-io.d.ts +75 -0
  334. package/dist/sync/local-file-io.d.ts.map +1 -0
  335. package/dist/sync/local-file-io.js +360 -0
  336. package/dist/sync/local-file-io.js.map +1 -0
  337. package/dist/sync/local-filesystem-root.cjs +27 -0
  338. package/dist/sync/local-filesystem-root.cjs.map +1 -0
  339. package/dist/sync/local-filesystem-root.d.cts +18 -0
  340. package/dist/sync/local-filesystem-root.d.cts.map +1 -0
  341. package/dist/sync/local-filesystem-root.d.ts +18 -0
  342. package/dist/sync/local-filesystem-root.d.ts.map +1 -0
  343. package/dist/sync/local-filesystem-root.js +26 -0
  344. package/dist/sync/local-filesystem-root.js.map +1 -0
  345. package/dist/sync/local-sha1.cjs +119 -0
  346. package/dist/sync/local-sha1.cjs.map +1 -0
  347. package/dist/sync/local-sha1.d.cts +39 -0
  348. package/dist/sync/local-sha1.d.cts.map +1 -0
  349. package/dist/sync/local-sha1.d.ts +39 -0
  350. package/dist/sync/local-sha1.d.ts.map +1 -0
  351. package/dist/sync/local-sha1.js +117 -0
  352. package/dist/sync/local-sha1.js.map +1 -0
  353. package/dist/sync/pairing.cjs +118 -29
  354. package/dist/sync/pairing.cjs.map +1 -1
  355. package/dist/sync/pairing.d.cts +7 -2
  356. package/dist/sync/pairing.d.cts.map +1 -1
  357. package/dist/sync/pairing.d.ts +7 -2
  358. package/dist/sync/pairing.d.ts.map +1 -1
  359. package/dist/sync/pairing.js +120 -31
  360. package/dist/sync/pairing.js.map +1 -1
  361. package/dist/sync/path-order.cjs +30 -0
  362. package/dist/sync/path-order.cjs.map +1 -0
  363. package/dist/sync/path-order.d.cts +28 -0
  364. package/dist/sync/path-order.d.cts.map +1 -0
  365. package/dist/sync/path-order.d.ts +28 -0
  366. package/dist/sync/path-order.d.ts.map +1 -0
  367. package/dist/sync/path-order.js +29 -0
  368. package/dist/sync/path-order.js.map +1 -0
  369. package/dist/sync/path-safety.cjs +111 -0
  370. package/dist/sync/path-safety.cjs.map +1 -0
  371. package/dist/sync/path-safety.d.cts +78 -0
  372. package/dist/sync/path-safety.d.cts.map +1 -0
  373. package/dist/sync/path-safety.d.ts +78 -0
  374. package/dist/sync/path-safety.d.ts.map +1 -0
  375. package/dist/sync/path-safety.js +105 -0
  376. package/dist/sync/path-safety.js.map +1 -0
  377. package/dist/sync/paths.d.cts +19 -0
  378. package/dist/sync/paths.d.cts.map +1 -0
  379. package/dist/sync/paths.d.ts +19 -0
  380. package/dist/sync/paths.d.ts.map +1 -0
  381. package/dist/sync/policies/compare.cjs +389 -11
  382. package/dist/sync/policies/compare.cjs.map +1 -1
  383. package/dist/sync/policies/compare.d.cts +100 -2
  384. package/dist/sync/policies/compare.d.cts.map +1 -1
  385. package/dist/sync/policies/compare.d.ts +100 -2
  386. package/dist/sync/policies/compare.d.ts.map +1 -1
  387. package/dist/sync/policies/compare.js +387 -13
  388. package/dist/sync/policies/compare.js.map +1 -1
  389. package/dist/sync/policies/index.cjs +76 -65
  390. package/dist/sync/policies/index.cjs.map +1 -1
  391. package/dist/sync/policies/index.d.cts +35 -6
  392. package/dist/sync/policies/index.d.cts.map +1 -1
  393. package/dist/sync/policies/index.d.ts +35 -6
  394. package/dist/sync/policies/index.d.ts.map +1 -1
  395. package/dist/sync/policies/index.js +76 -65
  396. package/dist/sync/policies/index.js.map +1 -1
  397. package/dist/sync/prefix.cjs +96 -0
  398. package/dist/sync/prefix.cjs.map +1 -0
  399. package/dist/sync/prefix.d.cts +55 -0
  400. package/dist/sync/prefix.d.cts.map +1 -0
  401. package/dist/sync/prefix.d.ts +55 -0
  402. package/dist/sync/prefix.d.ts.map +1 -0
  403. package/dist/sync/prefix.js +92 -0
  404. package/dist/sync/prefix.js.map +1 -0
  405. package/dist/sync/regexp-safety.cjs +192 -0
  406. package/dist/sync/regexp-safety.cjs.map +1 -0
  407. package/dist/sync/regexp-safety.d.cts +29 -0
  408. package/dist/sync/regexp-safety.d.cts.map +1 -0
  409. package/dist/sync/regexp-safety.d.ts +29 -0
  410. package/dist/sync/regexp-safety.d.ts.map +1 -0
  411. package/dist/sync/regexp-safety.js +190 -0
  412. package/dist/sync/regexp-safety.js.map +1 -0
  413. package/dist/sync/scan-events.cjs +33 -0
  414. package/dist/sync/scan-events.cjs.map +1 -0
  415. package/dist/sync/scan-events.d.cts +17 -0
  416. package/dist/sync/scan-events.d.cts.map +1 -0
  417. package/dist/sync/scan-events.d.ts +17 -0
  418. package/dist/sync/scan-events.d.ts.map +1 -0
  419. package/dist/sync/scan-events.js +32 -0
  420. package/dist/sync/scan-events.js.map +1 -0
  421. package/dist/sync/scan-limit.cjs +33 -0
  422. package/dist/sync/scan-limit.cjs.map +1 -0
  423. package/dist/sync/scan-limit.d.cts +21 -0
  424. package/dist/sync/scan-limit.d.cts.map +1 -0
  425. package/dist/sync/scan-limit.d.ts +21 -0
  426. package/dist/sync/scan-limit.d.ts.map +1 -0
  427. package/dist/sync/scan-limit.js +31 -0
  428. package/dist/sync/scan-limit.js.map +1 -0
  429. package/dist/sync/scanners/b2.cjs +239 -55
  430. package/dist/sync/scanners/b2.cjs.map +1 -1
  431. package/dist/sync/scanners/b2.d.cts +26 -7
  432. package/dist/sync/scanners/b2.d.cts.map +1 -1
  433. package/dist/sync/scanners/b2.d.ts +26 -7
  434. package/dist/sync/scanners/b2.d.ts.map +1 -1
  435. package/dist/sync/scanners/b2.js +239 -55
  436. package/dist/sync/scanners/b2.js.map +1 -1
  437. package/dist/sync/scanners/local.cjs +215 -56
  438. package/dist/sync/scanners/local.cjs.map +1 -1
  439. package/dist/sync/scanners/local.d.cts +22 -7
  440. package/dist/sync/scanners/local.d.cts.map +1 -1
  441. package/dist/sync/scanners/local.d.ts +22 -7
  442. package/dist/sync/scanners/local.d.ts.map +1 -1
  443. package/dist/sync/scanners/local.js +216 -57
  444. package/dist/sync/scanners/local.js.map +1 -1
  445. package/dist/sync/sha1-metadata.cjs +94 -0
  446. package/dist/sync/sha1-metadata.cjs.map +1 -0
  447. package/dist/sync/sha1-metadata.d.cts +82 -0
  448. package/dist/sync/sha1-metadata.d.cts.map +1 -0
  449. package/dist/sync/sha1-metadata.d.ts +82 -0
  450. package/dist/sync/sha1-metadata.d.ts.map +1 -0
  451. package/dist/sync/sha1-metadata.js +89 -0
  452. package/dist/sync/sha1-metadata.js.map +1 -0
  453. package/dist/sync/sha1-options.cjs +23 -0
  454. package/dist/sync/sha1-options.cjs.map +1 -0
  455. package/dist/sync/sha1-options.d.cts +14 -0
  456. package/dist/sync/sha1-options.d.cts.map +1 -0
  457. package/dist/sync/sha1-options.d.ts +14 -0
  458. package/dist/sync/sha1-options.d.ts.map +1 -0
  459. package/dist/sync/sha1-options.js +21 -0
  460. package/dist/sync/sha1-options.js.map +1 -0
  461. package/dist/sync/synchronizer.cjs +782 -197
  462. package/dist/sync/synchronizer.cjs.map +1 -1
  463. package/dist/sync/synchronizer.d.cts +47 -1
  464. package/dist/sync/synchronizer.d.cts.map +1 -1
  465. package/dist/sync/synchronizer.d.ts +47 -1
  466. package/dist/sync/synchronizer.d.ts.map +1 -1
  467. package/dist/sync/synchronizer.js +779 -173
  468. package/dist/sync/synchronizer.js.map +1 -1
  469. package/dist/sync/types.d.cts +208 -16
  470. package/dist/sync/types.d.cts.map +1 -1
  471. package/dist/sync/types.d.ts +208 -16
  472. package/dist/sync/types.d.ts.map +1 -1
  473. package/dist/types/auth.cjs +76 -56
  474. package/dist/types/auth.cjs.map +1 -1
  475. package/dist/types/auth.d.cts +67 -9
  476. package/dist/types/auth.d.cts.map +1 -1
  477. package/dist/types/auth.d.ts +67 -9
  478. package/dist/types/auth.d.ts.map +1 -1
  479. package/dist/types/auth.js +77 -57
  480. package/dist/types/auth.js.map +1 -1
  481. package/dist/types/bucket.cjs +74 -38
  482. package/dist/types/bucket.cjs.map +1 -1
  483. package/dist/types/bucket.js +75 -41
  484. package/dist/types/bucket.js.map +1 -1
  485. package/dist/types/encryption.cjs +364 -327
  486. package/dist/types/encryption.cjs.map +1 -1
  487. package/dist/types/encryption.d.cts +16 -0
  488. package/dist/types/encryption.d.cts.map +1 -1
  489. package/dist/types/encryption.d.ts +16 -0
  490. package/dist/types/encryption.d.ts.map +1 -1
  491. package/dist/types/encryption.js +364 -310
  492. package/dist/types/encryption.js.map +1 -1
  493. package/dist/types/errors.cjs +47 -0
  494. package/dist/types/errors.cjs.map +1 -0
  495. package/dist/types/errors.d.cts +13 -4
  496. package/dist/types/errors.d.cts.map +1 -1
  497. package/dist/types/errors.d.ts +13 -4
  498. package/dist/types/errors.d.ts.map +1 -1
  499. package/dist/types/errors.js +47 -0
  500. package/dist/types/errors.js.map +1 -0
  501. package/dist/types/file.cjs +36 -19
  502. package/dist/types/file.cjs.map +1 -1
  503. package/dist/types/file.d.cts +2 -2
  504. package/dist/types/file.d.cts.map +1 -1
  505. package/dist/types/file.d.ts +2 -2
  506. package/dist/types/file.d.ts.map +1 -1
  507. package/dist/types/file.js +37 -21
  508. package/dist/types/file.js.map +1 -1
  509. package/dist/types/ids.cjs +53 -9
  510. package/dist/types/ids.cjs.map +1 -1
  511. package/dist/types/ids.js +54 -15
  512. package/dist/types/ids.js.map +1 -1
  513. package/dist/types/index.d.cts +12 -11
  514. package/dist/types/index.d.cts.map +1 -1
  515. package/dist/types/index.d.ts +12 -11
  516. package/dist/types/index.d.ts.map +1 -1
  517. package/dist/types/key.d.cts +44 -8
  518. package/dist/types/key.d.cts.map +1 -1
  519. package/dist/types/key.d.ts +44 -8
  520. package/dist/types/key.d.ts.map +1 -1
  521. package/dist/types/lock.cjs +33 -13
  522. package/dist/types/lock.cjs.map +1 -1
  523. package/dist/types/lock.js +34 -15
  524. package/dist/types/lock.js.map +1 -1
  525. package/dist/types/notifications.cjs +44 -22
  526. package/dist/types/notifications.cjs.map +1 -1
  527. package/dist/types/notifications.js +45 -23
  528. package/dist/types/notifications.js.map +1 -1
  529. package/dist/types/upload.d.cts +39 -5
  530. package/dist/types/upload.d.cts.map +1 -1
  531. package/dist/types/upload.d.ts +39 -5
  532. package/dist/types/upload.d.ts.map +1 -1
  533. package/dist/upload/abort-scope.cjs +91 -0
  534. package/dist/upload/abort-scope.cjs.map +1 -0
  535. package/dist/upload/abort-scope.d.cts +48 -0
  536. package/dist/upload/abort-scope.d.cts.map +1 -0
  537. package/dist/upload/abort-scope.d.ts +48 -0
  538. package/dist/upload/abort-scope.d.ts.map +1 -0
  539. package/dist/upload/abort-scope.js +88 -0
  540. package/dist/upload/abort-scope.js.map +1 -0
  541. package/dist/upload/cancel.cjs +183 -8
  542. package/dist/upload/cancel.cjs.map +1 -1
  543. package/dist/upload/cancel.d.cts +110 -2
  544. package/dist/upload/cancel.d.cts.map +1 -1
  545. package/dist/upload/cancel.d.ts +110 -2
  546. package/dist/upload/cancel.d.ts.map +1 -1
  547. package/dist/upload/cancel.js +178 -9
  548. package/dist/upload/cancel.js.map +1 -1
  549. package/dist/upload/concurrency.cjs +79 -52
  550. package/dist/upload/concurrency.cjs.map +1 -1
  551. package/dist/upload/concurrency.js +79 -53
  552. package/dist/upload/concurrency.js.map +1 -1
  553. package/dist/upload/finish.cjs +59 -0
  554. package/dist/upload/finish.cjs.map +1 -0
  555. package/dist/upload/finish.d.cts +26 -0
  556. package/dist/upload/finish.d.cts.map +1 -0
  557. package/dist/upload/finish.d.ts +26 -0
  558. package/dist/upload/finish.d.ts.map +1 -0
  559. package/dist/upload/finish.js +59 -0
  560. package/dist/upload/finish.js.map +1 -0
  561. package/dist/upload/large.cjs +301 -218
  562. package/dist/upload/large.cjs.map +1 -1
  563. package/dist/upload/large.d.cts +76 -9
  564. package/dist/upload/large.d.cts.map +1 -1
  565. package/dist/upload/large.d.ts +76 -9
  566. package/dist/upload/large.d.ts.map +1 -1
  567. package/dist/upload/large.js +301 -218
  568. package/dist/upload/large.js.map +1 -1
  569. package/dist/upload/options.cjs +28 -0
  570. package/dist/upload/options.cjs.map +1 -0
  571. package/dist/upload/options.d.cts +104 -0
  572. package/dist/upload/options.d.cts.map +1 -0
  573. package/dist/upload/options.d.ts +104 -0
  574. package/dist/upload/options.d.ts.map +1 -0
  575. package/dist/upload/options.js +27 -0
  576. package/dist/upload/options.js.map +1 -0
  577. package/dist/upload/resume.cjs +333 -33
  578. package/dist/upload/resume.cjs.map +1 -1
  579. package/dist/upload/resume.d.cts +82 -12
  580. package/dist/upload/resume.d.cts.map +1 -1
  581. package/dist/upload/resume.d.ts +82 -12
  582. package/dist/upload/resume.d.ts.map +1 -1
  583. package/dist/upload/resume.js +330 -33
  584. package/dist/upload/resume.js.map +1 -1
  585. package/dist/upload/retry.cjs +186 -0
  586. package/dist/upload/retry.cjs.map +1 -0
  587. package/dist/upload/retry.d.cts +127 -0
  588. package/dist/upload/retry.d.cts.map +1 -0
  589. package/dist/upload/retry.d.ts +127 -0
  590. package/dist/upload/retry.d.ts.map +1 -0
  591. package/dist/upload/retry.js +182 -0
  592. package/dist/upload/retry.js.map +1 -0
  593. package/dist/upload/single.cjs +68 -44
  594. package/dist/upload/single.cjs.map +1 -1
  595. package/dist/upload/single.d.cts +2 -1
  596. package/dist/upload/single.d.cts.map +1 -1
  597. package/dist/upload/single.d.ts +2 -1
  598. package/dist/upload/single.d.ts.map +1 -1
  599. package/dist/upload/single.js +68 -44
  600. package/dist/upload/single.js.map +1 -1
  601. package/dist/upload/stream.cjs +317 -193
  602. package/dist/upload/stream.cjs.map +1 -1
  603. package/dist/upload/stream.d.cts +3 -1
  604. package/dist/upload/stream.d.cts.map +1 -1
  605. package/dist/upload/stream.d.ts +3 -1
  606. package/dist/upload/stream.d.ts.map +1 -1
  607. package/dist/upload/stream.js +316 -192
  608. package/dist/upload/stream.js.map +1 -1
  609. package/dist/util/best-effort.cjs +42 -8
  610. package/dist/util/best-effort.cjs.map +1 -1
  611. package/dist/util/best-effort.d.cts +2 -1
  612. package/dist/util/best-effort.d.cts.map +1 -1
  613. package/dist/util/best-effort.d.ts +2 -1
  614. package/dist/util/best-effort.d.ts.map +1 -1
  615. package/dist/util/best-effort.js +43 -9
  616. package/dist/util/best-effort.js.map +1 -1
  617. package/dist/util/bytes.cjs +23 -0
  618. package/dist/util/bytes.cjs.map +1 -0
  619. package/dist/util/bytes.d.cts +15 -0
  620. package/dist/util/bytes.d.cts.map +1 -0
  621. package/dist/util/bytes.d.ts +15 -0
  622. package/dist/util/bytes.d.ts.map +1 -0
  623. package/dist/util/bytes.js +23 -0
  624. package/dist/util/bytes.js.map +1 -0
  625. package/dist/util/crypto.cjs +68 -0
  626. package/dist/util/crypto.cjs.map +1 -0
  627. package/dist/util/crypto.d.cts +26 -0
  628. package/dist/util/crypto.d.cts.map +1 -0
  629. package/dist/util/crypto.d.ts +26 -0
  630. package/dist/util/crypto.d.ts.map +1 -0
  631. package/dist/util/crypto.js +66 -0
  632. package/dist/util/crypto.js.map +1 -0
  633. package/dist/util/defaults.cjs +25 -9
  634. package/dist/util/defaults.cjs.map +1 -1
  635. package/dist/util/defaults.js +26 -11
  636. package/dist/util/defaults.js.map +1 -1
  637. package/dist/util/error-reason.cjs +41 -0
  638. package/dist/util/error-reason.cjs.map +1 -0
  639. package/dist/util/error-reason.d.cts +9 -0
  640. package/dist/util/error-reason.d.cts.map +1 -0
  641. package/dist/util/error-reason.d.ts +9 -0
  642. package/dist/util/error-reason.d.ts.map +1 -0
  643. package/dist/util/error-reason.js +41 -0
  644. package/dist/util/error-reason.js.map +1 -0
  645. package/dist/util/http.cjs +23 -0
  646. package/dist/util/http.cjs.map +1 -0
  647. package/dist/util/http.d.cts +13 -0
  648. package/dist/util/http.d.cts.map +1 -0
  649. package/dist/util/http.d.ts +13 -0
  650. package/dist/util/http.d.ts.map +1 -0
  651. package/dist/util/http.js +23 -0
  652. package/dist/util/http.js.map +1 -0
  653. package/dist/util/normalize.cjs +66 -7
  654. package/dist/util/normalize.cjs.map +1 -1
  655. package/dist/util/normalize.js +67 -10
  656. package/dist/util/normalize.js.map +1 -1
  657. package/dist/util/paginator.cjs +63 -14
  658. package/dist/util/paginator.cjs.map +1 -1
  659. package/dist/util/paginator.js +64 -16
  660. package/dist/util/paginator.js.map +1 -1
  661. package/dist/util/plan-ranges.cjs +47 -22
  662. package/dist/util/plan-ranges.cjs.map +1 -1
  663. package/dist/util/plan-ranges.js +48 -24
  664. package/dist/util/plan-ranges.js.map +1 -1
  665. package/dist/util/sha1.cjs +27 -0
  666. package/dist/util/sha1.cjs.map +1 -0
  667. package/dist/util/sha1.d.cts +17 -0
  668. package/dist/util/sha1.d.cts.map +1 -0
  669. package/dist/util/sha1.d.ts +17 -0
  670. package/dist/util/sha1.d.ts.map +1 -0
  671. package/dist/util/sha1.js +26 -0
  672. package/dist/util/sha1.js.map +1 -0
  673. package/dist/util/text-codec.cjs +29 -5
  674. package/dist/util/text-codec.cjs.map +1 -1
  675. package/dist/util/text-codec.js +30 -7
  676. package/dist/util/text-codec.js.map +1 -1
  677. package/dist/util/to-error.cjs +18 -4
  678. package/dist/util/to-error.cjs.map +1 -1
  679. package/dist/util/to-error.js +19 -5
  680. package/dist/util/to-error.js.map +1 -1
  681. package/dist/version.cjs +17 -5
  682. package/dist/version.cjs.map +1 -1
  683. package/dist/version.d.cts +3 -3
  684. package/dist/version.d.ts +3 -3
  685. package/dist/version.js +19 -6
  686. package/dist/version.js.map +1 -1
  687. package/package.json +19 -16
  688. package/dist/auth/index.cjs.map +0 -1
  689. package/dist/auth/index.js.map +0 -1
  690. package/dist/index.cjs.map +0 -1
  691. package/dist/index.js.map +0 -1
  692. package/dist/notifications/index.cjs.map +0 -1
  693. package/dist/notifications/index.js.map +0 -1
  694. package/dist/package.json.cjs +0 -9
  695. package/dist/package.json.cjs.map +0 -1
  696. package/dist/package.json.js +0 -9
  697. package/dist/package.json.js.map +0 -1
  698. package/dist/streams/index.cjs.map +0 -1
  699. package/dist/streams/index.js.map +0 -1
  700. package/dist/sync/index.cjs.map +0 -1
  701. package/dist/sync/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"transport.js","sources":["../../src/http/transport.ts"],"sourcesContent":["import { B2Error, ExpiredAuthTokenError, NetworkError, classifyError } from '../errors/index.ts'\nimport type { B2ErrorResponse } from '../types/errors.ts'\nimport { DEFAULT_RETRY_OPTIONS, type RetryOptions, computeBackoff, sleep } from './retry.ts'\nimport { UrlGuard } from './url-guard.ts'\nimport { getUserAgent } from './user-agent.ts'\n\n/** Describes an outgoing HTTP request to the B2 API. */\nexport interface HttpRequest {\n /** Fully qualified URL to send the request to. */\n readonly url: string\n /** HTTP method. B2 uses GET for downloads (HEAD to fetch only headers) and POST for all other operations. */\n readonly method: 'GET' | 'HEAD' | 'POST'\n /** Request headers. Authorization and content-type are typically included. */\n readonly headers?: Record<string, string>\n /** Request body. Used for POST requests (JSON payloads or file uploads). */\n readonly body?: BodyInit | null\n /** Optional abort signal for request cancellation. */\n readonly signal?: AbortSignal\n}\n\n/** Represents a parsed HTTP response from the B2 API. */\nexport interface HttpResponse {\n /** HTTP status code. */\n readonly status: number\n /** Response headers. */\n readonly headers: Headers\n /** Response body as a readable stream, or null for empty responses. */\n readonly body: ReadableStream<Uint8Array> | null\n /** Parses the response body as JSON. */\n json<T>(): Promise<T>\n /** Reads the response body as a UTF-8 string. */\n text(): Promise<string>\n /** Reads the response body as an ArrayBuffer. */\n arrayBuffer(): Promise<ArrayBuffer>\n}\n\n/**\n * Transport layer abstraction for sending HTTP requests.\n * Implementations handle the actual network I/O and can be composed\n * (e.g. wrapping a {@link FetchTransport} with a {@link RetryTransport}).\n */\nexport interface HttpTransport {\n /** Sends an HTTP request and returns the response. */\n send(request: HttpRequest): Promise<HttpResponse>\n}\n\n/**\n * Default transport implementation using the global `fetch` API.\n * Automatically sets the User-Agent header on each request and applies the\n * SSRF {@link UrlGuard} (if configured) before opening the connection.\n */\nexport class FetchTransport implements HttpTransport {\n /** User-Agent string sent with every request. */\n private readonly userAgent: string\n /** SSRF allow-list applied to every outgoing URL. Mutable so `B2Client.authorize()` can lock it down post-auth. */\n readonly urlGuard: UrlGuard\n\n /**\n * Creates a new FetchTransport.\n * @param options - Optional configuration: custom User-Agent prefix and SSRF guard.\n */\n constructor(options?: { userAgent?: string; urlGuard?: UrlGuard }) {\n this.userAgent = getUserAgent(options?.userAgent)\n this.urlGuard = options?.urlGuard ?? new UrlGuard()\n }\n\n /**\n * Sends the request using the global `fetch` function.\n * @param request - The HTTP request to execute.\n *\n * @returns The HTTP response.\n *\n * @throws B2SsrfError when the URL fails the configured SSRF guard.\n */\n async send(request: HttpRequest): Promise<HttpResponse> {\n this.urlGuard.check(request.url)\n\n const headers = new Headers(request.headers)\n if (!headers.has('User-Agent')) {\n headers.set('User-Agent', this.userAgent)\n }\n\n const response = await fetch(request.url, {\n method: request.method,\n headers,\n body: request.body ?? null,\n ...(request.signal !== undefined ? { signal: request.signal } : {}),\n })\n\n return {\n status: response.status,\n headers: response.headers,\n body: response.body,\n json: <T>() => response.json() as Promise<T>,\n text: () => response.text(),\n arrayBuffer: () => response.arrayBuffer(),\n }\n }\n}\n\n/** Configuration for {@link RetryTransport}. */\nexport interface RetryTransportOptions {\n /** The underlying transport to delegate requests to. */\n readonly transport: HttpTransport\n /** Override default retry settings (max retries, delays). */\n readonly retry?: Partial<RetryOptions>\n /**\n * Callback invoked on expired auth token errors. Must refresh\n * credentials AND return the fresh auth token. The transport\n * substitutes the new token into `request.headers.Authorization`\n * before retrying — without this, the retried request would still\n * carry the expired token captured by the original caller and the\n * loop would never make progress.\n */\n readonly onReauth?: () => Promise<string>\n /**\n * Sleep implementation used between retry attempts. Defaults to the real\n * `sleep` from `./retry.js`. Test code can inject a no-op to avoid real\n * delays without relying on module mocking (which differs across runners).\n *\n * @internal\n */\n readonly sleepImpl?: (ms: number, signal?: AbortSignal) => Promise<void>\n}\n\n/**\n * Transport wrapper that adds automatic retry with exponential backoff.\n * Handles transient B2 errors (408, 429, 503), expired auth tokens,\n * and network failures. Delegates to an inner {@link HttpTransport}.\n */\nexport class RetryTransport implements HttpTransport {\n /** The wrapped transport that performs actual HTTP requests. */\n private readonly inner: HttpTransport\n /** Resolved retry options (defaults merged with user overrides). */\n private readonly options: RetryOptions\n /** Optional callback to refresh auth credentials on 401 — returns the fresh token. */\n private readonly onReauth?: () => Promise<string>\n /** Sleep implementation used between retries; injectable for tests. */\n private readonly sleepImpl: (ms: number, signal?: AbortSignal) => Promise<void>\n\n /**\n * Creates a new RetryTransport.\n * @param opts - Retry transport configuration.\n */\n constructor(opts: RetryTransportOptions) {\n this.inner = opts.transport\n this.options = { ...DEFAULT_RETRY_OPTIONS, ...opts.retry }\n if (opts.onReauth !== undefined) this.onReauth = opts.onReauth\n this.sleepImpl = opts.sleepImpl ?? sleep\n }\n\n /**\n * Sends the request with automatic retry on transient failures.\n * On expired auth tokens, calls {@link RetryTransportOptions.onReauth} and retries.\n * @param originalRequest - The HTTP request to execute. The caller's\n * reference is not mutated; on reauth, a copy with a refreshed\n * Authorization header is sent.\n *\n * @returns The HTTP response.\n */\n async send(originalRequest: HttpRequest): Promise<HttpResponse> {\n // `request` is reassigned (not mutated) when reauth produces a\n // fresh Authorization header, so the caller's `originalRequest`\n // stays untouched.\n let request: HttpRequest = originalRequest\n let lastError: B2Error | NetworkError | undefined\n\n for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {\n if (attempt > 0 && lastError) {\n const retryAfter = lastError instanceof NetworkError ? undefined : lastError.retryAfter\n const delay = computeBackoff(attempt - 1, this.options, retryAfter)\n await this.sleepImpl(delay, request.signal)\n }\n\n try {\n const response = await this.inner.send(request)\n\n if (response.status >= 200 && response.status < 300) {\n return response\n }\n\n let errorBody: B2ErrorResponse\n try {\n errorBody = await response.json<B2ErrorResponse>()\n } catch {\n errorBody = {\n status: response.status,\n code: 'internal_error',\n message: `HTTP ${response.status}`,\n }\n }\n\n const retryAfterHeader = response.headers.get('Retry-After')\n const retryAfterSec = retryAfterHeader ? Number.parseInt(retryAfterHeader, 10) : undefined\n const requestId = response.headers.get('X-Bz-Request-Id') ?? undefined\n\n const error = classifyError(errorBody, {\n ...(retryAfterSec !== undefined ? { retryAfter: retryAfterSec } : {}),\n ...(requestId !== undefined ? { requestId } : {}),\n })\n\n if (error instanceof ExpiredAuthTokenError && this.onReauth) {\n // Reauth returns the FRESH token; build a new request with a\n // shallow-copied headers object so the Authorization swap\n // doesn't mutate the caller's original request (which they\n // may hold for retry / introspection). Without this swap the\n // retry would carry the expired token captured at\n // request-build time and bounce off the server again,\n // exhausting the retry budget.\n const freshToken = await this.onReauth()\n request = {\n ...request,\n headers: { ...(request.headers ?? {}), Authorization: freshToken },\n }\n continue\n }\n\n if (!error.retryable || attempt === this.options.maxRetries) {\n throw error\n }\n\n lastError = error\n } catch (err) {\n if (err instanceof B2Error || err instanceof NetworkError) {\n throw err\n }\n\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw err\n }\n\n const networkErr = new NetworkError(\n err instanceof Error ? err.message : 'Network error',\n err,\n )\n\n if (attempt === this.options.maxRetries) {\n throw networkErr\n }\n\n lastError = networkErr\n }\n }\n\n throw lastError ?? new NetworkError('Max retries exceeded')\n }\n}\n"],"names":[],"mappings":";;;;AAmDO,MAAM,eAAwC;AAAA;AAAA,EAElC;AAAA;AAAA,EAER;AAAA;AAAA;AAAA;AAAA;AAAA,EAMT,YAAY,SAAuD;AACjE,SAAK,YAAY,aAAa,SAAS,SAAS;AAChD,SAAK,WAAW,SAAS,YAAY,IAAI,SAAA;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,KAAK,SAA6C;AACtD,SAAK,SAAS,MAAM,QAAQ,GAAG;AAE/B,UAAM,UAAU,IAAI,QAAQ,QAAQ,OAAO;AAC3C,QAAI,CAAC,QAAQ,IAAI,YAAY,GAAG;AAC9B,cAAQ,IAAI,cAAc,KAAK,SAAS;AAAA,IAC1C;AAEA,UAAM,WAAW,MAAM,MAAM,QAAQ,KAAK;AAAA,MACxC,QAAQ,QAAQ;AAAA,MAChB;AAAA,MACA,MAAM,QAAQ,QAAQ;AAAA,MACtB,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,WAAW,CAAA;AAAA,IAAC,CAClE;AAED,WAAO;AAAA,MACL,QAAQ,SAAS;AAAA,MACjB,SAAS,SAAS;AAAA,MAClB,MAAM,SAAS;AAAA,MACf,MAAM,MAAS,SAAS,KAAA;AAAA,MACxB,MAAM,MAAM,SAAS,KAAA;AAAA,MACrB,aAAa,MAAM,SAAS,YAAA;AAAA,IAAY;AAAA,EAE5C;AACF;AAgCO,MAAM,eAAwC;AAAA;AAAA,EAElC;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjB,YAAY,MAA6B;AACvC,SAAK,QAAQ,KAAK;AAClB,SAAK,UAAU,EAAE,GAAG,uBAAuB,GAAG,KAAK,MAAA;AACnD,QAAI,KAAK,aAAa,OAAW,MAAK,WAAW,KAAK;AACtD,SAAK,YAAY,KAAK,aAAa;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,KAAK,iBAAqD;AAI9D,QAAI,UAAuB;AAC3B,QAAI;AAEJ,aAAS,UAAU,GAAG,WAAW,KAAK,QAAQ,YAAY,WAAW;AACnE,UAAI,UAAU,KAAK,WAAW;AAC5B,cAAM,aAAa,qBAAqB,eAAe,SAAY,UAAU;AAC7E,cAAM,QAAQ,eAAe,UAAU,GAAG,KAAK,SAAS,UAAU;AAClE,cAAM,KAAK,UAAU,OAAO,QAAQ,MAAM;AAAA,MAC5C;AAEA,UAAI;AACF,cAAM,WAAW,MAAM,KAAK,MAAM,KAAK,OAAO;AAE9C,YAAI,SAAS,UAAU,OAAO,SAAS,SAAS,KAAK;AACnD,iBAAO;AAAA,QACT;AAEA,YAAI;AACJ,YAAI;AACF,sBAAY,MAAM,SAAS,KAAA;AAAA,QAC7B,QAAQ;AACN,sBAAY;AAAA,YACV,QAAQ,SAAS;AAAA,YACjB,MAAM;AAAA,YACN,SAAS,QAAQ,SAAS,MAAM;AAAA,UAAA;AAAA,QAEpC;AAEA,cAAM,mBAAmB,SAAS,QAAQ,IAAI,aAAa;AAC3D,cAAM,gBAAgB,mBAAmB,OAAO,SAAS,kBAAkB,EAAE,IAAI;AACjF,cAAM,YAAY,SAAS,QAAQ,IAAI,iBAAiB,KAAK;AAE7D,cAAM,QAAQ,cAAc,WAAW;AAAA,UACrC,GAAI,kBAAkB,SAAY,EAAE,YAAY,cAAA,IAAkB,CAAA;AAAA,UAClE,GAAI,cAAc,SAAY,EAAE,UAAA,IAAc,CAAA;AAAA,QAAC,CAChD;AAED,YAAI,iBAAiB,yBAAyB,KAAK,UAAU;AAQ3D,gBAAM,aAAa,MAAM,KAAK,SAAA;AAC9B,oBAAU;AAAA,YACR,GAAG;AAAA,YACH,SAAS,EAAE,GAAI,QAAQ,WAAW,CAAA,GAAK,eAAe,WAAA;AAAA,UAAW;AAEnE;AAAA,QACF;AAEA,YAAI,CAAC,MAAM,aAAa,YAAY,KAAK,QAAQ,YAAY;AAC3D,gBAAM;AAAA,QACR;AAEA,oBAAY;AAAA,MACd,SAAS,KAAK;AACZ,YAAI,eAAe,WAAW,eAAe,cAAc;AACzD,gBAAM;AAAA,QACR;AAEA,YAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;AAC5D,gBAAM;AAAA,QACR;AAEA,cAAM,aAAa,IAAI;AAAA,UACrB,eAAe,QAAQ,IAAI,UAAU;AAAA,UACrC;AAAA,QAAA;AAGF,YAAI,YAAY,KAAK,QAAQ,YAAY;AACvC,gBAAM;AAAA,QACR;AAEA,oBAAY;AAAA,MACd;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,aAAa,sBAAsB;AAAA,EAC5D;AACF;"}
1
+ {"version":3,"file":"transport.js","names":[],"sources":["../../src/http/transport.ts"],"sourcesContent":["import {\n B2Error,\n B2RedirectError,\n B2SsrfError,\n classifyError,\n ExpiredAuthTokenError,\n NetworkError,\n} from '../errors/index.ts'\nimport type { B2ErrorResponse } from '../types/errors.ts'\nimport { computeBackoff, DEFAULT_RETRY_OPTIONS, type RetryOptions, sleep } from './retry.ts'\nimport { UrlGuard } from './url-guard.ts'\nimport { getUserAgent } from './user-agent.ts'\n\nconst REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308])\nconst MAX_SAME_ORIGIN_REDIRECTS = 5\n\n/** Describes an outgoing HTTP request to the B2 API. */\nexport interface HttpRequest {\n /** Fully qualified URL to send the request to. */\n readonly url: string\n /** HTTP method. B2 uses GET for downloads (HEAD to fetch only headers) and POST for all other operations. */\n readonly method: 'GET' | 'HEAD' | 'POST'\n /** Request headers. Authorization and content-type are typically included. */\n readonly headers?: Record<string, string>\n /** Request body. Used for POST requests (JSON payloads or file uploads). */\n readonly body?: BodyInit | null\n /** Optional abort signal for request cancellation. */\n readonly signal?: AbortSignal\n /** Optional per-request retry override. */\n readonly retry?: Partial<RetryOptions>\n}\n\n/** Represents a parsed HTTP response from the B2 API. */\nexport interface HttpResponse {\n /** HTTP status code. */\n readonly status: number\n /** Response headers. */\n readonly headers: Headers\n /** Response body as a readable stream, or null for empty responses. */\n readonly body: ReadableStream<Uint8Array> | null\n /** Parses the response body as JSON. */\n json<T>(): Promise<T>\n /** Reads the response body as a UTF-8 string. */\n text(): Promise<string>\n /** Reads the response body as an ArrayBuffer. */\n arrayBuffer(): Promise<ArrayBuffer>\n}\n\n/**\n * Transport layer abstraction for sending HTTP requests.\n * Implementations handle the actual network I/O and can be composed\n * (e.g. wrapping a {@link FetchTransport} with a {@link RetryTransport}).\n */\nexport interface HttpTransport {\n /** Sends an HTTP request and returns the response. */\n send(request: HttpRequest): Promise<HttpResponse>\n}\n\n/**\n * Default transport implementation using the global `fetch` API.\n * Automatically sets the User-Agent header on each request and applies the\n * SSRF {@link UrlGuard} (if configured) before opening the connection.\n * Redirect following is disabled so redirected URLs cannot bypass the guard or\n * receive credential-bearing headers without an explicit checked request.\n */\nexport class FetchTransport implements HttpTransport {\n /** User-Agent string sent with every request. */\n private readonly userAgent: string\n /** Whether same-origin GET/HEAD redirects should be followed after guard checks. */\n private readonly followSameOriginRedirects: boolean\n /** SSRF allow-list applied to every outgoing URL. Mutable so `B2Client.authorize()` can lock it down post-auth. */\n readonly urlGuard: UrlGuard\n\n /**\n * Creates a new FetchTransport.\n * @param options - Optional configuration: custom User-Agent prefix and SSRF guard.\n */\n constructor(options?: {\n userAgent?: string\n urlGuard?: UrlGuard\n /**\n * Follow same-origin GET/HEAD redirects after checking the target with the\n * URL guard. POST redirects are still blocked to avoid replaying\n * credential-bearing payloads to an unexpected endpoint. Defaults to true.\n */\n followSameOriginRedirects?: boolean\n }) {\n this.userAgent = getUserAgent(options?.userAgent)\n this.followSameOriginRedirects = options?.followSameOriginRedirects ?? true\n this.urlGuard = options?.urlGuard ?? new UrlGuard()\n }\n\n /**\n * Sends the request using the global `fetch` function.\n * @param request - The HTTP request to execute.\n *\n * @returns The HTTP response.\n *\n * @throws B2SsrfError when the URL fails the configured SSRF guard.\n * @throws B2RedirectError when a response attempts to redirect.\n */\n async send(request: HttpRequest): Promise<HttpResponse> {\n let currentRequest = request\n let redirectCount = 0\n\n while (true) {\n this.urlGuard.check(currentRequest.url)\n\n const headers = new Headers(currentRequest.headers)\n if (!headers.has('User-Agent')) {\n headers.set('User-Agent', this.userAgent)\n }\n\n const timeoutScope = createRequestTimeoutScope(currentRequest)\n let response: Response\n try {\n response = await fetch(currentRequest.url, {\n method: currentRequest.method,\n headers,\n body: currentRequest.body ?? null,\n redirect: 'manual',\n ...(timeoutScope.signal !== undefined ? { signal: timeoutScope.signal } : {}),\n })\n } catch (err) {\n timeoutScope.dispose()\n if (timeoutScope.timedOut) {\n throw createRequestTimeoutError(timeoutScope)\n }\n throw err\n }\n\n if (isBlockedRedirect(response)) {\n const location = response.headers.get('Location')\n if (\n this.followSameOriginRedirects &&\n location !== null &&\n redirectCount < MAX_SAME_ORIGIN_REDIRECTS &&\n canFollowSameOriginRedirect(currentRequest, location)\n ) {\n const nextUrl = new URL(location, currentRequest.url).toString()\n await cancelResponseBody(response)\n timeoutScope.dispose()\n this.urlGuard.check(nextUrl)\n currentRequest = { ...currentRequest, url: nextUrl }\n redirectCount += 1\n continue\n }\n\n await cancelResponseBody(response)\n timeoutScope.dispose()\n throw new B2RedirectError(currentRequest.url, response.status, location)\n }\n\n return createTimedHttpResponse(response, timeoutScope)\n }\n }\n}\n\ninterface RequestTimeoutScope {\n readonly signal?: AbortSignal\n readonly timeoutMs: number\n readonly timedOut: boolean\n reset(): void\n dispose(): void\n}\n\nfunction createRequestTimeoutScope(request: HttpRequest): RequestTimeoutScope {\n const timeoutMs = request.retry?.requestTimeoutMs ?? DEFAULT_RETRY_OPTIONS.requestTimeoutMs\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n const scope: RequestTimeoutScope = {\n timeoutMs: 0,\n timedOut: false,\n reset() {},\n dispose() {},\n }\n if (request.signal !== undefined) return { ...scope, signal: request.signal }\n return scope\n }\n\n const controller = new AbortController()\n let timedOut = false\n let timer: ReturnType<typeof setTimeout>\n const abortFromUpstream = (): void => {\n clearTimeout(timer)\n controller.abort(request.signal?.reason ?? new DOMException('Aborted', 'AbortError'))\n }\n const armTimer = (): ReturnType<typeof setTimeout> => {\n const nextTimer = setTimeout(() => {\n timedOut = true\n controller.abort(new DOMException('HTTP request timed out', 'TimeoutError'))\n }, timeoutMs)\n unrefTimer(nextTimer)\n return nextTimer\n }\n timer = armTimer()\n\n const reset = (): void => {\n if (timedOut || controller.signal.aborted) return\n clearTimeout(timer)\n timer = armTimer()\n }\n\n if (request.signal?.aborted === true) {\n clearTimeout(timer)\n abortFromUpstream()\n } else {\n request.signal?.addEventListener('abort', abortFromUpstream, { once: true })\n }\n\n return {\n signal: controller.signal,\n timeoutMs,\n get timedOut() {\n return timedOut\n },\n reset,\n dispose() {\n clearTimeout(timer)\n request.signal?.removeEventListener('abort', abortFromUpstream)\n },\n }\n}\n\nfunction unrefTimer(timer: ReturnType<typeof setTimeout>): void {\n const maybeUnref = (timer as { unref?: () => void }).unref\n if (typeof maybeUnref === 'function') maybeUnref.call(timer)\n}\n\nfunction createRequestTimeoutError(scope: RequestTimeoutScope): DOMException {\n return new DOMException(`HTTP request timed out after ${scope.timeoutMs} ms`, 'TimeoutError')\n}\n\nfunction createTimedHttpResponse(\n response: Response,\n timeoutScope: RequestTimeoutScope,\n): HttpResponse {\n const body = response.body\n if (body === null) timeoutScope.dispose()\n let timedBody: ReadableStream<Uint8Array> | undefined\n\n return {\n status: response.status,\n headers: response.headers,\n get body() {\n if (body === null) return null\n timedBody ??= createTimedResponseBody(body, timeoutScope)\n return timedBody\n },\n json: <T>() =>\n readTimedResponseBody(timeoutScope, response, () => response.json() as Promise<T>),\n text: () => readTimedResponseBody(timeoutScope, response, () => response.text()),\n arrayBuffer: () => readTimedResponseBody(timeoutScope, response, () => response.arrayBuffer()),\n }\n}\n\nasync function readTimedResponseBody<T>(\n timeoutScope: RequestTimeoutScope,\n response: Response,\n read: () => Promise<T>,\n): Promise<T> {\n try {\n return await raceBodyReadWithAbort(timeoutScope, read(), (reason) =>\n cancelResponseBody(response, reason),\n )\n } catch (err) {\n if (timeoutScope.timedOut) {\n throw createRequestTimeoutError(timeoutScope)\n }\n throw err\n } finally {\n timeoutScope.dispose()\n }\n}\n\nfunction createTimedResponseBody(\n body: ReadableStream<Uint8Array>,\n timeoutScope: RequestTimeoutScope,\n): ReadableStream<Uint8Array> {\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined\n let disposed = false\n const dispose = (): void => {\n if (disposed) return\n disposed = true\n timeoutScope.dispose()\n }\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n reader ??= body.getReader()\n try {\n const result = await raceBodyReadWithAbort(timeoutScope, reader.read(), (reason) =>\n reader?.cancel(reason),\n )\n if (result.done) {\n dispose()\n controller.close()\n return\n }\n timeoutScope.reset()\n controller.enqueue(result.value)\n } catch (err) {\n dispose()\n if (timeoutScope.timedOut) {\n controller.error(createRequestTimeoutError(timeoutScope))\n return\n }\n controller.error(err)\n }\n },\n async cancel(reason) {\n dispose()\n try {\n if (reader !== undefined) {\n await reader.cancel(reason)\n return\n }\n await body.cancel(reason)\n } catch {\n // Best-effort cleanup when the caller cancels the wrapper body.\n }\n },\n })\n}\n\nasync function raceBodyReadWithAbort<T>(\n timeoutScope: RequestTimeoutScope,\n read: Promise<T>,\n abortCleanup?: ((reason: unknown) => Promise<void> | void) | undefined,\n): Promise<T> {\n const signal = timeoutScope.signal\n if (signal === undefined) return read\n const abortReason = (): unknown => signal.reason ?? new DOMException('Aborted', 'AbortError')\n if (signal.aborted) {\n void read.catch(() => {})\n const reason = abortReason()\n await runAbortCleanup(abortCleanup, reason)\n throw reason\n }\n\n let removeAbortListener: (() => void) | undefined\n const abort = new Promise<never>((_, reject) => {\n const onAbort = (): void => {\n const reason = abortReason()\n void read.catch(() => {})\n reject(reason)\n void runAbortCleanup(abortCleanup, reason)\n }\n signal.addEventListener('abort', onAbort, { once: true })\n removeAbortListener = () => signal.removeEventListener('abort', onAbort)\n })\n\n try {\n return await Promise.race([read, abort])\n } finally {\n removeAbortListener?.()\n }\n}\n\nasync function runAbortCleanup(\n abortCleanup: ((reason: unknown) => Promise<void> | void) | undefined,\n reason: unknown,\n): Promise<void> {\n try {\n await abortCleanup?.(reason)\n } catch {\n // Abort cleanup is best-effort and must not mask the abort reason.\n }\n}\n\nfunction isBlockedRedirect(response: Response): boolean {\n return response.type === 'opaqueredirect' || REDIRECT_STATUSES.has(response.status)\n}\n\nfunction canFollowSameOriginRedirect(request: HttpRequest, location: string): boolean {\n if (request.method !== 'GET' && request.method !== 'HEAD') return false\n try {\n return new URL(request.url).origin === new URL(location, request.url).origin\n } catch {\n return false\n }\n}\n\nasync function cancelResponseBody(response: Response, reason?: unknown): Promise<void> {\n try {\n await response.body?.cancel(reason)\n } catch {\n // Best-effort cleanup before throwing the primary error.\n }\n}\n\n/** Configuration for {@link RetryTransport}. */\nexport interface RetryTransportOptions {\n /** The underlying transport to delegate requests to. */\n readonly transport: HttpTransport\n /** Override default retry settings (max retries, delays). */\n readonly retry?: Partial<RetryOptions>\n /**\n * Callback invoked on expired auth token errors. Must refresh\n * credentials AND return the fresh auth token. The transport\n * substitutes the new token into `request.headers.Authorization`\n * before retrying — without this, the retried request would still\n * carry the expired token captured by the original caller and the\n * loop would never make progress.\n */\n readonly onReauth?: () => Promise<string>\n /**\n * Sleep implementation used between retry attempts. Defaults to the real\n * `sleep` from `./retry.js`. Test code can inject a no-op to avoid real\n * delays without relying on module mocking (which differs across runners).\n *\n * @internal\n */\n readonly sleepImpl?: (ms: number, signal?: AbortSignal) => Promise<void>\n}\n\n/**\n * Decide whether `url` points at a URL-pinned upload POST endpoint.\n *\n * @param url - Request URL to inspect.\n *\n * @returns Whether the request is a direct upload endpoint.\n */\nfunction isUploadEndpoint(url: string): boolean {\n const endpoint = b2ApiEndpointName(url)\n return endpoint === 'b2_upload_file' || endpoint === 'b2_upload_part'\n}\n\nfunction isFinishLargeFileEndpoint(url: string): boolean {\n return b2ApiEndpointName(url) === 'b2_finish_large_file'\n}\n\nfunction isStartLargeFileEndpoint(url: string): boolean {\n return b2ApiEndpointName(url) === 'b2_start_large_file'\n}\n\nfunction b2ApiEndpointName(url: string): string | undefined {\n const [, root, , endpoint] = new URL(url).pathname.split('/')\n if (root !== 'b2api') return undefined\n return endpoint\n}\n\nfunction isReplayUnsafePostEndpoint(url: string): boolean {\n return isUploadEndpoint(url) || isStartLargeFileEndpoint(url) || isFinishLargeFileEndpoint(url)\n}\n\n/**\n * Decide whether a classified error should be retried in place for `url`.\n * Transient errors normally retry; upload endpoints bubble to the upload layer\n * for fresh-URL retry except account-level 429 throttling, where fetching a new\n * upload URL only amplifies the rate limit.\n *\n * @param error - The classified, retryability-tagged error.\n * @param url - The request URL (used to detect upload endpoints).\n *\n * @returns Whether to retry the request in place.\n */\nfunction shouldRetryInPlace(error: B2Error, url: string): boolean {\n if (!error.retryable) return false\n if (isStartLargeFileEndpoint(url) || isFinishLargeFileEndpoint(url)) return false\n if (isUploadEndpoint(url) && error.status === 429) return true\n if (isUploadEndpoint(url)) return false\n return true\n}\n\nfunction isRequestTimeoutError(err: unknown): boolean {\n return err instanceof DOMException && err.name === 'TimeoutError'\n}\n\nfunction isTerminalTransportError(err: unknown): boolean {\n return (\n err instanceof B2Error ||\n err instanceof B2RedirectError ||\n err instanceof NetworkError ||\n err instanceof B2SsrfError ||\n (err instanceof DOMException && err.name === 'AbortError')\n )\n}\n\n/**\n * Transport wrapper that adds automatic retry with exponential backoff.\n * Handles transient errors (408, 429, and the transient 5xx set 500/502/503/504),\n * expired auth tokens, and network failures. Delegates to an inner\n * {@link HttpTransport}.\n *\n * Upload endpoints (`b2_upload_file` / `b2_upload_part`) are URL-pinned. Their\n * retryable pod failures bubble to the upload layer, which evicts the failed\n * URL, fetches a fresh one, and retries there. HTTP 429 remains an in-place\n * retry so account-level throttling does not trigger extra upload URL fetches.\n */\nexport class RetryTransport implements HttpTransport {\n /** The wrapped transport that performs actual HTTP requests. */\n private readonly inner: HttpTransport\n /** Resolved retry options (defaults merged with user overrides). */\n private readonly options: RetryOptions\n /** Optional callback to refresh auth credentials on 401 — returns the fresh token. */\n private readonly onReauth?: () => Promise<string>\n /** Sleep implementation used between retries; injectable for tests. */\n private readonly sleepImpl: (ms: number, signal?: AbortSignal) => Promise<void>\n\n /**\n * Creates a new RetryTransport.\n * @param opts - Retry transport configuration.\n */\n constructor(opts: RetryTransportOptions) {\n this.inner = opts.transport\n this.options = { ...DEFAULT_RETRY_OPTIONS, ...opts.retry }\n if (opts.onReauth !== undefined) this.onReauth = opts.onReauth\n this.sleepImpl = opts.sleepImpl ?? sleep\n }\n\n /**\n * Sends the request with automatic retry on transient failures.\n * On expired auth tokens, calls {@link RetryTransportOptions.onReauth} and retries.\n * @param originalRequest - The HTTP request to execute. The caller's\n * reference is not mutated; on reauth, a copy with a refreshed\n * Authorization header is sent.\n *\n * @returns The HTTP response.\n */\n async send(originalRequest: HttpRequest): Promise<HttpResponse> {\n // `request` is reassigned (not mutated) when reauth produces a\n // fresh Authorization header, so the caller's `originalRequest`\n // stays untouched.\n let request: HttpRequest = originalRequest\n const retryOptions = { ...this.options, ...originalRequest.retry }\n let lastError: B2Error | NetworkError | undefined\n let didReauth = false\n let attempt = 0\n\n while (attempt <= retryOptions.maxRetries) {\n throwIfSignalAborted(request.signal)\n\n if (attempt > 0 && lastError) {\n const retryAfter = lastError instanceof NetworkError ? undefined : lastError.retryAfter\n const delay = computeBackoff(attempt - 1, retryOptions, retryAfter)\n await this.sleepImpl(delay, request.signal)\n }\n\n try {\n const response = await this.inner.send({ ...request, retry: retryOptions })\n await throwIfSignalAbortedAfterResponse(request.signal, response)\n\n if (response.status >= 200 && response.status < 300) {\n return response\n }\n\n let errorBody: B2ErrorResponse\n try {\n errorBody = await response.json<B2ErrorResponse>()\n } catch (err) {\n if (isRequestTimeoutError(err)) throw err\n errorBody = {\n status: response.status,\n code: 'internal_error',\n message: `HTTP ${response.status}`,\n }\n }\n\n const retryAfterHeader = response.headers.get('Retry-After')\n const retryAfterSec = retryAfterHeader ? Number.parseInt(retryAfterHeader, 10) : undefined\n const requestId = response.headers.get('X-Bz-Request-Id') ?? undefined\n\n const error = classifyError(errorBody, {\n ...(retryAfterSec !== undefined ? { retryAfter: retryAfterSec } : {}),\n ...(requestId !== undefined ? { requestId } : {}),\n })\n\n if (\n error instanceof ExpiredAuthTokenError &&\n this.onReauth &&\n !isUploadEndpoint(request.url) &&\n !didReauth\n ) {\n // Reauth returns the FRESH token; build a new request with a\n // shallow-copied headers object so the Authorization swap\n // doesn't mutate the caller's original request (which they\n // may hold for retry / introspection). Without this swap the\n // retry would carry the expired token captured at\n // request-build time and bounce off the server again,\n // exhausting the retry budget.\n const freshToken = await this.onReauth()\n request = {\n ...request,\n headers: { ...(request.headers ?? {}), Authorization: freshToken },\n }\n didReauth = true\n lastError = undefined\n continue\n }\n\n if (!shouldRetryInPlace(error, request.url) || attempt === retryOptions.maxRetries) {\n throw error\n }\n\n lastError = error\n attempt += 1\n } catch (err) {\n throwIfSignalAborted(request.signal)\n if (isTerminalTransportError(err)) {\n throw err\n }\n\n const networkErr = new NetworkError(\n err instanceof Error ? err.message : 'Network error',\n err,\n )\n\n if (isReplayUnsafePostEndpoint(request.url) || attempt === retryOptions.maxRetries) {\n throw networkErr\n }\n\n lastError = networkErr\n attempt += 1\n }\n }\n\n throw lastError ?? new NetworkError('Max retries exceeded')\n }\n}\n\nfunction throwIfSignalAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted === true) {\n throw signal.reason ?? new DOMException('Aborted', 'AbortError')\n }\n}\n\nasync function throwIfSignalAbortedAfterResponse(\n signal: AbortSignal | undefined,\n response: HttpResponse,\n): Promise<void> {\n if (signal?.aborted !== true) return\n const reason = signal.reason ?? new DOMException('Aborted', 'AbortError')\n try {\n await response.body?.cancel(reason)\n } catch {\n // Best-effort cleanup before preserving the caller's abort reason.\n }\n throw reason\n}\n"],"mappings":"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"}
@@ -1,97 +1,162 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const errors_index = require("../errors/index.cjs");
4
- class UrlGuard {
5
- allowedSuffixes = [];
6
- /**
7
- * Lock the guard to the given host suffixes. A suffix matches a host
8
- * either exactly or as a `*.suffix` subdomain. For example,
9
- * `backblazeb2.com` allows `api.backblazeb2.com` and
10
- * `s3.us-west-004.backblazeb2.com`.
11
- *
12
- * Passing an empty array disables the guard (used by the simulator and
13
- * other test setups). Production code should always lock the guard after
14
- * a successful `b2_authorize_account`.
15
- *
16
- * @param suffixes - Allowed host suffixes.
17
- */
18
- setAllowedSuffixes(suffixes) {
19
- this.allowedSuffixes = suffixes;
20
- }
21
- /**
22
- * Returns the current allowed-suffix list (for tests and diagnostics).
23
- *
24
- * @returns The currently-configured list of allowed host suffixes.
25
- */
26
- getAllowedSuffixes() {
27
- return this.allowedSuffixes;
28
- }
29
- /**
30
- * Validate `rawUrl` against the allow-list. Throws {@link B2SsrfError} if
31
- * the URL points at a literal IP, a known-internal hostname, or a host
32
- * outside the allowed suffixes. Permissive (no-op) when no suffixes have
33
- * been configured yet.
34
- *
35
- * @param rawUrl - The URL the caller is about to fetch.
36
- *
37
- * @throws A `B2SsrfError` when the URL is rejected.
38
- */
39
- check(rawUrl) {
40
- if (this.allowedSuffixes.length === 0) return;
41
- let parsed;
42
- try {
43
- parsed = new URL(rawUrl);
44
- } catch {
45
- throw new errors_index.B2SsrfError(`malformed URL rejected by SSRF guard: ${rawUrl}`, rawUrl);
46
- }
47
- const host = parsed.hostname.toLowerCase();
48
- if (isLiteralIp(host)) {
49
- throw new errors_index.B2SsrfError(
50
- `literal IP host not allowed by SSRF guard (use a hostname): ${host}`,
51
- rawUrl
52
- );
53
- }
54
- if (isInternalHostname(host)) {
55
- throw new errors_index.B2SsrfError(`internal hostname not allowed by SSRF guard: ${host}`, rawUrl);
56
- }
57
- for (const suffix of this.allowedSuffixes) {
58
- const lowered = suffix.toLowerCase();
59
- if (host === lowered || host.endsWith(`.${lowered}`)) return;
60
- }
61
- throw new errors_index.B2SsrfError(
62
- `host outside allowed B2 realm: ${host} (allowed suffixes: ${this.allowedSuffixes.join(", ")})`,
63
- rawUrl
64
- );
65
- }
66
- }
1
+ const require_errors_index = require("../errors/index.cjs");
2
+ //#region src/http/url-guard.ts
3
+ /**
4
+ * URL allow-list guard. Defends against SSRF / URL-substitution attacks where
5
+ * a compromised or hostile B2 endpoint returns an upload URL pointing at an
6
+ * internal service (e.g. cloud metadata at `169.254.169.254`).
7
+ *
8
+ * The guard is built once per `B2Client` and updated by `B2Client.authorize()`.
9
+ * Before authorization it is permissive (so the very first
10
+ * `b2_authorize_account` request, whose URL the user configured, can succeed).
11
+ * After authorization it is locked to host suffixes derived from the realm's
12
+ * apiUrl/downloadUrl/s3ApiUrl, plus the well-known B2 upload-pod parent
13
+ * domain `backblaze.com`.
14
+ *
15
+ * The guard runs in `FetchTransport` before any outgoing request. It rejects:
16
+ *
17
+ * 1. Literal IPv4/IPv6 addresses (defense in depth, covers attempts to
18
+ * bypass DNS-based checks with raw IPs).
19
+ * 2. Well-known internal hostnames (`localhost`, `metadata`,
20
+ * `metadata.google.internal`, `.internal`, `.local`).
21
+ * 3. Hosts not matching any allowed suffix once the SDK is locked.
22
+ *
23
+ * Users supplying a custom `transport` to `B2Client` bypass the guard. That
24
+ * is their responsibility to document for their threat model.
25
+ *
26
+ * Threat-model note: the guard checks the URL's hostname before the
27
+ * `fetch()` call. It does NOT pin the resolved IP. A DNS rebinding attack
28
+ * could in principle resolve a permitted hostname to an internal IP between
29
+ * the guard's check and `fetch()`'s own resolution. This is theoretical
30
+ * against B2 because the allow-list is locked to a small set of stable
31
+ * Backblaze hostnames (the realm's apiUrl/downloadUrl/s3ApiUrl plus the
32
+ * `backblaze.com` parent), and DNS rebinding requires a hostname under
33
+ * attacker control. Defense in depth — pinning the IP from the first
34
+ * resolution and rejecting subsequent mismatches — would break legitimate
35
+ * CDN failovers and is not justified at this surface area. If your
36
+ * threat model requires it, supply a custom transport that does.
37
+ */
38
+ /** A URL allow-list that can be reconfigured after construction. */
39
+ var UrlGuard = class {
40
+ allowedSuffixes = [];
41
+ /**
42
+ * Lock the guard to the given host suffixes. A suffix matches a host
43
+ * either exactly or as a `*.suffix` subdomain. For example,
44
+ * `backblazeb2.com` allows `api.backblazeb2.com` and
45
+ * `s3.us-west-004.backblazeb2.com`.
46
+ *
47
+ * Passing an empty array disables the guard (used by the simulator and
48
+ * other test setups). Production code should always lock the guard after
49
+ * a successful `b2_authorize_account`.
50
+ *
51
+ * @param suffixes - Allowed host suffixes.
52
+ */
53
+ setAllowedSuffixes(suffixes) {
54
+ this.allowedSuffixes = suffixes;
55
+ }
56
+ /**
57
+ * Returns the current allowed-suffix list (for tests and diagnostics).
58
+ *
59
+ * @returns The currently-configured list of allowed host suffixes.
60
+ */
61
+ getAllowedSuffixes() {
62
+ return this.allowedSuffixes;
63
+ }
64
+ /**
65
+ * Validate `rawUrl` against the allow-list. Throws {@link B2SsrfError} if
66
+ * the URL points at a literal IP, a known-internal hostname, or a host
67
+ * outside the allowed suffixes. Permissive (no-op) when no suffixes have
68
+ * been configured yet.
69
+ *
70
+ * @param rawUrl - The URL the caller is about to fetch.
71
+ *
72
+ * @throws A `B2SsrfError` when the URL is rejected.
73
+ */
74
+ check(rawUrl) {
75
+ if (this.allowedSuffixes.length === 0) return;
76
+ let parsed;
77
+ try {
78
+ parsed = new URL(rawUrl);
79
+ } catch {
80
+ throw new require_errors_index.B2SsrfError(`malformed URL rejected by SSRF guard: ${rawUrl}`, rawUrl);
81
+ }
82
+ const host = parsed.hostname.toLowerCase();
83
+ if (isLiteralIp(host)) throw new require_errors_index.B2SsrfError(`literal IP host not allowed by SSRF guard (use a hostname): ${host}`, rawUrl);
84
+ if (isInternalHostname(host)) throw new require_errors_index.B2SsrfError(`internal hostname not allowed by SSRF guard: ${host}`, rawUrl);
85
+ if (hostMatchesAnyAllowedSuffix(host, this.allowedSuffixes)) return;
86
+ throw new require_errors_index.B2SsrfError(`host outside allowed B2 realm: ${host} (allowed suffixes: ${this.allowedSuffixes.join(", ")})`, rawUrl);
87
+ }
88
+ };
89
+ /**
90
+ * Extract host suffixes to allow from a B2 authorize-account response.
91
+ *
92
+ * Known B2 realm hosts under `backblazeb2.com` are collapsed to that parent.
93
+ * Unknown or custom realm hosts are used as scoped suffixes: the returned
94
+ * hostname and its subdomains are allowed, but sibling hosts and parent
95
+ * domains are not. This avoids accidentally trusting broad public suffixes
96
+ * such as `co.uk`.
97
+ *
98
+ * Always includes `backblaze.com` because upload-pod URLs returned by
99
+ * `b2_get_upload_url` use that parent domain (`pod-NNN-NNNN-NN.backblaze.com`)
100
+ * rather than `backblazeb2.com`.
101
+ *
102
+ * @param storageApi - The `apiInfo.storageApi` portion of the authorize response.
103
+ *
104
+ * @returns Sorted list of unique host suffixes to allow.
105
+ */
67
106
  function deriveAllowedSuffixes(storageApi) {
68
- const suffixes = /* @__PURE__ */ new Set(["backblaze.com"]);
69
- for (const url of [storageApi.apiUrl, storageApi.downloadUrl, storageApi.s3ApiUrl]) {
70
- try {
71
- const host = new URL(url).hostname;
72
- const parts = host.split(".");
73
- if (parts.length >= 2) {
74
- suffixes.add(parts.slice(-2).join("."));
75
- }
76
- } catch {
77
- }
78
- }
79
- return Array.from(suffixes).sort();
107
+ const suffixes = /* @__PURE__ */ new Set(["backblaze.com"]);
108
+ for (const url of [
109
+ storageApi.apiUrl,
110
+ storageApi.downloadUrl,
111
+ storageApi.s3ApiUrl
112
+ ]) try {
113
+ const host = new URL(url).hostname;
114
+ suffixes.add(host === "backblazeb2.com" || host.endsWith(".backblazeb2.com") ? "backblazeb2.com" : host);
115
+ } catch {}
116
+ return Array.from(suffixes).sort();
117
+ }
118
+ /**
119
+ * Checks a hostname against one allowed suffix using the SDK's exact-or-subdomain rule.
120
+ *
121
+ * @param hostname - URL hostname to check.
122
+ * @param suffix - Domain suffix that may match exactly or as a parent domain.
123
+ *
124
+ * @returns Whether the hostname is exactly the suffix or a subdomain of it.
125
+ */
126
+ function hostMatchesAllowedSuffix(hostname, suffix) {
127
+ const host = hostname.toLowerCase();
128
+ const lowered = suffix.toLowerCase();
129
+ return host === lowered || host.endsWith(`.${lowered}`);
130
+ }
131
+ /**
132
+ * Checks a hostname against an allowed-suffix list.
133
+ *
134
+ * @param hostname - URL hostname to check.
135
+ * @param suffixes - Domain suffixes to test with the SDK's suffix-matching rule.
136
+ *
137
+ * @returns Whether any suffix matches the hostname.
138
+ */
139
+ function hostMatchesAnyAllowedSuffix(hostname, suffixes) {
140
+ return suffixes.some((suffix) => hostMatchesAllowedSuffix(hostname, suffix));
80
141
  }
81
142
  function isLiteralIp(host) {
82
- if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host)) return true;
83
- if (host.includes(":")) return true;
84
- return false;
143
+ if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host)) return true;
144
+ if (host.includes(":")) return true;
145
+ return false;
85
146
  }
86
147
  function isInternalHostname(host) {
87
- if (host === "localhost") return true;
88
- if (host.endsWith(".localhost")) return true;
89
- if (host === "metadata") return true;
90
- if (host === "metadata.google.internal") return true;
91
- if (host.endsWith(".internal")) return true;
92
- if (host.endsWith(".local")) return true;
93
- return false;
148
+ if (host === "localhost") return true;
149
+ if (host.endsWith(".localhost")) return true;
150
+ if (host === "metadata") return true;
151
+ if (host === "metadata.google.internal") return true;
152
+ if (host.endsWith(".internal")) return true;
153
+ if (host.endsWith(".local")) return true;
154
+ return false;
94
155
  }
156
+ //#endregion
95
157
  exports.UrlGuard = UrlGuard;
96
158
  exports.deriveAllowedSuffixes = deriveAllowedSuffixes;
97
- //# sourceMappingURL=url-guard.cjs.map
159
+ exports.hostMatchesAllowedSuffix = hostMatchesAllowedSuffix;
160
+ exports.hostMatchesAnyAllowedSuffix = hostMatchesAnyAllowedSuffix;
161
+
162
+ //# sourceMappingURL=url-guard.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"url-guard.cjs","sources":["../../src/http/url-guard.ts"],"sourcesContent":["/**\n * URL allow-list guard. Defends against SSRF / URL-substitution attacks where\n * a compromised or hostile B2 endpoint returns an upload URL pointing at an\n * internal service (e.g. cloud metadata at `169.254.169.254`).\n *\n * The guard is built once per `B2Client` and updated by `B2Client.authorize()`.\n * Before authorization it is permissive (so the very first\n * `b2_authorize_account` request, whose URL the user configured, can succeed).\n * After authorization it is locked to host suffixes derived from the realm's\n * apiUrl/downloadUrl/s3ApiUrl, plus the well-known B2 upload-pod parent\n * domain `backblaze.com`.\n *\n * The guard runs in `FetchTransport` before any outgoing request. It rejects:\n *\n * 1. Literal IPv4/IPv6 addresses (defense in depth, covers attempts to\n * bypass DNS-based checks with raw IPs).\n * 2. Well-known internal hostnames (`localhost`, `metadata`,\n * `metadata.google.internal`, `.internal`, `.local`).\n * 3. Hosts not matching any allowed suffix once the SDK is locked.\n *\n * Users supplying a custom `transport` to `B2Client` bypass the guard. That\n * is their responsibility to document for their threat model.\n *\n * Threat-model note: the guard checks the URL's hostname before the\n * `fetch()` call. It does NOT pin the resolved IP. A DNS rebinding attack\n * could in principle resolve a permitted hostname to an internal IP between\n * the guard's check and `fetch()`'s own resolution. This is theoretical\n * against B2 because the allow-list is locked to a small set of stable\n * Backblaze hostnames (the realm's apiUrl/downloadUrl/s3ApiUrl plus the\n * `backblaze.com` parent), and DNS rebinding requires a hostname under\n * attacker control. Defense in depth — pinning the IP from the first\n * resolution and rejecting subsequent mismatches — would break legitimate\n * CDN failovers and is not justified at this surface area. If your\n * threat model requires it, supply a custom transport that does.\n */\n\nimport { B2SsrfError } from '../errors/index.ts'\n\n/** A URL allow-list that can be reconfigured after construction. */\nexport class UrlGuard {\n private allowedSuffixes: readonly string[] = []\n\n /**\n * Lock the guard to the given host suffixes. A suffix matches a host\n * either exactly or as a `*.suffix` subdomain. For example,\n * `backblazeb2.com` allows `api.backblazeb2.com` and\n * `s3.us-west-004.backblazeb2.com`.\n *\n * Passing an empty array disables the guard (used by the simulator and\n * other test setups). Production code should always lock the guard after\n * a successful `b2_authorize_account`.\n *\n * @param suffixes - Allowed host suffixes.\n */\n setAllowedSuffixes(suffixes: readonly string[]): void {\n this.allowedSuffixes = suffixes\n }\n\n /**\n * Returns the current allowed-suffix list (for tests and diagnostics).\n *\n * @returns The currently-configured list of allowed host suffixes.\n */\n getAllowedSuffixes(): readonly string[] {\n return this.allowedSuffixes\n }\n\n /**\n * Validate `rawUrl` against the allow-list. Throws {@link B2SsrfError} if\n * the URL points at a literal IP, a known-internal hostname, or a host\n * outside the allowed suffixes. Permissive (no-op) when no suffixes have\n * been configured yet.\n *\n * @param rawUrl - The URL the caller is about to fetch.\n *\n * @throws A `B2SsrfError` when the URL is rejected.\n */\n check(rawUrl: string): void {\n if (this.allowedSuffixes.length === 0) return\n\n let parsed: URL\n try {\n parsed = new URL(rawUrl)\n } catch {\n throw new B2SsrfError(`malformed URL rejected by SSRF guard: ${rawUrl}`, rawUrl)\n }\n\n const host = parsed.hostname.toLowerCase()\n\n if (isLiteralIp(host)) {\n throw new B2SsrfError(\n `literal IP host not allowed by SSRF guard (use a hostname): ${host}`,\n rawUrl,\n )\n }\n\n if (isInternalHostname(host)) {\n throw new B2SsrfError(`internal hostname not allowed by SSRF guard: ${host}`, rawUrl)\n }\n\n for (const suffix of this.allowedSuffixes) {\n const lowered = suffix.toLowerCase()\n if (host === lowered || host.endsWith(`.${lowered}`)) return\n }\n\n throw new B2SsrfError(\n `host outside allowed B2 realm: ${host} (allowed suffixes: ${this.allowedSuffixes.join(', ')})`,\n rawUrl,\n )\n }\n}\n\n/**\n * Extract host suffixes to allow from a B2 authorize-account response.\n *\n * Derives the parent domain (last two labels) of each realm URL so\n * `api.us-west-004.backblazeb2.com` becomes `backblazeb2.com`. Always\n * includes `backblaze.com` because upload-pod URLs returned by\n * `b2_get_upload_url` use that parent domain (`pod-NNN-NNNN-NN.backblaze.com`)\n * rather than `backblazeb2.com`.\n *\n * @param storageApi - The `apiInfo.storageApi` portion of the authorize response.\n *\n * @returns Sorted list of unique host suffixes to allow.\n */\nexport function deriveAllowedSuffixes(storageApi: {\n apiUrl: string\n downloadUrl: string\n s3ApiUrl: string\n}): readonly string[] {\n const suffixes = new Set<string>(['backblaze.com'])\n for (const url of [storageApi.apiUrl, storageApi.downloadUrl, storageApi.s3ApiUrl]) {\n try {\n const host = new URL(url).hostname\n const parts = host.split('.')\n if (parts.length >= 2) {\n suffixes.add(parts.slice(-2).join('.'))\n }\n } catch {\n // Skip malformed URLs. The auth response is from B2 itself; malformed\n // entries would already have caused other failures upstream.\n }\n }\n return Array.from(suffixes).sort()\n}\n\nfunction isLiteralIp(host: string): boolean {\n // IPv4 dotted quad: e.g. 169.254.169.254\n if (/^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(host)) return true\n // IPv6: URL.hostname strips the brackets but keeps colons. Any colon in a\n // hostname is an IPv6 literal.\n if (host.includes(':')) return true\n return false\n}\n\nfunction isInternalHostname(host: string): boolean {\n if (host === 'localhost') return true\n if (host.endsWith('.localhost')) return true\n if (host === 'metadata') return true\n if (host === 'metadata.google.internal') return true\n if (host.endsWith('.internal')) return true\n if (host.endsWith('.local')) return true\n return false\n}\n"],"names":["B2SsrfError"],"mappings":";;;AAuCO,MAAM,SAAS;AAAA,EACZ,kBAAqC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc7C,mBAAmB,UAAmC;AACpD,SAAK,kBAAkB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,qBAAwC;AACtC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,QAAsB;AAC1B,QAAI,KAAK,gBAAgB,WAAW,EAAG;AAEvC,QAAI;AACJ,QAAI;AACF,eAAS,IAAI,IAAI,MAAM;AAAA,IACzB,QAAQ;AACN,YAAM,IAAIA,aAAAA,YAAY,yCAAyC,MAAM,IAAI,MAAM;AAAA,IACjF;AAEA,UAAM,OAAO,OAAO,SAAS,YAAA;AAE7B,QAAI,YAAY,IAAI,GAAG;AACrB,YAAM,IAAIA,aAAAA;AAAAA,QACR,+DAA+D,IAAI;AAAA,QACnE;AAAA,MAAA;AAAA,IAEJ;AAEA,QAAI,mBAAmB,IAAI,GAAG;AAC5B,YAAM,IAAIA,aAAAA,YAAY,gDAAgD,IAAI,IAAI,MAAM;AAAA,IACtF;AAEA,eAAW,UAAU,KAAK,iBAAiB;AACzC,YAAM,UAAU,OAAO,YAAA;AACvB,UAAI,SAAS,WAAW,KAAK,SAAS,IAAI,OAAO,EAAE,EAAG;AAAA,IACxD;AAEA,UAAM,IAAIA,aAAAA;AAAAA,MACR,kCAAkC,IAAI,uBAAuB,KAAK,gBAAgB,KAAK,IAAI,CAAC;AAAA,MAC5F;AAAA,IAAA;AAAA,EAEJ;AACF;AAeO,SAAS,sBAAsB,YAIhB;AACpB,QAAM,WAAW,oBAAI,IAAY,CAAC,eAAe,CAAC;AAClD,aAAW,OAAO,CAAC,WAAW,QAAQ,WAAW,aAAa,WAAW,QAAQ,GAAG;AAClF,QAAI;AACF,YAAM,OAAO,IAAI,IAAI,GAAG,EAAE;AAC1B,YAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,UAAI,MAAM,UAAU,GAAG;AACrB,iBAAS,IAAI,MAAM,MAAM,EAAE,EAAE,KAAK,GAAG,CAAC;AAAA,MACxC;AAAA,IACF,QAAQ;AAAA,IAGR;AAAA,EACF;AACA,SAAO,MAAM,KAAK,QAAQ,EAAE,KAAA;AAC9B;AAEA,SAAS,YAAY,MAAuB;AAE1C,MAAI,uCAAuC,KAAK,IAAI,EAAG,QAAO;AAG9D,MAAI,KAAK,SAAS,GAAG,EAAG,QAAO;AAC/B,SAAO;AACT;AAEA,SAAS,mBAAmB,MAAuB;AACjD,MAAI,SAAS,YAAa,QAAO;AACjC,MAAI,KAAK,SAAS,YAAY,EAAG,QAAO;AACxC,MAAI,SAAS,WAAY,QAAO;AAChC,MAAI,SAAS,2BAA4B,QAAO;AAChD,MAAI,KAAK,SAAS,WAAW,EAAG,QAAO;AACvC,MAAI,KAAK,SAAS,QAAQ,EAAG,QAAO;AACpC,SAAO;AACT;;;"}
1
+ {"version":3,"file":"url-guard.cjs","names":[],"sources":["../../src/http/url-guard.ts"],"sourcesContent":["/**\n * URL allow-list guard. Defends against SSRF / URL-substitution attacks where\n * a compromised or hostile B2 endpoint returns an upload URL pointing at an\n * internal service (e.g. cloud metadata at `169.254.169.254`).\n *\n * The guard is built once per `B2Client` and updated by `B2Client.authorize()`.\n * Before authorization it is permissive (so the very first\n * `b2_authorize_account` request, whose URL the user configured, can succeed).\n * After authorization it is locked to host suffixes derived from the realm's\n * apiUrl/downloadUrl/s3ApiUrl, plus the well-known B2 upload-pod parent\n * domain `backblaze.com`.\n *\n * The guard runs in `FetchTransport` before any outgoing request. It rejects:\n *\n * 1. Literal IPv4/IPv6 addresses (defense in depth, covers attempts to\n * bypass DNS-based checks with raw IPs).\n * 2. Well-known internal hostnames (`localhost`, `metadata`,\n * `metadata.google.internal`, `.internal`, `.local`).\n * 3. Hosts not matching any allowed suffix once the SDK is locked.\n *\n * Users supplying a custom `transport` to `B2Client` bypass the guard. That\n * is their responsibility to document for their threat model.\n *\n * Threat-model note: the guard checks the URL's hostname before the\n * `fetch()` call. It does NOT pin the resolved IP. A DNS rebinding attack\n * could in principle resolve a permitted hostname to an internal IP between\n * the guard's check and `fetch()`'s own resolution. This is theoretical\n * against B2 because the allow-list is locked to a small set of stable\n * Backblaze hostnames (the realm's apiUrl/downloadUrl/s3ApiUrl plus the\n * `backblaze.com` parent), and DNS rebinding requires a hostname under\n * attacker control. Defense in depth — pinning the IP from the first\n * resolution and rejecting subsequent mismatches — would break legitimate\n * CDN failovers and is not justified at this surface area. If your\n * threat model requires it, supply a custom transport that does.\n */\n\nimport { B2SsrfError } from '../errors/index.ts'\n\n/** A URL allow-list that can be reconfigured after construction. */\nexport class UrlGuard {\n private allowedSuffixes: readonly string[] = []\n\n /**\n * Lock the guard to the given host suffixes. A suffix matches a host\n * either exactly or as a `*.suffix` subdomain. For example,\n * `backblazeb2.com` allows `api.backblazeb2.com` and\n * `s3.us-west-004.backblazeb2.com`.\n *\n * Passing an empty array disables the guard (used by the simulator and\n * other test setups). Production code should always lock the guard after\n * a successful `b2_authorize_account`.\n *\n * @param suffixes - Allowed host suffixes.\n */\n setAllowedSuffixes(suffixes: readonly string[]): void {\n this.allowedSuffixes = suffixes\n }\n\n /**\n * Returns the current allowed-suffix list (for tests and diagnostics).\n *\n * @returns The currently-configured list of allowed host suffixes.\n */\n getAllowedSuffixes(): readonly string[] {\n return this.allowedSuffixes\n }\n\n /**\n * Validate `rawUrl` against the allow-list. Throws {@link B2SsrfError} if\n * the URL points at a literal IP, a known-internal hostname, or a host\n * outside the allowed suffixes. Permissive (no-op) when no suffixes have\n * been configured yet.\n *\n * @param rawUrl - The URL the caller is about to fetch.\n *\n * @throws A `B2SsrfError` when the URL is rejected.\n */\n check(rawUrl: string): void {\n if (this.allowedSuffixes.length === 0) return\n\n let parsed: URL\n try {\n parsed = new URL(rawUrl)\n } catch {\n throw new B2SsrfError(`malformed URL rejected by SSRF guard: ${rawUrl}`, rawUrl)\n }\n\n const host = parsed.hostname.toLowerCase()\n\n if (isLiteralIp(host)) {\n throw new B2SsrfError(\n `literal IP host not allowed by SSRF guard (use a hostname): ${host}`,\n rawUrl,\n )\n }\n\n if (isInternalHostname(host)) {\n throw new B2SsrfError(`internal hostname not allowed by SSRF guard: ${host}`, rawUrl)\n }\n\n if (hostMatchesAnyAllowedSuffix(host, this.allowedSuffixes)) return\n\n throw new B2SsrfError(\n `host outside allowed B2 realm: ${host} (allowed suffixes: ${this.allowedSuffixes.join(', ')})`,\n rawUrl,\n )\n }\n}\n\n/**\n * Extract host suffixes to allow from a B2 authorize-account response.\n *\n * Known B2 realm hosts under `backblazeb2.com` are collapsed to that parent.\n * Unknown or custom realm hosts are used as scoped suffixes: the returned\n * hostname and its subdomains are allowed, but sibling hosts and parent\n * domains are not. This avoids accidentally trusting broad public suffixes\n * such as `co.uk`.\n *\n * Always includes `backblaze.com` because upload-pod URLs returned by\n * `b2_get_upload_url` use that parent domain (`pod-NNN-NNNN-NN.backblaze.com`)\n * rather than `backblazeb2.com`.\n *\n * @param storageApi - The `apiInfo.storageApi` portion of the authorize response.\n *\n * @returns Sorted list of unique host suffixes to allow.\n */\nexport function deriveAllowedSuffixes(storageApi: {\n apiUrl: string\n downloadUrl: string\n s3ApiUrl: string\n}): readonly string[] {\n const suffixes = new Set<string>(['backblaze.com'])\n for (const url of [storageApi.apiUrl, storageApi.downloadUrl, storageApi.s3ApiUrl]) {\n try {\n const host = new URL(url).hostname\n suffixes.add(\n host === 'backblazeb2.com' || host.endsWith('.backblazeb2.com') ? 'backblazeb2.com' : host,\n )\n } catch {\n // Skip malformed URLs. The auth response is from B2 itself; malformed\n // entries would already have caused other failures upstream.\n }\n }\n return Array.from(suffixes).sort()\n}\n\n/**\n * Checks a hostname against one allowed suffix using the SDK's exact-or-subdomain rule.\n *\n * @param hostname - URL hostname to check.\n * @param suffix - Domain suffix that may match exactly or as a parent domain.\n *\n * @returns Whether the hostname is exactly the suffix or a subdomain of it.\n */\nexport function hostMatchesAllowedSuffix(hostname: string, suffix: string): boolean {\n const host = hostname.toLowerCase()\n const lowered = suffix.toLowerCase()\n return host === lowered || host.endsWith(`.${lowered}`)\n}\n\n/**\n * Checks a hostname against an allowed-suffix list.\n *\n * @param hostname - URL hostname to check.\n * @param suffixes - Domain suffixes to test with the SDK's suffix-matching rule.\n *\n * @returns Whether any suffix matches the hostname.\n */\nexport function hostMatchesAnyAllowedSuffix(\n hostname: string,\n suffixes: readonly string[],\n): boolean {\n return suffixes.some((suffix) => hostMatchesAllowedSuffix(hostname, suffix))\n}\n\nfunction isLiteralIp(host: string): boolean {\n // IPv4 dotted quad: e.g. 169.254.169.254\n if (/^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(host)) return true\n // IPv6: URL.hostname strips the brackets but keeps colons. Any colon in a\n // hostname is an IPv6 literal.\n if (host.includes(':')) return true\n return false\n}\n\nfunction isInternalHostname(host: string): boolean {\n if (host === 'localhost') return true\n if (host.endsWith('.localhost')) return true\n if (host === 'metadata') return true\n if (host === 'metadata.google.internal') return true\n if (host.endsWith('.internal')) return true\n if (host.endsWith('.local')) return true\n return false\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAa,WAAb,MAAsB;CACpB,kBAA6C,CAAC;;;;;;;;;;;;;CAc9C,mBAAmB,UAAmC;EACpD,KAAK,kBAAkB;CACzB;;;;;;CAOA,qBAAwC;EACtC,OAAO,KAAK;CACd;;;;;;;;;;;CAYA,MAAM,QAAsB;EAC1B,IAAI,KAAK,gBAAgB,WAAW,GAAG;EAEvC,IAAI;EACJ,IAAI;GACF,SAAS,IAAI,IAAI,MAAM;EACzB,QAAQ;GACN,MAAM,IAAI,qBAAA,YAAY,yCAAyC,UAAU,MAAM;EACjF;EAEA,MAAM,OAAO,OAAO,SAAS,YAAY;EAEzC,IAAI,YAAY,IAAI,GAClB,MAAM,IAAI,qBAAA,YACR,+DAA+D,QAC/D,MACF;EAGF,IAAI,mBAAmB,IAAI,GACzB,MAAM,IAAI,qBAAA,YAAY,gDAAgD,QAAQ,MAAM;EAGtF,IAAI,4BAA4B,MAAM,KAAK,eAAe,GAAG;EAE7D,MAAM,IAAI,qBAAA,YACR,kCAAkC,KAAK,sBAAsB,KAAK,gBAAgB,KAAK,IAAI,EAAE,IAC7F,MACF;CACF;AACF;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,sBAAsB,YAIhB;CACpB,MAAM,2BAAW,IAAI,IAAY,CAAC,eAAe,CAAC;CAClD,KAAK,MAAM,OAAO;EAAC,WAAW;EAAQ,WAAW;EAAa,WAAW;CAAQ,GAC/E,IAAI;EACF,MAAM,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC;EAC1B,SAAS,IACP,SAAS,qBAAqB,KAAK,SAAS,kBAAkB,IAAI,oBAAoB,IACxF;CACF,QAAQ,CAGR;CAEF,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,KAAK;AACnC;;;;;;;;;AAUA,SAAgB,yBAAyB,UAAkB,QAAyB;CAClF,MAAM,OAAO,SAAS,YAAY;CAClC,MAAM,UAAU,OAAO,YAAY;CACnC,OAAO,SAAS,WAAW,KAAK,SAAS,IAAI,SAAS;AACxD;;;;;;;;;AAUA,SAAgB,4BACd,UACA,UACS;CACT,OAAO,SAAS,MAAM,WAAW,yBAAyB,UAAU,MAAM,CAAC;AAC7E;AAEA,SAAS,YAAY,MAAuB;CAE1C,IAAI,uCAAuC,KAAK,IAAI,GAAG,OAAO;CAG9D,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO;CAC/B,OAAO;AACT;AAEA,SAAS,mBAAmB,MAAuB;CACjD,IAAI,SAAS,aAAa,OAAO;CACjC,IAAI,KAAK,SAAS,YAAY,GAAG,OAAO;CACxC,IAAI,SAAS,YAAY,OAAO;CAChC,IAAI,SAAS,4BAA4B,OAAO;CAChD,IAAI,KAAK,SAAS,WAAW,GAAG,OAAO;CACvC,IAAI,KAAK,SAAS,QAAQ,GAAG,OAAO;CACpC,OAAO;AACT"}
@@ -70,9 +70,13 @@ export declare class UrlGuard {
70
70
  /**
71
71
  * Extract host suffixes to allow from a B2 authorize-account response.
72
72
  *
73
- * Derives the parent domain (last two labels) of each realm URL — so
74
- * `api.us-west-004.backblazeb2.com` becomes `backblazeb2.com`. Always
75
- * includes `backblaze.com` because upload-pod URLs returned by
73
+ * Known B2 realm hosts under `backblazeb2.com` are collapsed to that parent.
74
+ * Unknown or custom realm hosts are used as scoped suffixes: the returned
75
+ * hostname and its subdomains are allowed, but sibling hosts and parent
76
+ * domains are not. This avoids accidentally trusting broad public suffixes
77
+ * such as `co.uk`.
78
+ *
79
+ * Always includes `backblaze.com` because upload-pod URLs returned by
76
80
  * `b2_get_upload_url` use that parent domain (`pod-NNN-NNNN-NN.backblaze.com`)
77
81
  * rather than `backblazeb2.com`.
78
82
  *
@@ -85,4 +89,22 @@ export declare function deriveAllowedSuffixes(storageApi: {
85
89
  downloadUrl: string;
86
90
  s3ApiUrl: string;
87
91
  }): readonly string[];
92
+ /**
93
+ * Checks a hostname against one allowed suffix using the SDK's exact-or-subdomain rule.
94
+ *
95
+ * @param hostname - URL hostname to check.
96
+ * @param suffix - Domain suffix that may match exactly or as a parent domain.
97
+ *
98
+ * @returns Whether the hostname is exactly the suffix or a subdomain of it.
99
+ */
100
+ export declare function hostMatchesAllowedSuffix(hostname: string, suffix: string): boolean;
101
+ /**
102
+ * Checks a hostname against an allowed-suffix list.
103
+ *
104
+ * @param hostname - URL hostname to check.
105
+ * @param suffixes - Domain suffixes to test with the SDK's suffix-matching rule.
106
+ *
107
+ * @returns Whether any suffix matches the hostname.
108
+ */
109
+ export declare function hostMatchesAnyAllowedSuffix(hostname: string, suffixes: readonly string[]): boolean;
88
110
  //# sourceMappingURL=url-guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"url-guard.d.ts","sourceRoot":"","sources":["../../src/http/url-guard.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,oEAAoE;AACpE,qBAAa,QAAQ;IACnB,OAAO,CAAC,eAAe,CAAwB;IAE/C;;;;;;;;;;;OAWG;IACH,kBAAkB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAIrD;;;;OAIG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE;IAIvC;;;;;;;;;OASG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAiC5B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,SAAS,MAAM,EAAE,CAepB"}
1
+ {"version":3,"file":"url-guard.d.ts","sourceRoot":"","sources":["../../src/http/url-guard.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,oEAAoE;AACpE,qBAAa,QAAQ;IACnB,OAAO,CAAC,eAAe,CAAwB;IAE/C;;;;;;;;;;;OAWG;IACH,kBAAkB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAIrD;;;;OAIG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE;IAIvC;;;;;;;;;OASG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CA8B5B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,SAAS,MAAM,EAAE,CAcpB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAIlF;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAET"}
@@ -70,9 +70,13 @@ export declare class UrlGuard {
70
70
  /**
71
71
  * Extract host suffixes to allow from a B2 authorize-account response.
72
72
  *
73
- * Derives the parent domain (last two labels) of each realm URL — so
74
- * `api.us-west-004.backblazeb2.com` becomes `backblazeb2.com`. Always
75
- * includes `backblaze.com` because upload-pod URLs returned by
73
+ * Known B2 realm hosts under `backblazeb2.com` are collapsed to that parent.
74
+ * Unknown or custom realm hosts are used as scoped suffixes: the returned
75
+ * hostname and its subdomains are allowed, but sibling hosts and parent
76
+ * domains are not. This avoids accidentally trusting broad public suffixes
77
+ * such as `co.uk`.
78
+ *
79
+ * Always includes `backblaze.com` because upload-pod URLs returned by
76
80
  * `b2_get_upload_url` use that parent domain (`pod-NNN-NNNN-NN.backblaze.com`)
77
81
  * rather than `backblazeb2.com`.
78
82
  *
@@ -85,4 +89,22 @@ export declare function deriveAllowedSuffixes(storageApi: {
85
89
  downloadUrl: string;
86
90
  s3ApiUrl: string;
87
91
  }): readonly string[];
92
+ /**
93
+ * Checks a hostname against one allowed suffix using the SDK's exact-or-subdomain rule.
94
+ *
95
+ * @param hostname - URL hostname to check.
96
+ * @param suffix - Domain suffix that may match exactly or as a parent domain.
97
+ *
98
+ * @returns Whether the hostname is exactly the suffix or a subdomain of it.
99
+ */
100
+ export declare function hostMatchesAllowedSuffix(hostname: string, suffix: string): boolean;
101
+ /**
102
+ * Checks a hostname against an allowed-suffix list.
103
+ *
104
+ * @param hostname - URL hostname to check.
105
+ * @param suffixes - Domain suffixes to test with the SDK's suffix-matching rule.
106
+ *
107
+ * @returns Whether any suffix matches the hostname.
108
+ */
109
+ export declare function hostMatchesAnyAllowedSuffix(hostname: string, suffixes: readonly string[]): boolean;
88
110
  //# sourceMappingURL=url-guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"url-guard.d.ts","sourceRoot":"","sources":["../../src/http/url-guard.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,oEAAoE;AACpE,qBAAa,QAAQ;IACnB,OAAO,CAAC,eAAe,CAAwB;IAE/C;;;;;;;;;;;OAWG;IACH,kBAAkB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAIrD;;;;OAIG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE;IAIvC;;;;;;;;;OASG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAiC5B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,SAAS,MAAM,EAAE,CAepB"}
1
+ {"version":3,"file":"url-guard.d.ts","sourceRoot":"","sources":["../../src/http/url-guard.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,oEAAoE;AACpE,qBAAa,QAAQ;IACnB,OAAO,CAAC,eAAe,CAAwB;IAE/C;;;;;;;;;;;OAWG;IACH,kBAAkB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAIrD;;;;OAIG;IACH,kBAAkB,IAAI,SAAS,MAAM,EAAE;IAIvC;;;;;;;;;OASG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CA8B5B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,SAAS,MAAM,EAAE,CAcpB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAIlF;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAET"}