@backblaze-labs/b2-sdk 0.1.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 (512) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/LICENSE +21 -0
  3. package/README.md +664 -0
  4. package/dist/auth/account-info.d.cts +51 -0
  5. package/dist/auth/account-info.d.cts.map +1 -0
  6. package/dist/auth/account-info.d.ts +51 -0
  7. package/dist/auth/account-info.d.ts.map +1 -0
  8. package/dist/auth/file.cjs +210 -0
  9. package/dist/auth/file.cjs.map +1 -0
  10. package/dist/auth/file.d.cts +157 -0
  11. package/dist/auth/file.d.cts.map +1 -0
  12. package/dist/auth/file.d.ts +157 -0
  13. package/dist/auth/file.d.ts.map +1 -0
  14. package/dist/auth/file.js +210 -0
  15. package/dist/auth/file.js.map +1 -0
  16. package/dist/auth/in-memory.cjs +184 -0
  17. package/dist/auth/in-memory.cjs.map +1 -0
  18. package/dist/auth/in-memory.d.cts +147 -0
  19. package/dist/auth/in-memory.d.cts.map +1 -0
  20. package/dist/auth/in-memory.d.ts +147 -0
  21. package/dist/auth/in-memory.d.ts.map +1 -0
  22. package/dist/auth/in-memory.js +184 -0
  23. package/dist/auth/in-memory.js.map +1 -0
  24. package/dist/auth/index.cjs +10 -0
  25. package/dist/auth/index.cjs.map +1 -0
  26. package/dist/auth/index.d.cts +6 -0
  27. package/dist/auth/index.d.cts.map +1 -0
  28. package/dist/auth/index.d.ts +6 -0
  29. package/dist/auth/index.d.ts.map +1 -0
  30. package/dist/auth/index.js +10 -0
  31. package/dist/auth/index.js.map +1 -0
  32. package/dist/auth/realms.cjs +12 -0
  33. package/dist/auth/realms.cjs.map +1 -0
  34. package/dist/auth/realms.d.cts +12 -0
  35. package/dist/auth/realms.d.cts.map +1 -0
  36. package/dist/auth/realms.d.ts +12 -0
  37. package/dist/auth/realms.d.ts.map +1 -0
  38. package/dist/auth/realms.js +12 -0
  39. package/dist/auth/realms.js.map +1 -0
  40. package/dist/auth/upload-url-pool.cjs +52 -0
  41. package/dist/auth/upload-url-pool.cjs.map +1 -0
  42. package/dist/auth/upload-url-pool.d.cts +35 -0
  43. package/dist/auth/upload-url-pool.d.cts.map +1 -0
  44. package/dist/auth/upload-url-pool.d.ts +35 -0
  45. package/dist/auth/upload-url-pool.d.ts.map +1 -0
  46. package/dist/auth/upload-url-pool.js +52 -0
  47. package/dist/auth/upload-url-pool.js.map +1 -0
  48. package/dist/bucket.cjs +789 -0
  49. package/dist/bucket.cjs.map +1 -0
  50. package/dist/bucket.d.cts +659 -0
  51. package/dist/bucket.d.cts.map +1 -0
  52. package/dist/bucket.d.ts +659 -0
  53. package/dist/bucket.d.ts.map +1 -0
  54. package/dist/bucket.js +789 -0
  55. package/dist/bucket.js.map +1 -0
  56. package/dist/client.cjs +230 -0
  57. package/dist/client.cjs.map +1 -0
  58. package/dist/client.d.cts +222 -0
  59. package/dist/client.d.cts.map +1 -0
  60. package/dist/client.d.ts +222 -0
  61. package/dist/client.d.ts.map +1 -0
  62. package/dist/client.js +230 -0
  63. package/dist/client.js.map +1 -0
  64. package/dist/copy/large.cjs +74 -0
  65. package/dist/copy/large.cjs.map +1 -0
  66. package/dist/copy/large.d.cts +53 -0
  67. package/dist/copy/large.d.cts.map +1 -0
  68. package/dist/copy/large.d.ts +53 -0
  69. package/dist/copy/large.d.ts.map +1 -0
  70. package/dist/copy/large.js +74 -0
  71. package/dist/copy/large.js.map +1 -0
  72. package/dist/download/parallel.cjs +122 -0
  73. package/dist/download/parallel.cjs.map +1 -0
  74. package/dist/download/parallel.d.cts +42 -0
  75. package/dist/download/parallel.d.cts.map +1 -0
  76. package/dist/download/parallel.d.ts +42 -0
  77. package/dist/download/parallel.d.ts.map +1 -0
  78. package/dist/download/parallel.js +122 -0
  79. package/dist/download/parallel.js.map +1 -0
  80. package/dist/download/single.cjs +117 -0
  81. package/dist/download/single.cjs.map +1 -0
  82. package/dist/download/single.d.cts +152 -0
  83. package/dist/download/single.d.cts.map +1 -0
  84. package/dist/download/single.d.ts +152 -0
  85. package/dist/download/single.d.ts.map +1 -0
  86. package/dist/download/single.js +117 -0
  87. package/dist/download/single.js.map +1 -0
  88. package/dist/errors/index.cjs +268 -0
  89. package/dist/errors/index.cjs.map +1 -0
  90. package/dist/errors/index.d.cts +264 -0
  91. package/dist/errors/index.d.cts.map +1 -0
  92. package/dist/errors/index.d.ts +264 -0
  93. package/dist/errors/index.d.ts.map +1 -0
  94. package/dist/errors/index.js +268 -0
  95. package/dist/errors/index.js.map +1 -0
  96. package/dist/http/retry.cjs +36 -0
  97. package/dist/http/retry.cjs.map +1 -0
  98. package/dist/http/retry.d.cts +34 -0
  99. package/dist/http/retry.d.cts.map +1 -0
  100. package/dist/http/retry.d.ts +34 -0
  101. package/dist/http/retry.d.ts.map +1 -0
  102. package/dist/http/retry.js +36 -0
  103. package/dist/http/retry.js.map +1 -0
  104. package/dist/http/transport.cjs +143 -0
  105. package/dist/http/transport.cjs.map +1 -0
  106. package/dist/http/transport.d.cts +122 -0
  107. package/dist/http/transport.d.cts.map +1 -0
  108. package/dist/http/transport.d.ts +122 -0
  109. package/dist/http/transport.d.ts.map +1 -0
  110. package/dist/http/transport.js +143 -0
  111. package/dist/http/transport.js.map +1 -0
  112. package/dist/http/url-guard.cjs +97 -0
  113. package/dist/http/url-guard.cjs.map +1 -0
  114. package/dist/http/url-guard.d.cts +88 -0
  115. package/dist/http/url-guard.d.cts.map +1 -0
  116. package/dist/http/url-guard.d.ts +88 -0
  117. package/dist/http/url-guard.d.ts.map +1 -0
  118. package/dist/http/url-guard.js +97 -0
  119. package/dist/http/url-guard.js.map +1 -0
  120. package/dist/http/user-agent.cjs +51 -0
  121. package/dist/http/user-agent.cjs.map +1 -0
  122. package/dist/http/user-agent.d.cts +28 -0
  123. package/dist/http/user-agent.d.cts.map +1 -0
  124. package/dist/http/user-agent.d.ts +28 -0
  125. package/dist/http/user-agent.d.ts.map +1 -0
  126. package/dist/http/user-agent.js +51 -0
  127. package/dist/http/user-agent.js.map +1 -0
  128. package/dist/index.cjs +78 -0
  129. package/dist/index.cjs.map +1 -0
  130. package/dist/index.d.cts +39 -0
  131. package/dist/index.d.cts.map +1 -0
  132. package/dist/index.d.ts +39 -0
  133. package/dist/index.d.ts.map +1 -0
  134. package/dist/index.js +78 -0
  135. package/dist/index.js.map +1 -0
  136. package/dist/notifications/index.cjs +7 -0
  137. package/dist/notifications/index.cjs.map +1 -0
  138. package/dist/notifications/index.d.cts +3 -0
  139. package/dist/notifications/index.d.cts.map +1 -0
  140. package/dist/notifications/index.d.ts +3 -0
  141. package/dist/notifications/index.d.ts.map +1 -0
  142. package/dist/notifications/index.js +7 -0
  143. package/dist/notifications/index.js.map +1 -0
  144. package/dist/notifications/webhook.cjs +106 -0
  145. package/dist/notifications/webhook.cjs.map +1 -0
  146. package/dist/notifications/webhook.d.cts +146 -0
  147. package/dist/notifications/webhook.d.cts.map +1 -0
  148. package/dist/notifications/webhook.d.ts +146 -0
  149. package/dist/notifications/webhook.d.ts.map +1 -0
  150. package/dist/notifications/webhook.js +106 -0
  151. package/dist/notifications/webhook.js.map +1 -0
  152. package/dist/object.cjs +212 -0
  153. package/dist/object.cjs.map +1 -0
  154. package/dist/object.d.cts +263 -0
  155. package/dist/object.d.cts.map +1 -0
  156. package/dist/object.d.ts +263 -0
  157. package/dist/object.d.ts.map +1 -0
  158. package/dist/object.js +212 -0
  159. package/dist/object.js.map +1 -0
  160. package/dist/package.json.cjs +9 -0
  161. package/dist/package.json.cjs.map +1 -0
  162. package/dist/package.json.js +9 -0
  163. package/dist/package.json.js.map +1 -0
  164. package/dist/raw/encoding.cjs +47 -0
  165. package/dist/raw/encoding.cjs.map +1 -0
  166. package/dist/raw/encoding.d.cts +46 -0
  167. package/dist/raw/encoding.d.cts.map +1 -0
  168. package/dist/raw/encoding.d.ts +46 -0
  169. package/dist/raw/encoding.d.ts.map +1 -0
  170. package/dist/raw/encoding.js +47 -0
  171. package/dist/raw/encoding.js.map +1 -0
  172. package/dist/raw/index.cjs +600 -0
  173. package/dist/raw/index.cjs.map +1 -0
  174. package/dist/raw/index.d.cts +381 -0
  175. package/dist/raw/index.d.cts.map +1 -0
  176. package/dist/raw/index.d.ts +381 -0
  177. package/dist/raw/index.d.ts.map +1 -0
  178. package/dist/raw/index.js +601 -0
  179. package/dist/raw/index.js.map +1 -0
  180. package/dist/s3/index.cjs +23 -0
  181. package/dist/s3/index.cjs.map +1 -0
  182. package/dist/s3/index.d.cts +74 -0
  183. package/dist/s3/index.d.cts.map +1 -0
  184. package/dist/s3/index.d.ts +74 -0
  185. package/dist/s3/index.d.ts.map +1 -0
  186. package/dist/s3/index.js +23 -0
  187. package/dist/s3/index.js.map +1 -0
  188. package/dist/simulator/capabilities.cjs +53 -0
  189. package/dist/simulator/capabilities.cjs.map +1 -0
  190. package/dist/simulator/capabilities.d.cts +15 -0
  191. package/dist/simulator/capabilities.d.cts.map +1 -0
  192. package/dist/simulator/capabilities.d.ts +15 -0
  193. package/dist/simulator/capabilities.d.ts.map +1 -0
  194. package/dist/simulator/capabilities.js +53 -0
  195. package/dist/simulator/capabilities.js.map +1 -0
  196. package/dist/simulator/index.cjs +1559 -0
  197. package/dist/simulator/index.cjs.map +1 -0
  198. package/dist/simulator/index.d.cts +495 -0
  199. package/dist/simulator/index.d.cts.map +1 -0
  200. package/dist/simulator/index.d.ts +495 -0
  201. package/dist/simulator/index.d.ts.map +1 -0
  202. package/dist/simulator/index.js +1560 -0
  203. package/dist/simulator/index.js.map +1 -0
  204. package/dist/simulator/validation.cjs +174 -0
  205. package/dist/simulator/validation.cjs.map +1 -0
  206. package/dist/simulator/validation.d.cts +104 -0
  207. package/dist/simulator/validation.d.cts.map +1 -0
  208. package/dist/simulator/validation.d.ts +104 -0
  209. package/dist/simulator/validation.d.ts.map +1 -0
  210. package/dist/simulator/validation.js +174 -0
  211. package/dist/simulator/validation.js.map +1 -0
  212. package/dist/streams/collect.cjs +26 -0
  213. package/dist/streams/collect.cjs.map +1 -0
  214. package/dist/streams/collect.d.cts +18 -0
  215. package/dist/streams/collect.d.cts.map +1 -0
  216. package/dist/streams/collect.d.ts +18 -0
  217. package/dist/streams/collect.d.ts.map +1 -0
  218. package/dist/streams/collect.js +26 -0
  219. package/dist/streams/collect.js.map +1 -0
  220. package/dist/streams/hash.cjs +123 -0
  221. package/dist/streams/hash.cjs.map +1 -0
  222. package/dist/streams/hash.d.cts +42 -0
  223. package/dist/streams/hash.d.cts.map +1 -0
  224. package/dist/streams/hash.d.ts +42 -0
  225. package/dist/streams/hash.d.ts.map +1 -0
  226. package/dist/streams/hash.js +101 -0
  227. package/dist/streams/hash.js.map +1 -0
  228. package/dist/streams/index.cjs +13 -0
  229. package/dist/streams/index.cjs.map +1 -0
  230. package/dist/streams/index.d.cts +7 -0
  231. package/dist/streams/index.d.cts.map +1 -0
  232. package/dist/streams/index.d.ts +7 -0
  233. package/dist/streams/index.d.ts.map +1 -0
  234. package/dist/streams/index.js +13 -0
  235. package/dist/streams/index.js.map +1 -0
  236. package/dist/streams/progress.cjs +47 -0
  237. package/dist/streams/progress.cjs.map +1 -0
  238. package/dist/streams/progress.d.cts +53 -0
  239. package/dist/streams/progress.d.cts.map +1 -0
  240. package/dist/streams/progress.d.ts +53 -0
  241. package/dist/streams/progress.d.ts.map +1 -0
  242. package/dist/streams/progress.js +47 -0
  243. package/dist/streams/progress.js.map +1 -0
  244. package/dist/streams/source.cjs +155 -0
  245. package/dist/streams/source.cjs.map +1 -0
  246. package/dist/streams/source.d.cts +140 -0
  247. package/dist/streams/source.d.cts.map +1 -0
  248. package/dist/streams/source.d.ts +140 -0
  249. package/dist/streams/source.d.ts.map +1 -0
  250. package/dist/streams/source.js +155 -0
  251. package/dist/streams/source.js.map +1 -0
  252. package/dist/sync/actions/index.cjs +191 -0
  253. package/dist/sync/actions/index.cjs.map +1 -0
  254. package/dist/sync/actions/index.d.cts +167 -0
  255. package/dist/sync/actions/index.d.cts.map +1 -0
  256. package/dist/sync/actions/index.d.ts +167 -0
  257. package/dist/sync/actions/index.d.ts.map +1 -0
  258. package/dist/sync/actions/index.js +191 -0
  259. package/dist/sync/actions/index.js.map +1 -0
  260. package/dist/sync/index.cjs +23 -0
  261. package/dist/sync/index.cjs.map +1 -0
  262. package/dist/sync/index.d.cts +21 -0
  263. package/dist/sync/index.d.cts.map +1 -0
  264. package/dist/sync/index.d.ts +21 -0
  265. package/dist/sync/index.d.ts.map +1 -0
  266. package/dist/sync/index.js +23 -0
  267. package/dist/sync/index.js.map +1 -0
  268. package/dist/sync/pairing.cjs +31 -0
  269. package/dist/sync/pairing.cjs.map +1 -0
  270. package/dist/sync/pairing.d.cts +13 -0
  271. package/dist/sync/pairing.d.cts.map +1 -0
  272. package/dist/sync/pairing.d.ts +13 -0
  273. package/dist/sync/pairing.d.ts.map +1 -0
  274. package/dist/sync/pairing.js +31 -0
  275. package/dist/sync/pairing.js.map +1 -0
  276. package/dist/sync/policies/compare.cjs +14 -0
  277. package/dist/sync/policies/compare.cjs.map +1 -0
  278. package/dist/sync/policies/compare.d.cts +13 -0
  279. package/dist/sync/policies/compare.d.cts.map +1 -0
  280. package/dist/sync/policies/compare.d.ts +13 -0
  281. package/dist/sync/policies/compare.d.ts.map +1 -0
  282. package/dist/sync/policies/compare.js +14 -0
  283. package/dist/sync/policies/compare.js.map +1 -0
  284. package/dist/sync/policies/index.cjs +74 -0
  285. package/dist/sync/policies/index.cjs.map +1 -0
  286. package/dist/sync/policies/index.d.cts +42 -0
  287. package/dist/sync/policies/index.d.cts.map +1 -0
  288. package/dist/sync/policies/index.d.ts +42 -0
  289. package/dist/sync/policies/index.d.ts.map +1 -0
  290. package/dist/sync/policies/index.js +74 -0
  291. package/dist/sync/policies/index.js.map +1 -0
  292. package/dist/sync/scanners/b2.cjs +57 -0
  293. package/dist/sync/scanners/b2.cjs.map +1 -0
  294. package/dist/sync/scanners/b2.d.cts +20 -0
  295. package/dist/sync/scanners/b2.d.cts.map +1 -0
  296. package/dist/sync/scanners/b2.d.ts +20 -0
  297. package/dist/sync/scanners/b2.d.ts.map +1 -0
  298. package/dist/sync/scanners/b2.js +57 -0
  299. package/dist/sync/scanners/b2.js.map +1 -0
  300. package/dist/sync/scanners/local.cjs +58 -0
  301. package/dist/sync/scanners/local.cjs.map +1 -0
  302. package/dist/sync/scanners/local.d.cts +24 -0
  303. package/dist/sync/scanners/local.d.cts.map +1 -0
  304. package/dist/sync/scanners/local.d.ts +24 -0
  305. package/dist/sync/scanners/local.d.ts.map +1 -0
  306. package/dist/sync/scanners/local.js +58 -0
  307. package/dist/sync/scanners/local.js.map +1 -0
  308. package/dist/sync/synchronizer.cjs +203 -0
  309. package/dist/sync/synchronizer.cjs.map +1 -0
  310. package/dist/sync/synchronizer.d.cts +52 -0
  311. package/dist/sync/synchronizer.d.cts.map +1 -0
  312. package/dist/sync/synchronizer.d.ts +52 -0
  313. package/dist/sync/synchronizer.d.ts.map +1 -0
  314. package/dist/sync/synchronizer.js +181 -0
  315. package/dist/sync/synchronizer.js.map +1 -0
  316. package/dist/sync/types.d.cts +121 -0
  317. package/dist/sync/types.d.cts.map +1 -0
  318. package/dist/sync/types.d.ts +121 -0
  319. package/dist/sync/types.d.ts.map +1 -0
  320. package/dist/types/auth.cjs +58 -0
  321. package/dist/types/auth.cjs.map +1 -0
  322. package/dist/types/auth.d.cts +150 -0
  323. package/dist/types/auth.d.cts.map +1 -0
  324. package/dist/types/auth.d.ts +150 -0
  325. package/dist/types/auth.d.ts.map +1 -0
  326. package/dist/types/auth.js +58 -0
  327. package/dist/types/auth.js.map +1 -0
  328. package/dist/types/bucket.cjs +44 -0
  329. package/dist/types/bucket.cjs.map +1 -0
  330. package/dist/types/bucket.d.cts +245 -0
  331. package/dist/types/bucket.d.cts.map +1 -0
  332. package/dist/types/bucket.d.ts +245 -0
  333. package/dist/types/bucket.d.ts.map +1 -0
  334. package/dist/types/bucket.js +44 -0
  335. package/dist/types/bucket.js.map +1 -0
  336. package/dist/types/download.d.cts +110 -0
  337. package/dist/types/download.d.cts.map +1 -0
  338. package/dist/types/download.d.ts +110 -0
  339. package/dist/types/download.d.ts.map +1 -0
  340. package/dist/types/encryption.cjs +343 -0
  341. package/dist/types/encryption.cjs.map +1 -0
  342. package/dist/types/encryption.d.cts +132 -0
  343. package/dist/types/encryption.d.cts.map +1 -0
  344. package/dist/types/encryption.d.ts +132 -0
  345. package/dist/types/encryption.d.ts.map +1 -0
  346. package/dist/types/encryption.js +321 -0
  347. package/dist/types/encryption.js.map +1 -0
  348. package/dist/types/errors.d.cts +16 -0
  349. package/dist/types/errors.d.cts.map +1 -0
  350. package/dist/types/errors.d.ts +16 -0
  351. package/dist/types/errors.d.ts.map +1 -0
  352. package/dist/types/file.cjs +23 -0
  353. package/dist/types/file.cjs.map +1 -0
  354. package/dist/types/file.d.cts +220 -0
  355. package/dist/types/file.d.cts.map +1 -0
  356. package/dist/types/file.d.ts +220 -0
  357. package/dist/types/file.d.ts.map +1 -0
  358. package/dist/types/file.js +23 -0
  359. package/dist/types/file.js.map +1 -0
  360. package/dist/types/ids.cjs +27 -0
  361. package/dist/types/ids.cjs.map +1 -0
  362. package/dist/types/ids.d.cts +84 -0
  363. package/dist/types/ids.d.cts.map +1 -0
  364. package/dist/types/ids.d.ts +84 -0
  365. package/dist/types/ids.d.ts.map +1 -0
  366. package/dist/types/ids.js +27 -0
  367. package/dist/types/ids.js.map +1 -0
  368. package/dist/types/index.d.cts +25 -0
  369. package/dist/types/index.d.cts.map +1 -0
  370. package/dist/types/index.d.ts +25 -0
  371. package/dist/types/index.d.ts.map +1 -0
  372. package/dist/types/key.d.cts +84 -0
  373. package/dist/types/key.d.cts.map +1 -0
  374. package/dist/types/key.d.ts +84 -0
  375. package/dist/types/key.d.ts.map +1 -0
  376. package/dist/types/lock.cjs +17 -0
  377. package/dist/types/lock.cjs.map +1 -0
  378. package/dist/types/lock.d.cts +92 -0
  379. package/dist/types/lock.d.cts.map +1 -0
  380. package/dist/types/lock.d.ts +92 -0
  381. package/dist/types/lock.d.ts.map +1 -0
  382. package/dist/types/lock.js +17 -0
  383. package/dist/types/lock.js.map +1 -0
  384. package/dist/types/notifications.cjs +24 -0
  385. package/dist/types/notifications.cjs.map +1 -0
  386. package/dist/types/notifications.d.cts +97 -0
  387. package/dist/types/notifications.d.cts.map +1 -0
  388. package/dist/types/notifications.d.ts +97 -0
  389. package/dist/types/notifications.d.ts.map +1 -0
  390. package/dist/types/notifications.js +24 -0
  391. package/dist/types/notifications.js.map +1 -0
  392. package/dist/types/replication.d.cts +42 -0
  393. package/dist/types/replication.d.cts.map +1 -0
  394. package/dist/types/replication.d.ts +42 -0
  395. package/dist/types/replication.d.ts.map +1 -0
  396. package/dist/types/upload.d.cts +219 -0
  397. package/dist/types/upload.d.cts.map +1 -0
  398. package/dist/types/upload.d.ts +219 -0
  399. package/dist/types/upload.d.ts.map +1 -0
  400. package/dist/upload/cancel.cjs +10 -0
  401. package/dist/upload/cancel.cjs.map +1 -0
  402. package/dist/upload/cancel.d.cts +23 -0
  403. package/dist/upload/cancel.d.cts.map +1 -0
  404. package/dist/upload/cancel.d.ts +23 -0
  405. package/dist/upload/cancel.d.ts.map +1 -0
  406. package/dist/upload/cancel.js +10 -0
  407. package/dist/upload/cancel.js.map +1 -0
  408. package/dist/upload/concurrency.cjs +54 -0
  409. package/dist/upload/concurrency.cjs.map +1 -0
  410. package/dist/upload/concurrency.d.cts +46 -0
  411. package/dist/upload/concurrency.d.cts.map +1 -0
  412. package/dist/upload/concurrency.d.ts +46 -0
  413. package/dist/upload/concurrency.d.ts.map +1 -0
  414. package/dist/upload/concurrency.js +54 -0
  415. package/dist/upload/concurrency.js.map +1 -0
  416. package/dist/upload/large.cjs +222 -0
  417. package/dist/upload/large.cjs.map +1 -0
  418. package/dist/upload/large.d.cts +71 -0
  419. package/dist/upload/large.d.cts.map +1 -0
  420. package/dist/upload/large.d.ts +71 -0
  421. package/dist/upload/large.d.ts.map +1 -0
  422. package/dist/upload/large.js +222 -0
  423. package/dist/upload/large.js.map +1 -0
  424. package/dist/upload/resume.cjs +34 -0
  425. package/dist/upload/resume.cjs.map +1 -0
  426. package/dist/upload/resume.d.cts +33 -0
  427. package/dist/upload/resume.d.cts.map +1 -0
  428. package/dist/upload/resume.d.ts +33 -0
  429. package/dist/upload/resume.d.ts.map +1 -0
  430. package/dist/upload/resume.js +34 -0
  431. package/dist/upload/resume.js.map +1 -0
  432. package/dist/upload/single.cjs +47 -0
  433. package/dist/upload/single.cjs.map +1 -0
  434. package/dist/upload/single.d.cts +51 -0
  435. package/dist/upload/single.d.cts.map +1 -0
  436. package/dist/upload/single.d.ts +51 -0
  437. package/dist/upload/single.d.ts.map +1 -0
  438. package/dist/upload/single.js +47 -0
  439. package/dist/upload/single.js.map +1 -0
  440. package/dist/upload/stream.cjs +198 -0
  441. package/dist/upload/stream.cjs.map +1 -0
  442. package/dist/upload/stream.d.cts +61 -0
  443. package/dist/upload/stream.d.cts.map +1 -0
  444. package/dist/upload/stream.d.ts +61 -0
  445. package/dist/upload/stream.d.ts.map +1 -0
  446. package/dist/upload/stream.js +198 -0
  447. package/dist/upload/stream.js.map +1 -0
  448. package/dist/util/best-effort.cjs +10 -0
  449. package/dist/util/best-effort.cjs.map +1 -0
  450. package/dist/util/best-effort.d.cts +31 -0
  451. package/dist/util/best-effort.d.cts.map +1 -0
  452. package/dist/util/best-effort.d.ts +31 -0
  453. package/dist/util/best-effort.d.ts.map +1 -0
  454. package/dist/util/best-effort.js +10 -0
  455. package/dist/util/best-effort.js.map +1 -0
  456. package/dist/util/defaults.cjs +11 -0
  457. package/dist/util/defaults.cjs.map +1 -0
  458. package/dist/util/defaults.d.cts +54 -0
  459. package/dist/util/defaults.d.cts.map +1 -0
  460. package/dist/util/defaults.d.ts +54 -0
  461. package/dist/util/defaults.d.ts.map +1 -0
  462. package/dist/util/defaults.js +11 -0
  463. package/dist/util/defaults.js.map +1 -0
  464. package/dist/util/normalize.cjs +16 -0
  465. package/dist/util/normalize.cjs.map +1 -0
  466. package/dist/util/normalize.d.cts +62 -0
  467. package/dist/util/normalize.d.cts.map +1 -0
  468. package/dist/util/normalize.d.ts +62 -0
  469. package/dist/util/normalize.d.ts.map +1 -0
  470. package/dist/util/normalize.js +16 -0
  471. package/dist/util/normalize.js.map +1 -0
  472. package/dist/util/paginator.cjs +20 -0
  473. package/dist/util/paginator.cjs.map +1 -0
  474. package/dist/util/paginator.d.cts +120 -0
  475. package/dist/util/paginator.d.cts.map +1 -0
  476. package/dist/util/paginator.d.ts +120 -0
  477. package/dist/util/paginator.d.ts.map +1 -0
  478. package/dist/util/paginator.js +20 -0
  479. package/dist/util/paginator.js.map +1 -0
  480. package/dist/util/plan-ranges.cjs +28 -0
  481. package/dist/util/plan-ranges.cjs.map +1 -0
  482. package/dist/util/plan-ranges.d.cts +56 -0
  483. package/dist/util/plan-ranges.d.cts.map +1 -0
  484. package/dist/util/plan-ranges.d.ts +56 -0
  485. package/dist/util/plan-ranges.d.ts.map +1 -0
  486. package/dist/util/plan-ranges.js +28 -0
  487. package/dist/util/plan-ranges.js.map +1 -0
  488. package/dist/util/text-codec.cjs +7 -0
  489. package/dist/util/text-codec.cjs.map +1 -0
  490. package/dist/util/text-codec.d.cts +26 -0
  491. package/dist/util/text-codec.d.cts.map +1 -0
  492. package/dist/util/text-codec.d.ts +26 -0
  493. package/dist/util/text-codec.d.ts.map +1 -0
  494. package/dist/util/text-codec.js +7 -0
  495. package/dist/util/text-codec.js.map +1 -0
  496. package/dist/util/to-error.cjs +7 -0
  497. package/dist/util/to-error.cjs.map +1 -0
  498. package/dist/util/to-error.d.cts +15 -0
  499. package/dist/util/to-error.d.cts.map +1 -0
  500. package/dist/util/to-error.d.ts +15 -0
  501. package/dist/util/to-error.d.ts.map +1 -0
  502. package/dist/util/to-error.js +7 -0
  503. package/dist/util/to-error.js.map +1 -0
  504. package/dist/version.cjs +6 -0
  505. package/dist/version.cjs.map +1 -0
  506. package/dist/version.d.cts +14 -0
  507. package/dist/version.d.cts.map +1 -0
  508. package/dist/version.d.ts +14 -0
  509. package/dist/version.d.ts.map +1 -0
  510. package/dist/version.js +6 -0
  511. package/dist/version.js.map +1 -0
  512. package/package.json +198 -0
