@fgiova/aws-signature
Version:
[](https://www.npmjs.com/package/@fgiova/aws-signature)  [ • 3.55 kB
JavaScript
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;
}
}