UNPKG

apitally

Version:

Simple API monitoring & analytics for REST APIs built with Express, Fastify, NestJS, AdonisJS, Hono, H3, Elysia, Hapi, and Koa.

134 lines 3.84 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); import { createHash, randomUUID } from "node:crypto"; import { mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; const TEMP_DIR = join(tmpdir(), "apitally"); const MAX_SLOTS = 100; const MAX_LOCK_AGE_MS = 24 * 60 * 60 * 1e3; const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; function getOrCreateInstanceUuid(clientId, env) { try { mkdirSync(TEMP_DIR, { recursive: true }); } catch { return randomUUID(); } const hash = getAppEnvHash(clientId, env); validateLockFiles(hash); for (let slot = 0; slot < MAX_SLOTS; slot++) { const pidFile = join(TEMP_DIR, `instance_${hash}_${slot}.pid`); const uuidFile = join(TEMP_DIR, `instance_${hash}_${slot}.uuid`); try { writeFileSync(pidFile, String(process.pid), { flag: "wx" }); return getOrCreateUuid(uuidFile); } catch (err) { if (err.code !== "EEXIST") { continue; } } try { const pid = parseInt(readFileSync(pidFile, "utf-8"), 10); if (pid === process.pid) { return readFileSync(uuidFile, "utf-8").trim(); } } catch { } } return randomUUID(); } __name(getOrCreateInstanceUuid, "getOrCreateInstanceUuid"); function getAppEnvHash(clientId, env) { return createHash("sha256").update(`${clientId}:${env}`).digest("hex").slice(0, 8); } __name(getAppEnvHash, "getAppEnvHash"); function getOrCreateUuid(uuidFile) { try { const existingUuid = readFileSync(uuidFile, "utf-8").trim(); if (validateUuid(existingUuid)) { return existingUuid; } } catch { } const newUuid = randomUUID(); writeFileSync(uuidFile, newUuid); return newUuid; } __name(getOrCreateUuid, "getOrCreateUuid"); function validateUuid(value) { return UUID_REGEX.test(value); } __name(validateUuid, "validateUuid"); function isPidAlive(pid) { try { process.kill(pid, 0); return true; } catch { return false; } } __name(isPidAlive, "isPidAlive"); function deleteFiles(...paths) { for (const path of paths) { try { unlinkSync(path); } catch { } } } __name(deleteFiles, "deleteFiles"); function validateLockFiles(appEnvHash) { let files; try { files = readdirSync(TEMP_DIR); } catch { return; } const prefix = `instance_${appEnvHash}_`; const uuidFiles = files.filter((f) => f.startsWith(prefix) && f.endsWith(".uuid")).sort(); const pidFiles = files.filter((f) => f.startsWith(prefix) && f.endsWith(".pid")).sort(); const seenUuids = /* @__PURE__ */ new Set(); const now = Date.now(); for (const uuidFileName of uuidFiles) { const uuidFile = join(TEMP_DIR, uuidFileName); const pidFile = join(TEMP_DIR, uuidFileName.replace(".uuid", ".pid")); try { const stat = statSync(uuidFile); if (now - stat.mtimeMs > MAX_LOCK_AGE_MS) { deleteFiles(uuidFile, pidFile); continue; } const uuid = readFileSync(uuidFile, "utf-8").trim(); if (!validateUuid(uuid)) { deleteFiles(uuidFile, pidFile); continue; } if (seenUuids.has(uuid)) { deleteFiles(uuidFile, pidFile); continue; } seenUuids.add(uuid); } catch { } } for (const pidFileName of pidFiles) { const pidFile = join(TEMP_DIR, pidFileName); try { const pid = parseInt(readFileSync(pidFile, "utf-8"), 10); if (!isPidAlive(pid)) { deleteFiles(pidFile); } } catch { } } } __name(validateLockFiles, "validateLockFiles"); export { getOrCreateInstanceUuid, validateLockFiles }; //# sourceMappingURL=instance.js.map