@oneuptime/common 11.3.20 → 11.3.21

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.
Files changed (74) hide show
  1. package/Models/DatabaseModels/EnterpriseLicenseInstance.ts +173 -0
  2. package/Models/DatabaseModels/GlobalConfig.ts +19 -0
  3. package/Models/DatabaseModels/Index.ts +2 -0
  4. package/Server/API/DashboardAPI.ts +221 -0
  5. package/Server/API/EnterpriseLicenseAPI.ts +279 -12
  6. package/Server/API/GlobalConfigAPI.ts +81 -16
  7. package/Server/API/StatusPageAPI.ts +894 -791
  8. package/Server/EnvironmentConfig.ts +9 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1783226868418-AddEnterpriseLicenseInstance.ts +47 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  11. package/Server/Services/EnterpriseLicenseInstanceService.ts +10 -0
  12. package/Server/Services/Index.ts +2 -0
  13. package/Server/Services/IoTDeviceService.ts +99 -4
  14. package/Server/Services/StatusPageService.ts +104 -45
  15. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +6 -4
  16. package/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts +7 -6
  17. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +6 -4
  18. package/Server/Utils/Telemetry/IoTSnapshotScan.ts +45 -7
  19. package/ServiceRoute.ts +4 -0
  20. package/Tests/Server/Services/IoTDeviceService.test.ts +280 -0
  21. package/Tests/Server/Utils/Monitor/Criteria/TelemetryStepMetricAlias.test.ts +390 -0
  22. package/Tests/Server/Utils/Telemetry/IoTSnapshotScan.test.ts +202 -0
  23. package/Tests/Utils/EnterpriseLicenseUsage.test.ts +165 -0
  24. package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +18 -0
  25. package/Types/Monitor/MonitorStep.ts +32 -0
  26. package/UI/Components/EditionLabel/EditionLabel.tsx +172 -0
  27. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +125 -0
  28. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +197 -0
  29. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -0
  30. package/build/dist/Models/DatabaseModels/GlobalConfig.js +18 -0
  31. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  32. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  33. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  34. package/build/dist/Server/API/DashboardAPI.js +166 -16
  35. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  36. package/build/dist/Server/API/EnterpriseLicenseAPI.js +203 -13
  37. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  38. package/build/dist/Server/API/GlobalConfigAPI.js +62 -15
  39. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  40. package/build/dist/Server/API/StatusPageAPI.js +74 -31
  41. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  42. package/build/dist/Server/EnvironmentConfig.js +6 -0
  43. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  44. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783226868418-AddEnterpriseLicenseInstance.js +22 -0
  45. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783226868418-AddEnterpriseLicenseInstance.js.map +1 -0
  46. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  47. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  48. package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js +9 -0
  49. package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js.map +1 -0
  50. package/build/dist/Server/Services/Index.js +2 -0
  51. package/build/dist/Server/Services/Index.js.map +1 -1
  52. package/build/dist/Server/Services/IoTDeviceService.js +66 -4
  53. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  54. package/build/dist/Server/Services/StatusPageService.js +91 -43
  55. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  56. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +4 -5
  57. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  58. package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js +6 -5
  59. package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js.map +1 -1
  60. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -9
  61. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  62. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +33 -2
  63. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
  64. package/build/dist/ServiceRoute.js +1 -0
  65. package/build/dist/ServiceRoute.js.map +1 -1
  66. package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.js +2 -0
  67. package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.js.map +1 -0
  68. package/build/dist/Types/Monitor/MonitorStep.js +25 -0
  69. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  70. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +92 -0
  71. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  72. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +83 -0
  73. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -0
  74. package/package.json +1 -1
@@ -1,9 +1,18 @@
1
1
  import EnterpriseLicense from "../../Models/DatabaseModels/EnterpriseLicense";
2
+ import EnterpriseLicenseInstance from "../../Models/DatabaseModels/EnterpriseLicenseInstance";
2
3
  import BadDataException from "../../Types/Exception/BadDataException";
3
4
  import { JSONObject } from "../../Types/JSON";