@@ -0,0 +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;"}
@@ -0,0 +1,97 @@
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
+ }
67
+ 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();
80
+ }
81
+ 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;
85
+ }
86
+ 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;
94
+ }
95
+ exports.UrlGuard = UrlGuard;
96
+ exports.deriveAllowedSuffixes = deriveAllowedSuffixes;
97
+ //# sourceMappingURL=url-guard.cjs.map
@@ -0,0 +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;;;"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * URL allow-list guard. Defends against SSRF / URL-substitution attacks where
3
+ * a compromised or hostile B2 endpoint returns an upload URL pointing at an
4
+ * internal service (e.g. cloud metadata at `169.254.169.254`).
5
+ *
6
+ * The guard is built once per `B2Client` and updated by `B2Client.authorize()`.
7
+ * Before authorization it is permissive (so the very first
8
+ * `b2_authorize_account` request, whose URL the user configured, can succeed).
9
+ * After authorization it is locked to host suffixes derived from the realm's
10
+ * apiUrl/downloadUrl/s3ApiUrl, plus the well-known B2 upload-pod parent
11
+ * domain `backblaze.com`.
12
+ *
13
+ * The guard runs in `FetchTransport` before any outgoing request. It rejects:
14
+ *
15
+ * 1. Literal IPv4/IPv6 addresses (defense in depth, covers attempts to
16
+ * bypass DNS-based checks with raw IPs).
17
+ * 2. Well-known internal hostnames (`localhost`, `metadata`,
18
+ * `metadata.google.internal`, `.internal`, `.local`).
19
+ * 3. Hosts not matching any allowed suffix once the SDK is locked.
20
+ *
21
+ * Users supplying a custom `transport` to `B2Client` bypass the guard. That
22
+ * is their responsibility to document for their threat model.
23
+ *
24
+ * Threat-model note: the guard checks the URL's hostname before the
25
+ * `fetch()` call. It does NOT pin the resolved IP. A DNS rebinding attack
26
+ * could in principle resolve a permitted hostname to an internal IP between
27
+ * the guard's check and `fetch()`'s own resolution. This is theoretical
28
+ * against B2 because the allow-list is locked to a small set of stable
29
+ * Backblaze hostnames (the realm's apiUrl/downloadUrl/s3ApiUrl plus the
30
+ * `backblaze.com` parent), and DNS rebinding requires a hostname under
31
+ * attacker control. Defense in depth — pinning the IP from the first
32
+ * resolution and rejecting subsequent mismatches — would break legitimate
33
+ * CDN failovers and is not justified at this surface area. If your
34
+ * threat model requires it, supply a custom transport that does.
35
+ */
36
+ /** A URL allow-list that can be reconfigured after construction. */
37
+ export declare class UrlGuard {
38
+ private allowedSuffixes;
39
+ /**
40
+ * Lock the guard to the given host suffixes. A suffix matches a host
41
+ * either exactly or as a `*.suffix` subdomain. For example,
42
+ * `backblazeb2.com` allows `api.backblazeb2.com` and
43
+ * `s3.us-west-004.backblazeb2.com`.
44
+ *
45
+ * Passing an empty array disables the guard (used by the simulator and
46
+ * other test setups). Production code should always lock the guard after
47
+ * a successful `b2_authorize_account`.
48
+ *
49
+ * @param suffixes - Allowed host suffixes.
50
+ */
51
+ setAllowedSuffixes(suffixes: readonly string[]): void;
52
+ /**
53
+ * Returns the current allowed-suffix list (for tests and diagnostics).
54
+ *
55
+ * @returns The currently-configured list of allowed host suffixes.
56
+ */
57
+ getAllowedSuffixes(): readonly string[];
58
+ /**
59
+ * Validate `rawUrl` against the allow-list. Throws {@link B2SsrfError} if
60
+ * the URL points at a literal IP, a known-internal hostname, or a host
61
+ * outside the allowed suffixes. Permissive (no-op) when no suffixes have
62
+ * been configured yet.
63
+ *
64
+ * @param rawUrl - The URL the caller is about to fetch.
65
+ *
66
+ * @throws A `B2SsrfError` when the URL is rejected.
67
+ */
68
+ check(rawUrl: string): void;
69
+ }
70
+ /**
71
+ * Extract host suffixes to allow from a B2 authorize-account response.
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
76
+ * `b2_get_upload_url` use that parent domain (`pod-NNN-NNNN-NN.backblaze.com`)
77
+ * rather than `backblazeb2.com`.
78
+ *
79
+ * @param storageApi - The `apiInfo.storageApi` portion of the authorize response.
80
+ *
81
+ * @returns Sorted list of unique host suffixes to allow.
82
+ */
83
+ export declare function deriveAllowedSuffixes(storageApi: {
84
+ apiUrl: string;
85
+ downloadUrl: string;
86
+ s3ApiUrl: string;
87
+ }): readonly string[];
88
+ //# sourceMappingURL=url-guard.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * URL allow-list guard. Defends against SSRF / URL-substitution attacks where
3
+ * a compromised or hostile B2 endpoint returns an upload URL pointing at an
4
+ * internal service (e.g. cloud metadata at `169.254.169.254`).
5
+ *
6
+ * The guard is built once per `B2Client` and updated by `B2Client.authorize()`.
7
+ * Before authorization it is permissive (so the very first
8
+ * `b2_authorize_account` request, whose URL the user configured, can succeed).
9
+ * After authorization it is locked to host suffixes derived from the realm's
10
+ * apiUrl/downloadUrl/s3ApiUrl, plus the well-known B2 upload-pod parent
11
+ * domain `backblaze.com`.
12
+ *
13
+ * The guard runs in `FetchTransport` before any outgoing request. It rejects:
14
+ *
15
+ * 1. Literal IPv4/IPv6 addresses (defense in depth, covers attempts to
16
+ * bypass DNS-based checks with raw IPs).
17
+ * 2. Well-known internal hostnames (`localhost`, `metadata`,
18
+ * `metadata.google.internal`, `.internal`, `.local`).
19
+ * 3. Hosts not matching any allowed suffix once the SDK is locked.
20
+ *
21
+ * Users supplying a custom `transport` to `B2Client` bypass the guard. That
22
+ * is their responsibility to document for their threat model.
23
+ *
24
+ * Threat-model note: the guard checks the URL's hostname before the
25
+ * `fetch()` call. It does NOT pin the resolved IP. A DNS rebinding attack
26
+ * could in principle resolve a permitted hostname to an internal IP between
27
+ * the guard's check and `fetch()`'s own resolution. This is theoretical
28
+ * against B2 because the allow-list is locked to a small set of stable
29
+ * Backblaze hostnames (the realm's apiUrl/downloadUrl/s3ApiUrl plus the
30
+ * `backblaze.com` parent), and DNS rebinding requires a hostname under
31
+ * attacker control. Defense in depth — pinning the IP from the first
32
+ * resolution and rejecting subsequent mismatches — would break legitimate
33
+ * CDN failovers and is not justified at this surface area. If your
34
+ * threat model requires it, supply a custom transport that does.
35
+ */
36
+ /** A URL allow-list that can be reconfigured after construction. */
37
+ export declare class UrlGuard {
38
+ private allowedSuffixes;
39
+ /**
40
+ * Lock the guard to the given host suffixes. A suffix matches a host
41
+ * either exactly or as a `*.suffix` subdomain. For example,
42
+ * `backblazeb2.com` allows `api.backblazeb2.com` and
43
+ * `s3.us-west-004.backblazeb2.com`.
44
+ *
45
+ * Passing an empty array disables the guard (used by the simulator and
46
+ * other test setups). Production code should always lock the guard after
47
+ * a successful `b2_authorize_account`.
48
+ *
49
+ * @param suffixes - Allowed host suffixes.
50
+ */
51
+ setAllowedSuffixes(suffixes: readonly string[]): void;
52
+ /**
53
+ * Returns the current allowed-suffix list (for tests and diagnostics).
54
+ *
55
+ * @returns The currently-configured list of allowed host suffixes.
56
+ */
57
+ getAllowedSuffixes(): readonly string[];
58
+ /**
59
+ * Validate `rawUrl` against the allow-list. Throws {@link B2SsrfError} if
60
+ * the URL points at a literal IP, a known-internal hostname, or a host
61
+ * outside the allowed suffixes. Permissive (no-op) when no suffixes have
62
+ * been configured yet.
63
+ *
64
+ * @param rawUrl - The URL the caller is about to fetch.
65
+ *
66
+ * @throws A `B2SsrfError` when the URL is rejected.
67
+ */
68
+ check(rawUrl: string): void;
69
+ }
70
+ /**
71
+ * Extract host suffixes to allow from a B2 authorize-account response.
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
76
+ * `b2_get_upload_url` use that parent domain (`pod-NNN-NNNN-NN.backblaze.com`)
77
+ * rather than `backblazeb2.com`.
78
+ *
79
+ * @param storageApi - The `apiInfo.storageApi` portion of the authorize response.
80
+ *
81
+ * @returns Sorted list of unique host suffixes to allow.
82
+ */
83
+ export declare function deriveAllowedSuffixes(storageApi: {
84
+ apiUrl: string;
85
+ downloadUrl: string;
86
+ s3ApiUrl: string;
87
+ }): readonly string[];
88
+ //# sourceMappingURL=url-guard.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,97 @@
1
+ import { B2SsrfError } from "../errors/index.js";
2
+ class UrlGuard {
3
+ allowedSuffixes = [];
4
+ /**
5
+ * Lock the guard to the given host suffixes. A suffix matches a host
6
+ * either exactly or as a `*.suffix` subdomain. For example,
7
+ * `backblazeb2.com` allows `api.backblazeb2.com` and
8
+ * `s3.us-west-004.backblazeb2.com`.
9
+ *
10
+ * Passing an empty array disables the guard (used by the simulator and
11
+ * other test setups). Production code should always lock the guard after
12
+ * a successful `b2_authorize_account`.
13
+ *
14
+ * @param suffixes - Allowed host suffixes.
15
+ */
16
+ setAllowedSuffixes(suffixes) {
17
+ this.allowedSuffixes = suffixes;
18
+ }
19
+ /**
20
+ * Returns the current allowed-suffix list (for tests and diagnostics).
21
+ *
22
+ * @returns The currently-configured list of allowed host suffixes.
23
+ */
24
+ getAllowedSuffixes() {
25
+ return this.allowedSuffixes;
26
+ }
27
+ /**
28
+ * Validate `rawUrl` against the allow-list. Throws {@link B2SsrfError} if
29
+ * the URL points at a literal IP, a known-internal hostname, or a host
30
+ * outside the allowed suffixes. Permissive (no-op) when no suffixes have
31
+ * been configured yet.
32
+ *
33
+ * @param rawUrl - The URL the caller is about to fetch.
34
+ *
35
+ * @throws A `B2SsrfError` when the URL is rejected.
36
+ */
37
+ check(rawUrl) {
38
+ if (this.allowedSuffixes.length === 0) return;
39
+ let parsed;
40
+ try {
41
+ parsed = new URL(rawUrl);
42
+ } catch {
43
+ throw new B2SsrfError(`malformed URL rejected by SSRF guard: ${rawUrl}`, rawUrl);
44
+ }
45
+ const host = parsed.hostname.toLowerCase();
46
+ if (isLiteralIp(host)) {
47
+ throw new B2SsrfError(
48
+ `literal IP host not allowed by SSRF guard (use a hostname): ${host}`,
49
+ rawUrl
50
+ );
51
+ }
52
+ if (isInternalHostname(host)) {
53
+ throw new B2SsrfError(`internal hostname not allowed by SSRF guard: ${host}`, rawUrl);
54
+ }
55
+ for (const suffix of this.allowedSuffixes) {
56
+ const lowered = suffix.toLowerCase();
57
+ if (host === lowered || host.endsWith(`.${lowered}`)) return;
58
+ }
59
+ throw new B2SsrfError(
60
+ `host outside allowed B2 realm: ${host} (allowed suffixes: ${this.allowedSuffixes.join(", ")})`,
61
+ rawUrl
62
+ );
63
+ }
64
+ }
65
+ function deriveAllowedSuffixes(storageApi) {
66
+ const suffixes = /* @__PURE__ */ new Set(["backblaze.com"]);
67
+ for (const url of [storageApi.apiUrl, storageApi.downloadUrl, storageApi.s3ApiUrl]) {
68
+ try {
69
+ const host = new URL(url).hostname;
70
+ const parts = host.split(".");
71
+ if (parts.length >= 2) {
72
+ suffixes.add(parts.slice(-2).join("."));
73
+ }
74
+ } catch {
75
+ }
76
+ }
77
+ return Array.from(suffixes).sort();
78
+ }
79
+ function isLiteralIp(host) {
80
+ if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host)) return true;
81
+ if (host.includes(":")) return true;
82
+ return false;
83
+ }
84
+ function isInternalHostname(host) {
85
+ if (host === "localhost") return true;
86
+ if (host.endsWith(".localhost")) return true;
87
+ if (host === "metadata") return true;
88
+ if (host === "metadata.google.internal") return true;
89
+ if (host.endsWith(".internal")) return true;
90
+ if (host.endsWith(".local")) return true;
91
+ return false;
92
+ }
93
+ export {
94
+ UrlGuard,
95
+ deriveAllowedSuffixes
96
+ };
97
+ //# sourceMappingURL=url-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-guard.js","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":[],"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,IAAI,YAAY,yCAAyC,MAAM,IAAI,MAAM;AAAA,IACjF;AAEA,UAAM,OAAO,OAAO,SAAS,YAAA;AAE7B,QAAI,YAAY,IAAI,GAAG;AACrB,YAAM,IAAI;AAAA,QACR,+DAA+D,IAAI;AAAA,QACnE;AAAA,MAAA;AAAA,IAEJ;AAEA,QAAI,mBAAmB,IAAI,GAAG;AAC5B,YAAM,IAAI,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,IAAI;AAAA,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;"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const version = require("../version.cjs");
4
+ const SDK_PRODUCT = "b2-sdk-typescript";
5
+ const SDK_PACKAGE = "@backblaze-labs/b2-sdk";
6
+ function detectPlatform() {
7
+ const g = globalThis;
8
+ if (typeof g["Deno"] !== "undefined") {
9
+ const deno = g["Deno"];
10
+ return {
11
+ runtime: deno.version?.deno ? `deno/${deno.version.deno}` : "deno",
12
+ os: deno.build?.os,
13
+ arch: deno.build?.arch
14
+ };
15
+ }
16
+ if (typeof g["Bun"] !== "undefined") {
17
+ const bun = g["Bun"];
18
+ const proc = g["process"];
19
+ return {
20
+ runtime: bun.version ? `bun/${bun.version}` : "bun",
21
+ os: proc?.platform,
22
+ arch: proc?.arch
23
+ };
24
+ }
25
+ if (typeof g["process"] !== "undefined") {
26
+ const proc = g["process"];
27
+ if (proc.versions?.node) {
28
+ return {
29
+ runtime: `node/${proc.versions.node}`,
30
+ os: proc.platform,
31
+ arch: proc.arch
32
+ };
33
+ }
34
+ }
35
+ if (typeof g["navigator"] !== "undefined") {
36
+ return { runtime: "browser", os: void 0, arch: void 0 };
37
+ }
38
+ return { runtime: "unknown", os: void 0, arch: void 0 };
39
+ }
40
+ function getUserAgent(custom) {
41
+ const { runtime, os, arch } = detectPlatform();
42
+ const parts = ["typescript", SDK_PACKAGE, runtime];
43
+ if (os !== void 0) parts.push(os);
44
+ if (arch !== void 0) parts.push(arch);
45
+ const base = `${SDK_PRODUCT}/${version.VERSION} (${parts.join("; ")})`;
46
+ return custom ? `${custom} ${base}` : base;
47
+ }
48
+ exports.SDK_PACKAGE = SDK_PACKAGE;
49
+ exports.SDK_PRODUCT = SDK_PRODUCT;
50
+ exports.getUserAgent = getUserAgent;
51
+ //# sourceMappingURL=user-agent.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-agent.cjs","sources":["../../src/http/user-agent.ts"],"sourcesContent":["import { VERSION } from '../version.ts'\n\n/**\n * Stable identifier Backblaze can grep server logs for to find every request\n * issued by this SDK regardless of how the User-Agent comment evolves.\n * Treat as part of the public contract: do NOT rename without coordinating.\n */\nexport const SDK_PRODUCT = 'b2-sdk-typescript'\n\n/**\n * The npm package name. Embedded in the User-Agent comment alongside\n * {@link SDK_PRODUCT} so log queries that grep on either token work.\n */\nexport const SDK_PACKAGE = '@backblaze-labs/b2-sdk'\n\n/** Detected runtime + OS information for the User-Agent comment. */\ninterface Platform {\n /** Runtime identifier: `node/<x.y.z>`, `bun/<x.y.z>`, `deno/<x.y.z>`, `browser`, or `unknown`. */\n readonly runtime: string\n /** OS identifier (`linux`, `darwin`, `win32`, etc.) when available, or `undefined`. */\n readonly os: string | undefined\n /** CPU architecture (`x64`, `arm64`, etc.) when available, or `undefined`. */\n readonly arch: string | undefined\n}\n\n/**\n * Best-effort detection of the JS runtime and host OS. Used to populate the\n * User-Agent comment so server-side logs can spot Bun/Deno adoption and\n * triage OS-specific issues without asking for a repro environment.\n *\n * @returns The detected runtime, OS, and architecture tokens.\n */\nfunction detectPlatform(): Platform {\n const g = globalThis as Record<string, unknown>\n\n // Deno: Deno.version.deno, Deno.build.{os,arch}.\n if (typeof g['Deno'] !== 'undefined') {\n const deno = g['Deno'] as {\n version?: { deno?: string }\n build?: { os?: string; arch?: string }\n }\n return {\n runtime: deno.version?.deno ? `deno/${deno.version.deno}` : 'deno',\n os: deno.build?.os,\n arch: deno.build?.arch,\n }\n }\n\n // Bun: globalThis.Bun.version, process.platform/arch (Bun is Node-compat).\n if (typeof g['Bun'] !== 'undefined') {\n const bun = g['Bun'] as { version?: string }\n const proc = g['process'] as { platform?: string; arch?: string } | undefined\n return {\n runtime: bun.version ? `bun/${bun.version}` : 'bun',\n os: proc?.platform,\n arch: proc?.arch,\n }\n }\n\n // Node: process.versions.node, process.platform, process.arch.\n if (typeof g['process'] !== 'undefined') {\n const proc = g['process'] as {\n versions?: { node?: string }\n platform?: string\n arch?: string\n }\n if (proc.versions?.node) {\n return {\n runtime: `node/${proc.versions.node}`,\n os: proc.platform,\n arch: proc.arch,\n }\n }\n }\n\n // Browsers (and other navigator-only runtimes like Cloudflare Workers,\n // Vercel Edge, etc.). We deliberately do NOT parse the existing navigator\n // UA — that's noisy, often spoofed, and the request layer already adds the\n // browser's own UA on top.\n if (typeof g['navigator'] !== 'undefined') {\n return { runtime: 'browser', os: undefined, arch: undefined }\n }\n\n return { runtime: 'unknown', os: undefined, arch: undefined }\n}\n\n/**\n * Build the User-Agent header value the SDK sends on every B2 request.\n *\n * The product token, npm package name, language label, runtime, OS, and\n * architecture are emitted in that order, separated by semicolons inside a\n * single parenthesised comment block. OS and architecture are omitted on\n * runtimes that don't expose them (notably browsers). A custom prefix passed\n * via `B2ClientOptions.userAgent` is prepended verbatim so app-level\n * identifiers come first. See the README \"Identifying your traffic\" section\n * for examples.\n *\n * @param custom - Optional prefix prepended to the default User-Agent.\n *\n * @returns The formatted User-Agent header string.\n */\nexport function getUserAgent(custom?: string): string {\n const { runtime, os, arch } = detectPlatform()\n const parts = ['typescript', SDK_PACKAGE, runtime]\n if (os !== undefined) parts.push(os)\n if (arch !== undefined) parts.push(arch)\n const base = `${SDK_PRODUCT}/${VERSION} (${parts.join('; ')})`\n return custom ? `${custom} ${base}` : base\n}\n"],"names":["VERSION"],"mappings":";;;AAOO,MAAM,cAAc;AAMpB,MAAM,cAAc;AAmB3B,SAAS,iBAA2B;AAClC,QAAM,IAAI;AAGV,MAAI,OAAO,EAAE,MAAM,MAAM,aAAa;AACpC,UAAM,OAAO,EAAE,MAAM;AAIrB,WAAO;AAAA,MACL,SAAS,KAAK,SAAS,OAAO,QAAQ,KAAK,QAAQ,IAAI,KAAK;AAAA,MAC5D,IAAI,KAAK,OAAO;AAAA,MAChB,MAAM,KAAK,OAAO;AAAA,IAAA;AAAA,EAEtB;AAGA,MAAI,OAAO,EAAE,KAAK,MAAM,aAAa;AACnC,UAAM,MAAM,EAAE,KAAK;AACnB,UAAM,OAAO,EAAE,SAAS;AACxB,WAAO;AAAA,MACL,SAAS,IAAI,UAAU,OAAO,IAAI,OAAO,KAAK;AAAA,MAC9C,IAAI,MAAM;AAAA,MACV,MAAM,MAAM;AAAA,IAAA;AAAA,EAEhB;AAGA,MAAI,OAAO,EAAE,SAAS,MAAM,aAAa;AACvC,UAAM,OAAO,EAAE,SAAS;AAKxB,QAAI,KAAK,UAAU,MAAM;AACvB,aAAO;AAAA,QACL,SAAS,QAAQ,KAAK,SAAS,IAAI;AAAA,QACnC,IAAI,KAAK;AAAA,QACT,MAAM,KAAK;AAAA,MAAA;AAAA,IAEf;AAAA,EACF;AAMA,MAAI,OAAO,EAAE,WAAW,MAAM,aAAa;AACzC,WAAO,EAAE,SAAS,WAAW,IAAI,QAAW,MAAM,OAAA;AAAA,EACpD;AAEA,SAAO,EAAE,SAAS,WAAW,IAAI,QAAW,MAAM,OAAA;AACpD;AAiBO,SAAS,aAAa,QAAyB;AACpD,QAAM,EAAE,SAAS,IAAI,KAAA,IAAS,eAAA;AAC9B,QAAM,QAAQ,CAAC,cAAc,aAAa,OAAO;AACjD,MAAI,OAAO,OAAW,OAAM,KAAK,EAAE;AACnC,MAAI,SAAS,OAAW,OAAM,KAAK,IAAI;AACvC,QAAM,OAAO,GAAG,WAAW,IAAIA,QAAAA,OAAO,KAAK,MAAM,KAAK,IAAI,CAAC;AAC3D,SAAO,SAAS,GAAG,MAAM,IAAI,IAAI,KAAK;AACxC;;;;"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Stable identifier Backblaze can grep server logs for to find every request
3
+ * issued by this SDK regardless of how the User-Agent comment evolves.
4
+ * Treat as part of the public contract: do NOT rename without coordinating.
5
+ */
6
+ export declare const SDK_PRODUCT = "b2-sdk-typescript";
7
+ /**
8
+ * The npm package name. Embedded in the User-Agent comment alongside
9
+ * {@link SDK_PRODUCT} so log queries that grep on either token work.
10
+ */
11
+ export declare const SDK_PACKAGE = "@backblaze-labs/b2-sdk";
12
+ /**
13
+ * Build the User-Agent header value the SDK sends on every B2 request.
14
+ *
15
+ * The product token, npm package name, language label, runtime, OS, and
16
+ * architecture are emitted in that order, separated by semicolons inside a
17
+ * single parenthesised comment block. OS and architecture are omitted on
18
+ * runtimes that don't expose them (notably browsers). A custom prefix passed
19
+ * via `B2ClientOptions.userAgent` is prepended verbatim so app-level
20
+ * identifiers come first. See the README "Identifying your traffic" section
21
+ * for examples.
22
+ *
23
+ * @param custom - Optional prefix prepended to the default User-Agent.
24
+ *
25
+ * @returns The formatted User-Agent header string.
26
+ */
27
+ export declare function getUserAgent(custom?: string): string;
28
+ //# sourceMappingURL=user-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-agent.d.ts","sourceRoot":"","sources":["../../src/http/user-agent.js"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,WAAW,sBAAsB,CAAA;AAE9C;;;GAGG;AACH,eAAO,MAAM,WAAW,2BAA2B,CAAA;AAyEnD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAOpD"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Stable identifier Backblaze can grep server logs for to find every request
3
+ * issued by this SDK regardless of how the User-Agent comment evolves.
4
+ * Treat as part of the public contract: do NOT rename without coordinating.
5
+ */
6
+ export declare const SDK_PRODUCT = "b2-sdk-typescript";
7
+ /**
8
+ * The npm package name. Embedded in the User-Agent comment alongside
9
+ * {@link SDK_PRODUCT} so log queries that grep on either token work.
10
+ */
11
+ export declare const SDK_PACKAGE = "@backblaze-labs/b2-sdk";
12
+ /**
13
+ * Build the User-Agent header value the SDK sends on every B2 request.
14
+ *
15
+ * The product token, npm package name, language label, runtime, OS, and
16
+ * architecture are emitted in that order, separated by semicolons inside a
17
+ * single parenthesised comment block. OS and architecture are omitted on
18
+ * runtimes that don't expose them (notably browsers). A custom prefix passed
19
+ * via `B2ClientOptions.userAgent` is prepended verbatim so app-level
20
+ * identifiers come first. See the README "Identifying your traffic" section
21
+ * for examples.
22
+ *
23
+ * @param custom - Optional prefix prepended to the default User-Agent.
24
+ *
25
+ * @returns The formatted User-Agent header string.
26
+ */
27
+ export declare function getUserAgent(custom?: string): string;
28
+ //# sourceMappingURL=user-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-agent.d.ts","sourceRoot":"","sources":["../../src/http/user-agent.js"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,WAAW,sBAAsB,CAAA;AAE9C;;;GAGG;AACH,eAAO,MAAM,WAAW,2BAA2B,CAAA;AAyEnD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAOpD"}
@@ -0,0 +1,51 @@
1
+ import { VERSION } from "../version.js";
2
+ const SDK_PRODUCT = "b2-sdk-typescript";
3
+ const SDK_PACKAGE = "@backblaze-labs/b2-sdk";
4
+ function detectPlatform() {
5
+ const g = globalThis;
6
+ if (typeof g["Deno"] !== "undefined") {
7
+ const deno = g["Deno"];
8
+ return {
9
+ runtime: deno.version?.deno ? `deno/${deno.version.deno}` : "deno",
10
+ os: deno.build?.os,
11
+ arch: deno.build?.arch
12
+ };
13
+ }
14
+ if (typeof g["Bun"] !== "undefined") {
15
+ const bun = g["Bun"];
16
+ const proc = g["process"];
17
+ return {
18
+ runtime: bun.version ? `bun/${bun.version}` : "bun",
19
+ os: proc?.platform,
20
+ arch: proc?.arch
21
+ };
22
+ }
23
+ if (typeof g["process"] !== "undefined") {
24
+ const proc = g["process"];
25
+ if (proc.versions?.node) {
26
+ return {
27
+ runtime: `node/${proc.versions.node}`,
28
+ os: proc.platform,
29
+ arch: proc.arch
30
+ };
31
+ }
32
+ }
33
+ if (typeof g["navigator"] !== "undefined") {
34
+ return { runtime: "browser", os: void 0, arch: void 0 };
35
+ }
36
+ return { runtime: "unknown", os: void 0, arch: void 0 };
37
+ }
38
+ function getUserAgent(custom) {
39
+ const { runtime, os, arch } = detectPlatform();
40
+ const parts = ["typescript", SDK_PACKAGE, runtime];
41
+ if (os !== void 0) parts.push(os);
42
+ if (arch !== void 0) parts.push(arch);
43
+ const base = `${SDK_PRODUCT}/${VERSION} (${parts.join("; ")})`;
44
+ return custom ? `${custom} ${base}` : base;
45
+ }
46
+ export {
47
+ SDK_PACKAGE,
48
+ SDK_PRODUCT,
49
+ getUserAgent
50
+ };
51
+ //# sourceMappingURL=user-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-agent.js","sources":["../../src/http/user-agent.ts"],"sourcesContent":["import { VERSION } from '../version.ts'\n\n/**\n * Stable identifier Backblaze can grep server logs for to find every request\n * issued by this SDK regardless of how the User-Agent comment evolves.\n * Treat as part of the public contract: do NOT rename without coordinating.\n */\nexport const SDK_PRODUCT = 'b2-sdk-typescript'\n\n/**\n * The npm package name. Embedded in the User-Agent comment alongside\n * {@link SDK_PRODUCT} so log queries that grep on either token work.\n */\nexport const SDK_PACKAGE = '@backblaze-labs/b2-sdk'\n\n/** Detected runtime + OS information for the User-Agent comment. */\ninterface Platform {\n /** Runtime identifier: `node/<x.y.z>`, `bun/<x.y.z>`, `deno/<x.y.z>`, `browser`, or `unknown`. */\n readonly runtime: string\n /** OS identifier (`linux`, `darwin`, `win32`, etc.) when available, or `undefined`. */\n readonly os: string | undefined\n /** CPU architecture (`x64`, `arm64`, etc.) when available, or `undefined`. */\n readonly arch: string | undefined\n}\n\n/**\n * Best-effort detection of the JS runtime and host OS. Used to populate the\n * User-Agent comment so server-side logs can spot Bun/Deno adoption and\n * triage OS-specific issues without asking for a repro environment.\n *\n * @returns The detected runtime, OS, and architecture tokens.\n */\nfunction detectPlatform(): Platform {\n const g = globalThis as Record<string, unknown>\n\n // Deno: Deno.version.deno, Deno.build.{os,arch}.\n if (typeof g['Deno'] !== 'undefined') {\n const deno = g['Deno'] as {\n version?: { deno?: string }\n build?: { os?: string; arch?: string }\n }\n return {\n runtime: deno.version?.deno ? `deno/${deno.version.deno}` : 'deno',\n os: deno.build?.os,\n arch: deno.build?.arch,\n }\n }\n\n // Bun: globalThis.Bun.version, process.platform/arch (Bun is Node-compat).\n if (typeof g['Bun'] !== 'undefined') {\n const bun = g['Bun'] as { version?: string }\n const proc = g['process'] as { platform?: string; arch?: string } | undefined\n return {\n runtime: bun.version ? `bun/${bun.version}` : 'bun',\n os: proc?.platform,\n arch: proc?.arch,\n }\n }\n\n // Node: process.versions.node, process.platform, process.arch.\n if (typeof g['process'] !== 'undefined') {\n const proc = g['process'] as {\n versions?: { node?: string }\n platform?: string\n arch?: string\n }\n if (proc.versions?.node) {\n return {\n runtime: `node/${proc.versions.node}`,\n os: proc.platform,\n arch: proc.arch,\n }\n }\n }\n\n // Browsers (and other navigator-only runtimes like Cloudflare Workers,\n // Vercel Edge, etc.). We deliberately do NOT parse the existing navigator\n // UA — that's noisy, often spoofed, and the request layer already adds the\n // browser's own UA on top.\n if (typeof g['navigator'] !== 'undefined') {\n return { runtime: 'browser', os: undefined, arch: undefined }\n }\n\n return { runtime: 'unknown', os: undefined, arch: undefined }\n}\n\n/**\n * Build the User-Agent header value the SDK sends on every B2 request.\n *\n * The product token, npm package name, language label, runtime, OS, and\n * architecture are emitted in that order, separated by semicolons inside a\n * single parenthesised comment block. OS and architecture are omitted on\n * runtimes that don't expose them (notably browsers). A custom prefix passed\n * via `B2ClientOptions.userAgent` is prepended verbatim so app-level\n * identifiers come first. See the README \"Identifying your traffic\" section\n * for examples.\n *\n * @param custom - Optional prefix prepended to the default User-Agent.\n *\n * @returns The formatted User-Agent header string.\n */\nexport function getUserAgent(custom?: string): string {\n const { runtime, os, arch } = detectPlatform()\n const parts = ['typescript', SDK_PACKAGE, runtime]\n if (os !== undefined) parts.push(os)\n if (arch !== undefined) parts.push(arch)\n const base = `${SDK_PRODUCT}/${VERSION} (${parts.join('; ')})`\n return custom ? `${custom} ${base}` : base\n}\n"],"names":[],"mappings":";AAOO,MAAM,cAAc;AAMpB,MAAM,cAAc;AAmB3B,SAAS,iBAA2B;AAClC,QAAM,IAAI;AAGV,MAAI,OAAO,EAAE,MAAM,MAAM,aAAa;AACpC,UAAM,OAAO,EAAE,MAAM;AAIrB,WAAO;AAAA,MACL,SAAS,KAAK,SAAS,OAAO,QAAQ,KAAK,QAAQ,IAAI,KAAK;AAAA,MAC5D,IAAI,KAAK,OAAO;AAAA,MAChB,MAAM,KAAK,OAAO;AAAA,IAAA;AAAA,EAEtB;AAGA,MAAI,OAAO,EAAE,KAAK,MAAM,aAAa;AACnC,UAAM,MAAM,EAAE,KAAK;AACnB,UAAM,OAAO,EAAE,SAAS;AACxB,WAAO;AAAA,MACL,SAAS,IAAI,UAAU,OAAO,IAAI,OAAO,KAAK;AAAA,MAC9C,IAAI,MAAM;AAAA,MACV,MAAM,MAAM;AAAA,IAAA;AAAA,EAEhB;AAGA,MAAI,OAAO,EAAE,SAAS,MAAM,aAAa;AACvC,UAAM,OAAO,EAAE,SAAS;AAKxB,QAAI,KAAK,UAAU,MAAM;AACvB,aAAO;AAAA,QACL,SAAS,QAAQ,KAAK,SAAS,IAAI;AAAA,QACnC,IAAI,KAAK;AAAA,QACT,MAAM,KAAK;AAAA,MAAA;AAAA,IAEf;AAAA,EACF;AAMA,MAAI,OAAO,EAAE,WAAW,MAAM,aAAa;AACzC,WAAO,EAAE,SAAS,WAAW,IAAI,QAAW,MAAM,OAAA;AAAA,EACpD;AAEA,SAAO,EAAE,SAAS,WAAW,IAAI,QAAW,MAAM,OAAA;AACpD;AAiBO,SAAS,aAAa,QAAyB;AACpD,QAAM,EAAE,SAAS,IAAI,KAAA,IAAS,eAAA;AAC9B,QAAM,QAAQ,CAAC,cAAc,aAAa,OAAO;AACjD,MAAI,OAAO,OAAW,OAAM,KAAK,EAAE;AACnC,MAAI,SAAS,OAAW,OAAM,KAAK,IAAI;AACvC,QAAM,OAAO,GAAG,WAAW,IAAI,OAAO,KAAK,MAAM,KAAK,IAAI,CAAC;AAC3D,SAAO,SAAS,GAAG,MAAM,IAAI,IAAI,KAAK;AACxC;"}