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
JavaScript
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