5
+ import PartialEntity from "../../Types/Database/PartialEntity";
6
+ import EnterpriseLicenseInstanceSummary from "../../Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary";
7
+ import EnterpriseLicenseUsageUtil from "../../Utils/EnterpriseLicense/EnterpriseLicenseUsage";
8
+ import LIMIT_MAX from "../../Types/Database/LimitMax";
9
+ import ObjectID from "../../Types/ObjectID";
10
+ import PositiveNumber from "../../Types/PositiveNumber";
11
+ import SortOrder from "../../Types/BaseDatabase/SortOrder";
4
12
  import EnterpriseLicenseService, {
5
13
  Service as EnterpriseLicenseServiceType,
6
14
  } from "../Services/EnterpriseLicenseService";
15
+ import EnterpriseLicenseInstanceService from "../Services/EnterpriseLicenseInstanceService";
7
16
  import UserMiddleware from "../Middleware/UserAuthorization";
8
17
  import JSONWebToken from "../Utils/JsonWebToken";
9
18
  import OneUptimeDate from "../../Types/Date";
@@ -16,6 +25,23 @@ import {
16
25
  import BaseAPI from "./BaseAPI";
17
26
  // import { Host } from "../EnvironmentConfig";
18
27
 
28
+ export interface LicenseInstanceUpsert {
29
+ licenseId: ObjectID;
30
+ instanceId: string;
31
+ host: string | undefined;
32
+ // Usage fields are only set on report-user-count, not on validate.
33
+ userCount?: number | undefined;
34
+ userEmailHashes?: Array<string> | undefined;
35
+ lastReportedAt?: Date | undefined;
36
+ }
37
+
38
+ /*
39
+ * Bounds how many instance rows one license key can register. Real customers
40
+ * run a handful of instances; this stops a leaked key from being used to
41
+ * fill the table with junk rows.
42
+ */
43
+ const MAX_INSTANCES_PER_LICENSE: number = 100;
44
+
19
45
  export default class EnterpriseLicenseAPI extends BaseAPI<
20
46
  EnterpriseLicense,
21
47
  EnterpriseLicenseServiceType
@@ -50,6 +76,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
50
76
  licenseKey: licenseKey,
51
77
  },
52
78
  select: {
79
+ _id: true,
53
80
  companyName: true,
54
81
  expiresAt: true,
55
82
  licenseKey: true,
@@ -80,6 +107,27 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
80
107
  throw new BadDataException("License key has expired");
81
108
  }
82
109
 
110
+ /*
111
+ * The validating instance sends its instanceId and host so it
112
+ * shows up in the instance list right away (usage is reported
113
+ * later by the daily job on the instance).
114
+ */
115
+ const instanceId: string = this.parseShortText(
116
+ req.body["instanceId"],
117
+ );
118
+ const instanceHost: string = this.parseShortText(req.body["host"]);
119
+
120
+ if (instanceId) {
121
+ await this.upsertLicenseInstance({
122
+ licenseId: license.id!,
123
+ instanceId: instanceId,
124
+ host: instanceHost || undefined,
125
+ });
126
+ }
127
+
128
+ const instances: Array<EnterpriseLicenseInstance> =
129
+ await this.findLicenseInstances(license.id!);
130
+
83
131
  const payload: JSONObject = {
84
132
  companyName: license.companyName || "",
85
133
  expiresAt: license.expiresAt.toISOString(),
@@ -105,6 +153,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
105
153
  userCountUpdatedAt: license.userCountUpdatedAt
106
154
  ? license.userCountUpdatedAt.toISOString()
107
155
  : null,
156
+ instances: this.getInstanceSummaries(instances),
108
157
  token,
109
158
  });
