@tweedegolf/sab-adapter-azure-blob
Version:
Provides an abstraction layer for interacting with Microsoft Azure Blob Storage cloud service.
511 lines (508 loc) • 22.3 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdapterAzureBlob = void 0;
const stream_1 = require("stream");
const storage_blob_1 = require("@azure/storage-blob");
const identity_1 = require("@azure/identity");
const AbstractAdapter_1 = require("./AbstractAdapter");
const general_1 = require("./types/general");
const util_1 = require("./util");
class AdapterAzureBlob extends AbstractAdapter_1.AbstractAdapter {
constructor(config) {
super(config);
this._provider = general_1.Provider.AZURE;
this._configError = null;
if (typeof config !== "string") {
this._config = Object.assign({}, config);
}
else {
const { value, error } = (0, util_1.parseUrl)(config);
if (value === null) {
this._configError = `[configError] ${error}`;
}
else {
const { protocol: provider, username: accountName, password: accountKey, host: bucketName, searchParams, } = value;
if (searchParams !== null) {
this._config = Object.assign({ provider }, searchParams);
}
else {
this._config = { provider };
}
if (accountName !== null) {
this._config.accountName = accountName;
}
if (accountKey !== null) {
this._config.accountKey = accountKey;
}
if (bucketName !== null) {
this._config.bucketName = bucketName;
}
}
}
if (!this.config.accountName && !this.config.connectionString) {
this._configError =
'[configError] Please provide at least a value for "accountName" or for "connectionString';
return;
}
if (typeof this.config.accountKey !== "undefined") {
// option 1: accountName + accountKey
try {
this.sharedKeyCredential = new storage_blob_1.StorageSharedKeyCredential(this.config.accountName, this.config.accountKey);
}
catch (e) {
this._configError = `[configError] ${JSON.parse(e.code)}`;
}
try {
this._client = new storage_blob_1.BlobServiceClient(this.getBlobEndpoint(), this.sharedKeyCredential, this.config.options);
}
catch (e) {
this._configError = `[configError] ${(0, util_1.getErrorMessage)(e)}`;
}
}
else if (typeof this.config.sasToken !== "undefined") {
// option 2: accountName + sasToken
try {
this._client = new storage_blob_1.BlobServiceClient(`${this.getBlobEndpoint()}?${this.config.sasToken}`, new storage_blob_1.AnonymousCredential(), this.config.options);
}
catch (e) {
this._configError = `[configError] ${(0, util_1.getErrorMessage)(e)}`;
}
}
else if (typeof this.config.connectionString !== "undefined") {
// option 3: connection string
try {
this._client = storage_blob_1.BlobServiceClient.fromConnectionString(this.config.connectionString);
}
catch (e) {
this._configError = `[configError] ${(0, util_1.getErrorMessage)(e)}`;
}
}
else {
// option 4: passwordless / Microsoft Entra
// see: https://learn.microsoft.com/en-us/azure/developer/javascript/sdk/authentication/local-development-environment-developer-account?tabs=azure-portal%2Csign-in-azure-powershell
try {
this._client = new storage_blob_1.BlobServiceClient(this.getBlobEndpoint(), new identity_1.DefaultAzureCredential(), this.config.options);
}
catch (e) {
this._configError = `[configError] ${(0, util_1.getErrorMessage)(e)}`;
}
}
if (typeof this.config.bucketName !== "undefined") {
this._bucketName = this.config.bucketName;
}
}
getBlobEndpoint() {
let endpoint = "";
let protocol = "";
if (typeof this.config.blobDomain !== "undefined") {
let blobDomain = this.config.blobDomain;
if (blobDomain.indexOf("http") === 0) {
protocol = blobDomain.substring(0, blobDomain.indexOf("://") + 3);
}
blobDomain = blobDomain.replace(/^(https?:\/\/)/i, "");
// for local testing with Azurite
if (blobDomain.indexOf("127.0.0.1") === 0 || blobDomain.indexOf("localhost") === 0) {
endpoint = `${protocol === "" ? "http://" : protocol}${blobDomain}/${this.config.accountName}`;
}
else {
endpoint = `${protocol === "" ? "https://" : protocol}${this.config.accountName}.${blobDomain}`;
}
}
else {
endpoint = `https://${this.config.accountName}.blob.core.windows.net`;
}
// console.log(endpoint);
return endpoint;
}
// protected, called by methods of public API via AbstractAdapter
_listBuckets() {
return __awaiter(this, void 0, void 0, function* () {
var _a, e_1, _b, _c;
// let i = 0;
try {
const bucketNames = [];
try {
// let i = 0;
for (var _d = true, _e = __asyncValues(this._client.listContainers()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
_c = _f.value;
_d = false;
const container = _c;
// console.log(`${i++} ${container.name}`);
bucketNames.push(container.name);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
return { value: bucketNames, error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_createBucket(name, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
if (options.public === true && typeof options.access === "undefined") {
options.access = "blob";
}
const res = yield this._client.createContainer(name, options);
// const containerClient = this._client.getContainerClient(name);
// await containerClient.create();
return { value: "ok", error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_getFileAsStream(bucketName, fileName, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
const file = this._client.getContainerClient(bucketName).getBlobClient(fileName);
const { start, end } = options;
let offset;
let count;
if (typeof start !== "undefined") {
offset = start;
}
else {
offset = 0;
}
if (typeof end !== "undefined") {
count = end - offset + 1;
}
delete options.start;
delete options.end;
// console.log(offset, count, options);
try {
const stream = yield file.download(offset, count, options);
return { value: stream.readableStreamBody, error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_getPublicURL(bucketName, fileName, _options) {
return __awaiter(this, void 0, void 0, function* () {
try {
const file = this._client.getContainerClient(bucketName).getBlobClient(fileName);
return { value: file.url, error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_getSignedURL(bucketName, fileName, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
const file = this._client.getContainerClient(bucketName).getBlobClient(fileName);
const exp = new Date();
if (typeof options.expiresIn !== "number") {
exp.setUTCDate(exp.getUTCDate() + 7);
}
else {
exp.setSeconds(exp.getSeconds() + options.expiresIn);
}
// console.log(exp)
const sasOptions = {
permissions: options.permissions || storage_blob_1.BlobSASPermissions.parse("r"),
expiresOn: exp,
};
const url = yield file.generateSasUrl(sasOptions);
return { value: url, error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_clearBucket(name) {
return __awaiter(this, void 0, void 0, function* () {
var _a, e_2, _b, _c;
try {
// const containerClient = this._client.getContainerClient(name);
// const blobs = containerClient.listBlobsFlat();
// for await (const blob of blobs) {
// console.log(blob.name);
// await containerClient.deleteBlob(blob.name);
// }
const containerClient = this._client.getContainerClient(name);
const blobs = containerClient.listBlobsByHierarchy("/");
try {
for (var _d = true, blobs_1 = __asyncValues(blobs), blobs_1_1; blobs_1_1 = yield blobs_1.next(), _a = blobs_1_1.done, !_a; _d = true) {
_c = blobs_1_1.value;
_d = false;
const blob = _c;
if (blob.kind === "prefix") {
// console.log("prefix", blob);
}
else {
yield containerClient.deleteBlob(blob.name);
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (!_d && !_a && (_b = blobs_1.return)) yield _b.call(blobs_1);
}
finally { if (e_2) throw e_2.error; }
}
return { value: "ok", error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_deleteBucket(name) {
return __awaiter(this, void 0, void 0, function* () {
try {
const del = yield this._client.deleteContainer(name);
//console.log('deleting container: ', del);
return { value: "ok", error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_listFiles(bucketName, numFiles) {
return __awaiter(this, void 0, void 0, function* () {
var _a, e_3, _b, _c;
try {
let name = bucketName;
let prefix = "";
if (bucketName.indexOf("/") !== -1) {
[name, prefix] = bucketName.split("/");
}
const listOptions = {
includeMetadata: false,
includeSnapshots: false,
prefix, // Filter results by blob name prefix
};
const files = [];
const data = this._client.getContainerClient(name).listBlobsFlat(listOptions);
try {
for (var _d = true, data_1 = __asyncValues(data), data_1_1; data_1_1 = yield data_1.next(), _a = data_1_1.done, !_a; _d = true) {
_c = data_1_1.value;
_d = false;
const blob = _c;
if (typeof blob.properties.contentLength !== "undefined") {
files.push([blob.name, blob.properties.contentLength]);
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (!_d && !_a && (_b = data_1.return)) yield _b.call(data_1);
}
finally { if (e_3) throw e_3.error; }
}
return { value: files, error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_addFile(params) {
return __awaiter(this, void 0, void 0, function* () {
try {
let readStream;
if (typeof params.buffer !== "undefined") {
readStream = new stream_1.Readable();
readStream._read = () => { }; // _read is required but you can noop it
readStream.push(params.buffer);
readStream.push(null);
}
else if (typeof params.stream !== "undefined") {
readStream = params.stream;
}
if (typeof readStream === "undefined") {
return { value: null, error: `could not read local file, buffer or stream` };
}
const file = this._client
.getContainerClient(params.bucketName)
.getBlobClient(params.targetPath)
.getBlockBlobClient();
const writeStream = yield file.uploadStream(readStream, 64000, 20, params.options);
if (writeStream.errorCode) {
return { value: null, error: writeStream.errorCode };
}
else {
return { value: "ok", error: null };
}
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_removeFile(bucketName, fileName) {
return __awaiter(this, void 0, void 0, function* () {
try {
const container = this._client.getContainerClient(bucketName);
const file = yield container.getBlobClient(fileName).deleteIfExists();
return { value: "ok", error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_sizeOf(bucketName, fileName) {
return __awaiter(this, void 0, void 0, function* () {
try {
const blob = this._client.getContainerClient(bucketName).getBlobClient(fileName);
const length = (yield blob.getProperties()).contentLength;
if (typeof length === "undefined") {
return { value: null, error: `could not calculate filesize of ${fileName}` };
}
return { value: length, error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_bucketIsPublic(bucketName) {
return __awaiter(this, void 0, void 0, function* () {
try {
const containerClient = this._client.getContainerClient(bucketName);
const response = yield containerClient.getAccessPolicy();
const accessLevel = response.blobPublicAccess; // "container", "blob", or undefined/null ("none")
const value = accessLevel === "container" || accessLevel === "blob";
return { value, error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_bucketExists(name) {
return __awaiter(this, void 0, void 0, function* () {
try {
const cont = this._client.getContainerClient(name);
const exists = yield cont.exists();
return { value: exists, error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_fileExists(bucketName, fileName) {
return __awaiter(this, void 0, void 0, function* () {
try {
const exists = yield this._client
.getContainerClient(bucketName)
.getBlobClient(fileName)
.exists();
return { value: exists, error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
});
}
_getPresignedUploadURL(bucketName, fileName, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
let starts = new Date();
let offset = 1 * -60;
if (typeof options.startsAt !== "undefined") {
offset = Number.parseInt(options.startsAt, 10);
}
starts.setSeconds(starts.getSeconds() + offset);
let expires = new Date();
offset = 5 * 60;
if (typeof options.expiresIn !== "undefined") {
offset = Number.parseInt(options.expiresIn, 10);
}
expires.setSeconds(expires.getSeconds() + offset);
let permissions = { add: true, create: true, write: true };
if (typeof options.permissions !== "undefined") {
permissions = options.permissions;
}
const blockBlobClient = this._client
.getContainerClient(bucketName)
.getBlockBlobClient(fileName);
const url = yield blockBlobClient.generateSasUrl({
permissions: storage_blob_1.BlobSASPermissions.from(permissions),
expiresOn: expires,
startsOn: starts,
});
return { value: { url }, error: null };
}
catch (e) {
return { value: null, error: (0, util_1.getErrorMessage)(e) };
}
/*
// Set the permissions for the SAS token
const permissions = new ContainerSASPermissions();
permissions.write = true; // Allow write access
// Set the expiry time for the SAS token
const expiryDate = new Date();
expiryDate.setMinutes(expiryDate.getMinutes() + 30); // Token valid for 30 minutes
// Generate the SAS token
const sasToken = generateBlobSASQueryParameters({
containerName: bucketName,
blobName: fileName,
permissions: permissions,
startsOn: new Date(Date.now() - 1 * 60 * 1000),
expiresOn: expiryDate
}, new StorageSharedKeyCredential(this.config.accountName, this.config.accountKey)).toString();
console.log(this.config.accountName, this.config.accountKey, sasToken);
// Construct the presigned URL
const url = `${this._client.getContainerClient(bucketName).getBlobClient(fileName).url}?${sasToken}`;
return { value: { url }, error: null };
*/
// /*
});
}
// public
get config() {
return this._config;
}
getConfig() {
return this._config;
}
get serviceClient() {
return this._client;
}
getServiceClient() {
return this._client;
}
}
exports.AdapterAzureBlob = AdapterAzureBlob;
//# sourceMappingURL=AdapterAzureBlob.js.map