@backblaze-labs/b2-sdk 0.2.0 → 0.4.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.
- package/CHANGELOG.md +86 -1
- package/README.md +38 -7
- package/dist/_virtual/_b2-sdk-version-json.cjs +1 -1
- package/dist/_virtual/_b2-sdk-version-json.js +1 -1
- package/dist/auth/file.cjs.map +1 -1
- package/dist/auth/reauth-coalescer.cjs +65 -0
- package/dist/auth/reauth-coalescer.cjs.map +1 -0
- package/dist/auth/reauth-coalescer.d.cts +34 -0
- package/dist/auth/reauth-coalescer.d.cts.map +1 -0
- package/dist/auth/reauth-coalescer.d.ts +34 -0
- package/dist/auth/reauth-coalescer.d.ts.map +1 -0
- package/dist/auth/reauth-coalescer.js +65 -0
- package/dist/auth/reauth-coalescer.js.map +1 -0
- package/dist/backup/client.cjs +206 -0
- package/dist/backup/client.cjs.map +1 -0
- package/dist/backup/client.d.cts +238 -0
- package/dist/backup/client.d.cts.map +1 -0
- package/dist/backup/client.d.ts +238 -0
- package/dist/backup/client.d.ts.map +1 -0
- package/dist/backup/client.js +206 -0
- package/dist/backup/client.js.map +1 -0
- package/dist/backup/index.cjs +15 -0
- package/dist/backup/index.d.cts +22 -0
- package/dist/backup/index.d.cts.map +1 -0
- package/dist/backup/index.d.ts +22 -0
- package/dist/backup/index.d.ts.map +1 -0
- package/dist/backup/index.js +7 -0
- package/dist/backup/raw.cjs +168 -0
- package/dist/backup/raw.cjs.map +1 -0
- package/dist/backup/raw.d.cts +122 -0
- package/dist/backup/raw.d.cts.map +1 -0
- package/dist/backup/raw.d.ts +122 -0
- package/dist/backup/raw.d.ts.map +1 -0
- package/dist/backup/raw.js +168 -0
- package/dist/backup/raw.js.map +1 -0
- package/dist/bucket-validation.cjs +111 -0
- package/dist/bucket-validation.cjs.map +1 -0
- package/dist/bucket-validation.d.cts +27 -0
- package/dist/bucket-validation.d.cts.map +1 -0
- package/dist/bucket-validation.d.ts +27 -0
- package/dist/bucket-validation.d.ts.map +1 -0
- package/dist/bucket-validation.js +109 -0
- package/dist/bucket-validation.js.map +1 -0
- package/dist/bucket.cjs +125 -74
- package/dist/bucket.cjs.map +1 -1
- package/dist/bucket.d.cts +156 -61
- package/dist/bucket.d.cts.map +1 -1
- package/dist/bucket.d.ts +156 -61
- package/dist/bucket.d.ts.map +1 -1
- package/dist/bucket.js +125 -74
- package/dist/bucket.js.map +1 -1
- package/dist/client.cjs +29 -8
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +30 -9
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.ts +30 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +29 -8
- package/dist/client.js.map +1 -1
- package/dist/copy/large.cjs +5 -3
- package/dist/copy/large.cjs.map +1 -1
- package/dist/copy/large.d.cts.map +1 -1
- package/dist/copy/large.d.ts.map +1 -1
- package/dist/copy/large.js +5 -3
- package/dist/copy/large.js.map +1 -1
- package/dist/download/checksum.cjs +6 -2
- package/dist/download/checksum.cjs.map +1 -1
- package/dist/download/checksum.js +6 -2
- package/dist/download/checksum.js.map +1 -1
- package/dist/download/parallel.cjs +52 -15
- package/dist/download/parallel.cjs.map +1 -1
- package/dist/download/parallel.d.cts +8 -1
- package/dist/download/parallel.d.cts.map +1 -1
- package/dist/download/parallel.d.ts +8 -1
- package/dist/download/parallel.d.ts.map +1 -1
- package/dist/download/parallel.js +52 -15
- package/dist/download/parallel.js.map +1 -1
- package/dist/download/single.cjs +179 -19
- package/dist/download/single.cjs.map +1 -1
- package/dist/download/single.d.cts +10 -4
- package/dist/download/single.d.cts.map +1 -1
- package/dist/download/single.d.ts +10 -4
- package/dist/download/single.d.ts.map +1 -1
- package/dist/download/single.js +179 -19
- package/dist/download/single.js.map +1 -1
- package/dist/errors/index.cjs +196 -3
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +134 -0
- package/dist/errors/index.d.cts.map +1 -1
- package/dist/errors/index.d.ts +134 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +183 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/http/non-idempotent-mutations.cjs +38 -0
- package/dist/http/non-idempotent-mutations.cjs.map +1 -0
- package/dist/http/non-idempotent-mutations.d.cts +25 -0
- package/dist/http/non-idempotent-mutations.d.cts.map +1 -0
- package/dist/http/non-idempotent-mutations.d.ts +25 -0
- package/dist/http/non-idempotent-mutations.d.ts.map +1 -0
- package/dist/http/non-idempotent-mutations.js +35 -0
- package/dist/http/non-idempotent-mutations.js.map +1 -0
- package/dist/http/retry.cjs +1 -1
- package/dist/http/retry.cjs.map +1 -1
- package/dist/http/retry.js +1 -1
- package/dist/http/retry.js.map +1 -1
- package/dist/http/transport.cjs +62 -23
- package/dist/http/transport.cjs.map +1 -1
- package/dist/http/transport.d.cts +30 -3
- package/dist/http/transport.d.cts.map +1 -1
- package/dist/http/transport.d.ts +30 -3
- package/dist/http/transport.d.ts.map +1 -1
- package/dist/http/transport.js +60 -22
- package/dist/http/transport.js.map +1 -1
- package/dist/http/user-agent.cjs +12 -1
- package/dist/http/user-agent.cjs.map +1 -1
- package/dist/http/user-agent.d.cts +8 -0
- package/dist/http/user-agent.d.cts.map +1 -1
- package/dist/http/user-agent.d.ts +8 -0
- package/dist/http/user-agent.d.ts.map +1 -1
- package/dist/http/user-agent.js +13 -3
- package/dist/http/user-agent.js.map +1 -1
- package/dist/index.cjs +50 -2
- package/dist/index.d.cts +15 -12
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +15 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -10
- package/dist/notifications/index.cjs +3 -0
- package/dist/notifications/index.d.cts +2 -0
- package/dist/notifications/index.d.cts.map +1 -1
- package/dist/notifications/index.d.ts +2 -0
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +2 -1
- package/dist/object.cjs +13 -2
- package/dist/object.cjs.map +1 -1
- package/dist/object.d.cts +11 -3
- package/dist/object.d.cts.map +1 -1
- package/dist/object.d.ts +11 -3
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +13 -2
- package/dist/object.js.map +1 -1
- package/dist/partner/account-info.d.cts +43 -0
- package/dist/partner/account-info.d.cts.map +1 -0
- package/dist/partner/account-info.d.ts +43 -0
- package/dist/partner/account-info.d.ts.map +1 -0
- package/dist/partner/auth-clone.cjs +64 -0
- package/dist/partner/auth-clone.cjs.map +1 -0
- package/dist/partner/auth-clone.d.cts +27 -0
- package/dist/partner/auth-clone.d.cts.map +1 -0
- package/dist/partner/auth-clone.d.ts +27 -0
- package/dist/partner/auth-clone.d.ts.map +1 -0
- package/dist/partner/auth-clone.js +63 -0
- package/dist/partner/auth-clone.js.map +1 -0
- package/dist/partner/auth-core.cjs +156 -0
- package/dist/partner/auth-core.cjs.map +1 -0
- package/dist/partner/auth-core.d.cts +111 -0
- package/dist/partner/auth-core.d.cts.map +1 -0
- package/dist/partner/auth-core.d.ts +111 -0
- package/dist/partner/auth-core.d.ts.map +1 -0
- package/dist/partner/auth-core.js +156 -0
- package/dist/partner/auth-core.js.map +1 -0
- package/dist/partner/auth-shape.cjs +36 -0
- package/dist/partner/auth-shape.cjs.map +1 -0
- package/dist/partner/auth-shape.d.cts +12 -0
- package/dist/partner/auth-shape.d.cts.map +1 -0
- package/dist/partner/auth-shape.d.ts +12 -0
- package/dist/partner/auth-shape.d.ts.map +1 -0
- package/dist/partner/auth-shape.js +36 -0
- package/dist/partner/auth-shape.js.map +1 -0
- package/dist/partner/client.cjs +270 -0
- package/dist/partner/client.cjs.map +1 -0
- package/dist/partner/client.d.cts +357 -0
- package/dist/partner/client.d.cts.map +1 -0
- package/dist/partner/client.d.ts +357 -0
- package/dist/partner/client.d.ts.map +1 -0
- package/dist/partner/client.js +270 -0
- package/dist/partner/client.js.map +1 -0
- package/dist/partner/endpoint-url.cjs +109 -0
- package/dist/partner/endpoint-url.cjs.map +1 -0
- package/dist/partner/endpoint-url.d.cts +74 -0
- package/dist/partner/endpoint-url.d.cts.map +1 -0
- package/dist/partner/endpoint-url.d.ts +74 -0
- package/dist/partner/endpoint-url.d.ts.map +1 -0
- package/dist/partner/endpoint-url.js +106 -0
- package/dist/partner/endpoint-url.js.map +1 -0
- package/dist/partner/in-memory.cjs +135 -0
- package/dist/partner/in-memory.cjs.map +1 -0
- package/dist/partner/in-memory.d.cts +99 -0
- package/dist/partner/in-memory.d.cts.map +1 -0
- package/dist/partner/in-memory.d.ts +99 -0
- package/dist/partner/in-memory.d.ts.map +1 -0
- package/dist/partner/in-memory.js +135 -0
- package/dist/partner/in-memory.js.map +1 -0
- package/dist/partner/index.cjs +27 -0
- package/dist/partner/index.d.cts +32 -0
- package/dist/partner/index.d.cts.map +1 -0
- package/dist/partner/index.d.ts +32 -0
- package/dist/partner/index.d.ts.map +1 -0
- package/dist/partner/index.js +9 -0
- package/dist/partner/raw.cjs +400 -0
- package/dist/partner/raw.cjs.map +1 -0
- package/dist/partner/raw.d.cts +220 -0
- package/dist/partner/raw.d.cts.map +1 -0
- package/dist/partner/raw.d.ts +220 -0
- package/dist/partner/raw.d.ts.map +1 -0
- package/dist/partner/raw.js +398 -0
- package/dist/partner/raw.js.map +1 -0
- package/dist/partner/redaction-placeholders.cjs +10 -0
- package/dist/partner/redaction-placeholders.cjs.map +1 -0
- package/dist/partner/redaction-placeholders.d.cts +5 -0
- package/dist/partner/redaction-placeholders.d.cts.map +1 -0
- package/dist/partner/redaction-placeholders.d.ts +5 -0
- package/dist/partner/redaction-placeholders.d.ts.map +1 -0
- package/dist/partner/redaction-placeholders.js +9 -0
- package/dist/partner/redaction-placeholders.js.map +1 -0
- package/dist/partner/redaction.cjs +292 -0
- package/dist/partner/redaction.cjs.map +1 -0
- package/dist/partner/redaction.d.cts +158 -0
- package/dist/partner/redaction.d.cts.map +1 -0
- package/dist/partner/redaction.d.ts +158 -0
- package/dist/partner/redaction.d.ts.map +1 -0
- package/dist/partner/redaction.js +281 -0
- package/dist/partner/redaction.js.map +1 -0
- package/dist/raw/encoding.cjs +29 -10
- package/dist/raw/encoding.cjs.map +1 -1
- package/dist/raw/encoding.d.cts +3 -2
- package/dist/raw/encoding.d.cts.map +1 -1
- package/dist/raw/encoding.d.ts +3 -2
- package/dist/raw/encoding.d.ts.map +1 -1
- package/dist/raw/encoding.js +29 -10
- package/dist/raw/encoding.js.map +1 -1
- package/dist/raw/index.cjs +131 -44
- package/dist/raw/index.cjs.map +1 -1
- package/dist/raw/index.d.cts +21 -15
- package/dist/raw/index.d.cts.map +1 -1
- package/dist/raw/index.d.ts +21 -15
- package/dist/raw/index.d.ts.map +1 -1
- package/dist/raw/index.js +114 -45
- package/dist/raw/index.js.map +1 -1
- package/dist/raw/url.cjs +111 -0
- package/dist/raw/url.cjs.map +1 -0
- package/dist/raw/url.d.cts +41 -0
- package/dist/raw/url.d.cts.map +1 -0
- package/dist/raw/url.d.ts +41 -0
- package/dist/raw/url.d.ts.map +1 -0
- package/dist/raw/url.js +110 -0
- package/dist/raw/url.js.map +1 -0
- package/dist/s3/sigv4.cjs +15 -10
- package/dist/s3/sigv4.cjs.map +1 -1
- package/dist/s3/sigv4.js +15 -10
- package/dist/s3/sigv4.js.map +1 -1
- package/dist/simulator/index.cjs +1579 -257
- package/dist/simulator/index.cjs.map +1 -1
- package/dist/simulator/index.d.cts +124 -11
- package/dist/simulator/index.d.cts.map +1 -1
- package/dist/simulator/index.d.ts +124 -11
- package/dist/simulator/index.d.ts.map +1 -1
- package/dist/simulator/index.js +1568 -262
- package/dist/simulator/index.js.map +1 -1
- package/dist/simulator/partner-capabilities.cjs +77 -0
- package/dist/simulator/partner-capabilities.cjs.map +1 -0
- package/dist/simulator/partner-capabilities.d.cts +53 -0
- package/dist/simulator/partner-capabilities.d.cts.map +1 -0
- package/dist/simulator/partner-capabilities.d.ts +53 -0
- package/dist/simulator/partner-capabilities.d.ts.map +1 -0
- package/dist/simulator/partner-capabilities.js +73 -0
- package/dist/simulator/partner-capabilities.js.map +1 -0
- package/dist/simulator/partner.cjs +713 -0
- package/dist/simulator/partner.cjs.map +1 -0
- package/dist/simulator/partner.d.cts +186 -0
- package/dist/simulator/partner.d.cts.map +1 -0
- package/dist/simulator/partner.d.ts +186 -0
- package/dist/simulator/partner.d.ts.map +1 -0
- package/dist/simulator/partner.js +713 -0
- package/dist/simulator/partner.js.map +1 -0
- package/dist/simulator/validation.cjs +732 -25
- package/dist/simulator/validation.cjs.map +1 -1
- package/dist/simulator/validation.d.cts +145 -6
- package/dist/simulator/validation.d.cts.map +1 -1
- package/dist/simulator/validation.d.ts +145 -6
- package/dist/simulator/validation.d.ts.map +1 -1
- package/dist/simulator/validation.js +705 -26
- package/dist/simulator/validation.js.map +1 -1
- package/dist/streams/file-source.cjs +2 -2
- package/dist/streams/file-source.cjs.map +1 -1
- package/dist/streams/file-source.js +2 -2
- package/dist/streams/file-source.js.map +1 -1
- package/dist/streams/hash.cjs +246 -13
- package/dist/streams/hash.cjs.map +1 -1
- package/dist/streams/hash.d.cts +37 -12
- package/dist/streams/hash.d.cts.map +1 -1
- package/dist/streams/hash.d.ts +37 -12
- package/dist/streams/hash.d.ts.map +1 -1
- package/dist/streams/hash.js +246 -15
- package/dist/streams/hash.js.map +1 -1
- package/dist/streams/index.cjs +3 -1
- package/dist/streams/index.d.cts +1 -1
- package/dist/streams/index.d.cts.map +1 -1
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.d.ts.map +1 -1
- package/dist/streams/index.js +3 -3
- package/dist/streams/progress.cjs +63 -4
- package/dist/streams/progress.cjs.map +1 -1
- package/dist/streams/progress.d.cts +28 -2
- package/dist/streams/progress.d.cts.map +1 -1
- package/dist/streams/progress.d.ts +28 -2
- package/dist/streams/progress.d.ts.map +1 -1
- package/dist/streams/progress.js +63 -4
- package/dist/streams/progress.js.map +1 -1
- package/dist/streams/source.cjs +2 -1
- package/dist/streams/source.cjs.map +1 -1
- package/dist/streams/source.js +2 -1
- package/dist/streams/source.js.map +1 -1
- package/dist/sync/b2-sha1-reader.cjs +6 -2
- package/dist/sync/b2-sha1-reader.cjs.map +1 -1
- package/dist/sync/b2-sha1-reader.d.cts.map +1 -1
- package/dist/sync/b2-sha1-reader.d.ts.map +1 -1
- package/dist/sync/b2-sha1-reader.js +6 -2
- package/dist/sync/b2-sha1-reader.js.map +1 -1
- package/dist/sync/download-staging.cjs +156 -43
- package/dist/sync/download-staging.cjs.map +1 -1
- package/dist/sync/download-staging.d.cts +26 -2
- package/dist/sync/download-staging.d.cts.map +1 -1
- package/dist/sync/download-staging.d.ts +26 -2
- package/dist/sync/download-staging.d.ts.map +1 -1
- package/dist/sync/download-staging.js +155 -44
- package/dist/sync/download-staging.js.map +1 -1
- package/dist/sync/filters.cjs.map +1 -1
- package/dist/sync/filters.js.map +1 -1
- package/dist/sync/index.d.cts +1 -1
- package/dist/sync/index.d.cts.map +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/local-file-io.cjs +15 -6
- package/dist/sync/local-file-io.cjs.map +1 -1
- package/dist/sync/local-file-io.d.cts +3 -0
- package/dist/sync/local-file-io.d.cts.map +1 -1
- package/dist/sync/local-file-io.d.ts +3 -0
- package/dist/sync/local-file-io.d.ts.map +1 -1
- package/dist/sync/local-file-io.js +15 -6
- package/dist/sync/local-file-io.js.map +1 -1
- package/dist/sync/local-sha1.cjs +6 -15
- package/dist/sync/local-sha1.cjs.map +1 -1
- package/dist/sync/local-sha1.d.cts +0 -8
- package/dist/sync/local-sha1.d.cts.map +1 -1
- package/dist/sync/local-sha1.d.ts +0 -8
- package/dist/sync/local-sha1.d.ts.map +1 -1
- package/dist/sync/local-sha1.js +7 -15
- package/dist/sync/local-sha1.js.map +1 -1
- package/dist/sync/pairing.cjs +1 -0
- package/dist/sync/pairing.cjs.map +1 -1
- package/dist/sync/pairing.js +1 -0
- package/dist/sync/pairing.js.map +1 -1
- package/dist/sync/policies/compare.cjs +5 -3
- package/dist/sync/policies/compare.cjs.map +1 -1
- package/dist/sync/policies/compare.d.cts.map +1 -1
- package/dist/sync/policies/compare.d.ts.map +1 -1
- package/dist/sync/policies/compare.js +6 -4
- package/dist/sync/policies/compare.js.map +1 -1
- package/dist/sync/policies/index.cjs +4 -10
- package/dist/sync/policies/index.cjs.map +1 -1
- package/dist/sync/policies/index.js +4 -10
- package/dist/sync/policies/index.js.map +1 -1
- package/dist/sync/scanners/b2.cjs +4 -2
- package/dist/sync/scanners/b2.cjs.map +1 -1
- package/dist/sync/scanners/b2.d.cts.map +1 -1
- package/dist/sync/scanners/b2.d.ts.map +1 -1
- package/dist/sync/scanners/b2.js +4 -2
- package/dist/sync/scanners/b2.js.map +1 -1
- package/dist/sync/scanners/local.cjs +150 -33
- package/dist/sync/scanners/local.cjs.map +1 -1
- package/dist/sync/scanners/local.d.cts +6 -0
- package/dist/sync/scanners/local.d.cts.map +1 -1
- package/dist/sync/scanners/local.d.ts +6 -0
- package/dist/sync/scanners/local.d.ts.map +1 -1
- package/dist/sync/scanners/local.js +150 -33
- package/dist/sync/scanners/local.js.map +1 -1
- package/dist/sync/synchronizer.cjs +79 -31
- package/dist/sync/synchronizer.cjs.map +1 -1
- package/dist/sync/synchronizer.d.cts +1 -0
- package/dist/sync/synchronizer.d.cts.map +1 -1
- package/dist/sync/synchronizer.d.ts +1 -0
- package/dist/sync/synchronizer.d.ts.map +1 -1
- package/dist/sync/synchronizer.js +79 -31
- package/dist/sync/synchronizer.js.map +1 -1
- package/dist/sync/types.d.cts +19 -3
- package/dist/sync/types.d.cts.map +1 -1
- package/dist/sync/types.d.ts +19 -3
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/types/auth.cjs +4 -0
- package/dist/types/auth.cjs.map +1 -1
- package/dist/types/auth.d.cts +4 -0
- package/dist/types/auth.d.cts.map +1 -1
- package/dist/types/auth.d.ts +4 -0
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/auth.js +4 -0
- package/dist/types/auth.js.map +1 -1
- package/dist/types/backup.d.cts +85 -0
- package/dist/types/backup.d.cts.map +1 -0
- package/dist/types/backup.d.ts +85 -0
- package/dist/types/backup.d.ts.map +1 -0
- package/dist/types/bucket.cjs +81 -11
- package/dist/types/bucket.cjs.map +1 -1
- package/dist/types/bucket.d.cts +194 -41
- package/dist/types/bucket.d.cts.map +1 -1
- package/dist/types/bucket.d.ts +194 -41
- package/dist/types/bucket.d.ts.map +1 -1
- package/dist/types/bucket.js +67 -12
- package/dist/types/bucket.js.map +1 -1
- package/dist/types/download.cjs +62 -0
- package/dist/types/download.cjs.map +1 -0
- package/dist/types/download.d.cts +100 -16
- package/dist/types/download.d.cts.map +1 -1
- package/dist/types/download.d.ts +100 -16
- package/dist/types/download.d.ts.map +1 -1
- package/dist/types/download.js +61 -0
- package/dist/types/download.js.map +1 -0
- package/dist/types/encryption.cjs +76 -229
- package/dist/types/encryption.cjs.map +1 -1
- package/dist/types/encryption.d.cts +48 -6
- package/dist/types/encryption.d.cts.map +1 -1
- package/dist/types/encryption.d.ts +48 -6
- package/dist/types/encryption.d.ts.map +1 -1
- package/dist/types/encryption.js +74 -229
- package/dist/types/encryption.js.map +1 -1
- package/dist/types/errors.cjs +9 -0
- package/dist/types/errors.cjs.map +1 -1
- package/dist/types/errors.d.cts +1 -1
- package/dist/types/errors.d.cts.map +1 -1
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +9 -0
- package/dist/types/errors.js.map +1 -1
- package/dist/types/file.cjs +3 -0
- package/dist/types/file.cjs.map +1 -1
- package/dist/types/file.d.cts +140 -18
- package/dist/types/file.d.cts.map +1 -1
- package/dist/types/file.d.ts +140 -18
- package/dist/types/file.d.ts.map +1 -1
- package/dist/types/file.js +3 -1
- package/dist/types/file.js.map +1 -1
- package/dist/types/ids.cjs +30 -0
- package/dist/types/ids.cjs.map +1 -1
- package/dist/types/ids.d.cts +32 -0
- package/dist/types/ids.d.cts.map +1 -1
- package/dist/types/ids.d.ts +32 -0
- package/dist/types/ids.d.ts.map +1 -1
- package/dist/types/ids.js +28 -1
- package/dist/types/ids.js.map +1 -1
- package/dist/types/index.d.cts +16 -12
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +16 -12
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/key.d.cts +4 -3
- package/dist/types/key.d.cts.map +1 -1
- package/dist/types/key.d.ts +4 -3
- package/dist/types/key.d.ts.map +1 -1
- package/dist/types/lock.cjs.map +1 -1
- package/dist/types/lock.d.cts +14 -0
- package/dist/types/lock.d.cts.map +1 -1
- package/dist/types/lock.d.ts +14 -0
- package/dist/types/lock.d.ts.map +1 -1
- package/dist/types/lock.js.map +1 -1
- package/dist/types/notifications.cjs +31 -1
- package/dist/types/notifications.cjs.map +1 -1
- package/dist/types/notifications.d.cts +31 -4
- package/dist/types/notifications.d.cts.map +1 -1
- package/dist/types/notifications.d.ts +31 -4
- package/dist/types/notifications.d.ts.map +1 -1
- package/dist/types/notifications.js +30 -2
- package/dist/types/notifications.js.map +1 -1
- package/dist/types/partner.cjs +32 -0
- package/dist/types/partner.cjs.map +1 -0
- package/dist/types/partner.d.cts +386 -0
- package/dist/types/partner.d.cts.map +1 -0
- package/dist/types/partner.d.ts +386 -0
- package/dist/types/partner.d.ts.map +1 -0
- package/dist/types/partner.js +29 -0
- package/dist/types/partner.js.map +1 -0
- package/dist/types/upload.d.cts +35 -18
- package/dist/types/upload.d.cts.map +1 -1
- package/dist/types/upload.d.ts +35 -18
- package/dist/types/upload.d.ts.map +1 -1
- package/dist/upload/abort-scope.cjs +0 -45
- package/dist/upload/abort-scope.cjs.map +1 -1
- package/dist/upload/abort-scope.d.cts +0 -22
- package/dist/upload/abort-scope.d.cts.map +1 -1
- package/dist/upload/abort-scope.d.ts +0 -22
- package/dist/upload/abort-scope.d.ts.map +1 -1
- package/dist/upload/abort-scope.js +1 -44
- package/dist/upload/abort-scope.js.map +1 -1
- package/dist/upload/finish.cjs +3 -8
- package/dist/upload/finish.cjs.map +1 -1
- package/dist/upload/finish.d.cts.map +1 -1
- package/dist/upload/finish.d.ts.map +1 -1
- package/dist/upload/finish.js +1 -6
- package/dist/upload/finish.js.map +1 -1
- package/dist/upload/large.cjs +15 -8
- package/dist/upload/large.cjs.map +1 -1
- package/dist/upload/large.d.cts +31 -8
- package/dist/upload/large.d.cts.map +1 -1
- package/dist/upload/large.d.ts +31 -8
- package/dist/upload/large.d.ts.map +1 -1
- package/dist/upload/large.js +15 -8
- package/dist/upload/large.js.map +1 -1
- package/dist/upload/options.cjs.map +1 -1
- package/dist/upload/options.d.cts +24 -0
- package/dist/upload/options.d.cts.map +1 -1
- package/dist/upload/options.d.ts +24 -0
- package/dist/upload/options.d.ts.map +1 -1
- package/dist/upload/options.js.map +1 -1
- package/dist/upload/resume.cjs +6 -4
- package/dist/upload/resume.cjs.map +1 -1
- package/dist/upload/resume.d.cts +9 -5
- package/dist/upload/resume.d.cts.map +1 -1
- package/dist/upload/resume.d.ts +9 -5
- package/dist/upload/resume.d.ts.map +1 -1
- package/dist/upload/resume.js +6 -4
- package/dist/upload/resume.js.map +1 -1
- package/dist/upload/retry.cjs +25 -13
- package/dist/upload/retry.cjs.map +1 -1
- package/dist/upload/retry.d.cts.map +1 -1
- package/dist/upload/retry.d.ts.map +1 -1
- package/dist/upload/retry.js +25 -13
- package/dist/upload/retry.js.map +1 -1
- package/dist/upload/single.cjs +35 -30
- package/dist/upload/single.cjs.map +1 -1
- package/dist/upload/single.d.cts +12 -0
- package/dist/upload/single.d.cts.map +1 -1
- package/dist/upload/single.d.ts +12 -0
- package/dist/upload/single.d.ts.map +1 -1
- package/dist/upload/single.js +35 -30
- package/dist/upload/single.js.map +1 -1
- package/dist/upload/stream.cjs +18 -5
- package/dist/upload/stream.cjs.map +1 -1
- package/dist/upload/stream.d.cts +12 -0
- package/dist/upload/stream.d.cts.map +1 -1
- package/dist/upload/stream.d.ts +12 -0
- package/dist/upload/stream.d.ts.map +1 -1
- package/dist/upload/stream.js +15 -2
- package/dist/upload/stream.js.map +1 -1
- package/dist/util/abort.cjs +114 -0
- package/dist/util/abort.cjs.map +1 -0
- package/dist/util/abort.d.cts +65 -0
- package/dist/util/abort.d.cts.map +1 -0
- package/dist/util/abort.d.ts +65 -0
- package/dist/util/abort.d.ts.map +1 -0
- package/dist/util/abort.js +109 -0
- package/dist/util/abort.js.map +1 -0
- package/dist/util/defaults.cjs +3 -0
- package/dist/util/defaults.cjs.map +1 -1
- package/dist/util/defaults.d.cts +2 -0
- package/dist/util/defaults.d.cts.map +1 -1
- package/dist/util/defaults.d.ts +2 -0
- package/dist/util/defaults.d.ts.map +1 -1
- package/dist/util/defaults.js +3 -1
- package/dist/util/defaults.js.map +1 -1
- package/dist/util/md5.cjs +211 -0
- package/dist/util/md5.cjs.map +1 -0
- package/dist/util/md5.d.cts +43 -0
- package/dist/util/md5.d.cts.map +1 -0
- package/dist/util/md5.d.ts +43 -0
- package/dist/util/md5.d.ts.map +1 -0
- package/dist/util/md5.js +209 -0
- package/dist/util/md5.js.map +1 -0
- package/dist/util/normalize.cjs +5 -5
- package/dist/util/normalize.cjs.map +1 -1
- package/dist/util/normalize.d.cts +7 -7
- package/dist/util/normalize.d.cts.map +1 -1
- package/dist/util/normalize.d.ts +7 -7
- package/dist/util/normalize.d.ts.map +1 -1
- package/dist/util/normalize.js +5 -5
- package/dist/util/normalize.js.map +1 -1
- package/dist/util/paginator.cjs +7 -1
- package/dist/util/paginator.cjs.map +1 -1
- package/dist/util/paginator.d.cts +3 -1
- package/dist/util/paginator.d.cts.map +1 -1
- package/dist/util/paginator.d.ts +3 -1
- package/dist/util/paginator.d.ts.map +1 -1
- package/dist/util/paginator.js +7 -1
- package/dist/util/paginator.js.map +1 -1
- package/dist/version.cjs +56 -4
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +53 -4
- package/dist/version.d.cts.map +1 -1
- package/dist/version.d.ts +53 -4
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +52 -5
- package/dist/version.js.map +1 -1
- package/package.json +47 -18
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,89 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-09-01
|
|
11
|
+
|
|
12
|
+
> Upgrading across the v4 native-API changes below? See [MIGRATION.md](./MIGRATION.md) for per-change migration steps.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Streaming and SSE-C polish helpers.** `IncrementalSha256` and the streams `sha256Hex()` helper are now available for SHA-256 checksum workflows, `SseCKeyMaterial` is the shared SSE-C key shape, and `EncryptionKey.generate()` mints random SSE-C keys with the existing redacted key wrapper.
|
|
17
|
+
- **Named bucket/key option constants.** `BucketKeyOption.S3` and `KnownBucketKeyOption` document the currently known B2 bucket/application-key option while the response fields remain open to future B2-added option strings.
|
|
18
|
+
- **Local sync symlink policy.** Built-in local scans and `synchronize()` now accept `localSymlinks: 'skip' | 'follow'`; follow mode only follows child symlink targets that resolve inside the configured local root and deduplicates repeated followed directory targets.
|
|
19
|
+
- **Transfer progress cadence control.** Upload and single-request download options now accept `progressIntervalMillis`; set it to `0` to restore per-chunk byte progress callbacks.
|
|
20
|
+
- **B2Simulator v4 route and GET/query compatibility.** The public simulator now accepts canonical `/b2api/v4` JSON routes while continuing to support `/b2api/v3`, rejects unsupported future version segments explicitly, and parses documented GET query forms for supported storage read/list and copy endpoints while preserving POST JSON compatibility. Closes #111.
|
|
21
|
+
- **Partner redaction helpers are public.** The `@backblaze-labs/b2-sdk/partner` subpath now exports `PARTNER_TOKEN_REDACTED`, `APPLICATION_KEY_REDACTED`, and the pure `*ToRedactedJson` projection helpers and JSON result types for secret-scrubbed Partner authorization, group-member creation, and B2 Reserve trial-account responses. Closes #225.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **User-Agent release channel now distinguishes published SDK builds from development traffic.** `VERSION` remains the package semver string, while the outbound SDK product token reports `b2-sdk-typescript/<version>` only for stable publish-path builds and `b2-sdk-typescript/dev` for source, CI, `npm pack`, and prerelease builds. Closes #257.
|
|
26
|
+
- **Partner group `b2Stats` counters are numbers, not strings.** BREAKING: `PartnerB2Stats.b2BytesStoredCount`, `b2FilesStoredCount`, and `bucketCount` are now `number` — matching what live B2 `b2_list_groups` / `b2_list_group_members` actually return — instead of the previously-assumed quoted decimal `string`. Callers that coerced these with `Number(...)` / `parseInt(...)` should drop the conversion. The Partner stats timestamps (`b2StatsAsOfTimestamp`, `groupStats.createdTimestamp`, `groupStats.groupStatsAsOfTimestamp`) are documented and modeled as B2's `dYYYYMMDD_mHHMMSS` string format rather than ISO 8601, and the `B2Simulator` now emits that shape (and `B2_GOOD_STANDING`) to match live B2. Confirmed against live B2. Closes #214.
|
|
27
|
+
- **`b2_list_group_members` returns a single object, not an array.** BREAKING: `ListGroupMembersResponse` is now the single `ListGroupMembersResult` object (`{ groupId, groupName, groupMembers, nextEmail }`) that live B2 returns, matching `b2_list_groups`, rather than a one-element array. `PartnerClient.listGroupMembers()` and `PartnerRawClient.listGroupMembers()` resolve to that object — read `response.groupMembers` / `response.nextEmail` directly instead of `response[0]`. `PartnerClient.paginateGroupMembers()` is unchanged, and the `B2Simulator` now emits the object shape. Confirmed against live B2.
|
|
28
|
+
- **Repository Node.js 22 CI and toolchain checks now use the JSDoc lint floor.**
|
|
29
|
+
CI and contributor docs now use Node 22.22.2+ for full project checks while
|
|
30
|
+
the published SDK runtime contract remains Node.js 22.3+. Closes #282.
|
|
31
|
+
- **Progress byte callbacks are coalesced by default.** Transfer `onProgress` byte-only callbacks are throttled to the SDK's 100 ms default interval, while part-completion and final progress callbacks still emit immediately. Use `progressIntervalMillis: 0` when per-chunk progress sampling is required.
|
|
32
|
+
- **Bucket default retention metadata now matches B2's nested wire shape.** BREAKING: `BucketInfo.defaultRetention` was removed because B2 does not return a top-level default-retention field; read `bucket.info.fileLockConfiguration.value?.defaultRetention` instead. `Bucket.getDefaultRetention()` now reads that nested field and can return `undefined` when file-lock configuration is unreadable. The nested default retention type also includes B2's unset response shape `{ mode: null, period: null }`; callers that checked `BucketRetentionMode.None` for unset bucket defaults should handle `null` mode on response metadata.
|
|
33
|
+
- **Bucket replication configuration is now capability-filtered.** BREAKING: `BucketInfo.replicationConfiguration` changed from a bare `ReplicationConfiguration` to the wrapped `{ isClientAuthorizedToRead: boolean; value: ReplicationConfiguration | null }` shape B2 returns, and `Bucket.getReplication()` returns that wrapper. Callers accessing `.replicationConfiguration.asReplicationSource` must move to `.replicationConfiguration.value?.asReplicationSource`; `value` is `null` when replication is not configured or the caller is not authorized to read it (fail-closed). Request shapes (`CreateBucketRequest` / `UpdateBucketRequest`) keep the bare `ReplicationConfiguration`.
|
|
34
|
+
- **Bucket default server-side encryption is now capability-filtered.** BREAKING: `BucketInfo.defaultServerSideEncryption` changed from a bare `EncryptionSetting` to the wrapped `{ isClientAuthorizedToRead: boolean; value: BucketDefaultServerSideEncryption | null }` shape B2 returns; check `bucket.info.defaultServerSideEncryption.isClientAuthorizedToRead` before reading `.value`, which is `null` only when the caller is not authorized to read it (fail-closed). When readable, an unset bucket default is represented as B2's `{ mode: null, algorithm: null }` no-encryption wire shape. The readable value type `BucketDefaultServerSideEncryption` is `PublicEncryptionSetting`, matching the public SSE response union used for file and part metadata; B2 only sets SSE-B2 or none as a bucket default and never exposes SSE-C key material. Request shapes (`CreateBucketRequest` / `UpdateBucketRequest` and the `B2Client.createBucket` / `updateBucket` options) now accept `BucketDefaultServerSideEncryptionSetting` (`SseB2Setting | NoEncryption`) and reject SSE-C, which B2 cannot use as a bucket default. Closes #210.
|
|
35
|
+
- **Event notification custom headers match the B2 wire shape.** BREAKING: `EventNotificationRule.targetConfiguration.customHeaders` is now the documented array of `{ name, value }` objects instead of a lookup record, with simulator validation and round-trip behavior aligned to `b2_get_bucket_notification_rules` / `b2_set_bucket_notification_rules`. Existing record-shaped caller data can be migrated with `recordToNotificationCustomHeaders()`, and responses can be converted back to a lookup with `notificationCustomHeadersToRecord()`. Closes #189.
|
|
36
|
+
- **List endpoints model folder and hide entries as a discriminated union.** BREAKING: `FileVersion.action` narrowed from `FileAction` to `ConcreteFileAction` because a virtual folder row is no longer a `FileVersion`. `ListFileNamesResponse.files` is now `readonly ListedFileVersion[]` and never includes hide markers; `ListFileNamesWithDelimiterResponse.files` is now `readonly FileNameListEntry[]` (`ListedFileVersion | FolderFileVersion`). `ListFileVersionsResponse.files` is now `readonly ListedConcreteFileVersion[]` (`ListedFileVersion | ListedHideFileVersion`), and `ListFileVersionsWithDelimiterResponse.files` is now `readonly FileVersionListEntry[]` (`ListedConcreteFileVersion | FolderFileVersion`). `b2_list_file_versions` hide markers use the listed hide-row shape with omitted Object Lock/encryption metadata and `contentType: "application/x-bz-hide-marker"`; `Bucket.unhideFile()` now returns that `ListedHideFileVersion | null` shape. `B2SyncPath.allVersions` changed from `FileVersion[]` to `ListedConcreteFileVersion[]`, so hidden sync version history now uses the listed hide-row shape rather than full `FileVersion` metadata. New literal and dynamic `delimiter?: string` overloads on `RawClient.listFileNames` / `listFileVersions` and the `Bucket.listFileNames` / `listFileVersions` / `paginateFileNames` / `paginateFileVersions` facades surface folder unions only when delimiter may be present; `FolderFileVersion` virtual-folder rows have `fileId: null` and `contentType: null`, and non-delimiter facade calls reject folder/null-ID rows before returning them. The simulator now honors the `delimiter` (and its `prefix` interaction), emits folder entries only for visible file-name listings, and keeps hide markers in file-version listings. Closes #191.
|
|
37
|
+
- **Raw native storage endpoints now use the published `/b2api/v4` route.** All SDK-owned B2 native storage URLs are built from a single centralized v4 policy in the raw URL builder, replacing the previous mix of `/b2api/v3` and `/b2api/v4` per-endpoint segments. The version is no longer a per-call parameter, so endpoint/version drift is structurally prevented. This is runtime-compatible (B2 accepts both routes and the auth token is version-agnostic) and `RawClient` public method signatures are unchanged; read/list endpoints remain POST JSON as the docs permit. Closes #104.
|
|
38
|
+
- **Bucket type request and response contracts are now separate.** BREAKING: `BucketInfo.bucketType` is now the open `BucketResponseType` (`KnownBucketResponseType | (string & {})`), which adds the response-only `'shared'` value (`KnownBucketResponseType.Shared`) and tolerates future B2-added types, so exhaustive switches over the old closed `BucketType` on a response now need a default case. Create/update requests still accept only the settable `BucketType` (`allPublic` / `allPrivate` / `snapshot` / `restricted`). `ListBucketsRequest.bucketTypes` (and the `B2Client.listBuckets` filter option) changed from `BucketType[]` to `BucketTypesFilter` (`readonly ['all'] | readonly BucketListType[]`), which accepts `'shared'`, future type strings, and the special `['all']`; the simulator now rejects empty filters, `'all'` mixed with other values, and non-string entries at runtime, matching B2. Closes #107.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **B2-to-local sync removes the managed download staging root on completion.** Completed downloads now clean the SDK-owned `.b2sdk-download-staging` directory after per-run staging entries are gone, including no-op follow-up syncs that only find an older marker-only staging root. Closes #156.
|
|
43
|
+
- **Progress throttling drains part-boundary bytes safely.** A pending coalesced byte-progress update is emitted before the part-completion update, delayed progress listener errors no longer escape detached timers, and pending progress timers are cleared on transfer cleanup.
|
|
44
|
+
- **Followed local symlinks stay inside the sync root.** `localSymlinks: 'follow'` skips file and directory symlink targets outside the local root, avoids repeated traversal of the same followed directory identity, and `synchronize()` now honors followed in-root file targets before upload actions.
|
|
45
|
+
- **B2 content file-info upload headers are now percent-encoded.** `contentDisposition`, `contentLanguage`, `expires`, `cacheControl`, and `contentEncoding` values sent as `X-Bz-Info-b2-*` upload headers are now encoded with B2 file-info encoding, matching the custom `fileInfo` path, so values with `%`, non-ASCII, or other unsafe characters round-trip correctly. These values must now be passed raw (unencoded). Closes #213.
|
|
46
|
+
- **Partner and Computer Backup API paths pin their wire version per method.** Partner group-management endpoints (`b2_list_groups`, `b2_list_group_members`, `b2_create_group_member`, `b2_eject_group_member`, `b2_reserve_trial_create_account`) now target `/b2api/v4`, matching the Partner API docs' code samples, while Partner `b2_authorize_account` stays on `/b2api/v3` (the version B2 documents for the suite-grouped `apiInfo` response) and Computer Backup stays on `api/backup/v1`. Each version comes from a single per-suite URL builder instead of a per-call segment, and request-construction guardrails assert the pinned version per method so the routes cannot silently drift. A gated live smoke (`tests/integration/partner-backup-contracts.test.ts`, master-key credentials) is included to confirm, when run, that live B2 accepts the read-only routes at their pinned versions. Closes #228.
|
|
47
|
+
- **Partner and Computer Backup mutations no longer replay on 401 reauth.** Non-idempotent Partner/Backup mutations (`b2_create_group_member`, `b2_eject_group_member`, `b2_reserve_trial_create_account`, `bz_delete_computer`) are tagged `idempotent: false`, and `RetryTransport` now blocks both transient retries and expired-token reauth-replay for them from a single shared endpoint registry. Previously the retry-suppression (raw client `maxRetries: 0`) and the transport's reauth-replay guard were separate lists that could drift, risking a double account-create or delete if a new mutation was added to one but not the other; a known mutation URL now also fails closed if a transport wrapper strips the marker. The exported `HttpRequest` type gains an optional `idempotent?: false` field for custom transports. Closes #215.
|
|
48
|
+
- **Storage reauthorization coalesces concurrent 401s into a single refresh.** Expired-token reauth on `B2Client` now shares one in-flight `authorizeAccount` across all waiting requests through a shared coalescer (the Partner client's bespoke in-flight state machine was lifted into the same helper), instead of firing one authorize per concurrent 401. A failed or aborted refresh no longer clears the existing token first, so the client stays recoverable, and per-request `AbortSignal`s are honored (one waiter aborting does not cancel the shared refresh for the others). New `B2ClientAuthorizeOptions` / `AuthorizeAccountOptions` exports expose the abort signal on authorize and reauthorize. Closes #216.
|
|
49
|
+
- **B2Simulator large-file lock echo fidelity.** `b2_start_large_file` and `b2_list_unfinished_large_files` now redact the `fileRetention` / `legalHold` echoes for callers lacking `readFileRetentions` / `readFileLegalHolds`, matching B2's capability-filtered response shape; previously the simulator always reported `isClientAuthorizedToRead: true`. The `StartLargeFileResponse` / `UnfinishedLargeFile` DTOs already expose the `fileRetention` / `legalHold` / `serverSideEncryption` echo fields and no longer carry a phantom top-level bucket `defaultRetention`. Closes #41.
|
|
50
|
+
- **Upload retries no longer replay spoofable post-start network errors.** High-level uploads still retry network failures before an upload POST is attempted, but a post-start `NetworkError` with nested `ECONNREFUSED` no longer bypasses the default protection against ambiguous single-request replay. Set `retryResponseBodyFailures: true` only when duplicate versions or replayed parts are acceptable.
|
|
51
|
+
- **SSE-B2 large-file uploads no longer repeat encryption headers on each part.** SSE-B2 (B2-managed) encryption is configured once on `b2_start_large_file`; `b2_upload_part` only accepts customer-key headers, so the SDK no longer sends the SSE-B2 `serverSideEncryption` setting on each part upload. SSE-C customer keys are still applied per part.
|
|
52
|
+
|
|
53
|
+
## [0.3.0] - 2026-08-18
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- **Download response headers.** `DownloadHeaders` now exposes optional `contentDisposition`, `contentLanguage`, `contentEncoding`, `cacheControl`, `expires`, `contentRange`, `serverSideEncryption`, readable `fileRetention`/`legalHold`, typed `clientUnauthorizedToRead`, and `headerParseIssues`; new `DownloadHeaderName`, `DownloadClientUnauthorizedToReadMarker`, `DownloadServerSideEncryption`, and `SseCDownloadSetting` exports document the added surface.
|
|
58
|
+
- **Large-file part metadata.** Raw large-file response types now expose documented v4 metadata fields: `contentMd5` on `UploadPartResponse` and `PartInfo`, `serverSideEncryption` and `uploadTimestamp` on `CopyPartResponse`, `serverSideEncryption` on `PartInfo`, and `replicationStatus` on unfinished large-file metadata via the exported `ReplicationStatus` alias.
|
|
59
|
+
- **Custom upload timestamps.** Upload options now accept numeric `customUploadTimestamp` values to override B2's `uploadTimestamp`, distinct from `lastModifiedMillis` source metadata. Raw `b2_upload_file` headers serialize the value as `X-Bz-Custom-Upload-Timestamp`; raw `StartLargeFileRequest` accepts B2's documented decimal string/null body shape. Multipart uploads now also persist `lastModifiedMillis` as `src_last_modified_millis`, matching the small-file path; this metadata is included in multipart resume identity and older unfinished uploads that lack it can report `file-info-mismatch`. Custom timestamps require B2 account enablement, and resume diagnostics can report `upload-timestamp-mismatch` for incompatible unfinished large files.
|
|
60
|
+
- **Bucket lifecycle-rule capabilities.** `Capability` now exports `ReadBucketLifecycleRules` and `WriteBucketLifecycleRules`.
|
|
61
|
+
- **Partner authorize runtime surface.** New `@backblaze-labs/b2-sdk/partner` subpath exports `PartnerRawClient.authorizePartner()`, `PartnerAccountInfo`, and `InMemoryPartnerAccountInfo` for Master Application Key authorization against the Partner and Computer Backup suites.
|
|
62
|
+
- **Partner API runtime bindings.** `PartnerRawClient` now includes `createGroupMember()`, `ejectGroupMember()`, `listGroups()`, `listGroupMembers()`, and `reserveTrialCreateAccount()` bindings for Partner API group management and B2 Reserve trial-account creation.
|
|
63
|
+
- **High-level Partner facade.** The `@backblaze-labs/b2-sdk/partner` subpath now exports the experimental `PartnerClient` facade plus `PartnerClientOptions`, `PartnerAuthorizeOptions`, `ListGroupsOptions`, `PaginateGroupsOptions`, `ListGroupMembersOptions`, `PaginateGroupMembersOptions`, `CreateGroupMemberOptions`, `EjectGroupMemberOptions`, and `ReserveTrialAccountsOptions` for ergonomic Partner group/member pagination and B2 Reserve trial-account workflows.
|
|
64
|
+
- **Computer Backup runtime facade.** The new `@backblaze-labs/b2-sdk/backup` subpath exports experimental `BackupClient` and `BackupRawClient` bindings for Partner-authorized Computer Backup listing, pagination, and delete workflows.
|
|
65
|
+
- **Partner API and Computer Backup typed error subclasses.** New exported `TooManyMembersError`, `InvalidGroupIdError`, `InvalidEmailError`, `InvalidRegionError`, `InvalidSmsPhoneError`, `MethodFailureError`, `InvalidMemberAccountIdError`, `InvalidAccountIdError`, and `InvalidComputerIdError` classify Partner API and Computer Backup error codes; Partner 401 overload codes map to non-auth subclasses so they do not trigger reauth. Compatibility aliases preserve the issue-specified `TooManyGroupMembersError`, `MissingSmsPhoneError`, and `GroupMemberCreationFailedError` names.
|
|
66
|
+
- **Partner API and Computer Backup type layer.** Public exports now include Partner API and Computer Backup request/response types, branded `groupId()` / `computerId()` factories, `PartnerCapability`, and `Region`. The response types document each Partner and Backup endpoint's wire shape (array-shaped or single-object per the API docs) and preserve numeric-looking group IDs and B2 statistics counts as strings. Endpoint types still awaiting runtime methods are marked experimental until those methods are implemented and exercised.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- **Computer Backup facade validates list pages.** `BackupRawClient.listComputers()` returns the documented single-object `bz_list_computers` wire response, and `BackupClient.listComputers()` validates that page object and returns its `computers` and `nextComputerId`, rejecting malformed wire shapes before pagination can silently truncate results.
|
|
71
|
+
- **Partner auth cache validation is non-destructive during construction.** `PartnerClient` and `BackupClient` ignore unsafe cached Partner authorization locally until `authorize()` replaces it, instead of clearing a shared `PartnerAccountInfo` store from a client constructor.
|
|
72
|
+
- **Partner authorize JSON serialization redacts tokens.** BREAKING: `JSON.stringify()` of `PartnerRawClient.authorizePartner()`, `PartnerClient.authorize()`, and `PartnerAccountInfo.getAuth()` responses now emits `[redacted Partner token]` instead of round-tripping `authorizationToken`. Trusted durable auth caches should stringify `partnerAuthorizeResponseForPersistence(auth)` only for encrypted or otherwise credential-grade storage, or store `authorizationToken` directly in secure storage before rehydrating with `setAuth()`. Rehydrating the redacted placeholder now fails fast instead of sending it as a Partner API bearer token.
|
|
73
|
+
- **Replication status metadata matches B2's wire shape.** BREAKING: `ReplicationStatus` values changed from lowercase (`'pending' | 'completed' | 'failed' | 'replica' | null`) to B2's documented uppercase values (`'PENDING' | 'COMPLETED' | 'FAILED' | 'REPLICA'`), and `FileVersion.replicationStatus`, `StartLargeFileResponse.replicationStatus`, and `UnfinishedLargeFile.replicationStatus` are now omitted instead of `null` when the file is not covered by a replication rule. Callers switching on lowercase literals or `null` must update those comparisons.
|
|
74
|
+
- **Event notification rule target type narrowed.** `EventNotificationRule.targetConfiguration.targetType` is now the string literal `'webhook'` instead of `string` (the only value B2 accepts, corrected from the earlier `'url'` documentation placeholder), and the rule configuration gained an optional `maxEventsPerBatch` field. Code that annotated `targetType` as an arbitrary `string` may need a cast (#123).
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- **Unfinished large-file SHA-1 sentinel normalized.** `startLargeFile` and `listUnfinishedLargeFiles` now collapse B2's `contentSha1: 'none'` wire sentinel to `null`, matching finished and file-list endpoints; the optional `UnfinishedLargeFileMetadata.contentSha1` type is widened to `string | null`.
|
|
79
|
+
- **SHA-1 reader aborts classify as `AbortError`.** Pending stream reads aborted without an explicit reason now reject with an `AbortError` DOMException, and abort classification is centralized so abort-aware sync and upload-finish paths handle those cancellations consistently. Closes #208.
|
|
80
|
+
- **SSE-C keys serialized into JSON request bodies.** `copyFile`, `copyPart`, and `startLargeFile` now serialize customer-managed SSE-C key material into the `b2_copy_file` / `b2_copy_part` / `b2_start_large_file` JSON request bodies instead of the redacting `EncryptionKey` wrapper, which previously emitted the `[redacted SSE-C key]` placeholder and produced a `403` on server-side copy and large-file start with SSE-C. Header-based upload and download paths were unaffected. Closes #206.
|
|
81
|
+
- **B2 endpoint URL path hardening.** `b2Url()` now rejects literal and percent-encoded backslashes in prefix, version, and endpoint path components so WHATWG URL normalization cannot smuggle an endpoint into another path segment.
|
|
82
|
+
- **Real-B2 integration evidence is explicit and diagnosable.** Same-repo integration workflow runs now fail when required B2 secrets are missing instead of silently accepting an all-skipped suite, integration setup logs per-step timings and timeout failures for authorization, bucket listing, stale cleanup, and bucket creation, and contributor/release docs now spell out simulator-vs-live-B2 expectations.
|
|
83
|
+
- **B2Simulator upload write-path fidelity.** Simulator uploads now reject Content-Length/body mismatches, reject upload_part and copy_part part numbers outside B2's 1-10000 range, classify copy_part malformed ranges as 400 and unsatisfiable ranges as 416, verify large-file finish part SHA-1 entries, and report stored monotonic part timestamps from list_parts. Closes #21.
|
|
84
|
+
- **B2Simulator SSE-C fidelity.** Simulator uploads, large-file parts, downloads, copyFile, and copyPart now validate SSE-C customer-key headers more closely to B2, preserve destination encryption semantics, and keep customer keys out of public response metadata.
|
|
85
|
+
- **B2Simulator bucket-deletion fidelity.** `b2_delete_bucket` now rejects buckets that still contain file versions or unfinished large files with `400 cannot_delete_non_empty_bucket`, matching real B2.
|
|
86
|
+
- **B2Simulator bucket-configuration fidelity.** `b2_create_bucket` and `b2_update_bucket` now validate CORS, lifecycle, replication, and default retention rule shapes; `b2_list_buckets` honors the `bucketName`, `bucketId`, and `bucketTypes` filters; and `b2_update_bucket` enforces `ifRevisionIs` with a `409 conflict`. Closes #22.
|
|
87
|
+
- **B2Simulator create_key validation.** `b2_create_key` now rejects unknown or empty capabilities and out-of-range key names, and rejects capabilities that exceed the creating key's grant, matching real B2. Closes #18.
|
|
88
|
+
- **B2Simulator authorize grant fidelity.** `b2_authorize_account` now derives the response `allowed` capabilities and bucket/name-prefix scope from the authorizing key instead of always returning the full master grant, so a restricted key reports its real scope. Closes #19.
|
|
89
|
+
- **B2Simulator object-lock update fidelity.** `b2_update_file_retention` and `b2_update_file_legal_hold` now enforce retention and legal-hold update rules, governance-bypass requirements, and retention-clock validation, matching real B2. Closes #122.
|
|
90
|
+
- **B2Simulator download authorization fidelity.** `b2_get_download_authorization` tokens are now enforced on download: bucket/prefix scope validation, header normalization, and by-id download-auth source restriction. Closes #121.
|
|
91
|
+
- **B2Simulator notification-rule validation.** `b2_set_bucket_notification_rules` now validates event-notification rule shapes before accepting them. Closes #123.
|
|
92
|
+
|
|
10
93
|
## [0.2.0] - 2026-07-08
|
|
11
94
|
|
|
12
95
|
### Added
|
|
@@ -221,6 +304,8 @@ First public release of `@backblaze-labs/b2-sdk`. Everything below is new in thi
|
|
|
221
304
|
- TypeDoc for API documentation
|
|
222
305
|
- Vitest test suite with 486 tests across 20 files at ≥ 95% statement coverage. Tests run cleanly under both vitest (Node) and Bun's vitest-compat (no module-level mocking required)
|
|
223
306
|
|
|
224
|
-
[Unreleased]: https://github.com/backblaze-labs/b2-sdk-typescript/compare/v0.
|
|
307
|
+
[Unreleased]: git+https://github.com/backblaze-labs/b2-sdk-typescript/compare/v0.4.0...HEAD
|
|
308
|
+
[0.4.0]: git+https://github.com/backblaze-labs/b2-sdk-typescript/compare/v0.3.0...v0.4.0
|
|
309
|
+
[0.3.0]: git+https://github.com/backblaze-labs/b2-sdk-typescript/compare/v0.2.0...v0.3.0
|
|
225
310
|
[0.2.0]: https://github.com/backblaze-labs/b2-sdk-typescript/compare/v0.1.0...v0.2.0
|
|
226
311
|
[0.1.0]: https://github.com/backblaze-labs/b2-sdk-typescript/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://backblaze-labs.github.io/b2-sdk-typescript/)
|
|
5
5
|
[](https://www.npmjs.com/package/@backblaze-labs/b2-sdk)
|
|
6
6
|
[](LICENSE)
|
|
7
|
-
[](https://www.typescriptlang.org/)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
[](package.json)
|
|
10
10
|
|
|
@@ -80,6 +80,17 @@ await bucket.delete()
|
|
|
80
80
|
|
|
81
81
|
> The `BucketType`, `RetentionMode`, `LegalHoldValue`, `Capability`, `EventType`, and `EncryptionMode` `as const` objects exported from the main entry give you type-safe alternatives to the raw string literals — pick whichever style you prefer; both are accepted at the type level.
|
|
82
82
|
|
|
83
|
+
### Partner and Computer Backup APIs
|
|
84
|
+
|
|
85
|
+
Partner API and Computer Backup API helpers are exported from dedicated subpaths:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { PartnerClient } from '@backblaze-labs/b2-sdk/partner'
|
|
89
|
+
import { BackupClient } from '@backblaze-labs/b2-sdk/backup'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Both clients authorize with a Master Application Key through the Partner authorization flow. Account-creating Partner operations, including group member creation and trial reservation, require a valid SMS phone number on the administrator account. See [examples/README.md](examples/README.md#partner-and-computer-backup-examples) for current prerequisites and runnable examples.
|
|
93
|
+
|
|
83
94
|
### Uploads
|
|
84
95
|
|
|
85
96
|
Small files (under the recommended part size, typically 100 MB) are uploaded in a single request. Larger files automatically use multipart upload with parallel part uploads.
|
|
@@ -390,6 +401,7 @@ await bucket.setNotificationRules([
|
|
|
390
401
|
targetType: 'webhook',
|
|
391
402
|
url: 'https://my-app.com/webhooks/b2',
|
|
392
403
|
hmacSha256SigningSecret: process.env.B2_WEBHOOK_SECRET,
|
|
404
|
+
customHeaders: [{ name: 'X-B2-Source', value: 'uploads' }],
|
|
393
405
|
},
|
|
394
406
|
},
|
|
395
407
|
])
|
|
@@ -475,6 +487,10 @@ import {
|
|
|
475
487
|
import { InMemoryAccountInfo } from '@backblaze-labs/b2-sdk/auth'
|
|
476
488
|
import { FileAccountInfo } from '@backblaze-labs/b2-sdk/auth/file'
|
|
477
489
|
|
|
490
|
+
// Partner and Computer Backup API surfaces
|
|
491
|
+
import { PartnerClient, PartnerRawClient } from '@backblaze-labs/b2-sdk/partner'
|
|
492
|
+
import { BackupClient, BackupRawClient } from '@backblaze-labs/b2-sdk/backup'
|
|
493
|
+
|
|
478
494
|
// Streaming utilities + SSE-C key wrapper
|
|
479
495
|
import {
|
|
480
496
|
IncrementalSha1,
|
|
@@ -493,6 +509,9 @@ import {
|
|
|
493
509
|
presignS3PutObjectUrl,
|
|
494
510
|
} from '@backblaze-labs/b2-sdk/s3'
|
|
495
511
|
|
|
512
|
+
// Webhook signature verification for B2 event notifications
|
|
513
|
+
import { verifyWebhookSignature, requireValidWebhook } from '@backblaze-labs/b2-sdk/notifications'
|
|
514
|
+
|
|
496
515
|
// In-memory B2 server for tests (no network required)
|
|
497
516
|
import { B2Simulator } from '@backblaze-labs/b2-sdk/simulator'
|
|
498
517
|
```
|
|
@@ -506,6 +525,18 @@ and per-request retry overrides: pass `{ signal, retry }` to `getUploadUrl`,
|
|
|
506
525
|
`getUploadPartUrl`, `uploadFile`, and `uploadPart`. The older positional
|
|
507
526
|
`signal, retry` form remains available only for source compatibility.
|
|
508
527
|
|
|
528
|
+
### Raw native API version policy
|
|
529
|
+
|
|
530
|
+
`RawClient` builds SDK-owned B2 native storage URLs with `/b2api/v4`, matching the
|
|
531
|
+
current published native API docs. The version is centralized in the raw URL
|
|
532
|
+
builder path; upload and download URLs returned by B2 are used as returned.
|
|
533
|
+
|
|
534
|
+
Raw JSON endpoints use POST bodies, including read/list operations whose docs
|
|
535
|
+
also show GET query examples such as `b2_get_upload_url`, `b2_get_file_info`,
|
|
536
|
+
`b2_list_file_names`, and `b2_list_keys`. B2 documents POST JSON compatibility
|
|
537
|
+
for these operations, so the SDK keeps the long-standing POST shape while moving
|
|
538
|
+
the path version to v4.
|
|
539
|
+
|
|
509
540
|
Every export is documented with full type signatures in the [API reference](https://backblaze-labs.github.io/b2-sdk-typescript/).
|
|
510
541
|
|
|
511
542
|
## Sync filters
|
|
@@ -562,12 +593,12 @@ const client = new B2Client({
|
|
|
562
593
|
Every request the SDK issues carries a User-Agent header that Backblaze can grep server logs by:
|
|
563
594
|
|
|
564
595
|
```
|
|
565
|
-
b2-sdk-typescript/<version> (typescript; @backblaze-labs/b2-sdk; node/<node-version>; linux; x64)
|
|
596
|
+
b2-sdk-typescript/<version-or-dev> (typescript; @backblaze-labs/b2-sdk; node/<node-version>; linux; x64)
|
|
566
597
|
```
|
|
567
598
|
|
|
568
599
|
Both `b2-sdk-typescript/` (stable product token) and `@backblaze-labs/b2-sdk` (npm package name) are part of the documented contract — log queries that match either one find every request issued by this SDK. The comment block also reports the runtime (`node/<version>`, `bun/<version>`, `deno/<version>`, or `browser`) plus the OS and architecture on non-browser runtimes.
|
|
569
600
|
|
|
570
|
-
The
|
|
601
|
+
The public `VERSION` constant is always the package semver string. The User-Agent product token reports that semver only from a stable published package; source, CI, `npm pack`, and prerelease builds report `b2-sdk-typescript/dev` so development traffic stays distinguishable.
|
|
571
602
|
|
|
572
603
|
To prepend your own application identifier:
|
|
573
604
|
|
|
@@ -577,7 +608,7 @@ const client = new B2Client({
|
|
|
577
608
|
applicationKey,
|
|
578
609
|
userAgent: 'my-app/1.0',
|
|
579
610
|
})
|
|
580
|
-
// → "my-app/1.0 b2-sdk-typescript/<version> (typescript; @backblaze-labs/b2-sdk; node/<node-version>; linux; x64)"
|
|
611
|
+
// → "my-app/1.0 b2-sdk-typescript/<version-or-dev> (typescript; @backblaze-labs/b2-sdk; node/<node-version>; linux; x64)"
|
|
581
612
|
```
|
|
582
613
|
|
|
583
614
|
## SSRF guard
|
|
@@ -688,7 +719,7 @@ describe('my app', () => {
|
|
|
688
719
|
|
|
689
720
|
## Error handling
|
|
690
721
|
|
|
691
|
-
All B2 API errors are thrown as typed `B2Error` subclasses
|
|
722
|
+
All B2 API errors are thrown as typed `B2Error` subclasses. Client-side capability checks throw `B2InsufficientCapabilityError`.
|
|
692
723
|
|
|
693
724
|
```ts
|
|
694
725
|
import { BucketType } from '@backblaze-labs/b2-sdk'
|
|
@@ -755,14 +786,14 @@ So you get both: an `npm install`-ready `dist/` (ESM + CJS + DTS), *and* a `src/
|
|
|
755
786
|
|
|
756
787
|
| Runtime | Version | Status |
|
|
757
788
|
|---|---|---|
|
|
758
|
-
| Node.js | 22.3+ | Primary target. CI runs the fast suite on Linux, Windows, and macOS for Node 22.
|
|
789
|
+
| Node.js | 22.3+ | Primary target. CI runs the fast suite on Linux, Windows, and macOS for Node 22.22.2 + 24; slow and coverage jobs run on Linux. `FileSource` uses weaker size/mtime validation on Windows because portable dev/inode and ctime fields are unreliable there. |
|
|
759
790
|
| Bun | latest | Tested in CI via `bun test src/` + example typecheck. |
|
|
760
791
|
| Deno | 2.x | Source isomorphism verified in CI via `deno check` against `src/`. |
|
|
761
792
|
| Browsers | Chromium, Firefox, WebKit (last 2 evergreen) | Tested in CI via Playwright. |
|
|
762
793
|
| Cloudflare Workers | - | Compatible target when the standard Web APIs above are available; not yet smoke-tested in CI with a Workers runtime. |
|
|
763
794
|
| Vercel Edge | - | Compatible target when the standard Web APIs above are available; not yet smoke-tested in CI with Vercel Edge. |
|
|
764
795
|
|
|
765
|
-
Requires: `fetch`, Web Streams, `crypto.subtle`, `AbortSignal`. Node < 22.3 is not supported (Node 20 reached EOL April 2026). `FileSource(path)` throws on
|
|
796
|
+
Requires: `fetch`, Web Streams, `crypto.subtle`, `AbortSignal`. Node < 22.3 is not supported (Node 20 reached EOL April 2026). `FileSource(path)` throws on runtimes that lack `process.getBuiltinModule('node:fs')`; `FileSource.fromPath(path)` remains the async construction path for supported Node filesystem platforms. On Windows, `FileSource` is supported with weaker size/mtime validation because portable dev/inode and ctime fields are unreliable there.
|
|
766
797
|
|
|
767
798
|
The browser test suite (`pnpm test:browser`) runs the same source against real Chromium, Firefox, and WebKit instances. Only Node-specific tests (filename pattern `*.node.test.ts`, covering `node:fs`, `node:os`, `node:util.inspect`) are skipped.
|
|
768
799
|
|
package/dist/auth/file.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.cjs","names":[],"sources":["../../src/auth/file.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'\nimport { basename, dirname, join } from 'node:path'\nimport type { AuthorizeAccountResponse } from '../types/auth.ts'\nimport type { BucketId } from '../types/ids.ts'\nimport type { AccountInfo, AuthContextAwareAccountInfo, UploadUrlEntry } from './account-info.ts'\nimport { InMemoryAccountInfo } from './in-memory.ts'\nimport { getRealmUrl } from './realms.ts'\n\nconst PRIVATE_FILE_MODE = 0o600\nconst PERSISTED_AUTH_VERSION = 1\nconst BUILT_IN_PRODUCTION_REALM_URL = getRealmUrl('production')\nconst BUILT_IN_STAGING_REALM_URL = getRealmUrl('staging')\nconst PRODUCTION_HOST_SUFFIX = 'backblazeb2.com'\nconst STAGING_HOST_SUFFIX = 'backblaze.net'\n\ninterface PersistedAuthState {\n readonly _b2sdk?: {\n readonly version?: number\n readonly realmUrl?: string\n readonly applicationKeyId?: string\n }\n}\n\n/** Reason a loaded auth cache entry was ignored for the current client. */\nexport type FileAccountInfoDiscardReason =\n | 'realm_mismatch'\n | 'application_key_mismatch'\n | 'endpoint_mismatch'\n\n/** Event emitted when a loaded cache entry is ignored without modifying disk. */\nexport interface FileAccountInfoDiscardEvent {\n /** Absolute path of the auth cache file. */\n readonly path: string\n /** Why the loaded auth entry could not be reused. */\n readonly reason: FileAccountInfoDiscardReason\n /** Realm URL stored in the cache metadata, or null for legacy entries. */\n readonly cachedRealmUrl: string | null | undefined\n /** Realm URL configured on the current client, if already bound. */\n readonly expectedRealmUrl: string | undefined\n /** Application key ID stored in cache metadata, or null for legacy entries. */\n readonly cachedApplicationKeyId: string | null | undefined\n /** Application key ID configured on the current client, if already bound. */\n readonly expectedApplicationKeyId: string | undefined\n}\n\n/** Event emitted when an asynchronous auth-cache write fails. */\nexport interface FileAccountInfoWriteErrorEvent {\n /** Absolute path of the auth cache file. */\n readonly path: string\n /** Underlying filesystem or serialization error. */\n readonly error: unknown\n}\n\n/** Optional observers for file-backed auth cache behavior. */\nexport interface FileAccountInfoOptions {\n /**\n * Called when loaded auth is ignored because its metadata or cached\n * endpoints are unsafe for this client. The on-disk file is left untouched\n * so another process using a different realm/key is not clobbered.\n */\n readonly onDiscard?: (event: FileAccountInfoDiscardEvent) => void\n /** Called when an asynchronous auth-cache write fails. */\n readonly onWriteError?: (event: FileAccountInfoWriteErrorEvent) => void\n}\n\nfunction readPersistedAuthState(value: unknown): {\n auth: AuthorizeAccountResponse\n realmUrl: string | null\n applicationKeyId: string | null\n} | null {\n if (value === null || typeof value !== 'object') return null\n\n const maybeState = value as PersistedAuthState\n if (\n maybeState._b2sdk?.version !== undefined &&\n maybeState._b2sdk.version !== PERSISTED_AUTH_VERSION\n ) {\n return null\n }\n const realmUrl =\n maybeState._b2sdk !== undefined && typeof maybeState._b2sdk.realmUrl === 'string'\n ? maybeState._b2sdk.realmUrl\n : null\n const applicationKeyId =\n maybeState._b2sdk !== undefined && typeof maybeState._b2sdk.applicationKeyId === 'string'\n ? maybeState._b2sdk.applicationKeyId\n : null\n return { auth: value as AuthorizeAccountResponse, realmUrl, applicationKeyId }\n}\n\nfunction isProductionBackblazeEndpoint(url: string): boolean {\n try {\n const parsed = new URL(url)\n return parsed.protocol === 'https:' && hasHostSuffix(parsed.hostname, PRODUCTION_HOST_SUFFIX)\n } catch {\n return false\n }\n}\n\nfunction isProductionAuthResponse(auth: AuthorizeAccountResponse): boolean {\n try {\n const storageApi = auth.apiInfo.storageApi\n return [storageApi.apiUrl, storageApi.downloadUrl, storageApi.s3ApiUrl].every(\n isProductionBackblazeEndpoint,\n )\n } catch {\n return false\n }\n}\n\nfunction verifiedRealmEndpointSuffix(realmUrl: string): string | null {\n try {\n const realm = new URL(realmUrl)\n if (realm.origin === new URL(BUILT_IN_PRODUCTION_REALM_URL).origin) {\n return PRODUCTION_HOST_SUFFIX\n }\n if (realm.origin === new URL(BUILT_IN_STAGING_REALM_URL).origin) {\n return STAGING_HOST_SUFFIX\n }\n } catch {\n // Fall through below.\n }\n return null\n}\n\nfunction hasHostSuffix(hostname: string, suffix: string): boolean {\n const host = hostname.toLowerCase()\n const lowered = suffix.toLowerCase()\n return host === lowered || host.endsWith(`.${lowered}`)\n}\n\nfunction isEndpointInRealm(url: string, realmUrl: string): boolean {\n try {\n const realm = new URL(realmUrl)\n const endpoint = new URL(url)\n const endpointHost = endpoint.hostname.toLowerCase()\n const realmHost = realm.hostname.toLowerCase()\n if (isLoopbackEndpointHost(endpointHost) || isLoopbackEndpointHost(realmHost)) {\n return endpoint.origin === realm.origin\n }\n\n const verifiedSuffix = verifiedRealmEndpointSuffix(realmUrl)\n if (verifiedSuffix !== null) {\n return endpoint.protocol === 'https:' && hasHostSuffix(endpointHost, verifiedSuffix)\n }\n\n return endpoint.protocol === realm.protocol && hasHostSuffix(endpointHost, realmHost)\n } catch {\n return false\n }\n}\n\nfunction isLoopbackEndpointHost(host: string): boolean {\n if (host === 'localhost' || host === '::1' || host === '[::1]') return true\n\n const parts = host.split('.')\n return (\n parts.length === 4 &&\n parts[0] === '127' &&\n parts.every((part) => /^\\d+$/.test(part) && Number(part) <= 255)\n )\n}\n\nfunction authEndpointsMatchRealm(auth: AuthorizeAccountResponse, realmUrl: string): boolean {\n try {\n const storageApi = auth.apiInfo.storageApi\n return [storageApi.apiUrl, storageApi.downloadUrl, storageApi.s3ApiUrl].every((url) =>\n isEndpointInRealm(url, realmUrl),\n )\n } catch {\n return false\n }\n}\n\n/**\n * Node-only {@link AccountInfo} backend that persists the authorization\n * response to a JSON file. Upload URL pools remain in memory because URLs are\n * short-lived and shouldn't be shared across processes.\n * The JSON file contains a live B2 authorization token, so treat it as\n * sensitive and keep it out of shared or world-readable locations.\n * `FileAccountInfo` serializes writes within one process but does not provide\n * inter-process locking. If multiple processes share one path, use\n * {@link FileAccountInfoOptions.onDiscard} and {@link FileAccountInfoOptions.onWriteError}\n * to observe cache churn, or prefer one cache path per resolved realm and key.\n *\n * On instantiation, call {@link FileAccountInfo.load} to populate state from\n * disk (or start fresh if the file is missing or corrupt). The authorization\n * response is written back to disk on every {@link setAuth} or {@link clear}\n * call so a process restart can resume without re-authorizing.\n * Cache files without an SDK metadata application-key binding are ignored once\n * bound to a `B2Client`, because the SDK cannot verify which key wrote them.\n *\n * This module imports `node:fs/promises`; do not load it in browser bundles.\n */\nexport class FileAccountInfo implements AccountInfo, AuthContextAwareAccountInfo {\n private readonly inner = new InMemoryAccountInfo()\n private writeQueue: Promise<void> = Promise.resolve()\n private realmUrl: string | undefined\n private loadedAuthRealmUrl: string | null | undefined\n private applicationKeyId: string | undefined\n private loadedAuthApplicationKeyId: string | null | undefined\n\n /**\n * Constructs a `FileAccountInfo` bound to a JSON file on disk. The file is\n * created on the first `setAuth` write; reading it back happens via\n * {@link load}.\n *\n * @param path - Absolute path to the JSON file that backs this account info.\n * @param options - Optional discard and write-error observers.\n */\n constructor(\n public readonly path: string,\n private readonly options: FileAccountInfoOptions = {},\n ) {}\n\n /**\n * Reads the JSON file at the configured path and populates in-memory state.\n * If the file is missing or contains invalid JSON, leaves state empty and\n * returns silently (a re-auth is expected next).\n *\n * @returns A promise that resolves when load is complete.\n */\n async load(): Promise<void> {\n try {\n const text = await readFile(this.path, 'utf8')\n const parsed = readPersistedAuthState(JSON.parse(text) as unknown)\n if (parsed === null) return\n this.inner.setAuth(parsed.auth)\n this.loadedAuthRealmUrl = parsed.realmUrl\n this.loadedAuthApplicationKeyId = parsed.applicationKeyId\n this.discardMismatchedAuth()\n } catch {\n // Missing file, invalid JSON, or permission denied: start empty.\n }\n }\n\n /**\n * Binds this cache to the configured realm URL. `B2Client` calls this when a\n * FileAccountInfo instance is supplied, so stale auth produced by another\n * realm is discarded before it can be used.\n *\n * @param realmUrl - Resolved authorize-account realm URL for the client.\n */\n setRealmUrl(realmUrl: string): void {\n this.realmUrl = realmUrl\n this.discardMismatchedAuth()\n }\n\n /**\n * Binds this cache to the configured application key ID. `B2Client` calls\n * this when a FileAccountInfo instance is supplied, so stale auth produced by\n * another key is discarded before it can be used.\n *\n * @param applicationKeyId - Application key ID configured on the client.\n */\n setApplicationKeyId(applicationKeyId: string): void {\n this.applicationKeyId = applicationKeyId\n this.discardMismatchedAuth()\n }\n\n private discardMismatchedAuth(): void {\n const auth = this.inner.getAuth()\n if (auth === null) return\n\n const reason = this.mismatchReason(auth)\n if (reason === null) return\n\n this.inner.clear()\n this.options.onDiscard?.({\n path: this.path,\n reason,\n cachedRealmUrl: this.loadedAuthRealmUrl,\n expectedRealmUrl: this.realmUrl,\n cachedApplicationKeyId: this.loadedAuthApplicationKeyId,\n expectedApplicationKeyId: this.applicationKeyId,\n })\n this.loadedAuthRealmUrl = undefined\n this.loadedAuthApplicationKeyId = undefined\n }\n\n private mismatchReason(auth: AuthorizeAccountResponse): FileAccountInfoDiscardReason | null {\n const realmMatches =\n this.realmUrl === undefined ||\n this.loadedAuthRealmUrl === this.realmUrl ||\n (this.loadedAuthRealmUrl === null &&\n this.realmUrl === BUILT_IN_PRODUCTION_REALM_URL &&\n isProductionAuthResponse(auth))\n const applicationKeyMatches =\n this.applicationKeyId === undefined ||\n this.loadedAuthApplicationKeyId === this.applicationKeyId\n if (!realmMatches) return 'realm_mismatch'\n if (!applicationKeyMatches) return 'application_key_mismatch'\n if (this.realmUrl !== undefined && !authEndpointsMatchRealm(auth, this.realmUrl)) {\n return 'endpoint_mismatch'\n }\n\n return null\n }\n\n /**\n * Persists the current auth response to the file. Atomically queues writes\n * so concurrent updates serialize.\n *\n * @returns A promise that resolves when the write completes.\n */\n private flush(): Promise<void> {\n const auth = this.inner.getAuth()\n const next = this.writeQueue.then(async () => {\n if (auth === null) {\n try {\n await this.writePrivateFileAtomically('')\n } catch {\n // Best-effort\n }\n return\n }\n const contents =\n this.realmUrl !== undefined\n ? JSON.stringify({\n ...auth,\n _b2sdk: {\n version: PERSISTED_AUTH_VERSION,\n realmUrl: this.realmUrl,\n ...(this.applicationKeyId !== undefined\n ? { applicationKeyId: this.applicationKeyId }\n : {}),\n },\n })\n : JSON.stringify(auth)\n await this.writePrivateFileAtomically(contents)\n })\n this.writeQueue = next.catch((error: unknown) => {\n this.options.onWriteError?.({ path: this.path, error })\n })\n return next\n }\n\n private async writePrivateFileAtomically(contents: string): Promise<void> {\n const dir = dirname(this.path)\n const tempPath = join(dir, `.${basename(this.path)}.${process.pid}.${randomUUID()}.tmp`)\n await mkdir(dir, { recursive: true })\n\n try {\n await writeFile(tempPath, contents, { encoding: 'utf8', mode: PRIVATE_FILE_MODE })\n await chmod(tempPath, PRIVATE_FILE_MODE)\n await rename(tempPath, this.path)\n await chmod(this.path, PRIVATE_FILE_MODE)\n } catch (error) {\n await rm(tempPath, { force: true }).catch(() => {})\n throw error\n }\n }\n\n /**\n * Stores the given authorization response and triggers an async write to disk.\n *\n * @param auth - The authorize-account response to persist.\n */\n setAuth(auth: AuthorizeAccountResponse): void {\n this.inner.setAuth(auth)\n this.loadedAuthRealmUrl = this.realmUrl ?? null\n this.loadedAuthApplicationKeyId = this.applicationKeyId ?? null\n void this.flush()\n }\n\n /**\n * Returns the currently cached authorization response, or `null` if none.\n *\n * @returns The cached authorization response, or `null` if not authorized.\n */\n getAuth(): AuthorizeAccountResponse | null {\n return this.inner.getAuth()\n }\n\n /** Discards in-memory state and clears the on-disk file. */\n clear(): void {\n this.inner.clear()\n this.loadedAuthRealmUrl = undefined\n this.loadedAuthApplicationKeyId = undefined\n void this.flush()\n }\n\n /**\n * Returns the cached B2 API URL.\n *\n * @returns The base URL for B2 API calls.\n */\n getApiUrl(): string {\n return this.inner.getApiUrl()\n }\n\n /**\n * Returns the cached file-download URL.\n *\n * @returns The base URL for file downloads.\n */\n getDownloadUrl(): string {\n return this.inner.getDownloadUrl()\n }\n\n /**\n * Returns the cached session authorization token.\n *\n * @returns The current authorization token.\n */\n getAuthToken(): string {\n return this.inner.getAuthToken()\n }\n\n /**\n * Returns the cached account identifier.\n *\n * @returns The authorized account ID.\n */\n getAccountId(): string {\n return this.inner.getAccountId()\n }\n\n /**\n * Returns the server-recommended part size for multipart uploads.\n *\n * @returns The recommended part size in bytes.\n */\n getRecommendedPartSize(): number {\n return this.inner.getRecommendedPartSize()\n }\n\n /**\n * Returns the server-enforced minimum part size for multipart uploads.\n *\n * @returns The absolute minimum part size in bytes.\n */\n getAbsoluteMinimumPartSize(): number {\n return this.inner.getAbsoluteMinimumPartSize()\n }\n\n /**\n * Returns the cached S3-compatible API URL.\n *\n * @returns The base URL for the S3-compatible API.\n */\n getS3ApiUrl(): string {\n return this.inner.getS3ApiUrl()\n }\n\n /**\n * Returns the bucket the key is restricted to, if any.\n *\n * @returns The restricted bucket ID, or `null` if the key is unrestricted.\n */\n getAllowedBucketId(): BucketId | null {\n return this.inner.getAllowedBucketId()\n }\n\n /**\n * Returns the buckets the key is restricted to, if any.\n *\n * @returns The restricted bucket IDs, or `null` if the key is unrestricted.\n */\n getAllowedBucketIds(): readonly BucketId[] | null {\n return this.inner.getAllowedBucketIds()\n }\n\n /**\n * Takes an upload URL from the in-memory pool for the given bucket.\n *\n * @param bucketId - Bucket to check out an upload URL for.\n *\n * @returns A reusable upload URL entry, or `null` if none are pooled.\n */\n checkoutUploadUrl(bucketId: BucketId): UploadUrlEntry | null {\n return this.inner.checkoutUploadUrl(bucketId)\n }\n\n /**\n * Returns a still-valid upload URL to the pool for reuse.\n *\n * @param bucketId - Bucket the upload URL belongs to.\n * @param entry - Upload URL entry to return.\n */\n returnUploadUrl(bucketId: BucketId, entry: UploadUrlEntry): void {\n this.inner.returnUploadUrl(bucketId, entry)\n }\n\n /**\n * Removes an upload URL from the pool after an upload error.\n *\n * @param bucketId - Bucket the failed upload URL belongs to.\n * @param entry - Upload URL entry to evict.\n */\n evictUploadUrl(bucketId: BucketId, entry: UploadUrlEntry): void {\n this.inner.evictUploadUrl(bucketId, entry)\n }\n\n /**\n * Takes a large-file part upload URL from the in-memory pool.\n *\n * @param fileId - Large file to check out a part upload URL for.\n *\n * @returns A reusable part upload URL entry, or `null` if none are pooled.\n */\n checkoutPartUploadUrl(fileId: string): UploadUrlEntry | null {\n return this.inner.checkoutPartUploadUrl(fileId)\n }\n\n /**\n * Returns a still-valid part upload URL to the pool for reuse.\n *\n * @param fileId - Large file the part upload URL belongs to.\n * @param entry - Part upload URL entry to return.\n */\n returnPartUploadUrl(fileId: string, entry: UploadUrlEntry): void {\n this.inner.returnPartUploadUrl(fileId, entry)\n }\n\n /**\n * Removes a part upload URL from the pool after an error.\n *\n * @param fileId - Large file the failed part upload URL belongs to.\n * @param entry - Part upload URL entry to evict.\n */\n evictPartUploadUrl(fileId: string, entry: UploadUrlEntry): void {\n this.inner.evictPartUploadUrl(fileId, entry)\n }\n\n /**\n * Awaits any pending disk writes. Call before process exit to ensure the\n * latest state is persisted.\n *\n * @returns A promise that resolves once all queued writes are flushed.\n */\n async flushed(): Promise<void> {\n await this.writeQueue\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"file.cjs","names":[],"sources":["../../src/auth/file.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'\nimport { basename, dirname, join } from 'node:path'\nimport type { AuthorizeAccountResponse } from '../types/auth.ts'\nimport type { BucketId } from '../types/ids.ts'\nimport type { AccountInfo, AuthContextAwareAccountInfo, UploadUrlEntry } from './account-info.ts'\nimport { InMemoryAccountInfo } from './in-memory.ts'\nimport { getRealmUrl } from './realms.ts'\n\nconst PRIVATE_FILE_MODE = 0o600\nconst PERSISTED_AUTH_VERSION = 1\nconst BUILT_IN_PRODUCTION_REALM_URL = getRealmUrl('production')\nconst BUILT_IN_STAGING_REALM_URL = getRealmUrl('staging')\nconst PRODUCTION_HOST_SUFFIX = 'backblazeb2.com'\nconst STAGING_HOST_SUFFIX = 'backblaze.net'\n\ninterface PersistedAuthState {\n readonly _b2sdk?: {\n readonly version?: number\n readonly realmUrl?: string\n readonly applicationKeyId?: string\n }\n}\n\n/** Reason a loaded auth cache entry was ignored for the current client. */\nexport type FileAccountInfoDiscardReason =\n | 'realm_mismatch'\n | 'application_key_mismatch'\n | 'endpoint_mismatch'\n\n/** Event emitted when a loaded cache entry is ignored without modifying disk. */\nexport interface FileAccountInfoDiscardEvent {\n /** Absolute path of the auth cache file. */\n readonly path: string\n /** Why the loaded auth entry could not be reused. */\n readonly reason: FileAccountInfoDiscardReason\n /** Realm URL stored in the cache metadata, or null for legacy entries. */\n readonly cachedRealmUrl: string | null | undefined\n /** Realm URL configured on the current client, if already bound. */\n readonly expectedRealmUrl: string | undefined\n /** Application key ID stored in cache metadata, or null for legacy entries. */\n readonly cachedApplicationKeyId: string | null | undefined\n /** Application key ID configured on the current client, if already bound. */\n readonly expectedApplicationKeyId: string | undefined\n}\n\n/** Event emitted when an asynchronous auth-cache write fails. */\nexport interface FileAccountInfoWriteErrorEvent {\n /** Absolute path of the auth cache file. */\n readonly path: string\n /** Underlying filesystem or serialization error. */\n readonly error: unknown\n}\n\n/** Optional observers for file-backed auth cache behavior. */\nexport interface FileAccountInfoOptions {\n /**\n * Called when loaded auth is ignored because its metadata or cached\n * endpoints are unsafe for this client. The on-disk file is left untouched\n * so another process using a different realm/key is not clobbered.\n */\n readonly onDiscard?: (event: FileAccountInfoDiscardEvent) => void\n /** Called when an asynchronous auth-cache write fails. */\n readonly onWriteError?: (event: FileAccountInfoWriteErrorEvent) => void\n}\n\nfunction readPersistedAuthState(value: unknown): {\n auth: AuthorizeAccountResponse\n realmUrl: string | null\n applicationKeyId: string | null\n} | null {\n if (value === null || typeof value !== 'object') return null\n\n const maybeState = value as PersistedAuthState\n if (\n maybeState._b2sdk?.version !== undefined &&\n maybeState._b2sdk.version !== PERSISTED_AUTH_VERSION\n ) {\n return null\n }\n const realmUrl =\n maybeState._b2sdk !== undefined && typeof maybeState._b2sdk.realmUrl === 'string'\n ? maybeState._b2sdk.realmUrl\n : null\n const applicationKeyId =\n maybeState._b2sdk !== undefined && typeof maybeState._b2sdk.applicationKeyId === 'string'\n ? maybeState._b2sdk.applicationKeyId\n : null\n return { auth: value as AuthorizeAccountResponse, realmUrl, applicationKeyId }\n}\n\nfunction isProductionBackblazeEndpoint(url: string): boolean {\n try {\n const parsed = new URL(url)\n return parsed.protocol === 'https:' && hasHostSuffix(parsed.hostname, PRODUCTION_HOST_SUFFIX)\n } catch {\n return false\n }\n}\n\nfunction isProductionAuthResponse(auth: AuthorizeAccountResponse): boolean {\n try {\n const storageApi = auth.apiInfo.storageApi\n return [storageApi.apiUrl, storageApi.downloadUrl, storageApi.s3ApiUrl].every(\n isProductionBackblazeEndpoint,\n )\n } catch {\n return false\n }\n}\n\nfunction verifiedRealmEndpointSuffix(realmUrl: string): string | null {\n try {\n const realm = new URL(realmUrl)\n if (realm.origin === new URL(BUILT_IN_PRODUCTION_REALM_URL).origin) {\n return PRODUCTION_HOST_SUFFIX\n }\n if (realm.origin === new URL(BUILT_IN_STAGING_REALM_URL).origin) {\n return STAGING_HOST_SUFFIX\n }\n } catch {\n // Fall through below.\n }\n return null\n}\n\nfunction hasHostSuffix(hostname: string, suffix: string): boolean {\n const host = hostname.toLowerCase()\n const lowered = suffix.toLowerCase()\n return host === lowered || host.endsWith(`.${lowered}`)\n}\n\nfunction isEndpointInRealm(url: string, realmUrl: string): boolean {\n try {\n const realm = new URL(realmUrl)\n const endpoint = new URL(url)\n const endpointHost = endpoint.hostname.toLowerCase()\n const realmHost = realm.hostname.toLowerCase()\n if (isLoopbackEndpointHost(endpointHost) || isLoopbackEndpointHost(realmHost)) {\n return endpoint.origin === realm.origin\n }\n\n const verifiedSuffix = verifiedRealmEndpointSuffix(realmUrl)\n if (verifiedSuffix !== null) {\n return endpoint.protocol === 'https:' && hasHostSuffix(endpointHost, verifiedSuffix)\n }\n\n return endpoint.protocol === realm.protocol && hasHostSuffix(endpointHost, realmHost)\n } catch {\n return false\n }\n}\n\nfunction isLoopbackEndpointHost(host: string): boolean {\n if (host === 'localhost' || host === '::1' || host === '[::1]') return true\n\n const parts = host.split('.')\n return (\n parts.length === 4 &&\n parts[0] === '127' &&\n parts.every((part) => /^\\d+$/.test(part) && Number(part) <= 255)\n )\n}\n\nfunction authEndpointsMatchRealm(auth: AuthorizeAccountResponse, realmUrl: string): boolean {\n try {\n const storageApi = auth.apiInfo.storageApi\n return [storageApi.apiUrl, storageApi.downloadUrl, storageApi.s3ApiUrl].every((url) =>\n isEndpointInRealm(url, realmUrl),\n )\n } catch {\n return false\n }\n}\n\n/**\n * Node-only {@link AccountInfo} backend that persists the authorization\n * response to a JSON file. Upload URL pools remain in memory because URLs are\n * short-lived and shouldn't be shared across processes.\n * The JSON file contains a live B2 authorization token, so treat it as\n * sensitive and keep it out of shared or world-readable locations.\n * `FileAccountInfo` serializes writes within one process but does not provide\n * inter-process locking. If multiple processes share one path, use\n * {@link FileAccountInfoOptions.onDiscard} and {@link FileAccountInfoOptions.onWriteError}\n * to observe cache churn, or prefer one cache path per resolved realm and key.\n *\n * On instantiation, call {@link FileAccountInfo.load} to populate state from\n * disk (or start fresh if the file is missing or corrupt). The authorization\n * response is written back to disk on every {@link setAuth} or {@link clear}\n * call so a process restart can resume without re-authorizing.\n * Cache files without an SDK metadata application-key binding are ignored once\n * bound to a `B2Client`, because the SDK cannot verify which key wrote them.\n *\n * This module imports `node:fs/promises`; do not load it in browser bundles.\n */\nexport class FileAccountInfo implements AccountInfo, AuthContextAwareAccountInfo {\n private readonly inner = new InMemoryAccountInfo()\n private writeQueue: Promise<void> = Promise.resolve()\n private realmUrl: string | undefined\n private loadedAuthRealmUrl: string | null | undefined\n private applicationKeyId: string | undefined\n private loadedAuthApplicationKeyId: string | null | undefined\n\n /**\n * Constructs a `FileAccountInfo` bound to a JSON file on disk. The file is\n * created on the first `setAuth` write; reading it back happens via\n * {@link load}.\n *\n * @param path - Absolute path to the JSON file that backs this account info.\n * @param options - Optional discard and write-error observers.\n */\n constructor(\n public readonly path: string,\n private readonly options: FileAccountInfoOptions = {},\n ) {}\n\n /**\n * Reads the JSON file at the configured path and populates in-memory state.\n * If the file is missing or contains invalid JSON, leaves state empty and\n * returns silently (a re-auth is expected next).\n *\n * @returns A promise that resolves when load is complete.\n */\n async load(): Promise<void> {\n try {\n const text = await readFile(this.path, 'utf8')\n const parsed = readPersistedAuthState(JSON.parse(text) as unknown)\n if (parsed === null) return\n this.inner.setAuth(parsed.auth)\n this.loadedAuthRealmUrl = parsed.realmUrl\n this.loadedAuthApplicationKeyId = parsed.applicationKeyId\n this.discardMismatchedAuth()\n } catch {\n // Missing file, invalid JSON, or permission denied: start empty.\n }\n }\n\n /**\n * Binds this cache to the configured realm URL. `B2Client` calls this when a\n * FileAccountInfo instance is supplied, so stale auth produced by another\n * realm is discarded before it can be used.\n *\n * @param realmUrl - Resolved authorize-account realm URL for the client.\n */\n setRealmUrl(realmUrl: string): void {\n this.realmUrl = realmUrl\n this.discardMismatchedAuth()\n }\n\n /**\n * Binds this cache to the configured application key ID. `B2Client` calls\n * this when a FileAccountInfo instance is supplied, so stale auth produced by\n * another key is discarded before it can be used.\n *\n * @param applicationKeyId - Application key ID configured on the client.\n */\n setApplicationKeyId(applicationKeyId: string): void {\n this.applicationKeyId = applicationKeyId\n this.discardMismatchedAuth()\n }\n\n private discardMismatchedAuth(): void {\n const auth = this.inner.getAuth()\n if (auth === null) return\n\n const reason = this.mismatchReason(auth)\n if (reason === null) return\n\n this.inner.clear()\n this.options.onDiscard?.({\n path: this.path,\n reason,\n cachedRealmUrl: this.loadedAuthRealmUrl,\n expectedRealmUrl: this.realmUrl,\n cachedApplicationKeyId: this.loadedAuthApplicationKeyId,\n expectedApplicationKeyId: this.applicationKeyId,\n })\n this.loadedAuthRealmUrl = undefined\n this.loadedAuthApplicationKeyId = undefined\n }\n\n private mismatchReason(auth: AuthorizeAccountResponse): FileAccountInfoDiscardReason | null {\n const realmMatches =\n this.realmUrl === undefined ||\n this.loadedAuthRealmUrl === this.realmUrl ||\n (this.loadedAuthRealmUrl === null &&\n this.realmUrl === BUILT_IN_PRODUCTION_REALM_URL &&\n isProductionAuthResponse(auth))\n const applicationKeyMatches =\n this.applicationKeyId === undefined ||\n this.loadedAuthApplicationKeyId === this.applicationKeyId\n if (!realmMatches) return 'realm_mismatch'\n if (!applicationKeyMatches) return 'application_key_mismatch'\n if (this.realmUrl !== undefined && !authEndpointsMatchRealm(auth, this.realmUrl)) {\n return 'endpoint_mismatch'\n }\n\n return null\n }\n\n /**\n * Persists the current auth response to the file. Atomically queues writes\n * so concurrent updates serialize.\n *\n * @returns A promise that resolves when the write completes.\n */\n private flush(): Promise<void> {\n const auth = this.inner.getAuth()\n const next = this.writeQueue.then(async () => {\n if (auth === null) {\n try {\n await this.writePrivateFileAtomically('')\n } catch {\n // Best-effort\n }\n return\n }\n const contents =\n this.realmUrl !== undefined\n ? JSON.stringify({\n ...auth,\n _b2sdk: {\n version: PERSISTED_AUTH_VERSION,\n realmUrl: this.realmUrl,\n ...(this.applicationKeyId !== undefined\n ? { applicationKeyId: this.applicationKeyId }\n : {}),\n },\n })\n : JSON.stringify(auth)\n await this.writePrivateFileAtomically(contents)\n })\n this.writeQueue = next.catch((error: unknown) => {\n this.options.onWriteError?.({ path: this.path, error })\n })\n return next\n }\n\n private async writePrivateFileAtomically(contents: string): Promise<void> {\n const dir = dirname(this.path)\n const tempPath = join(dir, `.${basename(this.path)}.${process.pid}.${randomUUID()}.tmp`)\n await mkdir(dir, { recursive: true })\n\n try {\n await writeFile(tempPath, contents, { encoding: 'utf8', mode: PRIVATE_FILE_MODE })\n await chmod(tempPath, PRIVATE_FILE_MODE)\n await rename(tempPath, this.path)\n await chmod(this.path, PRIVATE_FILE_MODE)\n } catch (error) {\n await rm(tempPath, { force: true }).catch(() => {})\n throw error\n }\n }\n\n /**\n * Stores the given authorization response and triggers an async write to disk.\n *\n * @param auth - The authorize-account response to persist.\n */\n setAuth(auth: AuthorizeAccountResponse): void {\n this.inner.setAuth(auth)\n this.loadedAuthRealmUrl = this.realmUrl ?? null\n this.loadedAuthApplicationKeyId = this.applicationKeyId ?? null\n void this.flush()\n }\n\n /**\n * Returns the currently cached authorization response, or `null` if none.\n *\n * @returns The cached authorization response, or `null` if not authorized.\n */\n getAuth(): AuthorizeAccountResponse | null {\n return this.inner.getAuth()\n }\n\n /** Discards in-memory state and clears the on-disk file. */\n clear(): void {\n this.inner.clear()\n this.loadedAuthRealmUrl = undefined\n this.loadedAuthApplicationKeyId = undefined\n void this.flush()\n }\n\n /**\n * Returns the cached B2 API URL.\n *\n * @returns The base URL for B2 API calls.\n */\n getApiUrl(): string {\n return this.inner.getApiUrl()\n }\n\n /**\n * Returns the cached file-download URL.\n *\n * @returns The base URL for file downloads.\n */\n getDownloadUrl(): string {\n return this.inner.getDownloadUrl()\n }\n\n /**\n * Returns the cached session authorization token.\n *\n * @returns The current authorization token.\n */\n getAuthToken(): string {\n return this.inner.getAuthToken()\n }\n\n /**\n * Returns the cached account identifier.\n *\n * @returns The authorized account ID.\n */\n getAccountId(): string {\n return this.inner.getAccountId()\n }\n\n /**\n * Returns the server-recommended part size for multipart uploads.\n *\n * @returns The recommended part size in bytes.\n */\n getRecommendedPartSize(): number {\n return this.inner.getRecommendedPartSize()\n }\n\n /**\n * Returns the server-enforced minimum part size for multipart uploads.\n *\n * @returns The absolute minimum part size in bytes.\n */\n getAbsoluteMinimumPartSize(): number {\n return this.inner.getAbsoluteMinimumPartSize()\n }\n\n /**\n * Returns the cached S3-compatible API URL.\n *\n * @returns The base URL for the S3-compatible API.\n */\n getS3ApiUrl(): string {\n return this.inner.getS3ApiUrl()\n }\n\n /**\n * Returns the bucket the key is restricted to, if any.\n *\n * @returns The restricted bucket ID, or `null` if the key is unrestricted.\n */\n getAllowedBucketId(): BucketId | null {\n return this.inner.getAllowedBucketId()\n }\n\n /**\n * Returns the buckets the key is restricted to, if any.\n *\n * @returns The restricted bucket IDs, or `null` if the key is unrestricted.\n */\n getAllowedBucketIds(): readonly BucketId[] | null {\n return this.inner.getAllowedBucketIds()\n }\n\n /**\n * Takes an upload URL from the in-memory pool for the given bucket.\n *\n * @param bucketId - Bucket to check out an upload URL for.\n *\n * @returns A reusable upload URL entry, or `null` if none are pooled.\n */\n checkoutUploadUrl(bucketId: BucketId): UploadUrlEntry | null {\n return this.inner.checkoutUploadUrl(bucketId)\n }\n\n /**\n * Returns a still-valid upload URL to the pool for reuse.\n *\n * @param bucketId - Bucket the upload URL belongs to.\n * @param entry - Upload URL entry to return.\n */\n returnUploadUrl(bucketId: BucketId, entry: UploadUrlEntry): void {\n this.inner.returnUploadUrl(bucketId, entry)\n }\n\n /**\n * Removes an upload URL from the pool after an upload error.\n *\n * @param bucketId - Bucket the failed upload URL belongs to.\n * @param entry - Upload URL entry to evict.\n */\n evictUploadUrl(bucketId: BucketId, entry: UploadUrlEntry): void {\n this.inner.evictUploadUrl(bucketId, entry)\n }\n\n /**\n * Takes a large-file part upload URL from the in-memory pool.\n *\n * @param fileId - Large file to check out a part upload URL for.\n *\n * @returns A reusable part upload URL entry, or `null` if none are pooled.\n */\n checkoutPartUploadUrl(fileId: string): UploadUrlEntry | null {\n return this.inner.checkoutPartUploadUrl(fileId)\n }\n\n /**\n * Returns a still-valid part upload URL to the pool for reuse.\n *\n * @param fileId - Large file the part upload URL belongs to.\n * @param entry - Part upload URL entry to return.\n */\n returnPartUploadUrl(fileId: string, entry: UploadUrlEntry): void {\n this.inner.returnPartUploadUrl(fileId, entry)\n }\n\n /**\n * Removes a part upload URL from the pool after an error.\n *\n * @param fileId - Large file the failed part upload URL belongs to.\n * @param entry - Part upload URL entry to evict.\n */\n evictPartUploadUrl(fileId: string, entry: UploadUrlEntry): void {\n this.inner.evictPartUploadUrl(fileId, entry)\n }\n\n /**\n * Awaits any pending disk writes. Call before process exit to ensure the\n * latest state is persisted.\n *\n * @returns A promise that resolves once all queued writes are flushed.\n */\n async flushed(): Promise<void> {\n await this.writeQueue\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const require_abort = require("../util/abort.cjs");
|
|
2
|
+
//#region src/auth/reauth-coalescer.ts
|
|
3
|
+
/**
|
|
4
|
+
* Coalesces concurrent reauthorization requests into one in-flight refresh.
|
|
5
|
+
* Each caller waits independently with its own abort signal. The shared
|
|
6
|
+
* refresh receives a coalescer-owned signal and is aborted only when every
|
|
7
|
+
* active waiter has stopped waiting before the refresh settles. A successful,
|
|
8
|
+
* failed, or aborted refresh clears the in-flight slot so the next call can
|
|
9
|
+
* start a new refresh.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
var ReauthCoalescer = class {
|
|
14
|
+
#refresh;
|
|
15
|
+
#inflight = null;
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new reauthorization coalescer.
|
|
18
|
+
*
|
|
19
|
+
* @param refresh - The callback that performs one shared refresh.
|
|
20
|
+
*/
|
|
21
|
+
constructor(refresh) {
|
|
22
|
+
this.#refresh = refresh;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Runs or joins the current refresh. If `signal` is already aborted, the
|
|
26
|
+
* refresh callback is not called. If this waiter aborts while other waiters
|
|
27
|
+
* remain, only this call rejects; the shared refresh continues for the
|
|
28
|
+
* remaining waiters.
|
|
29
|
+
*
|
|
30
|
+
* @param signal - Optional abort signal for this waiter.
|
|
31
|
+
*
|
|
32
|
+
* @returns The shared refresh result.
|
|
33
|
+
*/
|
|
34
|
+
run(signal) {
|
|
35
|
+
require_abort.throwIfSignalAborted(signal);
|
|
36
|
+
const inflight = this.#inflight ?? this.start();
|
|
37
|
+
inflight.waiters += 1;
|
|
38
|
+
return require_abort.raceWithAbort(inflight.promise, signal).finally(() => {
|
|
39
|
+
inflight.waiters -= 1;
|
|
40
|
+
if (inflight.waiters === 0 && !inflight.settled && !inflight.controller.signal.aborted)
|
|
41
|
+
/* v8 ignore next -- waiters can only leave an unsettled refresh by aborting their signal */
|
|
42
|
+
inflight.controller.abort(signal?.aborted === true ? require_abort.abortReason(signal) : void 0);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
start() {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const inflight = {
|
|
48
|
+
controller,
|
|
49
|
+
promise: require_abort.raceWithAbort(this.#refresh(controller.signal), controller.signal).finally(() => {
|
|
50
|
+
/* v8 ignore next -- defensive: callers cannot start a replacement while this is current */
|
|
51
|
+
if (this.#inflight?.controller !== controller) return;
|
|
52
|
+
this.#inflight.settled = true;
|
|
53
|
+
this.#inflight = null;
|
|
54
|
+
}),
|
|
55
|
+
waiters: 0,
|
|
56
|
+
settled: false
|
|
57
|
+
};
|
|
58
|
+
this.#inflight = inflight;
|
|
59
|
+
return inflight;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
exports.ReauthCoalescer = ReauthCoalescer;
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=reauth-coalescer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reauth-coalescer.cjs","names":[],"sources":["../../src/auth/reauth-coalescer.ts"],"sourcesContent":["import { abortReason, raceWithAbort, throwIfSignalAborted } from '../util/abort.ts'\n\n/** Callback that performs the shared reauthorization work. */\nexport type ReauthRefresh<T> = (signal: AbortSignal) => Promise<T>\n\ninterface InflightReauth<T> {\n readonly controller: AbortController\n readonly promise: Promise<T>\n waiters: number\n settled: boolean\n}\n\n/**\n * Coalesces concurrent reauthorization requests into one in-flight refresh.\n * Each caller waits independently with its own abort signal. The shared\n * refresh receives a coalescer-owned signal and is aborted only when every\n * active waiter has stopped waiting before the refresh settles. A successful,\n * failed, or aborted refresh clears the in-flight slot so the next call can\n * start a new refresh.\n *\n * @internal\n */\nexport class ReauthCoalescer<T> {\n readonly #refresh: ReauthRefresh<T>\n #inflight: InflightReauth<T> | null = null\n\n /**\n * Creates a new reauthorization coalescer.\n *\n * @param refresh - The callback that performs one shared refresh.\n */\n constructor(refresh: ReauthRefresh<T>) {\n this.#refresh = refresh\n }\n\n /**\n * Runs or joins the current refresh. If `signal` is already aborted, the\n * refresh callback is not called. If this waiter aborts while other waiters\n * remain, only this call rejects; the shared refresh continues for the\n * remaining waiters.\n *\n * @param signal - Optional abort signal for this waiter.\n *\n * @returns The shared refresh result.\n */\n run(signal?: AbortSignal): Promise<T> {\n throwIfSignalAborted(signal)\n const inflight = this.#inflight ?? this.start()\n inflight.waiters += 1\n return raceWithAbort(inflight.promise, signal).finally(() => {\n inflight.waiters -= 1\n if (inflight.waiters === 0 && !inflight.settled && !inflight.controller.signal.aborted) {\n /* v8 ignore next -- waiters can only leave an unsettled refresh by aborting their signal */\n inflight.controller.abort(signal?.aborted === true ? abortReason(signal) : undefined)\n }\n })\n }\n\n private start(): InflightReauth<T> {\n const controller = new AbortController()\n const promise = raceWithAbort(this.#refresh(controller.signal), controller.signal).finally(\n () => {\n /* v8 ignore next -- defensive: callers cannot start a replacement while this is current */\n if (this.#inflight?.controller !== controller) return\n this.#inflight.settled = true\n this.#inflight = null\n },\n )\n const inflight: InflightReauth<T> = {\n controller,\n promise,\n waiters: 0,\n settled: false,\n }\n this.#inflight = inflight\n return inflight\n }\n}\n"],"mappings":";;;;;;;;;;;;AAsBA,IAAa,kBAAb,MAAgC;CAC9B;CACA,YAAsC;;;;;;CAOtC,YAAY,SAA2B;EACrC,KAAK,WAAW;CAClB;;;;;;;;;;;CAYA,IAAI,QAAkC;EACpC,cAAA,qBAAqB,MAAM;EAC3B,MAAM,WAAW,KAAK,aAAa,KAAK,MAAM;EAC9C,SAAS,WAAW;EACpB,OAAO,cAAA,cAAc,SAAS,SAAS,MAAM,CAAC,CAAC,cAAc;GAC3D,SAAS,WAAW;GACpB,IAAI,SAAS,YAAY,KAAK,CAAC,SAAS,WAAW,CAAC,SAAS,WAAW,OAAO;;GAE7E,SAAS,WAAW,MAAM,QAAQ,YAAY,OAAO,cAAA,YAAY,MAAM,IAAI,KAAA,CAAS;EAExF,CAAC;CACH;CAEA,QAAmC;EACjC,MAAM,aAAa,IAAI,gBAAgB;EASvC,MAAM,WAA8B;GAClC;GACA,SAVc,cAAA,cAAc,KAAK,SAAS,WAAW,MAAM,GAAG,WAAW,MAAM,CAAC,CAAC,cAC3E;;IAEJ,IAAI,KAAK,WAAW,eAAe,YAAY;IAC/C,KAAK,UAAU,UAAU;IACzB,KAAK,YAAY;GACnB,CAIA;GACA,SAAS;GACT,SAAS;EACX;EACA,KAAK,YAAY;EACjB,OAAO;CACT;AACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Callback that performs the shared reauthorization work. */
|
|
2
|
+
export type ReauthRefresh<T> = (signal: AbortSignal) => Promise<T>;
|
|
3
|
+
/**
|
|
4
|
+
* Coalesces concurrent reauthorization requests into one in-flight refresh.
|
|
5
|
+
* Each caller waits independently with its own abort signal. The shared
|
|
6
|
+
* refresh receives a coalescer-owned signal and is aborted only when every
|
|
7
|
+
* active waiter has stopped waiting before the refresh settles. A successful,
|
|
8
|
+
* failed, or aborted refresh clears the in-flight slot so the next call can
|
|
9
|
+
* start a new refresh.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare class ReauthCoalescer<T> {
|
|
14
|
+
#private;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new reauthorization coalescer.
|
|
17
|
+
*
|
|
18
|
+
* @param refresh - The callback that performs one shared refresh.
|
|
19
|
+
*/
|
|
20
|
+
constructor(refresh: ReauthRefresh<T>);
|
|
21
|
+
/**
|
|
22
|
+
* Runs or joins the current refresh. If `signal` is already aborted, the
|
|
23
|
+
* refresh callback is not called. If this waiter aborts while other waiters
|
|
24
|
+
* remain, only this call rejects; the shared refresh continues for the
|
|
25
|
+
* remaining waiters.
|
|
26
|
+
*
|
|
27
|
+
* @param signal - Optional abort signal for this waiter.
|
|
28
|
+
*
|
|
29
|
+
* @returns The shared refresh result.
|
|
30
|
+
*/
|
|
31
|
+
run(signal?: AbortSignal): Promise<T>;
|
|
32
|
+
private start;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=reauth-coalescer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reauth-coalescer.d.ts","sourceRoot":"","sources":["../../src/auth/reauth-coalescer.js"],"names":[],"mappings":"AAEA,8DAA8D;AAC9D,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;AASlE;;;;;;;;;GASG;AACH,qBAAa,eAAe,CAAC,CAAC;;IAI5B;;;;OAIG;gBACS,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;IAIrC;;;;;;;;;OASG;IACH,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAarC,OAAO,CAAC,KAAK;CAmBd"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Callback that performs the shared reauthorization work. */
|
|
2
|
+
export type ReauthRefresh<T> = (signal: AbortSignal) => Promise<T>;
|
|
3
|
+
/**
|
|
4
|
+
* Coalesces concurrent reauthorization requests into one in-flight refresh.
|
|
5
|
+
* Each caller waits independently with its own abort signal. The shared
|
|
6
|
+
* refresh receives a coalescer-owned signal and is aborted only when every
|
|
7
|
+
* active waiter has stopped waiting before the refresh settles. A successful,
|
|
8
|
+
* failed, or aborted refresh clears the in-flight slot so the next call can
|
|
9
|
+
* start a new refresh.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare class ReauthCoalescer<T> {
|
|
14
|
+
#private;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new reauthorization coalescer.
|
|
17
|
+
*
|
|
18
|
+
* @param refresh - The callback that performs one shared refresh.
|
|
19
|
+
*/
|
|
20
|
+
constructor(refresh: ReauthRefresh<T>);
|
|
21
|
+
/**
|
|
22
|
+
* Runs or joins the current refresh. If `signal` is already aborted, the
|
|
23
|
+
* refresh callback is not called. If this waiter aborts while other waiters
|
|
24
|
+
* remain, only this call rejects; the shared refresh continues for the
|
|
25
|
+
* remaining waiters.
|
|
26
|
+
*
|
|
27
|
+
* @param signal - Optional abort signal for this waiter.
|
|
28
|
+
*
|
|
29
|
+
* @returns The shared refresh result.
|
|
30
|
+
*/
|
|
31
|
+
run(signal?: AbortSignal): Promise<T>;
|
|
32
|
+
private start;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=reauth-coalescer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reauth-coalescer.d.ts","sourceRoot":"","sources":["../../src/auth/reauth-coalescer.js"],"names":[],"mappings":"AAEA,8DAA8D;AAC9D,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;AASlE;;;;;;;;;GASG;AACH,qBAAa,eAAe,CAAC,CAAC;;IAI5B;;;;OAIG;gBACS,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;IAIrC;;;;;;;;;OASG;IACH,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAarC,OAAO,CAAC,KAAK;CAmBd"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { abortReason, raceWithAbort, throwIfSignalAborted } from "../util/abort.js";
|
|
2
|
+
//#region src/auth/reauth-coalescer.ts
|
|
3
|
+
/**
|
|
4
|
+
* Coalesces concurrent reauthorization requests into one in-flight refresh.
|
|
5
|
+
* Each caller waits independently with its own abort signal. The shared
|
|
6
|
+
* refresh receives a coalescer-owned signal and is aborted only when every
|
|
7
|
+
* active waiter has stopped waiting before the refresh settles. A successful,
|
|
8
|
+
* failed, or aborted refresh clears the in-flight slot so the next call can
|
|
9
|
+
* start a new refresh.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
var ReauthCoalescer = class {
|
|
14
|
+
#refresh;
|
|
15
|
+
#inflight = null;
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new reauthorization coalescer.
|
|
18
|
+
*
|
|
19
|
+
* @param refresh - The callback that performs one shared refresh.
|
|
20
|
+
*/
|
|
21
|
+
constructor(refresh) {
|
|
22
|
+
this.#refresh = refresh;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Runs or joins the current refresh. If `signal` is already aborted, the
|
|
26
|
+
* refresh callback is not called. If this waiter aborts while other waiters
|
|
27
|
+
* remain, only this call rejects; the shared refresh continues for the
|
|
28
|
+
* remaining waiters.
|
|
29
|
+
*
|
|
30
|
+
* @param signal - Optional abort signal for this waiter.
|
|
31
|
+
*
|
|
32
|
+
* @returns The shared refresh result.
|
|
33
|
+
*/
|
|
34
|
+
run(signal) {
|
|
35
|
+
throwIfSignalAborted(signal);
|
|
36
|
+
const inflight = this.#inflight ?? this.start();
|
|
37
|
+
inflight.waiters += 1;
|
|
38
|
+
return raceWithAbort(inflight.promise, signal).finally(() => {
|
|
39
|
+
inflight.waiters -= 1;
|
|
40
|
+
if (inflight.waiters === 0 && !inflight.settled && !inflight.controller.signal.aborted)
|
|
41
|
+
/* v8 ignore next -- waiters can only leave an unsettled refresh by aborting their signal */
|
|
42
|
+
inflight.controller.abort(signal?.aborted === true ? abortReason(signal) : void 0);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
start() {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const inflight = {
|
|
48
|
+
controller,
|
|
49
|
+
promise: raceWithAbort(this.#refresh(controller.signal), controller.signal).finally(() => {
|
|
50
|
+
/* v8 ignore next -- defensive: callers cannot start a replacement while this is current */
|
|
51
|
+
if (this.#inflight?.controller !== controller) return;
|
|
52
|
+
this.#inflight.settled = true;
|
|
53
|
+
this.#inflight = null;
|
|
54
|
+
}),
|
|
55
|
+
waiters: 0,
|
|
56
|
+
settled: false
|
|
57
|
+
};
|
|
58
|
+
this.#inflight = inflight;
|
|
59
|
+
return inflight;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { ReauthCoalescer };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=reauth-coalescer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reauth-coalescer.js","names":[],"sources":["../../src/auth/reauth-coalescer.ts"],"sourcesContent":["import { abortReason, raceWithAbort, throwIfSignalAborted } from '../util/abort.ts'\n\n/** Callback that performs the shared reauthorization work. */\nexport type ReauthRefresh<T> = (signal: AbortSignal) => Promise<T>\n\ninterface InflightReauth<T> {\n readonly controller: AbortController\n readonly promise: Promise<T>\n waiters: number\n settled: boolean\n}\n\n/**\n * Coalesces concurrent reauthorization requests into one in-flight refresh.\n * Each caller waits independently with its own abort signal. The shared\n * refresh receives a coalescer-owned signal and is aborted only when every\n * active waiter has stopped waiting before the refresh settles. A successful,\n * failed, or aborted refresh clears the in-flight slot so the next call can\n * start a new refresh.\n *\n * @internal\n */\nexport class ReauthCoalescer<T> {\n readonly #refresh: ReauthRefresh<T>\n #inflight: InflightReauth<T> | null = null\n\n /**\n * Creates a new reauthorization coalescer.\n *\n * @param refresh - The callback that performs one shared refresh.\n */\n constructor(refresh: ReauthRefresh<T>) {\n this.#refresh = refresh\n }\n\n /**\n * Runs or joins the current refresh. If `signal` is already aborted, the\n * refresh callback is not called. If this waiter aborts while other waiters\n * remain, only this call rejects; the shared refresh continues for the\n * remaining waiters.\n *\n * @param signal - Optional abort signal for this waiter.\n *\n * @returns The shared refresh result.\n */\n run(signal?: AbortSignal): Promise<T> {\n throwIfSignalAborted(signal)\n const inflight = this.#inflight ?? this.start()\n inflight.waiters += 1\n return raceWithAbort(inflight.promise, signal).finally(() => {\n inflight.waiters -= 1\n if (inflight.waiters === 0 && !inflight.settled && !inflight.controller.signal.aborted) {\n /* v8 ignore next -- waiters can only leave an unsettled refresh by aborting their signal */\n inflight.controller.abort(signal?.aborted === true ? abortReason(signal) : undefined)\n }\n })\n }\n\n private start(): InflightReauth<T> {\n const controller = new AbortController()\n const promise = raceWithAbort(this.#refresh(controller.signal), controller.signal).finally(\n () => {\n /* v8 ignore next -- defensive: callers cannot start a replacement while this is current */\n if (this.#inflight?.controller !== controller) return\n this.#inflight.settled = true\n this.#inflight = null\n },\n )\n const inflight: InflightReauth<T> = {\n controller,\n promise,\n waiters: 0,\n settled: false,\n }\n this.#inflight = inflight\n return inflight\n }\n}\n"],"mappings":";;;;;;;;;;;;AAsBA,IAAa,kBAAb,MAAgC;CAC9B;CACA,YAAsC;;;;;;CAOtC,YAAY,SAA2B;EACrC,KAAK,WAAW;CAClB;;;;;;;;;;;CAYA,IAAI,QAAkC;EACpC,qBAAqB,MAAM;EAC3B,MAAM,WAAW,KAAK,aAAa,KAAK,MAAM;EAC9C,SAAS,WAAW;EACpB,OAAO,cAAc,SAAS,SAAS,MAAM,CAAC,CAAC,cAAc;GAC3D,SAAS,WAAW;GACpB,IAAI,SAAS,YAAY,KAAK,CAAC,SAAS,WAAW,CAAC,SAAS,WAAW,OAAO;;GAE7E,SAAS,WAAW,MAAM,QAAQ,YAAY,OAAO,YAAY,MAAM,IAAI,KAAA,CAAS;EAExF,CAAC;CACH;CAEA,QAAmC;EACjC,MAAM,aAAa,IAAI,gBAAgB;EASvC,MAAM,WAA8B;GAClC;GACA,SAVc,cAAc,KAAK,SAAS,WAAW,MAAM,GAAG,WAAW,MAAM,CAAC,CAAC,cAC3E;;IAEJ,IAAI,KAAK,WAAW,eAAe,YAAY;IAC/C,KAAK,UAAU,UAAU;IACzB,KAAK,YAAY;GACnB,CAIA;GACA,SAAS;GACT,SAAS;EACX;EACA,KAAK,YAAY;EACjB,OAAO;CACT;AACF"}
|