110
159
  } catch (err) {
@@ -158,23 +207,67 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
158
207
 
159
208
  const reportedAt: Date = OneUptimeDate.getCurrentDate();
160
209
 
161
- await EnterpriseLicenseService.updateOneById({
162
- id: license.id!,
163
- data: {
164
- currentUserCount: userCount,
165
- userCountUpdatedAt: reportedAt,
166
- },
167
- props: {
168
- isRoot: true,
169
- ignoreHooks: true,
170
- },
171
- });
210
+ const instanceId: string = this.parseShortText(
211
+ req.body["instanceId"],
212
+ );
213
+ const instanceHost: string = this.parseShortText(req.body["host"]);
214
+ const userEmailHashes: Array<string> =
215
+ EnterpriseLicenseUsageUtil.sanitizeUserEmailHashes(
216
+ req.body["userEmailHashes"],
217
+ );
218
+
219
+ if (instanceId) {
220
+ /*
221
+ * Multi-instance report: track usage per instance and count
222
+ * users uniquely across all instances of this license.
223
+ */
224
+ await this.upsertLicenseInstance({
225
+ licenseId: license.id!,
226
+ instanceId: instanceId,
227
+ host: instanceHost || undefined,
228
+ userCount: userCount,
229
+ userEmailHashes: userEmailHashes,
230
+ lastReportedAt: reportedAt,
231
+ });
232
+ }
233
+
234
+ const instances: Array<EnterpriseLicenseInstance> =
235
+ await this.findLicenseInstances(license.id!);
236
+
237
+ let currentUserCount: number = userCount;
238
+
239
+ if (instances.length > 0) {
240
+ currentUserCount = EnterpriseLicenseUsageUtil.getUniqueUserCount(
241
+ instances,
242
+ reportedAt,
243
+ );
244
+ }
245
+
246
+ /*
247
+ * Legacy reports (no instanceId) only drive the license-wide count
248
+ * while no instance has registered — otherwise they would stomp
249
+ * the deduplicated multi-instance count.
250
+ */
251
+ if (instanceId || instances.length === 0) {
252
+ await EnterpriseLicenseService.updateOneById({
253
+ id: license.id!,
254
+ data: {
255
+ currentUserCount: currentUserCount,
256
+ userCountUpdatedAt: reportedAt,
257
+ },
258
+ props: {
259
+ isRoot: true,
260
+ ignoreHooks: true,
261
+ },
262
+ });
263
+ }
172
264
 
173
265
  return Response.sendJsonObjectResponse(req, res, {
174
- currentUserCount: userCount,
266
+ currentUserCount: currentUserCount,
175
267
  userCountUpdatedAt: reportedAt.toISOString(),
176
268
  userLimit:
177
269
  typeof license.userLimit === "number" ? license.userLimit : null,
270
+ instances: this.getInstanceSummaries(instances),
178
271
  });
179
272
  } catch (err) {
180
273
  next(err);
@@ -182,4 +275,178 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
182
275
  },
183
276
  );
184
277
  }
