flydrive-azure
Version:
Flydrive Azure Driver
283 lines (282 loc) • 10.5 kB
JavaScript
import { BlobSASPermissions, BlobServiceClient, generateBlobSASQueryParameters, newPipeline, StorageSharedKeyCredential } from '@azure/storage-blob';
import { CannotCopyFileException, CannotDeleteFileException, CannotGetMetaDataException, CannotMoveFileException, CannotSetMetaDataException, CannotWriteFileException, FileNotFoundException, MethodNotImplementedException } from './types.js';
import { DefaultAzureCredential } from '@azure/identity';
import { buffer } from 'node:stream/consumers';
export class AzureDriver {
config;
/**
* Reference to the Azure storage instance
*/
adapter;
/**
* Constructor
* @param {AzureStorageDriverConfig} config - The configuration for the Azure storage driver
*/
constructor(config) {
this.config = config;
if (typeof config.connectionString !== 'undefined') {
// eslint-disable-next-line
this.adapter = BlobServiceClient.fromConnectionString(config.connectionString);
}
else {
// eslint-disable-next-line no-undef-init
let credential = undefined;
if (config.azureTenantId && config.azureClientId && config.azureClientSecret) {
credential = new DefaultAzureCredential();
}
else if (config.name && config.key) {
credential = new StorageSharedKeyCredential(config.name, config.key);
}
let url = `https://${this.config.name}.blob.core.windows.net`;
if (typeof this.config.localAddress !== 'undefined') {
url = this.config.localAddress;
}
const azurePipeline = newPipeline(credential);
this.adapter = new BlobServiceClient(url, azurePipeline);
}
}
getBlockBlobClient(location) {
const container = this.config.container;
if (!container)
throw new Error('Container is not set');
const containerClient = this.adapter.getContainerClient(container);
return containerClient.getBlockBlobClient(location);
}
/**
* Return a boolean value indicating if the file exists
* or not.
*/
async exists(key, optoins) {
try {
return await this.getBlockBlobClient(key).exists(optoins);
}
catch (error) {
return false;
}
}
/**
* Return the file contents as a UTF-8 string. Throw an exception
* if the file is missing.
*/
async get(key, options) {
try {
const blockBlobClient = this.getBlockBlobClient(key);
const res = await blockBlobClient.downloadToBuffer(0, 0, options);
return res.toString('utf-8');
}
catch (error) {
throw new FileNotFoundException(key, error);
}
}
/**
* Return the file contents as a Readable stream. Throw an exception
* if the file is missing.
*/
async getStream(key, options) {
try {
const res = await this.getBlockBlobClient(key).download(0, 0, options);
return res.readableStreamBody;
}
catch (error) {
throw new FileNotFoundException(key, error);
}
}
/**
* Return the file contents as a Uint8Array. Throw an exception
* if the file is missing.
*/
async getBytes(key, options) {
try {
const res = await this.getBlockBlobClient(key).download(0, 0, options);
if (!res.readableStreamBody)
throw new FileNotFoundException(key);
const buf = await buffer(res.readableStreamBody);
return new Uint8Array(buf);
}
catch (error) {
throw new FileNotFoundException(key, error);
}
}
/**
* Return metadata of the file. Throw an exception
* if the file is missing.
*/
async getMetaData(key) {
try {
const blockBlobClient = this.getBlockBlobClient(key);
const metaData = await blockBlobClient.getProperties();
return {
contentType: metaData.contentType,
contentLength: metaData.contentLength ?? 0,
etag: metaData.etag ?? '',
lastModified: metaData.lastModified ?? new Date()
};
}
catch (error) {
throw new FileNotFoundException(key, error);
}
}
/**
* Return visibility of the file. Infer visibility from the initial
* config, when the driver does not support the concept of visibility.
*/
async getVisibility(key) {
throw new CannotGetMetaDataException('Visibility not supported');
}
/**
* Return the public URL of the file. Throw an exception when the driver
* does not support generating URLs.
*/
async getUrl(key) {
return this.getBlockBlobClient(key).url;
}
/**
* Return the signed URL to serve a private file. Throw exception
* when the driver does not support generating URLs.
*/
async getSignedUrl(key, options) {
const blockBlobClient = this.getBlockBlobClient(key);
if (!this.config.container)
throw new Error('Container is not set');
const SASUrl = await this.generateBlobSASURL(blockBlobClient, {
containerName: this.config.container,
...options
});
return SASUrl;
}
/**
* Generate a signed URL for a blob.
* @param blockBlobClient - The block blob client to generate the URL for.
* @param options - The options for the signed URL.
* @returns The signed URL.
*/
async generateBlobSASURL(blockBlobClient, options) {
options.permissions =
options.permissions === undefined || typeof options.permissions === 'string'
? BlobSASPermissions.parse(options.permissions ?? 'r')
: options.permissions;
options.startsOn = options.startsOn ?? new Date();
options.expiresOn = options.expiresOn ?? new Date(options.startsOn.valueOf() + 3600 * 1000);
const blobSAS = generateBlobSASQueryParameters({
containerName: blockBlobClient.containerName, // Required
blobName: blockBlobClient.name, // Required
permissions: options.permissions, // Required
startsOn: options.startsOn,
expiresOn: options.expiresOn
}, blockBlobClient.credential);
return `${blockBlobClient.url}?${blobSAS.toString()}`;
}
/**
* Update the visibility of the file. Result in a NOOP
* when the driver does not support the concept of
* visibility.
*/
async setVisibility(key, visibility) {
throw new CannotSetMetaDataException('Visibility not supported');
}
/**
* Update the visibility of the file. Result in a NOOP
* when the driver does not support the concept of
* getting signed upload url.
*/
async getSignedUploadUrl(key, options) {
throw new CannotSetMetaDataException('getSignedUploadUrl not supported');
}
/**
* Create a new file or update an existing file. The contents
* will be a UTF-8 string or "Uint8Array".
*/
async put(key, contents, options) {
const blockBlobClient = this.getBlockBlobClient(key);
try {
await blockBlobClient.upload(contents, contents.length, options);
}
catch (error) {
throw new CannotWriteFileException(key, error);
}
}
/**
* Create a new file or update an existing file. The contents
* will be a Readable stream.
*/
async putStream(key, contents, options) {
const blockBlobClient = this.getBlockBlobClient(key);
try {
await blockBlobClient.uploadStream(contents, undefined, undefined, options);
}
catch (error) {
throw new CannotWriteFileException(key, error);
}
}
/**
* Copy the existing file to the destination. Make sure the new file
* has the same visibility as the existing file. It might require
* manually fetching the visibility of the "source" file.
*/
async copy(source, destination, options) {
if (!this.config.container)
throw new Error('Container is not set');
const sourceBlockBlobClient = this.getBlockBlobClient(source);
const destinationBlockBlobClient = this.getBlockBlobClient(destination);
const url = await this.generateBlobSASURL(sourceBlockBlobClient, {
containerName: this.config.container,
...options
});
try {
await destinationBlockBlobClient.syncCopyFromURL(url);
}
catch (error) {
throw new CannotCopyFileException(source, error);
}
}
/**
* Move the existing file to the destination. Make sure the new file
* has the same visibility as the existing file. It might require
* manually fetching the visibility of the "source" file.
*/
async move(source, destination, options) {
try {
await this.copy(source, destination, options);
await this.delete(source);
}
catch (error) {
throw new CannotMoveFileException(source, error);
}
}
/**
* Delete an existing file. Do not throw an error if the
* file is already missing
*/
async delete(key) {
try {
await this.getBlockBlobClient(key).delete();
}
catch (error) {
throw new CannotDeleteFileException(key, error);
}
}
/**
* Delete all files inside a folder. Do not throw an error
* if the folder does not exist or is empty.
*/
async deleteAll(prefix) {
throw new MethodNotImplementedException('deleteAll');
}
/**
* List all files from a given folder or the root of the storage.
* Do not throw an error if the request folder does not exist.
*/
async listAll(prefix, options) {
throw new MethodNotImplementedException('listAll');
}
}
// for adonisjs v6
export function AzureService(config) {
return {
type: 'provider',
resolver: async () => {
return () => new AzureDriver(config);
}
};
}
export { CannotCopyFileException, CannotDeleteFileException, CannotGetMetaDataException, CannotMoveFileException, CannotSetMetaDataException, CannotWriteFileException, FileNotFoundException, MethodNotImplementedException };