@fluyappgocore/commons-backend 1.0.213 → 1.0.214

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,14 +1,4 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
- /**
3
- * License feature guard middleware for microservices.
4
- * Caches license data from the licensing server and checks feature flags.
5
- *
6
- * Usage in any MS route:
7
- * router.get("/analytics", authFBMiddleware, licenseGuard("analytics-summary"), controller)
8
- *
9
- * Requires LICENSE_URL and INSTALLATION_UUID env vars.
10
- * Falls back to allowing access if license server is unreachable.
11
- */
12
2
  interface LicenseCache {
13
3
  valid: boolean;
14
4
  readOnly: boolean;
@@ -21,6 +11,9 @@ interface LicenseCache {
21
11
  };
22
12
  tier: string;
23
13
  fetchedAt: number;
14
+ unreachable?: boolean;
15
+ unreachableSince?: number | null;
16
+ unreachableGraceDaysLeft?: number | null;
24
17
  }
25
18
  /**
26
19
  * Check if a license feature is enabled.
@@ -47,10 +40,16 @@ export declare function licenseWriteGuard(): (req: Request, res: Response, next:
47
40
  export declare function licenseLoginGuard(): (_req: Request, res: Response, next: NextFunction) => Promise<void | Response<any, Record<string, any>>>;
48
41
  /**
49
42
  * Get current license status (for use in controllers).
43
+ * Includes the unreachable-grace fields so the frontend can render a
44
+ * "licensing server unreachable, X days left" banner.
50
45
  */
51
46
  export declare function getLicenseStatus(): Promise<{
52
47
  valid: boolean;
53
48
  readOnly: boolean;
54
49
  blocked: boolean;
50
+ tier: string;
51
+ unreachable: boolean;
52
+ unreachableSince: number | null;
53
+ unreachableGraceDaysLeft: number | null;
55
54
  } | null>;
56
55
  export {};
@@ -1,4 +1,15 @@
1
1
  "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
2
13
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
14
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
15
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -37,8 +48,74 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
37
48
  };
38
49
  Object.defineProperty(exports, "__esModule", { value: true });
39
50
  exports.getLicenseStatus = exports.licenseLoginGuard = exports.licenseWriteGuard = exports.licenseGuard = exports.getLicenseLimits = exports.isLicenseFeatureEnabled = void 0;
51
+ var fs_1 = require("fs");
52
+ /**
53
+ * License feature guard middleware for microservices.
54
+ * Caches license data from the licensing server and checks feature flags.
55
+ *
56
+ * Usage in any MS route:
57
+ * router.get("/analytics", authFBMiddleware, licenseGuard("analytics-summary"), controller)
58
+ *
59
+ * Requires LICENSE_URL and INSTALLATION_UUID env vars.
60
+ *
61
+ * Unreachable behaviour (PaaS policy):
62
+ * - If the licensing server can't be reached, the tenant keeps
63
+ * working for UNREACHABLE_GRACE_DAYS days using the last cached
64
+ * features (or open if there was none). After that, /license-status
65
+ * reports `blocked: true` and the standard guards reject traffic.
66
+ * - The client app is expected to read `unreachable` +
67
+ * `unreachableGraceDaysLeft` from `getLicenseStatus()` and show a
68
+ * warning banner.
69
+ */
70
+ var UNREACHABLE_GRACE_DAYS = 7;
71
+ var UNREACHABLE_GRACE_MS = UNREACHABLE_GRACE_DAYS * 24 * 60 * 60 * 1000;
72
+ var GRACE_STATE_FILE = "/tmp/fluyapp-license-grace.json";
40
73
  var cache = null;
41
74
  var CACHE_TTL = 1000 * 60 * 60; // 1 hour
75
+ function readGraceState() {
76
+ return __awaiter(this, void 0, void 0, function () {
77
+ var raw, _a;
78
+ return __generator(this, function (_b) {
79
+ switch (_b.label) {
80
+ case 0:
81
+ _b.trys.push([0, 2, , 3]);
82
+ return [4 /*yield*/, fs_1.promises.readFile(GRACE_STATE_FILE, "utf8")];
83
+ case 1:
84
+ raw = _b.sent();
85
+ return [2 /*return*/, JSON.parse(raw)];
86
+ case 2:
87
+ _a = _b.sent();
88
+ return [2 /*return*/, null];
89
+ case 3: return [2 /*return*/];
90
+ }
91
+ });
92
+ });
93
+ }
94
+ function writeGraceState(state) {
95
+ return __awaiter(this, void 0, void 0, function () {
96
+ var _a;
97
+ return __generator(this, function (_b) {
98
+ switch (_b.label) {
99
+ case 0:
100
+ _b.trys.push([0, 5, , 6]);
101
+ if (!state) return [3 /*break*/, 2];
102
+ return [4 /*yield*/, fs_1.promises.writeFile(GRACE_STATE_FILE, JSON.stringify(state))];
103
+ case 1:
104
+ _b.sent();
105
+ return [3 /*break*/, 4];
106
+ case 2: return [4 /*yield*/, fs_1.promises.unlink(GRACE_STATE_FILE).catch(function () { })];
107
+ case 3:
108
+ _b.sent();
109
+ _b.label = 4;
110
+ case 4: return [3 /*break*/, 6];
111
+ case 5:
112
+ _a = _b.sent();
113
+ return [3 /*break*/, 6];
114
+ case 6: return [2 /*return*/];
115
+ }
116
+ });
117
+ });
118
+ }
42
119
  /**
43
120
  * Where to fetch license data from. Two modes:
44
121
  *
@@ -125,18 +202,72 @@ function fetchLicense() {
125
202
  });
126
203
  }
127
204
  function getCachedLicense() {
205
+ var _a;
128
206
  return __awaiter(this, void 0, void 0, function () {
129
- var fresh;
130
- return __generator(this, function (_a) {
131
- switch (_a.label) {
207
+ var fresh, now, stored, since, elapsed, expired, daysLeft;
208
+ return __generator(this, function (_b) {
209
+ switch (_b.label) {
132
210
  case 0:
133
211
  if (cache && (Date.now() - cache.fetchedAt) < CACHE_TTL)
134
212
  return [2 /*return*/, cache];