278
+
279
+ private parseShortText(value: unknown): string {
280
+ if (typeof value !== "string") {
281
+ return "";
282
+ }
283
+
284
+ return value.trim().substring(0, 100);
285
+ }
286
+
287
+ private async findLicenseInstances(
288
+ licenseId: ObjectID,
289
+ ): Promise<Array<EnterpriseLicenseInstance>> {
290
+ return EnterpriseLicenseInstanceService.findBy({
291
+ query: {
292
+ enterpriseLicenseId: licenseId,
293
+ },
294
+ select: {
295
+ _id: true,
296
+ instanceId: true,
297
+ host: true,
298
+ userCount: true,
299
+ userEmailHashes: true,
300
+ lastReportedAt: true,
301
+ },
302
+ sort: {
303
+ createdAt: SortOrder.Ascending,
304
+ },
305
+ skip: 0,
306
+ limit: LIMIT_MAX,
307
+ props: {
308
+ isRoot: true,
309
+ },
310
+ });
311
+ }
312
+
313
+ private getInstanceSummaries(
314
+ instances: Array<EnterpriseLicenseInstance>,
315
+ ): Array<EnterpriseLicenseInstanceSummary> {
316
+ return instances.map(
317
+ (
318
+ instance: EnterpriseLicenseInstance,
319
+ ): EnterpriseLicenseInstanceSummary => {
320
+ return {
321
+ instanceId: instance.instanceId || "",
322
+ host: instance.host || null,
323
+ userCount:
324
+ typeof instance.userCount === "number" ? instance.userCount : null,
325
+ lastReportedAt: instance.lastReportedAt
326
+ ? instance.lastReportedAt.toISOString()
327
+ : null,
328
+ };
329
+ },
330
+ );
331
+ }
332
+
333
+ private async upsertLicenseInstance(
334
+ data: LicenseInstanceUpsert,
335
+ ): Promise<void> {
336
+ const updated: boolean = await this.updateLicenseInstanceIfExists(data);
337
+
338
+ if (updated) {
339
+ return;
340
+ }
341
+
342
+ const instanceCount: PositiveNumber =
343
+ await EnterpriseLicenseInstanceService.countBy({
344
+ query: {
345
+ enterpriseLicenseId: data.licenseId,
346
+ },
347
+ props: {
348
+ isRoot: true,
349
+ },
350
+ });
351
+
352
+ if (instanceCount.toNumber() >= MAX_INSTANCES_PER_LICENSE) {
353
+ throw new BadDataException(
354
+ "Too many instances are registered for this license. Please contact support@oneuptime.com.",
355
+ );
356
+ }
357
+
358
+ const newInstance: EnterpriseLicenseInstance =
359
+ new EnterpriseLicenseInstance();
360
+ newInstance.enterpriseLicenseId = data.licenseId;
361
+ newInstance.instanceId = data.instanceId;
362
+
363
+ if (data.host !== undefined) {
364
+ newInstance.host = data.host;
365
+ }
366
+
367
+ if (data.userCount !== undefined) {
368
+ newInstance.userCount = data.userCount;
369
+ }
370
+
371
+ if (data.userEmailHashes !== undefined) {
372
+ newInstance.userEmailHashes = data.userEmailHashes;
373
+ }
374
+
375
+ if (data.lastReportedAt !== undefined) {
376
+ newInstance.lastReportedAt = data.lastReportedAt;
377
+ }
378
+
379
+ try {
380
+ await EnterpriseLicenseInstanceService.create({
381
+ data: newInstance,
382
+ props: {
383
+ isRoot: true,
384
+ },
385
+ });
386
+ } catch (err) {
387
+ /*
388
+ * A concurrent request created the row between our check and this
389
+ * insert — the unique index on (enterpriseLicenseId, instanceId)
390
+ * rejected the duplicate. Apply the report as an update instead.
391
+ */
392
+ const retried: boolean = await this.updateLicenseInstanceIfExists(data);
393
+
394
+ if (!retried) {
395
+ throw err;
396
+ }
397
+ }
398
+ }
399
+
400
+ private async updateLicenseInstanceIfExists(
401
+ data: LicenseInstanceUpsert,
402
+ ): Promise<boolean> {
403
+ const existingInstance: EnterpriseLicenseInstance | null =
404
+ await EnterpriseLicenseInstanceService.findOneBy({
405
+ query: {
406
+ enterpriseLicenseId: data.licenseId,
407
+ instanceId: data.instanceId,
408
+ },
409
+ select: {
410
+ _id: true,
411
+ },
412
+ props: {
413
+ isRoot: true,
414
+ },
415
+ });
416
+
417
+ if (!existingInstance) {
418
+ return false;
419
+ }
420
+
421
+ const updateData: PartialEntity<EnterpriseLicenseInstance> = {};
422
+
423
+ if (data.host !== undefined) {
424
+ updateData.host = data.host;
425
+ }
426
+
427
+ if (data.userCount !== undefined) {
428
+ updateData.userCount = data.userCount;
429
+ }
430
+
431
+ if (data.userEmailHashes !== undefined) {
432
+ updateData.userEmailHashes = data.userEmailHashes;
433
+ }
434
+
435
+ if (data.lastReportedAt !== undefined) {
436
+ updateData.lastReportedAt = data.lastReportedAt;
437
+ }
438
+
439
+ if (Object.keys(updateData).length > 0) {
440
+ await EnterpriseLicenseInstanceService.updateOneById({
441
+ id: existingInstance.id!,
442
+ data: updateData,
443
+ props: {
444
+ isRoot: true,
445
+ ignoreHooks: true,
446
+ },
447
+ });
448
+ }
449
+
450
+ return true;
451
+ }
185
452
  }
