@azure/storage-blob
Version:
Microsoft Azure Storage SDK for JavaScript - Blob
1,006 lines • 142 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { getDefaultProxySettings } from "@azure/core-rest-pipeline";
import { isTokenCredential } from "@azure/core-auth";
import { isNodeLike } from "@azure/core-util";
import { randomUUID } from "@azure/core-util";
import { BlobDownloadResponse } from "./BlobDownloadResponse.js";
import { BlobQueryResponse } from "./BlobQueryResponse.js";
import { AnonymousCredential } from "./credentials/AnonymousCredential.js";
import { StorageSharedKeyCredential } from "./credentials/StorageSharedKeyCredential.js";
import { ensureCpkIfSpecified, toAccessTier } from "./models.js";
import { rangeResponseFromModel } from "./PageBlobRangeResponse.js";
import { newPipeline, isPipelineLike } from "./Pipeline.js";
import { BlobBeginCopyFromUrlPoller } from "./pollers/BlobStartCopyFromUrlPoller.js";
import { rangeToString } from "./Range.js";
import { StorageClient } from "./StorageClient.js";
import { Batch } from "./utils/Batch.js";
import { BufferScheduler } from "@azure/storage-common";
import { BlobDoesNotUseCustomerSpecifiedEncryption, BlobUsesCustomerSpecifiedEncryptionMsg, BLOCK_BLOB_MAX_BLOCKS, BLOCK_BLOB_MAX_STAGE_BLOCK_BYTES, BLOCK_BLOB_MAX_UPLOAD_BLOB_BYTES, DEFAULT_BLOB_DOWNLOAD_BLOCK_BYTES, DEFAULT_BLOCK_BUFFER_SIZE_BYTES, DEFAULT_MAX_DOWNLOAD_RETRY_REQUESTS, ETagAny, URLConstants, } from "./utils/constants.js";
import { tracingClient } from "./utils/tracing.js";
import { appendToURLPath, appendToURLQuery, assertResponse, extractConnectionStringParts, ExtractPageRangeInfoItems, generateBlockID, getURLParameter, httpAuthorizationToString, isIpEndpointStyle, parseObjectReplicationRecord, setURLParameter, toBlobTags, toBlobTagsString, toQuerySerialization, toTags, } from "./utils/utils.common.js";
import { fsCreateReadStream, fsStat, readStreamToLocalFile, streamToBuffer, } from "./utils/utils.js";
import { generateBlobSASQueryParameters, generateBlobSASQueryParametersInternal, } from "./sas/BlobSASSignatureValues.js";
import { BlobLeaseClient } from "./BlobLeaseClient.js";
/**
* A BlobClient represents a URL to an Azure Storage blob; the blob may be a block blob,
* append blob, or page blob.
*/
export class BlobClient extends StorageClient {
/**
* blobContext provided by protocol layer.
*/
blobContext;
_name;
_containerName;
_versionId;
_snapshot;
/**
* The name of the blob.
*/
get name() {
return this._name;
}
/**
* The name of the storage container the blob is associated with.
*/
get containerName() {
return this._containerName;
}
constructor(urlOrConnectionString, credentialOrPipelineOrContainerName, blobNameOrOptions,
// Legacy, no fix for eslint error without breaking. Disable it for this interface.
/* eslint-disable-next-line @azure/azure-sdk/ts-naming-options*/
options) {
options = options || {};
let pipeline;
let url;
if (isPipelineLike(credentialOrPipelineOrContainerName)) {
// (url: string, pipeline: Pipeline)
url = urlOrConnectionString;
pipeline = credentialOrPipelineOrContainerName;
}
else if ((isNodeLike && credentialOrPipelineOrContainerName instanceof StorageSharedKeyCredential) ||
credentialOrPipelineOrContainerName instanceof AnonymousCredential ||
isTokenCredential(credentialOrPipelineOrContainerName)) {
// (url: string, credential?: StorageSharedKeyCredential | AnonymousCredential | TokenCredential, options?: StoragePipelineOptions)
url = urlOrConnectionString;
options = blobNameOrOptions;
pipeline = newPipeline(credentialOrPipelineOrContainerName, options);
}
else if (!credentialOrPipelineOrContainerName &&
typeof credentialOrPipelineOrContainerName !== "string") {
// (url: string, credential?: StorageSharedKeyCredential | AnonymousCredential | TokenCredential, options?: StoragePipelineOptions)
// The second parameter is undefined. Use anonymous credential.
url = urlOrConnectionString;
if (blobNameOrOptions && typeof blobNameOrOptions !== "string") {
options = blobNameOrOptions;
}
pipeline = newPipeline(new AnonymousCredential(), options);
}
else if (credentialOrPipelineOrContainerName &&
typeof credentialOrPipelineOrContainerName === "string" &&
blobNameOrOptions &&
typeof blobNameOrOptions === "string") {
// (connectionString: string, containerName: string, blobName: string, options?: StoragePipelineOptions)
const containerName = credentialOrPipelineOrContainerName;
const blobName = blobNameOrOptions;
const extractedCreds = extractConnectionStringParts(urlOrConnectionString);
if (extractedCreds.kind === "AccountConnString") {
if (isNodeLike) {
const sharedKeyCredential = new StorageSharedKeyCredential(extractedCreds.accountName, extractedCreds.accountKey);
url = appendToURLPath(appendToURLPath(extractedCreds.url, encodeURIComponent(containerName)), encodeURIComponent(blobName));
if (!options.proxyOptions) {
options.proxyOptions = getDefaultProxySettings(extractedCreds.proxyUri);
}
pipeline = newPipeline(sharedKeyCredential, options);
}
else {
throw new Error("Account connection string is only supported in Node.js environment");
}
}
else if (extractedCreds.kind === "SASConnString") {
url =
appendToURLPath(appendToURLPath(extractedCreds.url, encodeURIComponent(containerName)), encodeURIComponent(blobName)) +
"?" +
extractedCreds.accountSas;
pipeline = newPipeline(new AnonymousCredential(), options);
}
else {
throw new Error("Connection string must be either an Account connection string or a SAS connection string");
}
}
else {
throw new Error("Expecting non-empty strings for containerName and blobName parameters");
}
super(url, pipeline);
({ blobName: this._name, containerName: this._containerName } =
this.getBlobAndContainerNamesFromUrl());
this.blobContext = this.storageClientContext.blob;
this._snapshot = getURLParameter(this.url, URLConstants.Parameters.SNAPSHOT);
this._versionId = getURLParameter(this.url, URLConstants.Parameters.VERSIONID);
}
/**
* Creates a new BlobClient object identical to the source but with the specified snapshot timestamp.
* Provide "" will remove the snapshot and return a Client to the base blob.
*
* @param snapshot - The snapshot timestamp.
* @returns A new BlobClient object identical to the source but with the specified snapshot timestamp
*/
withSnapshot(snapshot) {
return new BlobClient(setURLParameter(this.url, URLConstants.Parameters.SNAPSHOT, snapshot.length === 0 ? undefined : snapshot), this.pipeline);
}
/**
* Creates a new BlobClient object pointing to a version of this blob.
* Provide "" will remove the versionId and return a Client to the base blob.
*
* @param versionId - The versionId.
* @returns A new BlobClient object pointing to the version of this blob.
*/
withVersion(versionId) {
return new BlobClient(setURLParameter(this.url, URLConstants.Parameters.VERSIONID, versionId.length === 0 ? undefined : versionId), this.pipeline);
}
/**
* Creates a AppendBlobClient object.
*
*/
getAppendBlobClient() {
return new AppendBlobClient(this.url, this.pipeline);
}
/**
* Creates a BlockBlobClient object.
*
*/
getBlockBlobClient() {
return new BlockBlobClient(this.url, this.pipeline);
}
/**
* Creates a PageBlobClient object.
*
*/
getPageBlobClient() {
return new PageBlobClient(this.url, this.pipeline);
}
/**
* Reads or downloads a blob from the system, including its metadata and properties.
* You can also call Get Blob to read a snapshot.
*
* * In Node.js, data returns in a Readable stream readableStreamBody
* * In browsers, data returns in a promise blobBody
*
* @see https://learn.microsoft.com/rest/api/storageservices/get-blob
*
* @param offset - From which position of the blob to download, greater than or equal to 0
* @param count - How much data to be downloaded, greater than 0. Will download to the end when undefined
* @param options - Optional options to Blob Download operation.
*
*
* Example usage (Node.js):
*
* ```ts snippet:ReadmeSampleDownloadBlob_Node
* import { BlobServiceClient } from "@azure/storage-blob";
* import { DefaultAzureCredential } from "@azure/identity";
*
* const account = "<account>";
* const blobServiceClient = new BlobServiceClient(
* `https://${account}.blob.core.windows.net`,
* new DefaultAzureCredential(),
* );
*
* const containerName = "<container name>";
* const blobName = "<blob name>";
* const containerClient = blobServiceClient.getContainerClient(containerName);
* const blobClient = containerClient.getBlobClient(blobName);
*
* // Get blob content from position 0 to the end
* // In Node.js, get downloaded data by accessing downloadBlockBlobResponse.readableStreamBody
* const downloadBlockBlobResponse = await blobClient.download();
* if (downloadBlockBlobResponse.readableStreamBody) {
* const downloaded = await streamToString(downloadBlockBlobResponse.readableStreamBody);
* console.log(`Downloaded blob content: ${downloaded}`);
* }
*
* async function streamToString(stream: NodeJS.ReadableStream): Promise<string> {
* const result = await new Promise<Buffer<ArrayBuffer>>((resolve, reject) => {
* const chunks: Buffer[] = [];
* stream.on("data", (data) => {
* chunks.push(Buffer.isBuffer(data) ? data : Buffer.from(data));
* });
* stream.on("end", () => {
* resolve(Buffer.concat(chunks));
* });
* stream.on("error", reject);
* });
* return result.toString();
* }
* ```
*
* Example usage (browser):
*
* ```ts snippet:ReadmeSampleDownloadBlob_Browser
* import { BlobServiceClient } from "@azure/storage-blob";
* import { DefaultAzureCredential } from "@azure/identity";
*
* const account = "<account>";
* const blobServiceClient = new BlobServiceClient(
* `https://${account}.blob.core.windows.net`,
* new DefaultAzureCredential(),
* );
*
* const containerName = "<container name>";
* const blobName = "<blob name>";
* const containerClient = blobServiceClient.getContainerClient(containerName);
* const blobClient = containerClient.getBlobClient(blobName);
*
* // Get blob content from position 0 to the end
* // In browsers, get downloaded data by accessing downloadBlockBlobResponse.blobBody
* const downloadBlockBlobResponse = await blobClient.download();
* const blobBody = await downloadBlockBlobResponse.blobBody;
* if (blobBody) {
* const downloaded = await blobBody.text();
* console.log(`Downloaded blob content: ${downloaded}`);
* }
* ```
*/
async download(offset = 0, count, options = {}) {
options.conditions = options.conditions || {};
options.conditions = options.conditions || {};
ensureCpkIfSpecified(options.customerProvidedKey, this.isHttps);
return tracingClient.withSpan("BlobClient-download", options, async (updatedOptions) => {
const res = assertResponse(await this.blobContext.download({
abortSignal: options.abortSignal,
leaseAccessConditions: options.conditions,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
requestOptions: {
onDownloadProgress: isNodeLike ? undefined : options.onProgress, // for Node.js, progress is reported by RetriableReadableStream
},
range: offset === 0 && !count ? undefined : rangeToString({ offset, count }),
rangeGetContentMD5: options.rangeGetContentMD5,
rangeGetContentCRC64: options.rangeGetContentCrc64,
snapshot: options.snapshot,
cpkInfo: options.customerProvidedKey,
tracingOptions: updatedOptions.tracingOptions,
}));
const wrappedRes = {
...res,
_response: res._response, // _response is made non-enumerable
objectReplicationDestinationPolicyId: res.objectReplicationPolicyId,
objectReplicationSourceProperties: parseObjectReplicationRecord(res.objectReplicationRules),
};
// Return browser response immediately
if (!isNodeLike) {
return wrappedRes;
}
// We support retrying when download stream unexpected ends in Node.js runtime
// Following code shouldn't be bundled into browser build, however some
// bundlers may try to bundle following code and "FileReadResponse.ts".
// In this case, "FileDownloadResponse.browser.ts" will be used as a shim of "FileDownloadResponse.ts"
// The config is in package.json "browser" field
if (options.maxRetryRequests === undefined || options.maxRetryRequests < 0) {
// TODO: Default value or make it a required parameter?
options.maxRetryRequests = DEFAULT_MAX_DOWNLOAD_RETRY_REQUESTS;
}
if (res.contentLength === undefined) {
throw new RangeError(`File download response doesn't contain valid content length header`);
}
if (!res.etag) {
throw new RangeError(`File download response doesn't contain valid etag header`);
}
return new BlobDownloadResponse(wrappedRes, async (start) => {
const updatedDownloadOptions = {
leaseAccessConditions: options.conditions,
modifiedAccessConditions: {
ifMatch: options.conditions.ifMatch || res.etag,
ifModifiedSince: options.conditions.ifModifiedSince,
ifNoneMatch: options.conditions.ifNoneMatch,
ifUnmodifiedSince: options.conditions.ifUnmodifiedSince,
ifTags: options.conditions?.tagConditions,
},
range: rangeToString({
count: offset + res.contentLength - start,
offset: start,
}),
rangeGetContentMD5: options.rangeGetContentMD5,
rangeGetContentCRC64: options.rangeGetContentCrc64,
snapshot: options.snapshot,
cpkInfo: options.customerProvidedKey,
};
// Debug purpose only
// console.log(
// `Read from internal stream, range: ${
// updatedOptions.range
// }, options: ${JSON.stringify(updatedOptions)}`
// );
return (await this.blobContext.download({
abortSignal: options.abortSignal,
...updatedDownloadOptions,
})).readableStreamBody;
}, offset, res.contentLength, {
maxRetryRequests: options.maxRetryRequests,
onProgress: options.onProgress,
});
});
}
/**
* Returns true if the Azure blob resource represented by this client exists; false otherwise.
*
* NOTE: use this function with care since an existing blob might be deleted by other clients or
* applications. Vice versa new blobs might be added by other clients or applications after this
* function completes.
*
* @param options - options to Exists operation.
*/
async exists(options = {}) {
return tracingClient.withSpan("BlobClient-exists", options, async (updatedOptions) => {
try {
ensureCpkIfSpecified(options.customerProvidedKey, this.isHttps);
await this.getProperties({
abortSignal: options.abortSignal,
customerProvidedKey: options.customerProvidedKey,
conditions: options.conditions,
tracingOptions: updatedOptions.tracingOptions,
});
return true;
}
catch (e) {
if (e.statusCode === 404) {
// Expected exception when checking blob existence
return false;
}
else if (e.statusCode === 409 &&
(e.details.errorCode === BlobUsesCustomerSpecifiedEncryptionMsg ||
e.details.errorCode === BlobDoesNotUseCustomerSpecifiedEncryption)) {
// Expected exception when checking blob existence
return true;
}
throw e;
}
});
}
/**
* Returns all user-defined metadata, standard HTTP properties, and system properties
* for the blob. It does not return the content of the blob.
* @see https://learn.microsoft.com/rest/api/storageservices/get-blob-properties
*
* WARNING: The `metadata` object returned in the response will have its keys in lowercase, even if
* they originally contained uppercase characters. This differs from the metadata keys returned by
* the methods of {@link ContainerClient} that list blobs using the `includeMetadata` option, which
* will retain their original casing.
*
* @param options - Optional options to Get Properties operation.
*/
async getProperties(options = {}) {
options.conditions = options.conditions || {};
ensureCpkIfSpecified(options.customerProvidedKey, this.isHttps);
return tracingClient.withSpan("BlobClient-getProperties", options, async (updatedOptions) => {
const res = assertResponse(await this.blobContext.getProperties({
abortSignal: options.abortSignal,
leaseAccessConditions: options.conditions,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
cpkInfo: options.customerProvidedKey,
tracingOptions: updatedOptions.tracingOptions,
}));
return {
...res,
_response: res._response, // _response is made non-enumerable
objectReplicationDestinationPolicyId: res.objectReplicationPolicyId,
objectReplicationSourceProperties: parseObjectReplicationRecord(res.objectReplicationRules),
};
});
}
/**
* Marks the specified blob or snapshot for deletion. The blob is later deleted
* during garbage collection. Note that in order to delete a blob, you must delete
* all of its snapshots. You can delete both at the same time with the Delete
* Blob operation.
* @see https://learn.microsoft.com/rest/api/storageservices/delete-blob
*
* @param options - Optional options to Blob Delete operation.
*/
async delete(options = {}) {
options.conditions = options.conditions || {};
return tracingClient.withSpan("BlobClient-delete", options, async (updatedOptions) => {
return assertResponse(await this.blobContext.delete({
abortSignal: options.abortSignal,
deleteSnapshots: options.deleteSnapshots,
leaseAccessConditions: options.conditions,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
tracingOptions: updatedOptions.tracingOptions,
}));
});
}
/**
* Marks the specified blob or snapshot for deletion if it exists. The blob is later deleted
* during garbage collection. Note that in order to delete a blob, you must delete
* all of its snapshots. You can delete both at the same time with the Delete
* Blob operation.
* @see https://learn.microsoft.com/rest/api/storageservices/delete-blob
*
* @param options - Optional options to Blob Delete operation.
*/
async deleteIfExists(options = {}) {
return tracingClient.withSpan("BlobClient-deleteIfExists", options, async (updatedOptions) => {
try {
const res = assertResponse(await this.delete(updatedOptions));
return {
succeeded: true,
...res,
_response: res._response, // _response is made non-enumerable
};
}
catch (e) {
if (e.details?.errorCode === "BlobNotFound") {
return {
succeeded: false,
...e.response?.parsedHeaders,
_response: e.response,
};
}
throw e;
}
});
}
/**
* Restores the contents and metadata of soft deleted blob and any associated
* soft deleted snapshots. Undelete Blob is supported only on version 2017-07-29
* or later.
* @see https://learn.microsoft.com/rest/api/storageservices/undelete-blob
*
* @param options - Optional options to Blob Undelete operation.
*/
async undelete(options = {}) {
return tracingClient.withSpan("BlobClient-undelete", options, async (updatedOptions) => {
return assertResponse(await this.blobContext.undelete({
abortSignal: options.abortSignal,
tracingOptions: updatedOptions.tracingOptions,
}));
});
}
/**
* Sets system properties on the blob.
*
* If no value provided, or no value provided for the specified blob HTTP headers,
* these blob HTTP headers without a value will be cleared.
* @see https://learn.microsoft.com/rest/api/storageservices/set-blob-properties
*
* @param blobHTTPHeaders - If no value provided, or no value provided for
* the specified blob HTTP headers, these blob HTTP
* headers without a value will be cleared.
* A common header to set is `blobContentType`
* enabling the browser to provide functionality
* based on file type.
* @param options - Optional options to Blob Set HTTP Headers operation.
*/
async setHTTPHeaders(blobHTTPHeaders, options = {}) {
options.conditions = options.conditions || {};
ensureCpkIfSpecified(options.customerProvidedKey, this.isHttps);
return tracingClient.withSpan("BlobClient-setHTTPHeaders", options, async (updatedOptions) => {
return assertResponse(await this.blobContext.setHttpHeaders({
abortSignal: options.abortSignal,
blobHttpHeaders: blobHTTPHeaders,
leaseAccessConditions: options.conditions,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
// cpkInfo: options.customerProvidedKey, // CPK is not included in Swagger, should change this back when this issue is fixed in Swagger.
tracingOptions: updatedOptions.tracingOptions,
}));
});
}
/**
* Sets user-defined metadata for the specified blob as one or more name-value pairs.
*
* If no option provided, or no metadata defined in the parameter, the blob
* metadata will be removed.
* @see https://learn.microsoft.com/rest/api/storageservices/set-blob-metadata
*
* @param metadata - Replace existing metadata with this value.
* If no value provided the existing metadata will be removed.
* @param options - Optional options to Set Metadata operation.
*/
async setMetadata(metadata, options = {}) {
options.conditions = options.conditions || {};
ensureCpkIfSpecified(options.customerProvidedKey, this.isHttps);
return tracingClient.withSpan("BlobClient-setMetadata", options, async (updatedOptions) => {
return assertResponse(await this.blobContext.setMetadata({
abortSignal: options.abortSignal,
leaseAccessConditions: options.conditions,
metadata,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
cpkInfo: options.customerProvidedKey,
encryptionScope: options.encryptionScope,
tracingOptions: updatedOptions.tracingOptions,
}));
});
}
/**
* Sets tags on the underlying blob.
* A blob can have up to 10 tags. Tag keys must be between 1 and 128 characters. Tag values must be between 0 and 256 characters.
* Valid tag key and value characters include lower and upper case letters, digits (0-9),
* space (' '), plus ('+'), minus ('-'), period ('.'), foward slash ('/'), colon (':'), equals ('='), and underscore ('_').
*
* @param tags -
* @param options -
*/
async setTags(tags, options = {}) {
return tracingClient.withSpan("BlobClient-setTags", options, async (updatedOptions) => {
return assertResponse(await this.blobContext.setTags({
abortSignal: options.abortSignal,
leaseAccessConditions: options.conditions,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
tracingOptions: updatedOptions.tracingOptions,
tags: toBlobTags(tags),
}));
});
}
/**
* Gets the tags associated with the underlying blob.
*
* @param options -
*/
async getTags(options = {}) {
return tracingClient.withSpan("BlobClient-getTags", options, async (updatedOptions) => {
const response = assertResponse(await this.blobContext.getTags({
abortSignal: options.abortSignal,
leaseAccessConditions: options.conditions,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
tracingOptions: updatedOptions.tracingOptions,
}));
const wrappedResponse = {
...response,
_response: response._response, // _response is made non-enumerable
tags: toTags({ blobTagSet: response.blobTagSet }) || {},
};
return wrappedResponse;
});
}
/**
* Get a {@link BlobLeaseClient} that manages leases on the blob.
*
* @param proposeLeaseId - Initial proposed lease Id.
* @returns A new BlobLeaseClient object for managing leases on the blob.
*/
getBlobLeaseClient(proposeLeaseId) {
return new BlobLeaseClient(this, proposeLeaseId);
}
/**
* Creates a read-only snapshot of a blob.
* @see https://learn.microsoft.com/rest/api/storageservices/snapshot-blob
*
* @param options - Optional options to the Blob Create Snapshot operation.
*/
async createSnapshot(options = {}) {
options.conditions = options.conditions || {};
ensureCpkIfSpecified(options.customerProvidedKey, this.isHttps);
return tracingClient.withSpan("BlobClient-createSnapshot", options, async (updatedOptions) => {
return assertResponse(await this.blobContext.createSnapshot({
abortSignal: options.abortSignal,
leaseAccessConditions: options.conditions,
metadata: options.metadata,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
cpkInfo: options.customerProvidedKey,
encryptionScope: options.encryptionScope,
tracingOptions: updatedOptions.tracingOptions,
}));
});
}
/**
* Asynchronously copies a blob to a destination within the storage account.
* This method returns a long running operation poller that allows you to wait
* indefinitely until the copy is completed.
* You can also cancel a copy before it is completed by calling `cancelOperation` on the poller.
* Note that the onProgress callback will not be invoked if the operation completes in the first
* request, and attempting to cancel a completed copy will result in an error being thrown.
*
* In version 2012-02-12 and later, the source for a Copy Blob operation can be
* a committed blob in any Azure storage account.
* Beginning with version 2015-02-21, the source for a Copy Blob operation can be
* an Azure file in any Azure storage account.
* Only storage accounts created on or after June 7th, 2012 allow the Copy Blob
* operation to copy from another storage account.
* @see https://learn.microsoft.com/rest/api/storageservices/copy-blob
*
* ```ts snippet:ClientsBeginCopyFromURL
* import { BlobServiceClient } from "@azure/storage-blob";
* import { DefaultAzureCredential } from "@azure/identity";
*
* const account = "<account>";
* const blobServiceClient = new BlobServiceClient(
* `https://${account}.blob.core.windows.net`,
* new DefaultAzureCredential(),
* );
*
* const containerName = "<container name>";
* const blobName = "<blob name>";
* const containerClient = blobServiceClient.getContainerClient(containerName);
* const blobClient = containerClient.getBlobClient(blobName);
*
* // Example using automatic polling
* const automaticCopyPoller = await blobClient.beginCopyFromURL("url");
* const automaticResult = await automaticCopyPoller.pollUntilDone();
*
* // Example using manual polling
* const manualCopyPoller = await blobClient.beginCopyFromURL("url");
* while (!manualCopyPoller.isDone()) {
* await manualCopyPoller.poll();
* }
* const manualResult = manualCopyPoller.getResult();
*
* // Example using progress updates
* const progressUpdatesCopyPoller = await blobClient.beginCopyFromURL("url", {
* onProgress(state) {
* console.log(`Progress: ${state.copyProgress}`);
* },
* });
* const progressUpdatesResult = await progressUpdatesCopyPoller.pollUntilDone();
*
* // Example using a changing polling interval (default 15 seconds)
* const pollingIntervalCopyPoller = await blobClient.beginCopyFromURL("url", {
* intervalInMs: 1000, // poll blob every 1 second for copy progress
* });
* const pollingIntervalResult = await pollingIntervalCopyPoller.pollUntilDone();
*
* // Example using copy cancellation:
* const cancelCopyPoller = await blobClient.beginCopyFromURL("url");
* // cancel operation after starting it.
* try {
* await cancelCopyPoller.cancelOperation();
* // calls to get the result now throw PollerCancelledError
* cancelCopyPoller.getResult();
* } catch (err: any) {
* if (err.name === "PollerCancelledError") {
* console.log("The copy was cancelled.");
* }
* }
* ```
*
* @param copySource - url to the source Azure Blob/File.
* @param options - Optional options to the Blob Start Copy From URL operation.
*/
async beginCopyFromURL(copySource, options = {}) {
const client = {
abortCopyFromURL: (...args) => this.abortCopyFromURL(...args),
getProperties: (...args) => this.getProperties(...args),
startCopyFromURL: (...args) => this.startCopyFromURL(...args),
};
const poller = new BlobBeginCopyFromUrlPoller({
blobClient: client,
copySource,
intervalInMs: options.intervalInMs,
onProgress: options.onProgress,
resumeFrom: options.resumeFrom,
startCopyFromURLOptions: options,
});
// Trigger the startCopyFromURL call by calling poll.
// Any errors from this method should be surfaced to the user.
await poller.poll();
return poller;
}
/**
* Aborts a pending asynchronous Copy Blob operation, and leaves a destination blob with zero
* length and full metadata. Version 2012-02-12 and newer.
* @see https://learn.microsoft.com/rest/api/storageservices/abort-copy-blob
*
* @param copyId - Id of the Copy From URL operation.
* @param options - Optional options to the Blob Abort Copy From URL operation.
*/
async abortCopyFromURL(copyId, options = {}) {
return tracingClient.withSpan("BlobClient-abortCopyFromURL", options, async (updatedOptions) => {
return assertResponse(await this.blobContext.abortCopyFromURL(copyId, {
abortSignal: options.abortSignal,
leaseAccessConditions: options.conditions,
tracingOptions: updatedOptions.tracingOptions,
}));
});
}
/**
* The synchronous Copy From URL operation copies a blob or an internet resource to a new blob. It will not
* return a response until the copy is complete.
* @see https://learn.microsoft.com/rest/api/storageservices/copy-blob-from-url
*
* @param copySource - The source URL to copy from, Shared Access Signature(SAS) maybe needed for authentication
* @param options -
*/
async syncCopyFromURL(copySource, options = {}) {
options.conditions = options.conditions || {};
options.sourceConditions = options.sourceConditions || {};
return tracingClient.withSpan("BlobClient-syncCopyFromURL", options, async (updatedOptions) => {
return assertResponse(await this.blobContext.copyFromURL(copySource, {
abortSignal: options.abortSignal,
metadata: options.metadata,
leaseAccessConditions: options.conditions,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
sourceModifiedAccessConditions: {
sourceIfMatch: options.sourceConditions?.ifMatch,
sourceIfModifiedSince: options.sourceConditions?.ifModifiedSince,
sourceIfNoneMatch: options.sourceConditions?.ifNoneMatch,
sourceIfUnmodifiedSince: options.sourceConditions?.ifUnmodifiedSince,
},
sourceContentMD5: options.sourceContentMD5,
copySourceAuthorization: httpAuthorizationToString(options.sourceAuthorization),
tier: toAccessTier(options.tier),
blobTagsString: toBlobTagsString(options.tags),
immutabilityPolicyExpiry: options.immutabilityPolicy?.expiriesOn,
immutabilityPolicyMode: options.immutabilityPolicy?.policyMode,
legalHold: options.legalHold,
encryptionScope: options.encryptionScope,
copySourceTags: options.copySourceTags,
fileRequestIntent: options.sourceShareTokenIntent,
tracingOptions: updatedOptions.tracingOptions,
}));
});
}
/**
* Sets the tier on a blob. The operation is allowed on a page blob in a premium
* storage account and on a block blob in a blob storage account (locally redundant
* storage only). A premium page blob's tier determines the allowed size, IOPS,
* and bandwidth of the blob. A block blob's tier determines Hot/Cool/Archive
* storage type. This operation does not update the blob's ETag.
* @see https://learn.microsoft.com/rest/api/storageservices/set-blob-tier
*
* @param tier - The tier to be set on the blob. Valid values are Hot, Cool, or Archive.
* @param options - Optional options to the Blob Set Tier operation.
*/
async setAccessTier(tier, options = {}) {
return tracingClient.withSpan("BlobClient-setAccessTier", options, async (updatedOptions) => {
return assertResponse(await this.blobContext.setTier(toAccessTier(tier), {
abortSignal: options.abortSignal,
leaseAccessConditions: options.conditions,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
rehydratePriority: options.rehydratePriority,
tracingOptions: updatedOptions.tracingOptions,
}));
});
}
async downloadToBuffer(param1, param2, param3, param4 = {}) {
let buffer;
let offset = 0;
let count = 0;
let options = param4;
if (param1 instanceof Buffer) {
buffer = param1;
offset = param2 || 0;
count = typeof param3 === "number" ? param3 : 0;
}
else {
offset = typeof param1 === "number" ? param1 : 0;
count = typeof param2 === "number" ? param2 : 0;
options = param3 || {};
}
let blockSize = options.blockSize ?? 0;
if (blockSize < 0) {
throw new RangeError("blockSize option must be >= 0");
}
if (blockSize === 0) {
blockSize = DEFAULT_BLOB_DOWNLOAD_BLOCK_BYTES;
}
if (offset < 0) {
throw new RangeError("offset option must be >= 0");
}
if (count && count <= 0) {
throw new RangeError("count option must be greater than 0");
}
if (!options.conditions) {
options.conditions = {};
}
return tracingClient.withSpan("BlobClient-downloadToBuffer", options, async (updatedOptions) => {
// Customer doesn't specify length, get it
if (!count) {
const response = await this.getProperties({
...options,
tracingOptions: updatedOptions.tracingOptions,
});
count = response.contentLength - offset;
if (count < 0) {
throw new RangeError(`offset ${offset} shouldn't be larger than blob size ${response.contentLength}`);
}
}
// Allocate the buffer of size = count if the buffer is not provided
if (!buffer) {
try {
buffer = Buffer.alloc(count);
}
catch (error) {
throw new Error(`Unable to allocate the buffer of size: ${count}(in bytes). Please try passing your own buffer to the "downloadToBuffer" method or try using other methods like "download" or "downloadToFile".\t ${error.message}`);
}
}
if (buffer.length < count) {
throw new RangeError(`The buffer's size should be equal to or larger than the request count of bytes: ${count}`);
}
let transferProgress = 0;
const batch = new Batch(options.concurrency);
for (let off = offset; off < offset + count; off = off + blockSize) {
batch.addOperation(async () => {
// Exclusive chunk end position
let chunkEnd = offset + count;
if (off + blockSize < chunkEnd) {
chunkEnd = off + blockSize;
}
const response = await this.download(off, chunkEnd - off, {
abortSignal: options.abortSignal,
conditions: options.conditions,
maxRetryRequests: options.maxRetryRequestsPerBlock,
customerProvidedKey: options.customerProvidedKey,
tracingOptions: updatedOptions.tracingOptions,
});
const stream = response.readableStreamBody;
await streamToBuffer(stream, buffer, off - offset, chunkEnd - offset);
// Update progress after block is downloaded, in case of block trying
// Could provide finer grained progress updating inside HTTP requests,
// only if convenience layer download try is enabled
transferProgress += chunkEnd - off;
if (options.onProgress) {
options.onProgress({ loadedBytes: transferProgress });
}
});
}
await batch.do();
return buffer;
});
}
/**
* ONLY AVAILABLE IN NODE.JS RUNTIME.
*
* Downloads an Azure Blob to a local file.
* Fails if the the given file path already exits.
* Offset and count are optional, pass 0 and undefined respectively to download the entire blob.
*
* @param filePath -
* @param offset - From which position of the block blob to download.
* @param count - How much data to be downloaded. Will download to the end when passing undefined.
* @param options - Options to Blob download options.
* @returns The response data for blob download operation,
* but with readableStreamBody set to undefined since its
* content is already read and written into a local file
* at the specified path.
*/
async downloadToFile(filePath, offset = 0, count, options = {}) {
return tracingClient.withSpan("BlobClient-downloadToFile", options, async (updatedOptions) => {
const response = await this.download(offset, count, {
...options,
tracingOptions: updatedOptions.tracingOptions,
});
if (response.readableStreamBody) {
await readStreamToLocalFile(response.readableStreamBody, filePath);
}
// The stream is no longer accessible so setting it to undefined.
response.blobDownloadStream = undefined;
return response;
});
}
getBlobAndContainerNamesFromUrl() {
let containerName;
let blobName;
try {
// URL may look like the following
// "https://myaccount.blob.core.windows.net/mycontainer/blob?sasString";
// "https://myaccount.blob.core.windows.net/mycontainer/blob";
// "https://myaccount.blob.core.windows.net/mycontainer/blob/a.txt?sasString";
// "https://myaccount.blob.core.windows.net/mycontainer/blob/a.txt";
// IPv4/IPv6 address hosts, Endpoints - `http://127.0.0.1:10000/devstoreaccount1/containername/blob`
// http://localhost:10001/devstoreaccount1/containername/blob
const parsedUrl = new URL(this.url);
if (parsedUrl.host.split(".")[1] === "blob") {
// "https://myaccount.blob.core.windows.net/containername/blob".
// .getPath() -> /containername/blob
const pathComponents = parsedUrl.pathname.match("/([^/]*)(/(.*))?");
containerName = pathComponents[1];
blobName = pathComponents[3];
}
else if (isIpEndpointStyle(parsedUrl)) {
// IPv4/IPv6 address hosts... Example - http://192.0.0.10:10001/devstoreaccount1/containername/blob
// Single word domain without a [dot] in the endpoint... Example - http://localhost:10001/devstoreaccount1/containername/blob
// .getPath() -> /devstoreaccount1/containername/blob
const pathComponents = parsedUrl.pathname.match("/([^/]*)/([^/]*)(/(.*))?");
containerName = pathComponents[2];
blobName = pathComponents[4];
}
else {
// "https://customdomain.com/containername/blob".
// .getPath() -> /containername/blob
const pathComponents = parsedUrl.pathname.match("/([^/]*)(/(.*))?");
containerName = pathComponents[1];
blobName = pathComponents[3];
}
// decode the encoded blobName, containerName - to get all the special characters that might be present in them
containerName = decodeURIComponent(containerName);
blobName = decodeURIComponent(blobName);
// Azure Storage Server will replace "\" with "/" in the blob names
// doing the same in the SDK side so that the user doesn't have to replace "\" instances in the blobName
blobName = blobName.replace(/\\/g, "/");
if (!containerName) {
throw new Error("Provided containerName is invalid.");
}
return { blobName, containerName };
}
catch (error) {
throw new Error("Unable to extract blobName and containerName with provided information.");
}
}
/**
* Asynchronously copies a blob to a destination within the storage account.
* In version 2012-02-12 and later, the source for a Copy Blob operation can be
* a committed blob in any Azure storage account.
* Beginning with version 2015-02-21, the source for a Copy Blob operation can be
* an Azure file in any Azure storage account.
* Only storage accounts created on or after June 7th, 2012 allow the Copy Blob
* operation to copy from another storage account.
* @see https://learn.microsoft.com/rest/api/storageservices/copy-blob
*
* @param copySource - url to the source Azure Blob/File.
* @param options - Optional options to the Blob Start Copy From URL operation.
*/
async startCopyFromURL(copySource, options = {}) {
return tracingClient.withSpan("BlobClient-startCopyFromURL", options, async (updatedOptions) => {
options.conditions = options.conditions || {};
options.sourceConditions = options.sourceConditions || {};
return assertResponse(await this.blobContext.startCopyFromURL(copySource, {
abortSignal: options.abortSignal,
leaseAccessConditions: options.conditions,
metadata: options.metadata,
modifiedAccessConditions: {
...options.conditions,
ifTags: options.conditions?.tagConditions,
},
sourceModifiedAccessConditions: {
sourceIfMatch: options.sourceConditions.ifMatch,
sourceIfModifiedSince: options.sourceConditions.ifModifiedSince,
sourceIfNoneMatch: options.sourceConditions.ifNoneMatch,
sourceIfUnmodifiedSince: options.sourceConditions.ifUnmodifiedSince,
sourceIfTags: options.sourceConditions.tagConditions,
},
immutabilityPolicyExpiry: options.immutabilityPolicy?.expiriesOn,
immutabilityPolicyMode: options.immutabilityPolicy?.policyMode,
legalHold: options.legalHold,
rehydratePriority: options.rehydratePriority,
tier: toAccessTier(options.tier),
blobTagsString: toBlobTagsString(options.tags),
sealBlob: options.sealBlob,
tracingOptions: updatedOptions.tracingOptions,
}));
});
}
/**
* Only available for BlobClient constructed with a shared key credential.
*
* Generates a Blob Service Shared Access Signature (SAS) URI based on the client properties
* and parameters passed in. The SAS is signed by the shared key credential of the client.
*
* @see https://learn.microsoft.com/rest/api/storageservices/constructing-a-service-sas
*
* @param options - Optional parameters.
* @returns The SAS URI consisting of the URI to the resource represented by this client, followed by the generated SAS token.
*/
generateSasUrl(options) {
return new Promise((resolve) => {
if (!(this.credential instanceof StorageSharedKeyCredential)) {
throw new RangeError("Can only generate the SAS when the client is initialized with a shared key credential");
}
const sas =