UNPKG

@occupop/lib-s3-storage

Version:

Tiny S3 storage helper (AWS & LocalStack) with injectable bucket, signed URLs and public URLs.

1 lines 9.96 kB
{"version":3,"sources":["../src/index.ts","../src/s3-storage.ts"],"sourcesContent":["export * from './s3-storage'\n","import {GetObjectCommand, PutObjectCommand, PutObjectCommandInput, S3Client, S3ClientConfig,} from '@aws-sdk/client-s3'\nimport {getSignedUrl as presign} from '@aws-sdk/s3-request-presigner'\nimport crc32 from 'crc-32'\n\nexport type PublicUrlMode = 'raw' | 'signed'\n\nexport type S3StorageConfig = {\n region: string\n credentials: { accessKeyId: string; secretAccessKey: string }\n endpoint?: string\n accelerate?: boolean\n defaultBucket?: string\n defaultExpiresInSeconds?: number\n publicUrlMode?: PublicUrlMode\n publicAcl?: boolean\n cacheControl?: string\n httpConnectionTimeoutMs?: number\n forcePathStyle?: boolean\n}\n\nexport type SignedUrlResult = {\n url: string\n bucket: string\n key: string\n expiresIn: number\n}\n\nexport type PutObjectResult = {\n url: string\n bucket: string\n key: string\n}\n\nexport type PublicUrlInput = {\n path: string\n bucket?: string\n mode?: PublicUrlMode\n expiresInSeconds?: number\n}\n\nexport type SignedPutInput = {\n path: string\n bucket?: string\n contentType?: string\n fileName?: string\n expiresInSeconds?: number\n checksumCRC32Base64?: string\n}\n\nexport type PutObjectInput = {\n path: string\n bucket?: string\n contentType: string\n fileName?: string\n bodyBase64: string\n cacheControl?: string\n}\n\nexport type S3Storage = {\n getSignedPutUrl: (input: SignedPutInput) => Promise<SignedUrlResult>\n putObject: (input: PutObjectInput) => Promise<PutObjectResult>\n getPublicUrl: (input: PublicUrlInput) => Promise<string>\n getSignedGetUrl: (input: { path: string; bucket?: string; expiresInSeconds?: number }) => Promise<SignedUrlResult>\n}\n\nexport const createS3Storage = (cfg: S3StorageConfig): S3Storage => {\n const isLocal = !!cfg.endpoint && /localstack/i.test(cfg.endpoint)\n const accelerate = !isLocal && !!cfg.accelerate\n const region = cfg.region\n const defaultBucket = cfg.defaultBucket ?? ''\n const defaultExpires = cfg.defaultExpiresInSeconds ?? 180\n const publicMode = cfg.publicUrlMode ?? 'raw'\n const publicAcl = !!cfg.publicAcl\n const cacheControl = cfg.cacheControl ?? 'private, max-age=0, no-cache'\n\n const client = new S3Client({\n region: cfg.region,\n credentials: cfg.credentials,\n ...(cfg.endpoint && {endpoint: cfg.endpoint}),\n ...(typeof cfg.forcePathStyle !== 'undefined' && {forcePathStyle: cfg.forcePathStyle}),\n ...(cfg.accelerate && {useAccelerateEndpoint: true}),\n } as any)\n\n const pickBucket = (override?: string): string => {\n const b = (override ?? defaultBucket).trim()\n if (!b) throw new Error('Bucket is required')\n return b\n }\n\n const normalizeKey = (bucket: string, raw: string): string =>\n raw.replace(/^\\/+/, '').replace(/\\/{2,}/g, '/').replace(`${bucket}/`, '')\n\n const encodeKeyPath = (k: string): string =>\n k.split('/').map(encodeURIComponent).join('/')\n\n const buildRawUrl = (bucket: string, key: string): string => {\n const encoded = encodeKeyPath(key)\n if (isLocal && cfg.endpoint) {\n return `${cfg.endpoint.replace(/\\/$/, '')}/${bucket}/${encoded}`\n }\n if (accelerate) {\n return `https://${bucket}.s3-accelerate.amazonaws.com/${encoded}`\n }\n return `https://${bucket}.s3.${region}.amazonaws.com/${encoded}`\n }\n\n const crc32Base64 = (buf: Buffer): string => {\n const signed = crc32.buf(buf) >>> 0\n const b = Buffer.alloc(4)\n b.writeUInt32BE(signed, 0)\n return b.toString('base64')\n }\n\n const contentDisposition = (fileName?: string): string | undefined =>\n fileName ? `inline; filename=\"${encodeURIComponent(fileName)}\"` : undefined\n\n const getSignedPutUrl = async (input: SignedPutInput): Promise<SignedUrlResult> => {\n const bucket = pickBucket(input.bucket)\n const key = normalizeKey(bucket, input.path)\n const expiresIn = input.expiresInSeconds ?? defaultExpires\n\n const cmd = new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n ContentType: input.contentType,\n // ContentDisposition: contentDisposition(input.fileName),\n ...(input.checksumCRC32Base64 ? {ChecksumCRC32: input.checksumCRC32Base64} : {}),\n })\n\n const url = await presign(client, cmd, {expiresIn})\n return {url, bucket, key, expiresIn}\n }\n\n const putObject = async (input: PutObjectInput): Promise<PutObjectResult> => {\n const bucket = pickBucket(input.bucket)\n const key = normalizeKey(bucket, input.path)\n const body = Buffer.from(input.bodyBase64, 'base64')\n const checksum = crc32Base64(body)\n\n const commandInput: PutObjectCommandInput = {\n Bucket: bucket,\n Key: key,\n Body: body,\n ContentType: input.contentType,\n // ContentDisposition: contentDisposition(input.fileName),\n CacheControl: input.cacheControl ?? cacheControl,\n ChecksumCRC32: checksum,\n ...(publicAcl ? {ACL: 'public-read'} : {}),\n }\n\n const command = new PutObjectCommand(commandInput) as any\n\n await client.send(command)\n\n const url = publicMode === 'signed'\n ? (await getSignedGetUrl({path: key, bucket})).url\n : buildRawUrl(bucket, key)\n\n return {url, bucket, key}\n }\n\n const getSignedGetUrl = async ({\n path,\n bucket,\n expiresInSeconds,\n }: {\n path: string\n bucket?: string\n expiresInSeconds?: number\n }): Promise<SignedUrlResult> => {\n const b = pickBucket(bucket)\n const key = normalizeKey(b, path)\n const expiresIn = expiresInSeconds ?? defaultExpires\n const cmd = new GetObjectCommand({Bucket: b, Key: key})\n const url = await presign(client, cmd, {expiresIn})\n return {url, bucket: b, key, expiresIn}\n }\n\n const getPublicUrl = async (input: PublicUrlInput): Promise<string> => {\n const bucket = pickBucket(input.bucket)\n const key = normalizeKey(bucket, input.path)\n const mode = input.mode ?? publicMode\n if (mode === 'signed') {\n return (await getSignedGetUrl({path: key, bucket, expiresInSeconds: input.expiresInSeconds ?? 300})).url\n }\n return buildRawUrl(bucket, key)\n }\n\n return {\n getSignedPutUrl,\n putObject,\n getPublicUrl,\n getSignedGetUrl,\n }\n}\n"],"mappings":"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","names":["crc32","presign"]}