@@ -5,6 +5,7 @@ import {
5
5
  ExpressRequest,
6
6
  ExpressResponse,
7
7
  NextFunction,
8
+ OneUptimeRequest,
8
9
  } from "../Utils/Express";
9
10
  import Response from "../Utils/Response";
10
11
  import BaseAPI from "./BaseAPI";
@@ -16,7 +17,8 @@ import API from "../../Utils/API";
16
17
  import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse";
17
18
  import HTTPResponse from "../../Types/API/HTTPResponse";
18
19
  import PartialEntity from "../../Types/Database/PartialEntity";
19
- import { EnterpriseLicenseValidationUrl } from "../EnvironmentConfig";
20
+ import { EnterpriseLicenseValidationUrl, Host } from "../EnvironmentConfig";
21
+ import EnterpriseLicenseInstanceSummary from "../../Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary";
20
22
  import UserMiddleware from "../Middleware/UserAuthorization";
21
23
 
22
24
  export default class GlobalConfigAPI extends BaseAPI<
@@ -54,6 +56,7 @@ export default class GlobalConfigAPI extends BaseAPI<
54
56
 
55
57
  this.router.get(
56
58
  `${new this.entityType().getCrudApiPath()?.toString()}/license`,
59
+ UserMiddleware.getUserMiddleware,
57
60
  async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
58
61
  try {
59
62
  const config: GlobalConfig | null =
@@ -67,19 +70,42 @@ export default class GlobalConfigAPI extends BaseAPI<
67
70
  enterpriseLicenseUserLimit: true,
68
71
  enterpriseLicenseCurrentUserCount: true,
69
72
  enterpriseLicenseUserCountUpdatedAt: true,
73
+ enterpriseLicenseInstances: true,
74
+ instanceId: true,
70
75
  },
71
76
  props: {
72
77
  isRoot: true,
73
78
  },
74
79
  });
75
80
 
81
+ /*
82
+ * This route also serves the login page (before sign-in), so it
83
+ * cannot require authentication outright. Anonymous callers only
84
+ * get enough to render the edition pill — the license key, token
85
+ * and instance topology require a signed-in user.
86
+ */
87
+ const isAuthenticatedUser: boolean = Boolean(
88
+ (req as OneUptimeRequest).userAuthorization?.userId,
89
+ );
90
+
91
+ const licenseValid: boolean = Boolean(
92
+ config?.enterpriseLicenseToken &&
93
+ config?.enterpriseLicenseExpiresAt &&
94
+ config.enterpriseLicenseExpiresAt.getTime() > Date.now(),
95
+ );
96
+
76
97
  const responseBody: JSONObject = {
77
98
  companyName: config?.enterpriseCompanyName || null,
78
99
  expiresAt: config?.enterpriseLicenseExpiresAt
79
100
  ? config.enterpriseLicenseExpiresAt.toISOString()
80
101
  : null,
81
- licenseKey: config?.enterpriseLicenseKey || null,
82
- token: config?.enterpriseLicenseToken || null,
102
+ licenseKey: isAuthenticatedUser
103
+ ? config?.enterpriseLicenseKey || null
104
+ : null,
105
+ token: isAuthenticatedUser
106
+ ? config?.enterpriseLicenseToken || null
107
+ : null,
108
+ licenseValid: licenseValid,
83
109
  userLimit:
84
110
  typeof config?.enterpriseLicenseUserLimit === "number"
85
111
  ? config.enterpriseLicenseUserLimit
@@ -91,6 +117,15 @@ export default class GlobalConfigAPI extends BaseAPI<
91
117
  userCountUpdatedAt: config?.enterpriseLicenseUserCountUpdatedAt
92
118
  ? config.enterpriseLicenseUserCountUpdatedAt.toISOString()
93
119
  : null,
120
+ instances:
121
+ isAuthenticatedUser &&
122
+ Array.isArray(config?.enterpriseLicenseInstances)
123
+ ? config.enterpriseLicenseInstances
124
+ : [],
125
+ instanceId:
126
+ isAuthenticatedUser && config?.instanceId
127
+ ? config.instanceId.toString()
128
+ : null,
94
129
  };
