UNPKG

@fgiova/aws-signature

Version:

[![NPM version](https://img.shields.io/npm/v/@fgiova/aws-signature.svg?style=flat)](https://www.npmjs.com/package/@fgiova/aws-signature) ![CI workflow](https://github.com/fgiova/aws-signature/actions/workflows/node.js.yml/badge.svg) [![TypeScript](https:/

103 lines (102 loc) 3.55 kB
import { cpus } from "node:os"; import path from "node:path"; import { Type } from "@sinclair/typebox"; import { envSchema } from "env-schema"; import { LRUCache } from "lru-cache"; import Piscina from "piscina"; /* c8 ignore start */ const isTS = path.resolve(import.meta.filename).endsWith(".ts"); const isMjs = path.resolve(import.meta.filename).endsWith(".mjs"); const runEnv = { ext: isTS ? "ts" : isMjs ? "mjs" : "js", execArgv: isTS ? ["-r", "ts-node/register"] : undefined, }; /* c8 ignore end */ const keyCache = new LRUCache({ max: 50, ttl: 1000 * 60 * 60 * 24, }); const ConfigSchema = Type.Object({ AWS_ACCESS_KEY_ID: Type.Optional(Type.String()), AWS_SECRET_ACCESS_KEY: Type.Optional(Type.String()), AWS_REGION: Type.String({ default: "" }), }); export class Signer { constructor(options = {}) { this.cpuCount = (() => { try { return cpus().length; } catch { /* istanbul ignore next */ return 1; } })(); const { minThreads, maxThreads, idleTimeout, maxQueue, concurrentTasksPerWorker, resourceLimits, credentials: credentialsOptions, } = options; const config = envSchema({ schema: ConfigSchema, }); this.credentials = { region: config.AWS_REGION, accessKeyId: config.AWS_ACCESS_KEY_ID || "", secretAccessKey: config.AWS_SECRET_ACCESS_KEY || "", ...credentialsOptions, }; if (!this.credentials.accessKeyId || !this.credentials.secretAccessKey) { throw new Error("AWS credentials are required"); } this.worker = new Piscina({ filename: path.resolve(import.meta.dirname, `./sign_worker.${runEnv.ext}`), execArgv: runEnv.execArgv, name: "generateKey", minThreads: minThreads ?? Math.max(this.cpuCount / 2, 1), maxThreads: maxThreads ?? this.cpuCount * 1.5, idleTimeout, maxQueue, concurrentTasksPerWorker, resourceLimits, }); } millsToNextDay() { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(0, 0, 0, 0); return Math.abs(tomorrow.getTime() - Date.now()); } async request(request, service, region, date = new Date()) { const requestCredentials = { ...this.credentials, region: region || this.credentials.region, }; if (!requestCredentials.region) { throw new Error("Region is required"); } const keyId = `${service}-${requestCredentials.region}`; let key = keyCache.get(keyId); if (!key) { key = (await this.worker.run({ credentials: requestCredentials, service, date, })); keyCache.set(keyId, key, { ttl: this.millsToNextDay(), }); } return (await this.worker.run({ credentials: requestCredentials, request, service, key, date }, { name: "signRequest" })); } async destroy() { return this.worker.destroy(); } } export class SignerSingleton { constructor() { throw new Error("Use SignerSingleton.getSigner()"); } static getSigner(options) { if (!SignerSingleton.signer) { SignerSingleton.signer = new Signer(options); } return SignerSingleton.signer; } }