UNPKG

@needle-tools/networking

Version:
199 lines (190 loc) 6.71 kB
const S3 = require("@aws-sdk/client-s3"); const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); class S3Storage { /** * @param {{endpoint:string, region:string, bucket:string, accessKeyId:string, accessKey:string, prefix:string}} args */ constructor(args) { this.client = new S3.S3Client({ endpoint: args.endpoint, region: args.region, credentials: { accessKeyId: args.accessKeyId, secretAccessKey: args.accessKey } }); this.bucket = args.bucket; this.prefix = args.prefix; } /** * @param {string} roomName * @returns {Promise<{success:boolean, message?:string, state?:any}>} */ async loadRoomState(roomName) { try { const params = { Bucket: this.bucket, Key: `${this.prefix}${roomName}/state.json` }; const cmd = new S3.GetObjectCommand(params); const response = await this.client.send(cmd); const data = await response.Body?.transformToString(); if (typeof data === "string") { const state = JSON.parse(data); return { success: true, state }; } } catch (e) { if (e instanceof S3.NoSuchKey) { return { success: false, message: "file does not exist", }; } else { console.error(e); return { success: false, message: "unknown error", }; } } return { success: false, message: "unknown error", }; } /** * @param {string} roomName * @param {any} state */ async saveRoomState(roomName, state) { const params = { Bucket: this.bucket, Key: `${this.prefix}${roomName}/state.json`, Body: JSON.stringify(state), ContentType: "application/json" }; const cmd = new S3.PutObjectCommand(params); try { await this.client.send(cmd); } catch (e) { console.error(e); } } /** * @param {string} key * @returns {Promise<boolean>} */ async blobExists(key) { const params = { Bucket: this.bucket, Key: key, }; const cmd = new S3.HeadObjectCommand(params); try { await this.client.send(cmd); return true; } catch (e) { if (e instanceof S3.NoSuchKey) { return false; } else if (e.name === "NotFound") { return false; } console.error(e); return false; } } /** * @param {string} key * @param {{content_md5:string, content_type:string, content_length:number, filename:string}} data * @returns {Promise<string | {error:string}>} */ async getUploadURL(key, data) { const { content_md5, content_type, content_length, filename } = data; if (!key || !filename?.length) { return { error: "Missing required parameters. Required: key, filename" }; } // we can cache this indefinitely because the content is immutable const maxAge = 60 * 60 * 24 * 365; const command = new S3.PutObjectCommand({ Bucket: this.bucket, Key: key, ContentMD5: content_md5, ContentType: content_type, // ChecksumSHA256: checksum, // ChecksumAlgorithm: "SHA256", ContentLength: content_length, ContentDisposition: `attachment; filename="${filename}"`, CacheControl: "public, max-age=" + maxAge, ServerSideEncryption: "AES256" }); const url = await getSignedUrl(this.client, command, { expiresIn: 20 }).catch(e => { return { error: e.message } }); return url; } /** * @param {string} key * @returns {Promise<string | {error:string}>} the presigned url if successful otherwise an object with an error message */ async getDownloadURL(key) { const command = new S3.GetObjectCommand({ Bucket: this.bucket, Key: key }); const url = await getSignedUrl(this.client, command, { expiresIn: 60 }).catch(e => { return { error: e.message } }); return url; } } function tryCreate() { try { const dotenv = require("dotenv"); dotenv.config(); const endpoint = process.env.NEEDLE_NETWORKING_S3_ENDPOINT; if (!endpoint) { console.error("Can not create S3 storage: missing NEEDLE_NETWORKING_S3_ENDPOINT"); return null; } const region = process.env.NEEDLE_NETWORKING_S3_REGION; if (!region) { console.error("Can not create S3 storage: missing NEEDLE_NETWORKING_S3_REGION"); return null; } const bucket = process.env.NEEDLE_NETWORKING_S3_BUCKET; if (!bucket) { console.error("Can not create S3 storage: missing NEEDLE_NETWORKING_S3_BUCKET"); return null; } const accessKeyId = process.env.NEEDLE_NETWORKING_S3_ACCESS_KEY_ID; if (!accessKeyId) { console.error("Can not create S3 storage: missing NEEDLE_NETWORKING_S3_ACCESS_KEY_ID"); return null; } const accessKey = process.env.NEEDLE_NETWORKING_S3_ACCESS_KEY; if (!accessKey) { console.error("Can not create S3 storage: missing NEEDLE_NETWORKING_S3_ACCESS_KEY"); return null; } const prefix = process.env.NEEDLE_NETWORKING_S3_PREFIX; if (!prefix || prefix === "/" || prefix === "" || prefix.includes("..") || !prefix?.length) { console.error("Can not create S3 storage: missing or invalid NEEDLE_NETWORKING_S3_PREFIX"); return null; } const s3 = new S3Storage({ endpoint, region, bucket, accessKeyId, accessKey, prefix }); console.log("S3 storage created"); return s3; } catch (e) { console.error(e); } return null; } module.exports = { S3Storage, tryCreateS3Storage: tryCreate };