95
130
 
96
131
  return Response.sendJsonObjectResponse(req, res, responseBody);
@@ -112,12 +147,38 @@ export default class GlobalConfigAPI extends BaseAPI<
112
147
  throw new BadDataException("License key is required");
113
148
  }
114
149
 
150
+ const globalConfigId: ObjectID = ObjectID.getZeroObjectID();
151
+
152
+ const existingConfig: GlobalConfig | null =
153
+ await GlobalConfigService.findOneById({
154
+ id: globalConfigId,
155
+ select: {
156
+ _id: true,
157
+ instanceId: true,
158
+ },
159
+ props: {
160
+ isRoot: true,
161
+ ignoreHooks: true,
162
+ },
163
+ });
164
+
165
+ /*
166
+ * Send this instance's id and host along with the key so the
167
+ * license server registers this instance against the license and
168
+ * it shows up in the instance list on all instances that share
169
+ * the license.
170
+ */
171
+ const instanceId: ObjectID =
172
+ existingConfig?.instanceId || ObjectID.generate();
173
+
115
174
  const validationResponse:
116
175
  | HTTPResponse<JSONObject>
117
176
  | HTTPErrorResponse = await API.post<JSONObject>({
118
177
  url: EnterpriseLicenseValidationUrl,
119
178
  data: {
120
179
  licenseKey,
180
+ instanceId: instanceId.toString(),
181
+ host: Host,
121
182
  },
122
183
  });
123
184
 
@@ -178,6 +239,13 @@ export default class GlobalConfigAPI extends BaseAPI<
178
239
  }
179
240
  }
180
241
 
242
+ const instances: Array<EnterpriseLicenseInstanceSummary> =
243
+ Array.isArray(payload["instances"])
244
+ ? (payload[
245
+ "instances"
246
+ ] as Array<EnterpriseLicenseInstanceSummary>)
247
+ : [];
248
+
181
249
  const updatePayload: PartialEntity<GlobalConfig> = {
182
250
  enterpriseCompanyName: companyNameRaw || null,
183
251
  enterpriseLicenseKey: licenseKeyRaw || null,
@@ -186,21 +254,13 @@ export default class GlobalConfigAPI extends BaseAPI<
186
254
  enterpriseLicenseUserLimit: userLimit,
187
255
  enterpriseLicenseCurrentUserCount: currentUserCount,
188
256
  enterpriseLicenseUserCountUpdatedAt: userCountUpdatedAt,
257
+ enterpriseLicenseInstances: instances,
189
258
  };
190
259
 
191
- const globalConfigId: ObjectID = ObjectID.getZeroObjectID();
192
-
193
- const existingConfig: GlobalConfig | null =
194
- await GlobalConfigService.findOneById({
195
- id: globalConfigId,
196
- select: {
197
- _id: true,
198
- },
199
- props: {
200
- isRoot: true,
201
- ignoreHooks: true,
202
- },
203
- });
260
+ if (!existingConfig?.instanceId) {
261
+ // Installs that predate instance ids: persist the one we generated.
262
+ updatePayload.instanceId = instanceId;
263
+ }
204
264
 
205
265
  if (existingConfig) {
206
266
  await GlobalConfigService.updateOneById({
@@ -244,6 +304,9 @@ export default class GlobalConfigAPI extends BaseAPI<
244
304
  userCountUpdatedAt;
245
305
  }
246
306
 
307
+ newConfig.enterpriseLicenseInstances = instances;
308
+ newConfig.instanceId = instanceId;
309
+
247
310
  await GlobalConfigService.create({
248
311
  data: newConfig,
249
312
  props: {
@@ -263,6 +326,8 @@ export default class GlobalConfigAPI extends BaseAPI<
263
326
  userCountUpdatedAt: userCountUpdatedAt
264
327
  ? userCountUpdatedAt.toISOString()
265
328
  : null,
329
+ instances: instances,
330
+ instanceId: instanceId.toString(),
266
331
  });
267
332
  } catch (err) {
268
333
  next(err);