@uns-kit/api 2.0.0 → 2.0.3

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.
@@ -1,5 +1,6 @@
1
1
  import UnsProxyProcess from "@uns-kit/core/uns/uns-proxy-process.js";
2
2
  import UnsApiProxy from "./uns-api-proxy.js";
3
+ import { UnsPacket } from "@uns-kit/core/uns/uns-packet.js";
3
4
  const apiProxyRegistry = new WeakMap();
4
5
  const getApiProxies = (instance) => {
5
6
  let proxies = apiProxyRegistry.get(instance);
@@ -33,6 +34,13 @@ const unsApiPlugin = ({ define }) => {
33
34
  properties: { messageExpiryInterval: 120000 },
34
35
  });
35
36
  });
37
+ unsApiProxy.event.on("mqttProxyStatus", (event) => {
38
+ const time = UnsPacket.formatToISO8601(new Date());
39
+ const unsMessage = { data: { time, value: event.value, uom: event.uom } };
40
+ UnsPacket.unsPacketFromUnsMessage(unsMessage).then((packet) => {
41
+ internals.processMqttProxy.publish(event.statusTopic, JSON.stringify(packet));
42
+ });
43
+ });
36
44
  internals.unsApiProxies.push(unsApiProxy);
37
45
  getApiProxies(this).push(unsApiProxy);
38
46
  return unsApiProxy;
