node-cloudflare-r2
Version:
S3 wrapper for Cloudflare R2.
399 lines (398 loc) • 16.5 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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Bucket = void 0;
const client_s3_1 = require("@aws-sdk/client-s3");
const lib_storage_1 = require("@aws-sdk/lib-storage");
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
const fs_1 = require("fs");
const path_1 = require("path");
class Bucket {
/**
* Instantiate `Bucket`.
* @param r2 R2 instance.
* @param bucketName Name of the bucket.
* @param endpoint Cloudflare R2 base endpoint.
*/
constructor(r2, bucketName, endpoint) {
this.bucketPublicUrls = [];
this.r2 = r2;
this.name = bucketName;
this.endpoint = endpoint;
this.uri = `${this.endpoint}/${this.name}`;
}
/**
* Returns the name of the current bucket.
*/
getBucketName() {
return this.name;
}
/**
* Returns the URI for the current bucket.
*/
getUri() {
return this.uri;
}
/**
* Sets the public URL for the current bucket. If public access to the bucket is allowed, use this method to provide bucket public URL to this `Bucket` object.
* @param bucketPublicUrl The public URL of the current bucket.
* @note If public access to the bucket is not allowed, the public URL set by this method will not be accessible to the public. Invoking this function will not have any effect on the security or access permissions of the bucket.
*/
provideBucketPublicUrl(bucketPublicUrl) {
if (typeof bucketPublicUrl === 'string') {
const origin = new URL(bucketPublicUrl).origin;
if (!this.bucketPublicUrls.includes(origin))
this.bucketPublicUrls.push(origin);
}
else if (Array.isArray(bucketPublicUrl)) {
for (const url of bucketPublicUrl) {
this.provideBucketPublicUrl(url);
}
}
return this;
}
/**
* **DEPRECATED. This method will be removed in the next major version. Use `getPublicUrls()` instead.**
*
* Returns the bucket public URL if it's set with `provideBucketPublicUrl` method.
* @deprecated
*/
getPublicUrl() {
return this.bucketPublicUrls.length ? this.bucketPublicUrls.at(0) : undefined;
}
/**
* Returns all public URLs of the bucket if it's set with `provideBucketPublicUrl()` method.
*/
getPublicUrls() {
return this.bucketPublicUrls;
}
/**
* Returns the signed URL of an object. This method does not check whether the object exists or not.
* @param objectKey
* @param expiresIn Expiration time in seconds.
*/
getObjectSignedUrl(objectKey, expiresIn) {
return __awaiter(this, void 0, void 0, function* () {
const obj = new client_s3_1.GetObjectCommand({
Bucket: this.name,
Key: objectKey,
});
const signedUrl = yield (0, s3_request_presigner_1.getSignedUrl)(this.r2, obj, { expiresIn });
return signedUrl;
});
}
/**
* Generates object public URL if the bucket public URL is set with `provideBucketPublicUrl` method.
* @param objectKey
*/
generateObjectPublicUrl(objectKey) {
if (!this.bucketPublicUrls.length)
return null;
return `${this.bucketPublicUrls.at(0)}/${objectKey}`;
}
/**
* Generates object public URLs if the bucket public URL is set with `provideBucketPublicUrl` method.
* @param objectKey
*/
generateObjectPublicUrls(objectKey) {
if (!this.bucketPublicUrls.length)
return [];
return this.bucketPublicUrls.map((publicUrl) => `${publicUrl}/${objectKey}`);
}
/**
* Returns all public URL of an object in the bucket.
* @param objectKey
*/
getObjectPublicUrls(objectKey) {
return this.bucketPublicUrls.map((bucketPublicUrl) => `${bucketPublicUrl}/${objectKey}`);
}
/**
* Checks if the bucket exists and you have permission to access it.
* @param bucketName
*/
exists() {
return __awaiter(this, void 0, void 0, function* () {
try {
const result = yield this.r2.send(new client_s3_1.HeadBucketCommand({
Bucket: this.name,
}));
return result.$metadata.httpStatusCode === 200;
}
catch (_a) {
return false;
}
});
}
/**
* **DEPRECATED. This method will be remove in the next major version. Use `getCorsPolicies()` instead.**
*
* Returns Cross-Origin Resource Sharing (CORS) policies of the bucket.
* @deprecated
*/
getCors() {
return __awaiter(this, void 0, void 0, function* () {
return this.getCorsPolicies();
});
}
/**
* Returns Cross-Origin Resource Sharing (CORS) policies of the bucket.
*/
getCorsPolicies() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
try {
const result = yield this.r2.send(new client_s3_1.GetBucketCorsCommand({
Bucket: this.name,
}));
const corsPolicies = ((_a = result.CORSRules) === null || _a === void 0 ? void 0 : _a.map((rule) => {
const { AllowedHeaders: allowedHeaders, AllowedMethods: allowedMethods, AllowedOrigins: allowedOrigins, ExposeHeaders: exposeHeaders, ID: id, MaxAgeSeconds: maxAgeSeconds, } = rule;
return {
allowedHeaders,
allowedMethods,
allowedOrigins,
exposeHeaders,
id,
maxAgeSeconds,
};
})) || [];
return corsPolicies;
}
catch (_b) {
return [];
}
});
}
/**
* Returns the region the bucket resides in.
* @param bucketName
*/
getRegion() {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.r2.send(new client_s3_1.GetBucketLocationCommand({
Bucket: this.name,
}));
return result.LocationConstraint || 'auto';
});
}
/**
* Returns the encryption configuration of the bucket.
*/
getEncryption() {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const result = yield this.r2.send(new client_s3_1.GetBucketEncryptionCommand({
Bucket: this.name,
}));
const rules = ((_b = (_a = result.ServerSideEncryptionConfiguration) === null || _a === void 0 ? void 0 : _a.Rules) === null || _b === void 0 ? void 0 : _b.map((rule) => {
var _a, _b;
return {
applyServerSideEncryptionByDefault: {
sseAlgorithm: (_a = rule.ApplyServerSideEncryptionByDefault) === null || _a === void 0 ? void 0 : _a.SSEAlgorithm,
kmsMasterKeyId: (_b = rule.ApplyServerSideEncryptionByDefault) === null || _b === void 0 ? void 0 : _b.KMSMasterKeyID,
},
bucketKeyEnabled: rule.BucketKeyEnabled,
};
})) || [];
return rules;
});
}
/**
* Upload a local file to the bucket. If the file already exists in the bucket, it will be overwritten.
* @param file File location.
* @param destination Name of the file to put in the bucket. If `destination` contains slash character(s), this will put the file inside directories.
* @param customMetadata Custom metadata to set to the uploaded file.
* @param mimeType Optional mime type. (Default: `application/octet-stream`)
*/
uploadFile(file, destination, customMetadata, mimeType) {
return __awaiter(this, void 0, void 0, function* () {
const fileStream = (0, fs_1.createReadStream)(file);
try {
const result = yield this.upload(fileStream, destination || (0, path_1.basename)(file.toString()), customMetadata, mimeType);
fileStream.close();
return result;
}
catch (error) {
fileStream.close();
throw error;
}
});
}
/**
* Upload an object to the bucket.
* @param contents The object contents to upload.
* @param destination The name of the file to put in the bucket. If `destination` contains slash character(s), this will put the file inside directories. If the file already exists in the bucket, it will be overwritten.
* @param customMetadata Custom metadata to set to the uploaded file.
* @param mimeType Optional mime type. (Default: `application/octet-stream`)
*/
upload(contents, destination, customMetadata, mimeType) {
return __awaiter(this, void 0, void 0, function* () {
destination = destination.startsWith('/') ? destination.replace(/^\/+/, '') : destination;
const result = yield this.r2.send(new client_s3_1.PutObjectCommand({
Bucket: this.name,
Key: destination,
Body: contents,
ContentType: mimeType || 'application/octet-stream',
Metadata: customMetadata,
}));
return {
objectKey: destination,
uri: `${this.uri}/${destination}`,
publicUrl: this.generateObjectPublicUrl(destination),
publicUrls: this.generateObjectPublicUrls(destination),
etag: result.ETag,
versionId: result.VersionId,
};
});
}
/**
* Upload an object or stream to the bucket. This is a new method to put object to the bucket using multipart upload.
* @param contents The object contents to upload.
* @param destination The name of the file to put in the bucket. If `destination` contains slash character(s), this will put the file inside directories. If the file already exists in the bucket, it will be overwritten.
* @param customMetadata Custom metadata to set to the uploaded file.
* @param mimeType Optional mime type. (Default: `application/octet-stream`)
* @param onProgress A callback to handle progress data.
*/
uploadStream(contents, destination, customMetadata, mimeType, onProgress) {
return __awaiter(this, void 0, void 0, function* () {
destination = destination.startsWith('/') ? destination.replace(/^\/+/, '') : destination;
const upload = new lib_storage_1.Upload({
client: this.r2,
params: {
Bucket: this.name,
Key: destination,
Body: contents,
ContentType: mimeType || 'application/octet-stream',
Metadata: customMetadata,
},
});
if (onProgress)
upload.on('httpUploadProgress', (progress) => onProgress(progress));
const result = yield upload.done();
return {
objectKey: destination,
uri: `${this.uri}/${destination}`,
publicUrl: this.generateObjectPublicUrl(destination),
publicUrls: this.generateObjectPublicUrls(destination),
etag: result.ETag,
versionId: result.VersionId,
};
});
}
/**
* **DEPRECATED. This method will be removed in the next major version. Use `deleteObject()` instead.**
*
* Deletes a file in the bucket.
* @param file
* @deprecated
*/
deleteFile(file) {
return __awaiter(this, void 0, void 0, function* () {
return this.deleteObject(file);
});
}
/**
* Deletes an object in the bucket.
* @param objectKey
*/
deleteObject(objectKey) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.r2.send(new client_s3_1.DeleteObjectCommand({
Bucket: this.name,
Key: objectKey,
}));
return (result.$metadata.httpStatusCode &&
result.$metadata.httpStatusCode >= 200 &&
result.$metadata.httpStatusCode < 300);
});
}
/**
* Retrieves metadata from an object without returning the object itself.
* @param objectKey
*/
headObject(objectKey) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.r2.send(new client_s3_1.HeadObjectCommand({
Bucket: this.name,
Key: objectKey,
}));
return {
lastModified: result.LastModified,
contentLength: result.ContentLength,
acceptRanges: result.AcceptRanges,
etag: result.ETag,
contentType: result.ContentType,
customMetadata: result.Metadata,
};
});
}
/**
* Returns some or all (up to 1,000) of the objects in the bucket with each request.
* @param maxResults The maximum number of objects to return per request. (Default: 1000)
* @param marker A token that specifies where to start the listing.
*/
listObjects() {
return __awaiter(this, arguments, void 0, function* (maxResults = 1000, marker) {
var _a;
const result = yield this.r2.send(new client_s3_1.ListObjectsCommand({
Bucket: this.name,
MaxKeys: maxResults,
Marker: marker,
}));
return {
objects: ((_a = result.Contents) === null || _a === void 0 ? void 0 : _a.map((content) => {
const { Key: key, LastModified: lastModified, ETag: etag, ChecksumAlgorithm: checksumAlgorithm, Size: size, StorageClass: storageClass, } = content;
return {
key,
lastModified,
etag,
checksumAlgorithm,
size,
storageClass,
};
})) || [],
continuationToken: result.Marker,
nextContinuationToken: result.NextMarker,
};
});
}
/**
* Copies an object from the current storage bucket to a new destination object in the same bucket.
* @param sourceObjectKey The key of the source object to be copied.
* @param destinationObjectKey The key of the destination object where the source object will be copied to.
*/
copyObject(sourceObjectKey, destinationObjectKey) {
return __awaiter(this, void 0, void 0, function* () {
const copySource = `${this.name}/${sourceObjectKey}`;
const result = yield this.r2.send(new client_s3_1.CopyObjectCommand({
Bucket: this.name,
CopySource: copySource,
Key: destinationObjectKey,
}));
return result;
});
}
/**
* Checks if an object exists in the bucket.
* @param objectkey
*/
objectExists(objectkey) {
return __awaiter(this, void 0, void 0, function* () {
try {
const result = yield this.headObject(objectkey);
return result.contentLength ? true : false;
}
catch (_a) {
return false;
}
});
}
}
exports.Bucket = Bucket;