apitally 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/client.cjs +2 -1
- package/dist/common/client.cjs.map +1 -1
- package/dist/common/client.js +2 -1
- package/dist/common/client.js.map +1 -1
- package/dist/common/instance.cjs +158 -0
- package/dist/common/instance.cjs.map +1 -0
- package/dist/common/instance.d.cts +4 -0
- package/dist/common/instance.d.ts +4 -0
- package/dist/common/instance.js +134 -0
- package/dist/common/instance.js.map +1 -0
- package/dist/common/requestLogger.cjs +12 -17
- package/dist/common/requestLogger.cjs.map +1 -1
- package/dist/common/requestLogger.js +12 -17
- package/dist/common/requestLogger.js.map +1 -1
- package/dist/common/response.cjs +2 -2
- package/dist/common/response.cjs.map +1 -1
- package/dist/common/response.js +2 -2
- package/dist/common/response.js.map +1 -1
- package/dist/common/serverErrorCounter.cjs +3 -0
- package/dist/common/serverErrorCounter.cjs.map +1 -1
- package/dist/common/serverErrorCounter.js +3 -0
- package/dist/common/serverErrorCounter.js.map +1 -1
- package/dist/common/tempGzipFile.cjs +25 -2
- package/dist/common/tempGzipFile.cjs.map +1 -1
- package/dist/common/tempGzipFile.d.cts +3 -2
- package/dist/common/tempGzipFile.d.ts +3 -2
- package/dist/common/tempGzipFile.js +22 -3
- package/dist/common/tempGzipFile.js.map +1 -1
- package/dist/elysia/plugin.cjs +20 -17
- package/dist/elysia/plugin.cjs.map +1 -1
- package/dist/elysia/plugin.js +20 -17
- package/dist/elysia/plugin.js.map +1 -1
- package/dist/fastify/plugin.cjs +2 -2
- package/dist/fastify/plugin.cjs.map +1 -1
- package/dist/fastify/plugin.js +2 -2
- package/dist/fastify/plugin.js.map +1 -1
- package/dist/h3/plugin.cjs +2 -2
- package/dist/h3/plugin.cjs.map +1 -1
- package/dist/h3/plugin.js +2 -2
- package/dist/h3/plugin.js.map +1 -1
- package/dist/hapi/plugin.cjs +6 -6
- package/dist/hapi/plugin.cjs.map +1 -1
- package/dist/hapi/plugin.js +6 -6
- package/dist/hapi/plugin.js.map +1 -1
- package/dist/loggers/pino.cjs +1 -1
- package/dist/loggers/pino.cjs.map +1 -1
- package/dist/loggers/pino.js +1 -1
- package/dist/loggers/pino.js.map +1 -1
- package/package.json +5 -5
package/dist/common/client.cjs
CHANGED
|
@@ -37,6 +37,7 @@ module.exports = __toCommonJS(client_exports);
|
|
|
37
37
|
var import_fetch_retry = __toESM(require("fetch-retry"), 1);
|
|
38
38
|
var import_node_crypto = require("node:crypto");
|
|
39
39
|
var import_consumerRegistry = __toESM(require("./consumerRegistry.js"), 1);
|
|
40
|
+
var import_instance = require("./instance.js");
|
|
40
41
|
var import_logging = require("./logging.js");
|
|
41
42
|
var import_paramValidation = require("./paramValidation.js");
|
|
42
43
|
var import_requestCounter = __toESM(require("./requestCounter.js"), 1);
|
|
@@ -91,7 +92,7 @@ const _ApitallyClient = class _ApitallyClient {
|
|
|
91
92
|
_ApitallyClient.instance = this;
|
|
92
93
|
this.clientId = clientId;
|
|
93
94
|
this.env = env;
|
|
94
|
-
this.instanceUuid = (0,
|
|
95
|
+
this.instanceUuid = (0, import_instance.getOrCreateInstanceUuid)(clientId, env);
|
|
95
96
|
this.syncDataQueue = [];
|
|
96
97
|
this.requestCounter = new import_requestCounter.default();
|
|
97
98
|
this.requestLogger = new import_requestLogger.default(requestLogging ?? requestLoggingConfig);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/common/client.ts"],"sourcesContent":["import fetchRetry from \"fetch-retry\";\nimport { randomUUID } from \"node:crypto\";\n\nimport ConsumerRegistry from \"./consumerRegistry.js\";\nimport { Logger, getLogger } from \"./logging.js\";\nimport { isValidClientId, isValidEnv } from \"./paramValidation.js\";\nimport RequestCounter from \"./requestCounter.js\";\nimport RequestLogger from \"./requestLogger.js\";\nimport { getCpuMemoryUsage } from \"./resources.js\";\nimport ServerErrorCounter from \"./serverErrorCounter.js\";\nimport {\n ApitallyConfig,\n StartupData,\n StartupPayload,\n SyncPayload,\n} from \"./types.js\";\nimport ValidationErrorCounter from \"./validationErrorCounter.js\";\n\nconst SYNC_INTERVAL = 60000; // 60 seconds\nconst INITIAL_SYNC_INTERVAL = 10000; // 10 seconds\nconst INITIAL_SYNC_INTERVAL_DURATION = 3600000; // 1 hour\nconst MAX_QUEUE_TIME = 3.6e6; // 1 hour\n\nclass HTTPError extends Error {\n public response: Response;\n\n constructor(response: Response) {\n const reason = response.status\n ? `status code ${response.status}`\n : \"an unknown error\";\n super(`Request failed with ${reason}`);\n this.response = response;\n }\n}\n\nexport class ApitallyClient {\n private clientId: string;\n private env: string;\n\n private static instance?: ApitallyClient;\n private instanceUuid: string;\n private syncDataQueue: SyncPayload[];\n private syncIntervalId?: NodeJS.Timeout;\n public startupData?: StartupData;\n private startupDataSent: boolean = false;\n private enabled: boolean = true;\n\n public requestCounter: RequestCounter;\n public requestLogger: RequestLogger;\n public validationErrorCounter: ValidationErrorCounter;\n public serverErrorCounter: ServerErrorCounter;\n public consumerRegistry: ConsumerRegistry;\n public logger: Logger;\n\n constructor({\n clientId,\n env = \"dev\",\n requestLogging,\n requestLoggingConfig,\n logger,\n }: ApitallyConfig) {\n if (ApitallyClient.instance) {\n throw new Error(\"Apitally client is already initialized\");\n }\n\n this.logger = logger ?? getLogger();\n\n if (!isValidClientId(clientId)) {\n this.logger.error(\n `Invalid Apitally client ID '${clientId}' (expecting hexadecimal UUID format)`,\n );\n this.enabled = false;\n }\n if (!isValidEnv(env)) {\n this.logger.error(\n `Invalid Apitally env '${env}' (expecting 1-32 alphanumeric characters and hyphens only)`,\n );\n this.enabled = false;\n }\n if (requestLoggingConfig && !requestLogging) {\n console.warn(\n \"requestLoggingConfig is deprecated, use requestLogging instead.\",\n );\n }\n\n ApitallyClient.instance = this;\n this.clientId = clientId;\n this.env = env;\n this.instanceUuid = randomUUID();\n this.syncDataQueue = [];\n this.requestCounter = new RequestCounter();\n this.requestLogger = new RequestLogger(\n requestLogging ?? requestLoggingConfig,\n );\n this.validationErrorCounter = new ValidationErrorCounter();\n this.serverErrorCounter = new ServerErrorCounter();\n this.consumerRegistry = new ConsumerRegistry();\n this.handleShutdown = this.handleShutdown.bind(this);\n }\n\n public static getInstance() {\n if (!ApitallyClient.instance) {\n throw new Error(\"Apitally client is not initialized\");\n }\n return ApitallyClient.instance;\n }\n\n public isEnabled() {\n return this.enabled;\n }\n\n public static async shutdown() {\n if (ApitallyClient.instance) {\n await ApitallyClient.instance.handleShutdown();\n }\n }\n\n public async handleShutdown() {\n this.enabled = false;\n this.stopSync();\n await this.sendSyncData();\n await this.sendLogData();\n await this.requestLogger.close();\n ApitallyClient.instance = undefined;\n }\n\n private getHubUrlPrefix() {\n const baseURL =\n process.env.APITALLY_HUB_BASE_URL || \"https://hub.apitally.io\";\n const version = \"v2\";\n return `${baseURL}/${version}/${this.clientId}/${this.env}/`;\n }\n\n private async sendData(url: string, payload: any) {\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n const response = await fetchWithRetry(this.getHubUrlPrefix() + url, {\n method: \"POST\",\n body: JSON.stringify(payload),\n headers: { \"Content-Type\": \"application/json\" },\n });\n if (!response.ok) {\n throw new HTTPError(response);\n }\n }\n\n public startSync() {\n if (!this.enabled) {\n return;\n }\n this.sync();\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, INITIAL_SYNC_INTERVAL);\n setTimeout(() => {\n if (this.syncIntervalId) {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, SYNC_INTERVAL);\n }\n }, INITIAL_SYNC_INTERVAL_DURATION);\n }\n\n private async sync() {\n try {\n const promises = [this.sendSyncData(), this.sendLogData()];\n if (!this.startupDataSent) {\n promises.push(this.sendStartupData());\n }\n await Promise.all(promises);\n } catch (error) {\n this.logger.error(\"Error while syncing with Apitally Hub\", {\n error,\n });\n }\n }\n\n private stopSync() {\n if (this.syncIntervalId) {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = undefined;\n }\n }\n\n public setStartupData(data: StartupData) {\n this.startupData = data;\n this.startupDataSent = false;\n }\n\n private async sendStartupData() {\n if (this.startupData) {\n this.logger.debug(\"Sending startup data to Apitally Hub\");\n const payload: StartupPayload = {\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n ...this.startupData,\n };\n try {\n await this.sendData(\"startup\", payload);\n this.startupDataSent = true;\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.error((error as Error).message);\n this.logger.debug(\n \"Error while sending startup data to Apitally Hub (will retry)\",\n { error },\n );\n }\n }\n }\n }\n\n private async sendSyncData() {\n this.logger.debug(\"Synchronizing data with Apitally Hub\");\n const newPayload: SyncPayload = {\n timestamp: Date.now() / 1000,\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n requests: this.requestCounter.getAndResetRequests(),\n validation_errors:\n this.validationErrorCounter.getAndResetValidationErrors(),\n server_errors: this.serverErrorCounter.getAndResetServerErrors(),\n consumers: this.consumerRegistry.getAndResetUpdatedConsumers(),\n resources: getCpuMemoryUsage(),\n };\n this.syncDataQueue.push(newPayload);\n\n let i = 0;\n while (this.syncDataQueue.length > 0) {\n const payload = this.syncDataQueue.shift();\n if (payload) {\n try {\n if (Date.now() - payload.timestamp * 1000 <= MAX_QUEUE_TIME) {\n if (i > 0) {\n await this.randomDelay();\n }\n await this.sendData(\"sync\", payload);\n i += 1;\n }\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.debug(\n \"Error while synchronizing data with Apitally Hub (will retry)\",\n { error },\n );\n this.syncDataQueue.push(payload);\n break;\n }\n }\n }\n }\n }\n\n private async sendLogData() {\n this.logger.debug(\"Sending request log data to Apitally Hub\");\n await this.requestLogger.rotateFile();\n\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n\n let i = 0;\n let logFile;\n while ((logFile = this.requestLogger.getFile())) {\n if (i > 0) {\n await this.randomDelay();\n }\n\n try {\n const response = await fetchWithRetry(\n `${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`,\n {\n method: \"POST\",\n body: (await logFile.getContent()) as any,\n },\n );\n\n if (response.status === 402 && response.headers.has(\"Retry-After\")) {\n const retryAfter = parseInt(\n response.headers.get(\"Retry-After\") ?? \"0\",\n );\n if (retryAfter > 0) {\n this.requestLogger.suspendUntil = Date.now() + retryAfter * 1000;\n this.requestLogger.clear();\n return;\n }\n }\n\n if (!response.ok) {\n throw new HTTPError(response);\n }\n\n logFile.delete();\n } catch (error) {\n this.requestLogger.retryFileLater(logFile);\n break;\n }\n\n i++;\n if (i >= 10) break;\n }\n }\n\n private handleHubError(error: unknown) {\n if (error instanceof HTTPError) {\n if (error.response.status === 404) {\n this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);\n this.enabled = false;\n this.stopSync();\n return true;\n }\n if (error.response.status === 422) {\n this.logger.error(\"Received validation error from Apitally Hub\");\n return true;\n }\n }\n return false;\n }\n\n private async randomDelay() {\n const delay = 100 + Math.random() * 400;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n}\n"],"mappings":"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","names":["SYNC_INTERVAL","INITIAL_SYNC_INTERVAL","INITIAL_SYNC_INTERVAL_DURATION","MAX_QUEUE_TIME","HTTPError","Error","response","reason","status","ApitallyClient","clientId","env","instanceUuid","syncDataQueue","syncIntervalId","startupData","startupDataSent","enabled","requestCounter","requestLogger","validationErrorCounter","serverErrorCounter","consumerRegistry","logger","requestLogging","requestLoggingConfig","instance","getLogger","isValidClientId","error","isValidEnv","console","warn","randomUUID","RequestCounter","RequestLogger","ValidationErrorCounter","ServerErrorCounter","ConsumerRegistry","handleShutdown","bind","getInstance","isEnabled","shutdown","stopSync","sendSyncData","sendLogData","close","undefined","getHubUrlPrefix","baseURL","process","APITALLY_HUB_BASE_URL","version","sendData","url","payload","fetchWithRetry","fetchRetry","fetch","retries","retryDelay","retryOn","method","body","JSON","stringify","headers","ok","startSync","sync","setInterval","setTimeout","clearInterval","promises","push","sendStartupData","Promise","all","setStartupData","data","debug","instance_uuid","message_uuid","handled","handleHubError","message","newPayload","timestamp","Date","now","requests","getAndResetRequests","validation_errors","getAndResetValidationErrors","server_errors","getAndResetServerErrors","consumers","getAndResetUpdatedConsumers","resources","getCpuMemoryUsage","i","length","shift","randomDelay","rotateFile","logFile","getFile","uuid","getContent","has","retryAfter","parseInt","get","suspendUntil","clear","delete","retryFileLater","delay","Math","random","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../../src/common/client.ts"],"sourcesContent":["import fetchRetry from \"fetch-retry\";\nimport { randomUUID } from \"node:crypto\";\n\nimport ConsumerRegistry from \"./consumerRegistry.js\";\nimport { getOrCreateInstanceUuid } from \"./instance.js\";\nimport { Logger, getLogger } from \"./logging.js\";\nimport { isValidClientId, isValidEnv } from \"./paramValidation.js\";\nimport RequestCounter from \"./requestCounter.js\";\nimport RequestLogger from \"./requestLogger.js\";\nimport { getCpuMemoryUsage } from \"./resources.js\";\nimport ServerErrorCounter from \"./serverErrorCounter.js\";\nimport {\n ApitallyConfig,\n StartupData,\n StartupPayload,\n SyncPayload,\n} from \"./types.js\";\nimport ValidationErrorCounter from \"./validationErrorCounter.js\";\n\nconst SYNC_INTERVAL = 60000; // 60 seconds\nconst INITIAL_SYNC_INTERVAL = 10000; // 10 seconds\nconst INITIAL_SYNC_INTERVAL_DURATION = 3600000; // 1 hour\nconst MAX_QUEUE_TIME = 3.6e6; // 1 hour\n\nclass HTTPError extends Error {\n public response: Response;\n\n constructor(response: Response) {\n const reason = response.status\n ? `status code ${response.status}`\n : \"an unknown error\";\n super(`Request failed with ${reason}`);\n this.response = response;\n }\n}\n\nexport class ApitallyClient {\n private clientId: string;\n private env: string;\n\n private static instance?: ApitallyClient;\n private instanceUuid: string;\n private syncDataQueue: SyncPayload[];\n private syncIntervalId?: NodeJS.Timeout;\n public startupData?: StartupData;\n private startupDataSent: boolean = false;\n private enabled: boolean = true;\n\n public requestCounter: RequestCounter;\n public requestLogger: RequestLogger;\n public validationErrorCounter: ValidationErrorCounter;\n public serverErrorCounter: ServerErrorCounter;\n public consumerRegistry: ConsumerRegistry;\n public logger: Logger;\n\n constructor({\n clientId,\n env = \"dev\",\n requestLogging,\n requestLoggingConfig,\n logger,\n }: ApitallyConfig) {\n if (ApitallyClient.instance) {\n throw new Error(\"Apitally client is already initialized\");\n }\n\n this.logger = logger ?? getLogger();\n\n if (!isValidClientId(clientId)) {\n this.logger.error(\n `Invalid Apitally client ID '${clientId}' (expecting hexadecimal UUID format)`,\n );\n this.enabled = false;\n }\n if (!isValidEnv(env)) {\n this.logger.error(\n `Invalid Apitally env '${env}' (expecting 1-32 alphanumeric characters and hyphens only)`,\n );\n this.enabled = false;\n }\n if (requestLoggingConfig && !requestLogging) {\n console.warn(\n \"requestLoggingConfig is deprecated, use requestLogging instead.\",\n );\n }\n\n ApitallyClient.instance = this;\n this.clientId = clientId;\n this.env = env;\n this.instanceUuid = getOrCreateInstanceUuid(clientId, env);\n this.syncDataQueue = [];\n this.requestCounter = new RequestCounter();\n this.requestLogger = new RequestLogger(\n requestLogging ?? requestLoggingConfig,\n );\n this.validationErrorCounter = new ValidationErrorCounter();\n this.serverErrorCounter = new ServerErrorCounter();\n this.consumerRegistry = new ConsumerRegistry();\n this.handleShutdown = this.handleShutdown.bind(this);\n }\n\n public static getInstance() {\n if (!ApitallyClient.instance) {\n throw new Error(\"Apitally client is not initialized\");\n }\n return ApitallyClient.instance;\n }\n\n public isEnabled() {\n return this.enabled;\n }\n\n public static async shutdown() {\n if (ApitallyClient.instance) {\n await ApitallyClient.instance.handleShutdown();\n }\n }\n\n public async handleShutdown() {\n this.enabled = false;\n this.stopSync();\n await this.sendSyncData();\n await this.sendLogData();\n await this.requestLogger.close();\n ApitallyClient.instance = undefined;\n }\n\n private getHubUrlPrefix() {\n const baseURL =\n process.env.APITALLY_HUB_BASE_URL || \"https://hub.apitally.io\";\n const version = \"v2\";\n return `${baseURL}/${version}/${this.clientId}/${this.env}/`;\n }\n\n private async sendData(url: string, payload: any) {\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n const response = await fetchWithRetry(this.getHubUrlPrefix() + url, {\n method: \"POST\",\n body: JSON.stringify(payload),\n headers: { \"Content-Type\": \"application/json\" },\n });\n if (!response.ok) {\n throw new HTTPError(response);\n }\n }\n\n public startSync() {\n if (!this.enabled) {\n return;\n }\n this.sync();\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, INITIAL_SYNC_INTERVAL);\n setTimeout(() => {\n if (this.syncIntervalId) {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, SYNC_INTERVAL);\n }\n }, INITIAL_SYNC_INTERVAL_DURATION);\n }\n\n private async sync() {\n try {\n const promises = [this.sendSyncData(), this.sendLogData()];\n if (!this.startupDataSent) {\n promises.push(this.sendStartupData());\n }\n await Promise.all(promises);\n } catch (error) {\n this.logger.error(\"Error while syncing with Apitally Hub\", {\n error,\n });\n }\n }\n\n private stopSync() {\n if (this.syncIntervalId) {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = undefined;\n }\n }\n\n public setStartupData(data: StartupData) {\n this.startupData = data;\n this.startupDataSent = false;\n }\n\n private async sendStartupData() {\n if (this.startupData) {\n this.logger.debug(\"Sending startup data to Apitally Hub\");\n const payload: StartupPayload = {\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n ...this.startupData,\n };\n try {\n await this.sendData(\"startup\", payload);\n this.startupDataSent = true;\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.error((error as Error).message);\n this.logger.debug(\n \"Error while sending startup data to Apitally Hub (will retry)\",\n { error },\n );\n }\n }\n }\n }\n\n private async sendSyncData() {\n this.logger.debug(\"Synchronizing data with Apitally Hub\");\n const newPayload: SyncPayload = {\n timestamp: Date.now() / 1000,\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n requests: this.requestCounter.getAndResetRequests(),\n validation_errors:\n this.validationErrorCounter.getAndResetValidationErrors(),\n server_errors: this.serverErrorCounter.getAndResetServerErrors(),\n consumers: this.consumerRegistry.getAndResetUpdatedConsumers(),\n resources: getCpuMemoryUsage(),\n };\n this.syncDataQueue.push(newPayload);\n\n let i = 0;\n while (this.syncDataQueue.length > 0) {\n const payload = this.syncDataQueue.shift();\n if (payload) {\n try {\n if (Date.now() - payload.timestamp * 1000 <= MAX_QUEUE_TIME) {\n if (i > 0) {\n await this.randomDelay();\n }\n await this.sendData(\"sync\", payload);\n i += 1;\n }\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.debug(\n \"Error while synchronizing data with Apitally Hub (will retry)\",\n { error },\n );\n this.syncDataQueue.push(payload);\n break;\n }\n }\n }\n }\n }\n\n private async sendLogData() {\n this.logger.debug(\"Sending request log data to Apitally Hub\");\n await this.requestLogger.rotateFile();\n\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n\n let i = 0;\n let logFile;\n while ((logFile = this.requestLogger.getFile())) {\n if (i > 0) {\n await this.randomDelay();\n }\n\n try {\n const response = await fetchWithRetry(\n `${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`,\n {\n method: \"POST\",\n body: (await logFile.getContent()) as any,\n },\n );\n\n if (response.status === 402 && response.headers.has(\"Retry-After\")) {\n const retryAfter = parseInt(\n response.headers.get(\"Retry-After\") ?? \"0\",\n );\n if (retryAfter > 0) {\n this.requestLogger.suspendUntil = Date.now() + retryAfter * 1000;\n this.requestLogger.clear();\n return;\n }\n }\n\n if (!response.ok) {\n throw new HTTPError(response);\n }\n\n logFile.delete();\n } catch (error) {\n this.requestLogger.retryFileLater(logFile);\n break;\n }\n\n i++;\n if (i >= 10) break;\n }\n }\n\n private handleHubError(error: unknown) {\n if (error instanceof HTTPError) {\n if (error.response.status === 404) {\n this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);\n this.enabled = false;\n this.stopSync();\n return true;\n }\n if (error.response.status === 422) {\n this.logger.error(\"Received validation error from Apitally Hub\");\n return true;\n }\n }\n return false;\n }\n\n private async randomDelay() {\n const delay = 100 + Math.random() * 400;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n}\n"],"mappings":"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","names":["SYNC_INTERVAL","INITIAL_SYNC_INTERVAL","INITIAL_SYNC_INTERVAL_DURATION","MAX_QUEUE_TIME","HTTPError","Error","response","reason","status","ApitallyClient","clientId","env","instanceUuid","syncDataQueue","syncIntervalId","startupData","startupDataSent","enabled","requestCounter","requestLogger","validationErrorCounter","serverErrorCounter","consumerRegistry","logger","requestLogging","requestLoggingConfig","instance","getLogger","isValidClientId","error","isValidEnv","console","warn","getOrCreateInstanceUuid","RequestCounter","RequestLogger","ValidationErrorCounter","ServerErrorCounter","ConsumerRegistry","handleShutdown","bind","getInstance","isEnabled","shutdown","stopSync","sendSyncData","sendLogData","close","undefined","getHubUrlPrefix","baseURL","process","APITALLY_HUB_BASE_URL","version","sendData","url","payload","fetchWithRetry","fetchRetry","fetch","retries","retryDelay","retryOn","method","body","JSON","stringify","headers","ok","startSync","sync","setInterval","setTimeout","clearInterval","promises","push","sendStartupData","Promise","all","setStartupData","data","debug","instance_uuid","message_uuid","randomUUID","handled","handleHubError","message","newPayload","timestamp","Date","now","requests","getAndResetRequests","validation_errors","getAndResetValidationErrors","server_errors","getAndResetServerErrors","consumers","getAndResetUpdatedConsumers","resources","getCpuMemoryUsage","i","length","shift","randomDelay","rotateFile","logFile","getFile","uuid","getContent","has","retryAfter","parseInt","get","suspendUntil","clear","delete","retryFileLater","delay","Math","random","resolve"]}
|
package/dist/common/client.js
CHANGED
|
@@ -6,6 +6,7 @@ var _a;
|
|
|
6
6
|
import fetchRetry from "fetch-retry";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import ConsumerRegistry from "./consumerRegistry.js";
|
|
9
|
+
import { getOrCreateInstanceUuid } from "./instance.js";
|
|
9
10
|
import { getLogger } from "./logging.js";
|
|
10
11
|
import { isValidClientId, isValidEnv } from "./paramValidation.js";
|
|
11
12
|
import RequestCounter from "./requestCounter.js";
|
|
@@ -59,7 +60,7 @@ const _ApitallyClient = class _ApitallyClient {
|
|
|
59
60
|
_ApitallyClient.instance = this;
|
|
60
61
|
this.clientId = clientId;
|
|
61
62
|
this.env = env;
|
|
62
|
-
this.instanceUuid =
|
|
63
|
+
this.instanceUuid = getOrCreateInstanceUuid(clientId, env);
|
|
63
64
|
this.syncDataQueue = [];
|
|
64
65
|
this.requestCounter = new RequestCounter();
|
|
65
66
|
this.requestLogger = new RequestLogger(requestLogging ?? requestLoggingConfig);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/common/client.ts"],"sourcesContent":["import fetchRetry from \"fetch-retry\";\nimport { randomUUID } from \"node:crypto\";\n\nimport ConsumerRegistry from \"./consumerRegistry.js\";\nimport { Logger, getLogger } from \"./logging.js\";\nimport { isValidClientId, isValidEnv } from \"./paramValidation.js\";\nimport RequestCounter from \"./requestCounter.js\";\nimport RequestLogger from \"./requestLogger.js\";\nimport { getCpuMemoryUsage } from \"./resources.js\";\nimport ServerErrorCounter from \"./serverErrorCounter.js\";\nimport {\n ApitallyConfig,\n StartupData,\n StartupPayload,\n SyncPayload,\n} from \"./types.js\";\nimport ValidationErrorCounter from \"./validationErrorCounter.js\";\n\nconst SYNC_INTERVAL = 60000; // 60 seconds\nconst INITIAL_SYNC_INTERVAL = 10000; // 10 seconds\nconst INITIAL_SYNC_INTERVAL_DURATION = 3600000; // 1 hour\nconst MAX_QUEUE_TIME = 3.6e6; // 1 hour\n\nclass HTTPError extends Error {\n public response: Response;\n\n constructor(response: Response) {\n const reason = response.status\n ? `status code ${response.status}`\n : \"an unknown error\";\n super(`Request failed with ${reason}`);\n this.response = response;\n }\n}\n\nexport class ApitallyClient {\n private clientId: string;\n private env: string;\n\n private static instance?: ApitallyClient;\n private instanceUuid: string;\n private syncDataQueue: SyncPayload[];\n private syncIntervalId?: NodeJS.Timeout;\n public startupData?: StartupData;\n private startupDataSent: boolean = false;\n private enabled: boolean = true;\n\n public requestCounter: RequestCounter;\n public requestLogger: RequestLogger;\n public validationErrorCounter: ValidationErrorCounter;\n public serverErrorCounter: ServerErrorCounter;\n public consumerRegistry: ConsumerRegistry;\n public logger: Logger;\n\n constructor({\n clientId,\n env = \"dev\",\n requestLogging,\n requestLoggingConfig,\n logger,\n }: ApitallyConfig) {\n if (ApitallyClient.instance) {\n throw new Error(\"Apitally client is already initialized\");\n }\n\n this.logger = logger ?? getLogger();\n\n if (!isValidClientId(clientId)) {\n this.logger.error(\n `Invalid Apitally client ID '${clientId}' (expecting hexadecimal UUID format)`,\n );\n this.enabled = false;\n }\n if (!isValidEnv(env)) {\n this.logger.error(\n `Invalid Apitally env '${env}' (expecting 1-32 alphanumeric characters and hyphens only)`,\n );\n this.enabled = false;\n }\n if (requestLoggingConfig && !requestLogging) {\n console.warn(\n \"requestLoggingConfig is deprecated, use requestLogging instead.\",\n );\n }\n\n ApitallyClient.instance = this;\n this.clientId = clientId;\n this.env = env;\n this.instanceUuid = randomUUID();\n this.syncDataQueue = [];\n this.requestCounter = new RequestCounter();\n this.requestLogger = new RequestLogger(\n requestLogging ?? requestLoggingConfig,\n );\n this.validationErrorCounter = new ValidationErrorCounter();\n this.serverErrorCounter = new ServerErrorCounter();\n this.consumerRegistry = new ConsumerRegistry();\n this.handleShutdown = this.handleShutdown.bind(this);\n }\n\n public static getInstance() {\n if (!ApitallyClient.instance) {\n throw new Error(\"Apitally client is not initialized\");\n }\n return ApitallyClient.instance;\n }\n\n public isEnabled() {\n return this.enabled;\n }\n\n public static async shutdown() {\n if (ApitallyClient.instance) {\n await ApitallyClient.instance.handleShutdown();\n }\n }\n\n public async handleShutdown() {\n this.enabled = false;\n this.stopSync();\n await this.sendSyncData();\n await this.sendLogData();\n await this.requestLogger.close();\n ApitallyClient.instance = undefined;\n }\n\n private getHubUrlPrefix() {\n const baseURL =\n process.env.APITALLY_HUB_BASE_URL || \"https://hub.apitally.io\";\n const version = \"v2\";\n return `${baseURL}/${version}/${this.clientId}/${this.env}/`;\n }\n\n private async sendData(url: string, payload: any) {\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n const response = await fetchWithRetry(this.getHubUrlPrefix() + url, {\n method: \"POST\",\n body: JSON.stringify(payload),\n headers: { \"Content-Type\": \"application/json\" },\n });\n if (!response.ok) {\n throw new HTTPError(response);\n }\n }\n\n public startSync() {\n if (!this.enabled) {\n return;\n }\n this.sync();\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, INITIAL_SYNC_INTERVAL);\n setTimeout(() => {\n if (this.syncIntervalId) {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, SYNC_INTERVAL);\n }\n }, INITIAL_SYNC_INTERVAL_DURATION);\n }\n\n private async sync() {\n try {\n const promises = [this.sendSyncData(), this.sendLogData()];\n if (!this.startupDataSent) {\n promises.push(this.sendStartupData());\n }\n await Promise.all(promises);\n } catch (error) {\n this.logger.error(\"Error while syncing with Apitally Hub\", {\n error,\n });\n }\n }\n\n private stopSync() {\n if (this.syncIntervalId) {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = undefined;\n }\n }\n\n public setStartupData(data: StartupData) {\n this.startupData = data;\n this.startupDataSent = false;\n }\n\n private async sendStartupData() {\n if (this.startupData) {\n this.logger.debug(\"Sending startup data to Apitally Hub\");\n const payload: StartupPayload = {\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n ...this.startupData,\n };\n try {\n await this.sendData(\"startup\", payload);\n this.startupDataSent = true;\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.error((error as Error).message);\n this.logger.debug(\n \"Error while sending startup data to Apitally Hub (will retry)\",\n { error },\n );\n }\n }\n }\n }\n\n private async sendSyncData() {\n this.logger.debug(\"Synchronizing data with Apitally Hub\");\n const newPayload: SyncPayload = {\n timestamp: Date.now() / 1000,\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n requests: this.requestCounter.getAndResetRequests(),\n validation_errors:\n this.validationErrorCounter.getAndResetValidationErrors(),\n server_errors: this.serverErrorCounter.getAndResetServerErrors(),\n consumers: this.consumerRegistry.getAndResetUpdatedConsumers(),\n resources: getCpuMemoryUsage(),\n };\n this.syncDataQueue.push(newPayload);\n\n let i = 0;\n while (this.syncDataQueue.length > 0) {\n const payload = this.syncDataQueue.shift();\n if (payload) {\n try {\n if (Date.now() - payload.timestamp * 1000 <= MAX_QUEUE_TIME) {\n if (i > 0) {\n await this.randomDelay();\n }\n await this.sendData(\"sync\", payload);\n i += 1;\n }\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.debug(\n \"Error while synchronizing data with Apitally Hub (will retry)\",\n { error },\n );\n this.syncDataQueue.push(payload);\n break;\n }\n }\n }\n }\n }\n\n private async sendLogData() {\n this.logger.debug(\"Sending request log data to Apitally Hub\");\n await this.requestLogger.rotateFile();\n\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n\n let i = 0;\n let logFile;\n while ((logFile = this.requestLogger.getFile())) {\n if (i > 0) {\n await this.randomDelay();\n }\n\n try {\n const response = await fetchWithRetry(\n `${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`,\n {\n method: \"POST\",\n body: (await logFile.getContent()) as any,\n },\n );\n\n if (response.status === 402 && response.headers.has(\"Retry-After\")) {\n const retryAfter = parseInt(\n response.headers.get(\"Retry-After\") ?? \"0\",\n );\n if (retryAfter > 0) {\n this.requestLogger.suspendUntil = Date.now() + retryAfter * 1000;\n this.requestLogger.clear();\n return;\n }\n }\n\n if (!response.ok) {\n throw new HTTPError(response);\n }\n\n logFile.delete();\n } catch (error) {\n this.requestLogger.retryFileLater(logFile);\n break;\n }\n\n i++;\n if (i >= 10) break;\n }\n }\n\n private handleHubError(error: unknown) {\n if (error instanceof HTTPError) {\n if (error.response.status === 404) {\n this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);\n this.enabled = false;\n this.stopSync();\n return true;\n }\n if (error.response.status === 422) {\n this.logger.error(\"Received validation error from Apitally Hub\");\n return true;\n }\n }\n return false;\n }\n\n private async randomDelay() {\n const delay = 100 + Math.random() * 400;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n}\n"],"mappings":"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","names":["fetchRetry","randomUUID","ConsumerRegistry","getLogger","isValidClientId","isValidEnv","RequestCounter","RequestLogger","getCpuMemoryUsage","ServerErrorCounter","ValidationErrorCounter","SYNC_INTERVAL","INITIAL_SYNC_INTERVAL","INITIAL_SYNC_INTERVAL_DURATION","MAX_QUEUE_TIME","HTTPError","Error","response","reason","status","ApitallyClient","clientId","env","instanceUuid","syncDataQueue","syncIntervalId","startupData","startupDataSent","enabled","requestCounter","requestLogger","validationErrorCounter","serverErrorCounter","consumerRegistry","logger","requestLogging","requestLoggingConfig","instance","error","console","warn","handleShutdown","bind","getInstance","isEnabled","shutdown","stopSync","sendSyncData","sendLogData","close","undefined","getHubUrlPrefix","baseURL","process","APITALLY_HUB_BASE_URL","version","sendData","url","payload","fetchWithRetry","fetch","retries","retryDelay","retryOn","method","body","JSON","stringify","headers","ok","startSync","sync","setInterval","setTimeout","clearInterval","promises","push","sendStartupData","Promise","all","setStartupData","data","debug","instance_uuid","message_uuid","handled","handleHubError","message","newPayload","timestamp","Date","now","requests","getAndResetRequests","validation_errors","getAndResetValidationErrors","server_errors","getAndResetServerErrors","consumers","getAndResetUpdatedConsumers","resources","i","length","shift","randomDelay","rotateFile","logFile","getFile","uuid","getContent","has","retryAfter","parseInt","get","suspendUntil","clear","delete","retryFileLater","delay","Math","random","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../../src/common/client.ts"],"sourcesContent":["import fetchRetry from \"fetch-retry\";\nimport { randomUUID } from \"node:crypto\";\n\nimport ConsumerRegistry from \"./consumerRegistry.js\";\nimport { getOrCreateInstanceUuid } from \"./instance.js\";\nimport { Logger, getLogger } from \"./logging.js\";\nimport { isValidClientId, isValidEnv } from \"./paramValidation.js\";\nimport RequestCounter from \"./requestCounter.js\";\nimport RequestLogger from \"./requestLogger.js\";\nimport { getCpuMemoryUsage } from \"./resources.js\";\nimport ServerErrorCounter from \"./serverErrorCounter.js\";\nimport {\n ApitallyConfig,\n StartupData,\n StartupPayload,\n SyncPayload,\n} from \"./types.js\";\nimport ValidationErrorCounter from \"./validationErrorCounter.js\";\n\nconst SYNC_INTERVAL = 60000; // 60 seconds\nconst INITIAL_SYNC_INTERVAL = 10000; // 10 seconds\nconst INITIAL_SYNC_INTERVAL_DURATION = 3600000; // 1 hour\nconst MAX_QUEUE_TIME = 3.6e6; // 1 hour\n\nclass HTTPError extends Error {\n public response: Response;\n\n constructor(response: Response) {\n const reason = response.status\n ? `status code ${response.status}`\n : \"an unknown error\";\n super(`Request failed with ${reason}`);\n this.response = response;\n }\n}\n\nexport class ApitallyClient {\n private clientId: string;\n private env: string;\n\n private static instance?: ApitallyClient;\n private instanceUuid: string;\n private syncDataQueue: SyncPayload[];\n private syncIntervalId?: NodeJS.Timeout;\n public startupData?: StartupData;\n private startupDataSent: boolean = false;\n private enabled: boolean = true;\n\n public requestCounter: RequestCounter;\n public requestLogger: RequestLogger;\n public validationErrorCounter: ValidationErrorCounter;\n public serverErrorCounter: ServerErrorCounter;\n public consumerRegistry: ConsumerRegistry;\n public logger: Logger;\n\n constructor({\n clientId,\n env = \"dev\",\n requestLogging,\n requestLoggingConfig,\n logger,\n }: ApitallyConfig) {\n if (ApitallyClient.instance) {\n throw new Error(\"Apitally client is already initialized\");\n }\n\n this.logger = logger ?? getLogger();\n\n if (!isValidClientId(clientId)) {\n this.logger.error(\n `Invalid Apitally client ID '${clientId}' (expecting hexadecimal UUID format)`,\n );\n this.enabled = false;\n }\n if (!isValidEnv(env)) {\n this.logger.error(\n `Invalid Apitally env '${env}' (expecting 1-32 alphanumeric characters and hyphens only)`,\n );\n this.enabled = false;\n }\n if (requestLoggingConfig && !requestLogging) {\n console.warn(\n \"requestLoggingConfig is deprecated, use requestLogging instead.\",\n );\n }\n\n ApitallyClient.instance = this;\n this.clientId = clientId;\n this.env = env;\n this.instanceUuid = getOrCreateInstanceUuid(clientId, env);\n this.syncDataQueue = [];\n this.requestCounter = new RequestCounter();\n this.requestLogger = new RequestLogger(\n requestLogging ?? requestLoggingConfig,\n );\n this.validationErrorCounter = new ValidationErrorCounter();\n this.serverErrorCounter = new ServerErrorCounter();\n this.consumerRegistry = new ConsumerRegistry();\n this.handleShutdown = this.handleShutdown.bind(this);\n }\n\n public static getInstance() {\n if (!ApitallyClient.instance) {\n throw new Error(\"Apitally client is not initialized\");\n }\n return ApitallyClient.instance;\n }\n\n public isEnabled() {\n return this.enabled;\n }\n\n public static async shutdown() {\n if (ApitallyClient.instance) {\n await ApitallyClient.instance.handleShutdown();\n }\n }\n\n public async handleShutdown() {\n this.enabled = false;\n this.stopSync();\n await this.sendSyncData();\n await this.sendLogData();\n await this.requestLogger.close();\n ApitallyClient.instance = undefined;\n }\n\n private getHubUrlPrefix() {\n const baseURL =\n process.env.APITALLY_HUB_BASE_URL || \"https://hub.apitally.io\";\n const version = \"v2\";\n return `${baseURL}/${version}/${this.clientId}/${this.env}/`;\n }\n\n private async sendData(url: string, payload: any) {\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n const response = await fetchWithRetry(this.getHubUrlPrefix() + url, {\n method: \"POST\",\n body: JSON.stringify(payload),\n headers: { \"Content-Type\": \"application/json\" },\n });\n if (!response.ok) {\n throw new HTTPError(response);\n }\n }\n\n public startSync() {\n if (!this.enabled) {\n return;\n }\n this.sync();\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, INITIAL_SYNC_INTERVAL);\n setTimeout(() => {\n if (this.syncIntervalId) {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, SYNC_INTERVAL);\n }\n }, INITIAL_SYNC_INTERVAL_DURATION);\n }\n\n private async sync() {\n try {\n const promises = [this.sendSyncData(), this.sendLogData()];\n if (!this.startupDataSent) {\n promises.push(this.sendStartupData());\n }\n await Promise.all(promises);\n } catch (error) {\n this.logger.error(\"Error while syncing with Apitally Hub\", {\n error,\n });\n }\n }\n\n private stopSync() {\n if (this.syncIntervalId) {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = undefined;\n }\n }\n\n public setStartupData(data: StartupData) {\n this.startupData = data;\n this.startupDataSent = false;\n }\n\n private async sendStartupData() {\n if (this.startupData) {\n this.logger.debug(\"Sending startup data to Apitally Hub\");\n const payload: StartupPayload = {\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n ...this.startupData,\n };\n try {\n await this.sendData(\"startup\", payload);\n this.startupDataSent = true;\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.error((error as Error).message);\n this.logger.debug(\n \"Error while sending startup data to Apitally Hub (will retry)\",\n { error },\n );\n }\n }\n }\n }\n\n private async sendSyncData() {\n this.logger.debug(\"Synchronizing data with Apitally Hub\");\n const newPayload: SyncPayload = {\n timestamp: Date.now() / 1000,\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n requests: this.requestCounter.getAndResetRequests(),\n validation_errors:\n this.validationErrorCounter.getAndResetValidationErrors(),\n server_errors: this.serverErrorCounter.getAndResetServerErrors(),\n consumers: this.consumerRegistry.getAndResetUpdatedConsumers(),\n resources: getCpuMemoryUsage(),\n };\n this.syncDataQueue.push(newPayload);\n\n let i = 0;\n while (this.syncDataQueue.length > 0) {\n const payload = this.syncDataQueue.shift();\n if (payload) {\n try {\n if (Date.now() - payload.timestamp * 1000 <= MAX_QUEUE_TIME) {\n if (i > 0) {\n await this.randomDelay();\n }\n await this.sendData(\"sync\", payload);\n i += 1;\n }\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.debug(\n \"Error while synchronizing data with Apitally Hub (will retry)\",\n { error },\n );\n this.syncDataQueue.push(payload);\n break;\n }\n }\n }\n }\n }\n\n private async sendLogData() {\n this.logger.debug(\"Sending request log data to Apitally Hub\");\n await this.requestLogger.rotateFile();\n\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n\n let i = 0;\n let logFile;\n while ((logFile = this.requestLogger.getFile())) {\n if (i > 0) {\n await this.randomDelay();\n }\n\n try {\n const response = await fetchWithRetry(\n `${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`,\n {\n method: \"POST\",\n body: (await logFile.getContent()) as any,\n },\n );\n\n if (response.status === 402 && response.headers.has(\"Retry-After\")) {\n const retryAfter = parseInt(\n response.headers.get(\"Retry-After\") ?? \"0\",\n );\n if (retryAfter > 0) {\n this.requestLogger.suspendUntil = Date.now() + retryAfter * 1000;\n this.requestLogger.clear();\n return;\n }\n }\n\n if (!response.ok) {\n throw new HTTPError(response);\n }\n\n logFile.delete();\n } catch (error) {\n this.requestLogger.retryFileLater(logFile);\n break;\n }\n\n i++;\n if (i >= 10) break;\n }\n }\n\n private handleHubError(error: unknown) {\n if (error instanceof HTTPError) {\n if (error.response.status === 404) {\n this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);\n this.enabled = false;\n this.stopSync();\n return true;\n }\n if (error.response.status === 422) {\n this.logger.error(\"Received validation error from Apitally Hub\");\n return true;\n }\n }\n return false;\n }\n\n private async randomDelay() {\n const delay = 100 + Math.random() * 400;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n}\n"],"mappings":"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","names":["fetchRetry","randomUUID","ConsumerRegistry","getOrCreateInstanceUuid","getLogger","isValidClientId","isValidEnv","RequestCounter","RequestLogger","getCpuMemoryUsage","ServerErrorCounter","ValidationErrorCounter","SYNC_INTERVAL","INITIAL_SYNC_INTERVAL","INITIAL_SYNC_INTERVAL_DURATION","MAX_QUEUE_TIME","HTTPError","Error","response","reason","status","ApitallyClient","clientId","env","instanceUuid","syncDataQueue","syncIntervalId","startupData","startupDataSent","enabled","requestCounter","requestLogger","validationErrorCounter","serverErrorCounter","consumerRegistry","logger","requestLogging","requestLoggingConfig","instance","error","console","warn","handleShutdown","bind","getInstance","isEnabled","shutdown","stopSync","sendSyncData","sendLogData","close","undefined","getHubUrlPrefix","baseURL","process","APITALLY_HUB_BASE_URL","version","sendData","url","payload","fetchWithRetry","fetch","retries","retryDelay","retryOn","method","body","JSON","stringify","headers","ok","startSync","sync","setInterval","setTimeout","clearInterval","promises","push","sendStartupData","Promise","all","setStartupData","data","debug","instance_uuid","message_uuid","handled","handleHubError","message","newPayload","timestamp","Date","now","requests","getAndResetRequests","validation_errors","getAndResetValidationErrors","server_errors","getAndResetServerErrors","consumers","getAndResetUpdatedConsumers","resources","i","length","shift","randomDelay","rotateFile","logFile","getFile","uuid","getContent","has","retryAfter","parseInt","get","suspendUntil","clear","delete","retryFileLater","delay","Math","random","resolve"]}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var instance_exports = {};
|
|
21
|
+
__export(instance_exports, {
|
|
22
|
+
getOrCreateInstanceUuid: () => getOrCreateInstanceUuid,
|
|
23
|
+
validateLockFiles: () => validateLockFiles
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(instance_exports);
|
|
26
|
+
var import_node_crypto = require("node:crypto");
|
|
27
|
+
var import_node_fs = require("node:fs");
|
|
28
|
+
var import_node_os = require("node:os");
|
|
29
|
+
var import_node_path = require("node:path");
|
|
30
|
+
const TEMP_DIR = (0, import_node_path.join)((0, import_node_os.tmpdir)(), "apitally");
|
|
31
|
+
const MAX_SLOTS = 100;
|
|
32
|
+
const MAX_LOCK_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
33
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
34
|
+
function getOrCreateInstanceUuid(clientId, env) {
|
|
35
|
+
try {
|
|
36
|
+
(0, import_node_fs.mkdirSync)(TEMP_DIR, {
|
|
37
|
+
recursive: true
|
|
38
|
+
});
|
|
39
|
+
} catch {
|
|
40
|
+
return (0, import_node_crypto.randomUUID)();
|
|
41
|
+
}
|
|
42
|
+
const hash = getAppEnvHash(clientId, env);
|
|
43
|
+
validateLockFiles(hash);
|
|
44
|
+
for (let slot = 0; slot < MAX_SLOTS; slot++) {
|
|
45
|
+
const pidFile = (0, import_node_path.join)(TEMP_DIR, `instance_${hash}_${slot}.pid`);
|
|
46
|
+
const uuidFile = (0, import_node_path.join)(TEMP_DIR, `instance_${hash}_${slot}.uuid`);
|
|
47
|
+
try {
|
|
48
|
+
(0, import_node_fs.writeFileSync)(pidFile, String(process.pid), {
|
|
49
|
+
flag: "wx"
|
|
50
|
+
});
|
|
51
|
+
return getOrCreateUuid(uuidFile);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err.code !== "EEXIST") {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const pid = parseInt((0, import_node_fs.readFileSync)(pidFile, "utf-8"), 10);
|
|
59
|
+
if (pid === process.pid) {
|
|
60
|
+
return (0, import_node_fs.readFileSync)(uuidFile, "utf-8").trim();
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return (0, import_node_crypto.randomUUID)();
|
|
66
|
+
}
|
|
67
|
+
__name(getOrCreateInstanceUuid, "getOrCreateInstanceUuid");
|
|
68
|
+
function getAppEnvHash(clientId, env) {
|
|
69
|
+
return (0, import_node_crypto.createHash)("sha256").update(`${clientId}:${env}`).digest("hex").slice(0, 8);
|
|
70
|
+
}
|
|
71
|
+
__name(getAppEnvHash, "getAppEnvHash");
|
|
72
|
+
function getOrCreateUuid(uuidFile) {
|
|
73
|
+
try {
|
|
74
|
+
const existingUuid = (0, import_node_fs.readFileSync)(uuidFile, "utf-8").trim();
|
|
75
|
+
if (validateUuid(existingUuid)) {
|
|
76
|
+
return existingUuid;
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
const newUuid = (0, import_node_crypto.randomUUID)();
|
|
81
|
+
(0, import_node_fs.writeFileSync)(uuidFile, newUuid);
|
|
82
|
+
return newUuid;
|
|
83
|
+
}
|
|
84
|
+
__name(getOrCreateUuid, "getOrCreateUuid");
|
|
85
|
+
function validateUuid(value) {
|
|
86
|
+
return UUID_REGEX.test(value);
|
|
87
|
+
}
|
|
88
|
+
__name(validateUuid, "validateUuid");
|
|
89
|
+
function isPidAlive(pid) {
|
|
90
|
+
try {
|
|
91
|
+
process.kill(pid, 0);
|
|
92
|
+
return true;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
__name(isPidAlive, "isPidAlive");
|
|
98
|
+
function deleteFiles(...paths) {
|
|
99
|
+
for (const path of paths) {
|
|
100
|
+
try {
|
|
101
|
+
(0, import_node_fs.unlinkSync)(path);
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
__name(deleteFiles, "deleteFiles");
|
|
107
|
+
function validateLockFiles(appEnvHash) {
|
|
108
|
+
let files;
|
|
109
|
+
try {
|
|
110
|
+
files = (0, import_node_fs.readdirSync)(TEMP_DIR);
|
|
111
|
+
} catch {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const prefix = `instance_${appEnvHash}_`;
|
|
115
|
+
const uuidFiles = files.filter((f) => f.startsWith(prefix) && f.endsWith(".uuid")).sort();
|
|
116
|
+
const pidFiles = files.filter((f) => f.startsWith(prefix) && f.endsWith(".pid")).sort();
|
|
117
|
+
const seenUuids = /* @__PURE__ */ new Set();
|
|
118
|
+
const now = Date.now();
|
|
119
|
+
for (const uuidFileName of uuidFiles) {
|
|
120
|
+
const uuidFile = (0, import_node_path.join)(TEMP_DIR, uuidFileName);
|
|
121
|
+
const pidFile = (0, import_node_path.join)(TEMP_DIR, uuidFileName.replace(".uuid", ".pid"));
|
|
122
|
+
try {
|
|
123
|
+
const stat = (0, import_node_fs.statSync)(uuidFile);
|
|
124
|
+
if (now - stat.mtimeMs > MAX_LOCK_AGE_MS) {
|
|
125
|
+
deleteFiles(uuidFile, pidFile);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const uuid = (0, import_node_fs.readFileSync)(uuidFile, "utf-8").trim();
|
|
129
|
+
if (!validateUuid(uuid)) {
|
|
130
|
+
deleteFiles(uuidFile, pidFile);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (seenUuids.has(uuid)) {
|
|
134
|
+
deleteFiles(uuidFile, pidFile);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
seenUuids.add(uuid);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const pidFileName of pidFiles) {
|
|
142
|
+
const pidFile = (0, import_node_path.join)(TEMP_DIR, pidFileName);
|
|
143
|
+
try {
|
|
144
|
+
const pid = parseInt((0, import_node_fs.readFileSync)(pidFile, "utf-8"), 10);
|
|
145
|
+
if (!isPidAlive(pid)) {
|
|
146
|
+
deleteFiles(pidFile);
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
__name(validateLockFiles, "validateLockFiles");
|
|
153
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
154
|
+
0 && (module.exports = {
|
|
155
|
+
getOrCreateInstanceUuid,
|
|
156
|
+
validateLockFiles
|
|
157
|
+
});
|
|
158
|
+
//# sourceMappingURL=instance.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/common/instance.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport {\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst TEMP_DIR = join(tmpdir(), \"apitally\");\nconst MAX_SLOTS = 100;\nconst MAX_LOCK_AGE_MS = 24 * 60 * 60 * 1000;\nconst UUID_REGEX =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport function getOrCreateInstanceUuid(clientId: string, env: string): string {\n try {\n mkdirSync(TEMP_DIR, { recursive: true });\n } catch {\n return randomUUID();\n }\n\n const hash = getAppEnvHash(clientId, env);\n validateLockFiles(hash);\n\n for (let slot = 0; slot < MAX_SLOTS; slot++) {\n const pidFile = join(TEMP_DIR, `instance_${hash}_${slot}.pid`);\n const uuidFile = join(TEMP_DIR, `instance_${hash}_${slot}.uuid`);\n\n // Try atomic exclusive create of PID file\n try {\n writeFileSync(pidFile, String(process.pid), { flag: \"wx\" });\n return getOrCreateUuid(uuidFile);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") {\n continue;\n }\n }\n\n // PID file exists - check if it's ours (hot reload)\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf-8\"), 10);\n if (pid === process.pid) {\n return readFileSync(uuidFile, \"utf-8\").trim();\n }\n } catch {\n // Ignore read error\n }\n }\n\n return randomUUID();\n}\n\nfunction getAppEnvHash(clientId: string, env: string): string {\n return createHash(\"sha256\")\n .update(`${clientId}:${env}`)\n .digest(\"hex\")\n .slice(0, 8);\n}\n\nfunction getOrCreateUuid(uuidFile: string): string {\n try {\n const existingUuid = readFileSync(uuidFile, \"utf-8\").trim();\n if (validateUuid(existingUuid)) {\n return existingUuid;\n }\n } catch {\n // File doesn't exist or read error\n }\n\n const newUuid = randomUUID();\n writeFileSync(uuidFile, newUuid);\n return newUuid;\n}\n\nfunction validateUuid(value: string): boolean {\n return UUID_REGEX.test(value);\n}\n\nfunction isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction deleteFiles(...paths: string[]) {\n for (const path of paths) {\n try {\n unlinkSync(path);\n } catch {\n // Ignore errors\n }\n }\n}\n\nexport function validateLockFiles(appEnvHash: string) {\n let files: string[];\n try {\n files = readdirSync(TEMP_DIR);\n } catch {\n return;\n }\n\n const prefix = `instance_${appEnvHash}_`;\n const uuidFiles = files\n .filter((f) => f.startsWith(prefix) && f.endsWith(\".uuid\"))\n .sort();\n const pidFiles = files\n .filter((f) => f.startsWith(prefix) && f.endsWith(\".pid\"))\n .sort();\n const seenUuids = new Set<string>();\n const now = Date.now();\n\n // Clean up UUID files\n for (const uuidFileName of uuidFiles) {\n const uuidFile = join(TEMP_DIR, uuidFileName);\n const pidFile = join(TEMP_DIR, uuidFileName.replace(\".uuid\", \".pid\"));\n\n try {\n const stat = statSync(uuidFile);\n\n // Delete if older than 24 hours\n if (now - stat.mtimeMs > MAX_LOCK_AGE_MS) {\n deleteFiles(uuidFile, pidFile);\n continue;\n }\n\n // Delete if UUID is invalid\n const uuid = readFileSync(uuidFile, \"utf-8\").trim();\n if (!validateUuid(uuid)) {\n deleteFiles(uuidFile, pidFile);\n continue;\n }\n\n // Delete if UUID is a duplicate\n if (seenUuids.has(uuid)) {\n deleteFiles(uuidFile, pidFile);\n continue;\n }\n seenUuids.add(uuid);\n } catch {\n // Ignore stat or read error\n }\n }\n\n // Clean up PID files from dead processes\n for (const pidFileName of pidFiles) {\n const pidFile = join(TEMP_DIR, pidFileName);\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf-8\"), 10);\n if (!isPidAlive(pid)) {\n deleteFiles(pidFile);\n }\n } catch {\n // Ignore read error\n }\n }\n}\n"],"mappings":"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","names":["TEMP_DIR","join","tmpdir","MAX_SLOTS","MAX_LOCK_AGE_MS","UUID_REGEX","getOrCreateInstanceUuid","clientId","env","mkdirSync","recursive","randomUUID","hash","getAppEnvHash","validateLockFiles","slot","pidFile","uuidFile","writeFileSync","String","process","pid","flag","getOrCreateUuid","err","code","parseInt","readFileSync","trim","createHash","update","digest","slice","existingUuid","validateUuid","newUuid","value","test","isPidAlive","kill","deleteFiles","paths","path","unlinkSync","appEnvHash","files","readdirSync","prefix","uuidFiles","filter","f","startsWith","endsWith","sort","pidFiles","seenUuids","Set","now","Date","uuidFileName","replace","stat","statSync","mtimeMs","uuid","has","add","pidFileName"]}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
const TEMP_DIR = join(tmpdir(), "apitally");
|
|
8
|
+
const MAX_SLOTS = 100;
|
|
9
|
+
const MAX_LOCK_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
10
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11
|
+
function getOrCreateInstanceUuid(clientId, env) {
|
|
12
|
+
try {
|
|
13
|
+
mkdirSync(TEMP_DIR, {
|
|
14
|
+
recursive: true
|
|
15
|
+
});
|
|
16
|
+
} catch {
|
|
17
|
+
return randomUUID();
|
|
18
|
+
}
|
|
19
|
+
const hash = getAppEnvHash(clientId, env);
|
|
20
|
+
validateLockFiles(hash);
|
|
21
|
+
for (let slot = 0; slot < MAX_SLOTS; slot++) {
|
|
22
|
+
const pidFile = join(TEMP_DIR, `instance_${hash}_${slot}.pid`);
|
|
23
|
+
const uuidFile = join(TEMP_DIR, `instance_${hash}_${slot}.uuid`);
|
|
24
|
+
try {
|
|
25
|
+
writeFileSync(pidFile, String(process.pid), {
|
|
26
|
+
flag: "wx"
|
|
27
|
+
});
|
|
28
|
+
return getOrCreateUuid(uuidFile);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (err.code !== "EEXIST") {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8"), 10);
|
|
36
|
+
if (pid === process.pid) {
|
|
37
|
+
return readFileSync(uuidFile, "utf-8").trim();
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return randomUUID();
|
|
43
|
+
}
|
|
44
|
+
__name(getOrCreateInstanceUuid, "getOrCreateInstanceUuid");
|
|
45
|
+
function getAppEnvHash(clientId, env) {
|
|
46
|
+
return createHash("sha256").update(`${clientId}:${env}`).digest("hex").slice(0, 8);
|
|
47
|
+
}
|
|
48
|
+
__name(getAppEnvHash, "getAppEnvHash");
|
|
49
|
+
function getOrCreateUuid(uuidFile) {
|
|
50
|
+
try {
|
|
51
|
+
const existingUuid = readFileSync(uuidFile, "utf-8").trim();
|
|
52
|
+
if (validateUuid(existingUuid)) {
|
|
53
|
+
return existingUuid;
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
const newUuid = randomUUID();
|
|
58
|
+
writeFileSync(uuidFile, newUuid);
|
|
59
|
+
return newUuid;
|
|
60
|
+
}
|
|
61
|
+
__name(getOrCreateUuid, "getOrCreateUuid");
|
|
62
|
+
function validateUuid(value) {
|
|
63
|
+
return UUID_REGEX.test(value);
|
|
64
|
+
}
|
|
65
|
+
__name(validateUuid, "validateUuid");
|
|
66
|
+
function isPidAlive(pid) {
|
|
67
|
+
try {
|
|
68
|
+
process.kill(pid, 0);
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
__name(isPidAlive, "isPidAlive");
|
|
75
|
+
function deleteFiles(...paths) {
|
|
76
|
+
for (const path of paths) {
|
|
77
|
+
try {
|
|
78
|
+
unlinkSync(path);
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
__name(deleteFiles, "deleteFiles");
|
|
84
|
+
function validateLockFiles(appEnvHash) {
|
|
85
|
+
let files;
|
|
86
|
+
try {
|
|
87
|
+
files = readdirSync(TEMP_DIR);
|
|
88
|
+
} catch {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const prefix = `instance_${appEnvHash}_`;
|
|
92
|
+
const uuidFiles = files.filter((f) => f.startsWith(prefix) && f.endsWith(".uuid")).sort();
|
|
93
|
+
const pidFiles = files.filter((f) => f.startsWith(prefix) && f.endsWith(".pid")).sort();
|
|
94
|
+
const seenUuids = /* @__PURE__ */ new Set();
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
for (const uuidFileName of uuidFiles) {
|
|
97
|
+
const uuidFile = join(TEMP_DIR, uuidFileName);
|
|
98
|
+
const pidFile = join(TEMP_DIR, uuidFileName.replace(".uuid", ".pid"));
|
|
99
|
+
try {
|
|
100
|
+
const stat = statSync(uuidFile);
|
|
101
|
+
if (now - stat.mtimeMs > MAX_LOCK_AGE_MS) {
|
|
102
|
+
deleteFiles(uuidFile, pidFile);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const uuid = readFileSync(uuidFile, "utf-8").trim();
|
|
106
|
+
if (!validateUuid(uuid)) {
|
|
107
|
+
deleteFiles(uuidFile, pidFile);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (seenUuids.has(uuid)) {
|
|
111
|
+
deleteFiles(uuidFile, pidFile);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
seenUuids.add(uuid);
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const pidFileName of pidFiles) {
|
|
119
|
+
const pidFile = join(TEMP_DIR, pidFileName);
|
|
120
|
+
try {
|
|
121
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8"), 10);
|
|
122
|
+
if (!isPidAlive(pid)) {
|
|
123
|
+
deleteFiles(pidFile);
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
__name(validateLockFiles, "validateLockFiles");
|
|
130
|
+
export {
|
|
131
|
+
getOrCreateInstanceUuid,
|
|
132
|
+
validateLockFiles
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=instance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/common/instance.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport {\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst TEMP_DIR = join(tmpdir(), \"apitally\");\nconst MAX_SLOTS = 100;\nconst MAX_LOCK_AGE_MS = 24 * 60 * 60 * 1000;\nconst UUID_REGEX =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport function getOrCreateInstanceUuid(clientId: string, env: string): string {\n try {\n mkdirSync(TEMP_DIR, { recursive: true });\n } catch {\n return randomUUID();\n }\n\n const hash = getAppEnvHash(clientId, env);\n validateLockFiles(hash);\n\n for (let slot = 0; slot < MAX_SLOTS; slot++) {\n const pidFile = join(TEMP_DIR, `instance_${hash}_${slot}.pid`);\n const uuidFile = join(TEMP_DIR, `instance_${hash}_${slot}.uuid`);\n\n // Try atomic exclusive create of PID file\n try {\n writeFileSync(pidFile, String(process.pid), { flag: \"wx\" });\n return getOrCreateUuid(uuidFile);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") {\n continue;\n }\n }\n\n // PID file exists - check if it's ours (hot reload)\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf-8\"), 10);\n if (pid === process.pid) {\n return readFileSync(uuidFile, \"utf-8\").trim();\n }\n } catch {\n // Ignore read error\n }\n }\n\n return randomUUID();\n}\n\nfunction getAppEnvHash(clientId: string, env: string): string {\n return createHash(\"sha256\")\n .update(`${clientId}:${env}`)\n .digest(\"hex\")\n .slice(0, 8);\n}\n\nfunction getOrCreateUuid(uuidFile: string): string {\n try {\n const existingUuid = readFileSync(uuidFile, \"utf-8\").trim();\n if (validateUuid(existingUuid)) {\n return existingUuid;\n }\n } catch {\n // File doesn't exist or read error\n }\n\n const newUuid = randomUUID();\n writeFileSync(uuidFile, newUuid);\n return newUuid;\n}\n\nfunction validateUuid(value: string): boolean {\n return UUID_REGEX.test(value);\n}\n\nfunction isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction deleteFiles(...paths: string[]) {\n for (const path of paths) {\n try {\n unlinkSync(path);\n } catch {\n // Ignore errors\n }\n }\n}\n\nexport function validateLockFiles(appEnvHash: string) {\n let files: string[];\n try {\n files = readdirSync(TEMP_DIR);\n } catch {\n return;\n }\n\n const prefix = `instance_${appEnvHash}_`;\n const uuidFiles = files\n .filter((f) => f.startsWith(prefix) && f.endsWith(\".uuid\"))\n .sort();\n const pidFiles = files\n .filter((f) => f.startsWith(prefix) && f.endsWith(\".pid\"))\n .sort();\n const seenUuids = new Set<string>();\n const now = Date.now();\n\n // Clean up UUID files\n for (const uuidFileName of uuidFiles) {\n const uuidFile = join(TEMP_DIR, uuidFileName);\n const pidFile = join(TEMP_DIR, uuidFileName.replace(\".uuid\", \".pid\"));\n\n try {\n const stat = statSync(uuidFile);\n\n // Delete if older than 24 hours\n if (now - stat.mtimeMs > MAX_LOCK_AGE_MS) {\n deleteFiles(uuidFile, pidFile);\n continue;\n }\n\n // Delete if UUID is invalid\n const uuid = readFileSync(uuidFile, \"utf-8\").trim();\n if (!validateUuid(uuid)) {\n deleteFiles(uuidFile, pidFile);\n continue;\n }\n\n // Delete if UUID is a duplicate\n if (seenUuids.has(uuid)) {\n deleteFiles(uuidFile, pidFile);\n continue;\n }\n seenUuids.add(uuid);\n } catch {\n // Ignore stat or read error\n }\n }\n\n // Clean up PID files from dead processes\n for (const pidFileName of pidFiles) {\n const pidFile = join(TEMP_DIR, pidFileName);\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf-8\"), 10);\n if (!isPidAlive(pid)) {\n deleteFiles(pidFile);\n }\n } catch {\n // Ignore read error\n }\n }\n}\n"],"mappings":"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","names":["createHash","randomUUID","mkdirSync","readFileSync","readdirSync","statSync","unlinkSync","writeFileSync","tmpdir","join","TEMP_DIR","MAX_SLOTS","MAX_LOCK_AGE_MS","UUID_REGEX","getOrCreateInstanceUuid","clientId","env","recursive","hash","getAppEnvHash","validateLockFiles","slot","pidFile","uuidFile","String","process","pid","flag","getOrCreateUuid","err","code","parseInt","trim","update","digest","slice","existingUuid","validateUuid","newUuid","value","test","isPidAlive","kill","deleteFiles","paths","path","appEnvHash","files","prefix","uuidFiles","filter","f","startsWith","endsWith","sort","pidFiles","seenUuids","Set","now","Date","uuidFileName","replace","stat","mtimeMs","uuid","has","add","pidFileName"]}
|
|
@@ -37,9 +37,6 @@ module.exports = __toCommonJS(requestLogger_exports);
|
|
|
37
37
|
var import_async_lock = __toESM(require("async-lock"), 1);
|
|
38
38
|
var import_node_buffer = require("node:buffer");
|
|
39
39
|
var import_node_crypto = require("node:crypto");
|
|
40
|
-
var import_node_fs = require("node:fs");
|
|
41
|
-
var import_node_os = require("node:os");
|
|
42
|
-
var import_node_path = require("node:path");
|
|
43
40
|
var import_sentry = require("./sentry.js");
|
|
44
41
|
var import_serverErrorCounter = require("./serverErrorCounter.js");
|
|
45
42
|
var import_tempGzipFile = __toESM(require("./tempGzipFile.js"), 1);
|
|
@@ -66,7 +63,16 @@ const EXCLUDE_PATH_PATTERNS = [
|
|
|
66
63
|
/\/_?heart[_-]?beats?$/i,
|
|
67
64
|
/\/ping$/i,
|
|
68
65
|
/\/ready$/i,
|
|
69
|
-
/\/live$/i
|
|
66
|
+
/\/live$/i,
|
|
67
|
+
/\/favicon(?:-[\w-]+)?\.(ico|png|svg)$/,
|
|
68
|
+
/\/apple-touch-icon(?:-[\w-]+)?\.png$/,
|
|
69
|
+
/\/robots\.txt$/,
|
|
70
|
+
/\/sitemap\.xml$/,
|
|
71
|
+
/\/manifest\.json$/,
|
|
72
|
+
/\/site\.webmanifest$/,
|
|
73
|
+
/\/service-worker\.js$/,
|
|
74
|
+
/\/sw\.js$/,
|
|
75
|
+
/\/\.well-known\//
|
|
70
76
|
];
|
|
71
77
|
const EXCLUDE_USER_AGENT_PATTERNS = [
|
|
72
78
|
/health[-_ ]?check/i,
|
|
@@ -127,7 +133,7 @@ const _RequestLogger = class _RequestLogger {
|
|
|
127
133
|
...DEFAULT_CONFIG,
|
|
128
134
|
...config
|
|
129
135
|
};
|
|
130
|
-
this.enabled = this.config.enabled && checkWritableFs();
|
|
136
|
+
this.enabled = this.config.enabled && (0, import_tempGzipFile.checkWritableFs)();
|
|
131
137
|
if (this.enabled) {
|
|
132
138
|
this.maintainIntervalId = setInterval(() => {
|
|
133
139
|
this.maintain();
|
|
@@ -322,7 +328,7 @@ const _RequestLogger = class _RequestLogger {
|
|
|
322
328
|
}
|
|
323
329
|
return this.lock.acquire("file", async () => {
|
|
324
330
|
if (!this.currentFile) {
|
|
325
|
-
this.currentFile = new import_tempGzipFile.default();
|
|
331
|
+
this.currentFile = new import_tempGzipFile.default("request_logs");
|
|
326
332
|
}
|
|
327
333
|
while (this.pendingWrites.length > 0) {
|
|
328
334
|
let item = this.pendingWrites.shift();
|
|
@@ -478,17 +484,6 @@ function truncateLogMessage(msg) {
|
|
|
478
484
|
return msg;
|
|
479
485
|
}
|
|
480
486
|
__name(truncateLogMessage, "truncateLogMessage");
|
|
481
|
-
function checkWritableFs() {
|
|
482
|
-
try {
|
|
483
|
-
const testPath = (0, import_node_path.join)((0, import_node_os.tmpdir)(), `apitally-${(0, import_node_crypto.randomUUID)()}`);
|
|
484
|
-
(0, import_node_fs.writeFileSync)(testPath, "test");
|
|
485
|
-
(0, import_node_fs.unlinkSync)(testPath);
|
|
486
|
-
return true;
|
|
487
|
-
} catch (error) {
|
|
488
|
-
return false;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
__name(checkWritableFs, "checkWritableFs");
|
|
492
487
|
// Annotate the CommonJS export names for ESM import in node:
|
|
493
488
|
0 && (module.exports = {
|
|
494
489
|
convertBody,
|