135
213
  return [4 /*yield*/, fetchLicense()];
136
214
  case 1:
137
- fresh = _a.sent();
138
- if (fresh)
139
- cache = fresh;
215
+ fresh = _b.sent();
216
+ if (!fresh) return [3 /*break*/, 3];
217
+ // Successful refresh: clear any pending grace state.
218
+ return [4 /*yield*/, writeGraceState(null)];
219
+ case 2:
220
+ // Successful refresh: clear any pending grace state.
221
+ _b.sent();
222
+ cache = __assign(__assign({}, fresh), { unreachable: false, unreachableSince: null, unreachableGraceDaysLeft: null });
223
+ return [2 /*return*/, cache];
224
+ case 3:
225
+ now = Date.now();
226
+ return [4 /*yield*/, readGraceState()];
227
+ case 4:
228
+ stored = _b.sent();
229
+ since = (_a = stored === null || stored === void 0 ? void 0 : stored.unreachableSince) !== null && _a !== void 0 ? _a : now;
230
+ if (!!stored) return [3 /*break*/, 6];
231
+ return [4 /*yield*/, writeGraceState({ unreachableSince: now })];
232
+ case 5:
233
+ _b.sent();
234
+ _b.label = 6;
235
+ case 6:
236
+ elapsed = now - since;
237
+ expired = elapsed >= UNREACHABLE_GRACE_MS;
238
+ daysLeft = expired ? 0 : Math.ceil((UNREACHABLE_GRACE_MS - elapsed) / (24 * 60 * 60 * 1000));
239
+ if (expired) {
240
+ // Past 7 days without a successful fetch — treat as blocked.
241
+ cache = {
242
+ valid: false,
243
+ readOnly: false,
244
+ blocked: true,
245
+ features: {},
246
+ limits: { maxBranches: 0, maxAgents: 0, maxServices: 0 },
247
+ tier: "BASIC",
248
+ fetchedAt: now,
249
+ unreachable: true,
250
+ unreachableSince: since,
251
+ unreachableGraceDaysLeft: 0,
252
+ };
253
+ return [2 /*return*/, cache];
254
+ }
255
+ // Inside grace: keep last known features if we had any, otherwise open.
256
+ if (cache && cache.valid !== false) {
257
+ return [2 /*return*/, __assign(__assign({}, cache), { unreachable: true, unreachableSince: since, unreachableGraceDaysLeft: daysLeft, fetchedAt: now })];
258
+ }
259
+ cache = {
260
+ valid: true,
261
+ readOnly: false,
262
+ blocked: false,
263
+ features: {},
264
+ limits: { maxBranches: 0, maxAgents: 0, maxServices: 0 },
265
+ tier: "GRACE",
266
+ fetchedAt: now,
267
+ unreachable: true,
268
+ unreachableSince: since,
269
+ unreachableGraceDaysLeft: daysLeft,
270
+ };
140
271
  return [2 /*return*/, cache];
141
272
  }
142
273
  });
@@ -297,11 +428,30 @@ function licenseLoginGuard() {
297
428
  exports.licenseLoginGuard = licenseLoginGuard;
298
429
  /**
299
430
  * Get current license status (for use in controllers).
431
+ * Includes the unreachable-grace fields so the frontend can render a
432
+ * "licensing server unreachable, X days left" banner.
300
433
  */
301
434
  function getLicenseStatus() {
435
+ var _a, _b;
302
436
  return __awaiter(this, void 0, void 0, function () {
303
- return __generator(this, function (_a) {
304
- return [2 /*return*/, getCachedLicense()];
437
+ var lic;
438
+ return __generator(this, function (_c) {
439
+ switch (_c.label) {
440
+ case 0: return [4 /*yield*/, getCachedLicense()];
441
+ case 1:
442
+ lic = _c.sent();
443
+ if (!lic)
444
+ return [2 /*return*/, null];
445
+ return [2 /*return*/, {
446
+ valid: lic.valid,
447
+ readOnly: lic.readOnly,
448
+ blocked: lic.blocked,
449
+ tier: lic.tier,
450
+ unreachable: lic.unreachable === true,
451
+ unreachableSince: (_a = lic.unreachableSince) !== null && _a !== void 0 ? _a : null,
452
+ unreachableGraceDaysLeft: (_b = lic.unreachableGraceDaysLeft) !== null && _b !== void 0 ? _b : null,
453
+ }];
454
+ }
305
455
  });
306
456
  });
307
457
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluyappgocore/commons-backend",
3
- "version": "1.0.213",
3
+ "version": "1.0.214",
4
4
  "description": "",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",