@@ -13,6 +13,9 @@ export default class UnsApiProxy extends UnsProxy {
13
13
  private options;
14
14
  private jwksCache?;
15
15
  private catchAllRouteRegistered;
16
+ private startedAt;
17
+ private statusInterval;
18
+ private readonly statusIntervalMs;
16
19
  constructor(processName: string, instanceName: string, options: IApiProxyOptions);
17
20
  /**
18
21
  * Unregister endpoint
@@ -43,8 +46,11 @@ export default class UnsApiProxy extends UnsProxy {
43
46
  queryParams?: IGetEndpointOptions["queryParams"];
44
47
  }): Promise<void>;
45
48
  post(..._args: any[]): any;
49
+ private emitStatusMetrics;
50
+ private registerHealthEndpoint;
46
51
  private extractBearerToken;
47
52
  private getPublicKeyFromJwks;
48
53
  private fetchJwksKeys;
49
54
  private certFromX5c;
55
+ stop(): Promise<void>;
50
56
  }
@@ -9,6 +9,7 @@ import { MqttTopicBuilder } from "@uns-kit/core/uns-mqtt/mqtt-topic-builder.js";
9
9
  import { UnsPacket } from "@uns-kit/core/uns/uns-packet.js";
10
10
  import UnsProxy from "@uns-kit/core/uns/uns-proxy.js";
11
11
  import { UnsTopicMatcher } from "@uns-kit/core/uns/uns-topic-matcher.js";
12
+ import { DataSizeMeasurements, PhysicalMeasurements } from "@uns-kit/core/uns/uns-measurements.js";
12
13
  import App from "./app.js";
13
14
  const packageJsonPath = path.join(basePath, "package.json");
14
15
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
@@ -21,11 +22,15 @@ export default class UnsApiProxy extends UnsProxy {
21
22
  options;
22
23
  jwksCache;
23
24
  catchAllRouteRegistered = false;
25
+ startedAt;
26
+ statusInterval = null;
27
+ statusIntervalMs = 10_000;
24
28
  constructor(processName, instanceName, options) {
25
29
  super();
26
30
  this.options = options;
27
31
  this.app = new App(0, processName, instanceName);
28
32
  this.app.start();
33
+ this.startedAt = Date.now();
29
34
  this.instanceName = instanceName;
30
35
  this.processName = processName;
31
36
  // Create the topic builder using packageJson values and the processName.
@@ -36,6 +41,10 @@ export default class UnsApiProxy extends UnsProxy {
36
41
  this.instanceStatusTopic = this.processStatusTopic + instanceName + "/";
37
42
  // Concatenate processName with instanceName for the worker identification.
38
43
  this.instanceNameWithSuffix = `${processName}-${instanceName}`;
44
+ this.registerHealthEndpoint();
45
+ // Emit once after listeners are attached in the plugin, then on the regular cadence.
46
+ setTimeout(() => this.emitStatusMetrics(), 0);
47
+ this.statusInterval = setInterval(() => this.emitStatusMetrics(), this.statusIntervalMs);
39
48
  }
40
49
  /**
41
50
  * Unregister endpoint
@@ -312,6 +321,61 @@ export default class UnsApiProxy extends UnsProxy {
312
321
  // Implement POST logic or route binding here
313
322
  return "POST called";
314
323
  }
324
+ emitStatusMetrics() {
325
+ const uptimeMinutes = Math.round((Date.now() - this.startedAt) / 60000);
326
+ // Process-level status
327
+ this.event.emit("mqttProxyStatus", {
328
+ event: "uptime",
329
+ value: uptimeMinutes,
330
+ uom: PhysicalMeasurements.Minute,
331
+ statusTopic: this.processStatusTopic + "uptime",
332
+ });
333
+ this.event.emit("mqttProxyStatus", {
334
+ event: "alive",
335
+ value: 1,
336
+ uom: DataSizeMeasurements.Bit,
337
+ statusTopic: this.processStatusTopic + "alive",
338
+ });
339
+ // Instance-level status
340
+ this.event.emit("mqttProxyStatus", {
341
+ event: "uptime",
342
+ value: uptimeMinutes,
343
+ uom: PhysicalMeasurements.Minute,
344
+ statusTopic: this.instanceStatusTopic + "uptime",
345
+ });
346
+ this.event.emit("mqttProxyStatus", {
347
+ event: "alive",
348
+ value: 1,
349
+ uom: DataSizeMeasurements.Bit,
350
+ statusTopic: this.instanceStatusTopic + "alive",
351
+ });
352
+ }
353
+ registerHealthEndpoint() {
354
+ const fullPath = "/status";
355
+ this.app.router.get(fullPath, (_req, res) => {
356
+ res.json({
357
+ alive: true,
358
+ processName: this.processName,
359
+ instanceName: this.instanceName,
360
+ package: packageJson.name,
361
+ version: packageJson.version,
362
+ startedAt: new Date(this.startedAt).toISOString(),
363
+ uptimeMs: Date.now() - this.startedAt,
364
+ timestamp: new Date().toISOString(),
365
+ });
366
+ });
367
+ if (this.app.swaggerSpec) {
368
+ this.app.swaggerSpec.paths = this.app.swaggerSpec.paths || {};
369
+ this.app.swaggerSpec.paths[`/api${fullPath}`] = {
370
+ get: {
371
+ summary: "Health status",
372
+ responses: {
373
+ "200": { description: "OK" },
374
+ },
375
+ },
376
+ };
377
+ }
378
+ }
315
379
  extractBearerToken(req, res) {
316
380
  const authHeader = req.headers["authorization"];
317
381
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
@@ -377,4 +441,11 @@ export default class UnsApiProxy extends UnsProxy {
377
441
  const pemBody = x5cFirst.match(/.{1,64}/g)?.join("\n") ?? x5cFirst;
378
442
  return `-----BEGIN CERTIFICATE-----\n${pemBody}\n-----END CERTIFICATE-----\n`;
379
443
  }
444
+ async stop() {
445
+ if (this.statusInterval) {
446
+ clearInterval(this.statusInterval);
447
+ this.statusInterval = null;
448
+ }
449
+ await super.stop();
450
+ }
380
451
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uns-kit/api",
3
- "version": "2.0.0",
3
+ "version": "2.0.3",
4
4
  "description": "Express-powered API gateway plugin for UnsProxyProcess with JWT/JWKS support.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "cookie-parser": "^1.4.7",
36
36
  "express": "^5.1.0",
37
37
  "multer": "^2.0.2",
38
- "@uns-kit/core": "2.0.0"
38
+ "@uns-kit/core": "2.0.3"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/jsonwebtoken": "^9.0.10",