UNPKG

flydrive-azure

Version:
283 lines (282 loc) 10.5 kB
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 };