@oneuptime/common 9.5.6 → 9.5.8

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 (105) hide show
  1. package/Models/DatabaseModels/AlertEpisode.ts +25 -0
  2. package/Models/DatabaseModels/AlertGroupingRule.ts +2 -2
  3. package/Models/DatabaseModels/IncidentEpisode.ts +25 -0
  4. package/Models/DatabaseModels/IncidentGroupingRule.ts +2 -2
  5. package/Models/DatabaseModels/Index.ts +2 -0
  6. package/Models/DatabaseModels/OpenSourceDeployment.ts +140 -0
  7. package/Models/DatabaseModels/UserPush.ts +2 -1
  8. package/Server/API/OpenSourceDeploymentAPI.ts +73 -0
  9. package/Server/API/UserPushAPI.ts +51 -4
  10. package/Server/EnvironmentConfig.ts +3 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1770237245070-MigrationName.ts +7 -7
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1770668054908-MigrationName.ts +26 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1770728946893-MigrationName.ts +47 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1770732721195-MigrationName.ts +27 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  16. package/Server/Middleware/UserAuthorization.ts +14 -9
  17. package/Server/Services/AlertEpisodeMemberService.ts +10 -20
  18. package/Server/Services/AlertEpisodeService.ts +3 -2
  19. package/Server/Services/AlertGroupingEngineService.ts +1 -0
  20. package/Server/Services/IncidentEpisodeMemberService.ts +12 -20
  21. package/Server/Services/IncidentEpisodeService.ts +65 -3
  22. package/Server/Services/IncidentGroupingEngineService.ts +1 -0
  23. package/Server/Services/Index.ts +2 -0
  24. package/Server/Services/OpenSourceDeploymentService.ts +10 -0
  25. package/Server/Services/PushNotificationService.ts +129 -27
  26. package/Server/Services/UserNotificationRuleService.ts +13 -3
  27. package/Server/Services/UserPushService.ts +2 -1
  28. package/Server/Utils/PushNotificationUtil.ts +56 -0
  29. package/Types/PushNotification/PushDeviceType.ts +7 -0
  30. package/Types/PushNotification/PushNotificationRequest.ts +3 -1
  31. package/UI/Components/Forms/BasicForm.tsx +8 -1
  32. package/UI/Components/Forms/Types/Field.ts +3 -0
  33. package/UI/Components/ModelDelete/ModelDelete.tsx +4 -1
  34. package/UI/Components/ModelDetail/CardModelDetail.tsx +4 -0
  35. package/UI/Components/ModelDetail/ModelDetail.tsx +4 -1
  36. package/UI/Components/Page/ModelPage.tsx +4 -1
  37. package/build/dist/Models/DatabaseModels/AlertEpisode.js +26 -0
  38. package/build/dist/Models/DatabaseModels/AlertEpisode.js.map +1 -1
  39. package/build/dist/Models/DatabaseModels/AlertGroupingRule.js +2 -2
  40. package/build/dist/Models/DatabaseModels/AlertGroupingRule.js.map +1 -1
  41. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +26 -0
  42. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  43. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js +2 -2
  44. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js.map +1 -1
  45. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  46. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  47. package/build/dist/Models/DatabaseModels/OpenSourceDeployment.js +167 -0
  48. package/build/dist/Models/DatabaseModels/OpenSourceDeployment.js.map +1 -0
  49. package/build/dist/Models/DatabaseModels/UserPush.js +2 -1
  50. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  51. package/build/dist/Server/API/OpenSourceDeploymentAPI.js +55 -0
  52. package/build/dist/Server/API/OpenSourceDeploymentAPI.js.map +1 -0
  53. package/build/dist/Server/API/UserPushAPI.js +34 -3
  54. package/build/dist/Server/API/UserPushAPI.js.map +1 -1
  55. package/build/dist/Server/EnvironmentConfig.js +1 -0
  56. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  57. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770237245070-MigrationName.js +7 -7
  58. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770237245070-MigrationName.js.map +1 -1
  59. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770668054908-MigrationName.js +36 -0
  60. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770668054908-MigrationName.js.map +1 -0
  61. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770728946893-MigrationName.js +22 -0
  62. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770728946893-MigrationName.js.map +1 -0
  63. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770732721195-MigrationName.js +16 -0
  64. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770732721195-MigrationName.js.map +1 -0
  65. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  66. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  67. package/build/dist/Server/Middleware/UserAuthorization.js +10 -4
  68. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  69. package/build/dist/Server/Services/AlertEpisodeMemberService.js +7 -13
  70. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
  71. package/build/dist/Server/Services/AlertEpisodeService.js +3 -2
  72. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  73. package/build/dist/Server/Services/AlertGroupingEngineService.js +1 -0
  74. package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -1
  75. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +7 -13
  76. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  77. package/build/dist/Server/Services/IncidentEpisodeService.js +43 -3
  78. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  79. package/build/dist/Server/Services/IncidentGroupingEngineService.js +1 -0
  80. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  81. package/build/dist/Server/Services/Index.js +2 -0
  82. package/build/dist/Server/Services/Index.js.map +1 -1
  83. package/build/dist/Server/Services/OpenSourceDeploymentService.js +9 -0
  84. package/build/dist/Server/Services/OpenSourceDeploymentService.js.map +1 -0
  85. package/build/dist/Server/Services/PushNotificationService.js +77 -21
  86. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  87. package/build/dist/Server/Services/UserNotificationRuleService.js +12 -9
  88. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  89. package/build/dist/Server/Services/UserPushService.js +2 -1
  90. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  91. package/build/dist/Server/Utils/PushNotificationUtil.js +32 -8
  92. package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
  93. package/build/dist/Types/PushNotification/PushDeviceType.js +8 -0
  94. package/build/dist/Types/PushNotification/PushDeviceType.js.map +1 -0
  95. package/build/dist/UI/Components/Forms/BasicForm.js +3 -1
  96. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  97. package/build/dist/UI/Components/ModelDelete/ModelDelete.js +2 -1
  98. package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
  99. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
  100. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  101. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +2 -1
  102. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  103. package/build/dist/UI/Components/Page/ModelPage.js +2 -1
  104. package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
  105. package/package.json +2 -1
@@ -94,26 +94,16 @@ export class Service extends DatabaseService<Model> {
94
94
  });
95
95
 
96
96
  // Update episode's alertCount and lastAlertAddedAt
97
- Promise.resolve()
98
- .then(async () => {
99
- try {
100
- await AlertEpisodeService.updateAlertCount(
101
- createdItem.alertEpisodeId!,
102
- );
103
- await AlertEpisodeService.updateLastAlertAddedAt(
104
- createdItem.alertEpisodeId!,
105
- );
106
- } catch (error) {
107
- logger.error(
108
- `Error updating episode counts in AlertEpisodeMemberService.onCreateSuccess: ${error}`,
109
- );
110
- }
111
- })
112
- .catch((error: Error) => {
113
- logger.error(
114
- `Critical error in AlertEpisodeMemberService.onCreateSuccess: ${error}`,
115
- );
116
- });
97
+ try {
98
+ await AlertEpisodeService.updateAlertCount(createdItem.alertEpisodeId!);
99
+ await AlertEpisodeService.updateLastAlertAddedAt(
100
+ createdItem.alertEpisodeId!,
101
+ );
102
+ } catch (error) {
103
+ logger.error(
104
+ `Error updating episode counts in AlertEpisodeMemberService.onCreateSuccess: ${error}`,
105
+ );
106
+ }
117
107
 
118
108
  // Get alert details for feed
119
109
  const alert: Alert | null = await AlertService.findOneById({
@@ -815,11 +815,12 @@ export class Service extends DatabaseService<Model> {
815
815
  },
816
816
  });
817
817
 
818
- // Clear resolved timestamp
818
+ // Clear resolved timestamp and allAlertsResolvedAt
819
819
  await this.updateOneById({
820
820
  id: episodeId,
821
821
  data: {
822
- resolvedAt: undefined as any,
822
+ resolvedAt: null,
823
+ allAlertsResolvedAt: null,
823
824
  },
824
825
  props: {
825
826
  isRoot: true,
@@ -688,6 +688,7 @@ class AlertGroupingEngineServiceClass {
688
688
  newEpisode.alertGroupingRuleId = rule.id!;
689
689
  newEpisode.groupingKey = groupingKey;
690
690
  newEpisode.isManuallyCreated = false;
691
+ newEpisode.lastAlertAddedAt = OneUptimeDate.getCurrentDate();
691
692
 
692
693
  // Set severity from alert
693
694
  if (alert.alertSeverityId) {
@@ -96,26 +96,18 @@ export class Service extends DatabaseService<Model> {
96
96
  });
97
97
 
98
98
  // Update episode's incidentCount and lastIncidentAddedAt
99
- Promise.resolve()
100
- .then(async () => {
101
- try {
102
- await IncidentEpisodeService.updateIncidentCount(
103
- createdItem.incidentEpisodeId!,
104
- );
105
- await IncidentEpisodeService.updateLastIncidentAddedAt(
106
- createdItem.incidentEpisodeId!,
107
- );
108
- } catch (error) {
109
- logger.error(
110
- `Error updating episode counts in IncidentEpisodeMemberService.onCreateSuccess: ${error}`,
111
- );
112
- }
113
- })
114
- .catch((error: Error) => {
115
- logger.error(
116
- `Critical error in IncidentEpisodeMemberService.onCreateSuccess: ${error}`,
117
- );
118
- });
99
+ try {
100
+ await IncidentEpisodeService.updateIncidentCount(
101
+ createdItem.incidentEpisodeId!,
102
+ );
103
+ await IncidentEpisodeService.updateLastIncidentAddedAt(
104
+ createdItem.incidentEpisodeId!,
105
+ );
106
+ } catch (error) {
107
+ logger.error(
108
+ `Error updating episode counts in IncidentEpisodeMemberService.onCreateSuccess: ${error}`,
109
+ );
110
+ }
119
111
 
120
112
  // Get incident details for feed
121
113
  const incident: Incident | null = await IncidentService.findOneById({
@@ -609,6 +609,18 @@ export class Service extends DatabaseService<Model> {
609
609
  },
610
610
  cascadeToIncidents: cascadeToIncidents,
611
611
  });
612
+
613
+ // Clear resolved timestamp and allIncidentsResolvedAt when episode is reopened
614
+ await this.updateOneById({
615
+ id: episodeId,
616
+ data: {
617
+ resolvedAt: null,
618
+ allIncidentsResolvedAt: null,
619
+ },
620
+ props: {
621
+ isRoot: true,
622
+ },
623
+ });
612
624
  }
613
625
 
614
626
  @CaptureSpan()
@@ -712,15 +724,65 @@ export class Service extends DatabaseService<Model> {
712
724
  },
713
725
  });
714
726
 
715
- await this.updateOneById({
727
+ const incidentCount: number = count.toNumber();
728
+
729
+ // Get the episode to check for templates
730
+ const episode: Model | null = await this.findOneById({
716
731
  id: episodeId,
717
- data: {
718
- incidentCount: count.toNumber(),
732
+ select: {
733
+ titleTemplate: true,
734
+ descriptionTemplate: true,
735
+ title: true,
736
+ description: true,
719
737
  },
720
738
  props: {
721
739
  isRoot: true,
722
740
  },
723
741
  });
742
+
743
+ const updateData: {
744
+ incidentCount: number;
745
+ title?: string;
746
+ description?: string;
747
+ } = {
748
+ incidentCount: incidentCount,
749
+ };
750
+
751
+ // Update title with dynamic variables if template exists
752
+ if (episode?.titleTemplate) {
753
+ updateData.title = this.renderTemplateWithDynamicValues(
754
+ episode.titleTemplate,
755
+ incidentCount,
756
+ );
757
+ }
758
+
759
+ // Update description with dynamic variables if template exists
760
+ if (episode?.descriptionTemplate) {
761
+ updateData.description = this.renderTemplateWithDynamicValues(
762
+ episode.descriptionTemplate,
763
+ incidentCount,
764
+ );
765
+ }
766
+
767
+ await this.updateOneById({
768
+ id: episodeId,
769
+ data: updateData,
770
+ props: {
771
+ isRoot: true,
772
+ },
773
+ });
774
+ }
775
+
776
+ private renderTemplateWithDynamicValues(
777
+ template: string,
778
+ incidentCount: number,
779
+ ): string {
780
+ let result: string = template;
781
+
782
+ // Replace dynamic variables
783
+ result = result.replace(/\{\{incidentCount\}\}/g, incidentCount.toString());
784
+
785
+ return result;
724
786
  }
725
787
 
726
788
  @CaptureSpan()
@@ -757,6 +757,7 @@ class IncidentGroupingEngineServiceClass {
757
757
  newEpisode.incidentGroupingRuleId = rule.id!;
758
758
  newEpisode.groupingKey = groupingKey;
759
759
  newEpisode.isManuallyCreated = false;
760
+ newEpisode.lastIncidentAddedAt = OneUptimeDate.getCurrentDate();
760
761
 
761
762
  // Set severity from incident
762
763
  if (incident.incidentSeverityId) {
@@ -79,6 +79,7 @@ import ProjectSmtpConfigService from "./ProjectSmtpConfigService";
79
79
  import ProjectSsoService from "./ProjectSsoService";
80
80
  import PromoCodeService from "./PromoCodeService";
81
81
  import EnterpriseLicenseService from "./EnterpriseLicenseService";
82
+ import OpenSourceDeploymentService from "./OpenSourceDeploymentService";
82
83
  import ResellerPlanService from "./ResellerPlanService";
83
84
  import ResellerService from "./ResellerService";
84
85
  import ScheduledMaintenanceCustomFieldService from "./ScheduledMaintenanceCustomFieldService";
@@ -210,6 +211,7 @@ const services: Array<BaseService> = [
210
211
  AcmeCertificateService,
211
212
  PromoCodeService,
212
213
  EnterpriseLicenseService,
214
+ OpenSourceDeploymentService,
213
215
 
214
216
  ResellerService,
215
217
  ResellerPlanService,
@@ -0,0 +1,10 @@
1
+ import DatabaseService from "./DatabaseService";
2
+ import OpenSourceDeployment from "../../Models/DatabaseModels/OpenSourceDeployment";
3
+
4
+ export class Service extends DatabaseService<OpenSourceDeployment> {
5
+ public constructor() {
6
+ super(OpenSourceDeployment);
7
+ }
8
+ }
9
+
10
+ export default new Service();
@@ -1,5 +1,6 @@
1
1
  import PushNotificationRequest from "../../Types/PushNotification/PushNotificationRequest";
2
2
  import PushNotificationMessage from "../../Types/PushNotification/PushNotificationMessage";
3
+ import PushDeviceType from "../../Types/PushNotification/PushDeviceType";
3
4
  import ObjectID from "../../Types/ObjectID";
4
5
  import logger from "../Utils/Logger";
5
6
  import UserPushService from "./UserPushService";
@@ -11,6 +12,7 @@ import {
11
12
  VapidSubject,
12
13
  } from "../EnvironmentConfig";
13
14
  import webpush from "web-push";
15
+ import { Expo, ExpoPushMessage, ExpoPushTicket } from "expo-server-sdk";
14
16
  import PushNotificationUtil from "../Utils/PushNotificationUtil";
15
17
  import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
16
18
  import UserPush from "../../Models/DatabaseModels/UserPush";
@@ -41,6 +43,7 @@ export interface PushNotificationOptions {
41
43
 
42
44
  export default class PushNotificationService {
43
45
  public static isWebPushInitialized = false;
46
+ private static expoClient: Expo = new Expo();
44
47
 
45
48
  public static initializeWebPush(): void {
46
49
  if (this.isWebPushInitialized) {
@@ -76,13 +79,8 @@ export default class PushNotificationService {
76
79
  throw new Error("No devices provided");
77
80
  }
78
81
 
79
- if (request.deviceType !== "web") {
80
- logger.error(`Unsupported device type: ${request.deviceType}`);
81
- throw new Error("Only web push notifications are supported");
82
- }
83
-
84
82
  logger.info(
85
- `Sending web push notifications to ${request.devices.length} devices`,
83
+ `Sending ${request.deviceType} push notifications to ${request.devices.length} devices`,
86
84
  );
87
85
  logger.info(`Notification message: ${JSON.stringify(request.message)}`);
88
86
 
@@ -98,9 +96,25 @@ export default class PushNotificationService {
98
96
  const promises: Promise<void>[] = [];
99
97
 
100
98
  for (const device of request.devices) {
101
- promises.push(
102
- this.sendWebPushNotification(device.token, request.message, options),
103
- );
99
+ if (request.deviceType === PushDeviceType.Web) {
100
+ promises.push(
101
+ this.sendWebPushNotification(device.token, request.message, options),
102
+ );
103
+ } else if (
104
+ request.deviceType === PushDeviceType.iOS ||
105
+ request.deviceType === PushDeviceType.Android
106
+ ) {
107
+ promises.push(
108
+ this.sendExpoPushNotification(
109
+ device.token,
110
+ request.message,
111
+ request.deviceType,
112
+ options,
113
+ ),
114
+ );
115
+ } else {
116
+ logger.error(`Unsupported device type: ${request.deviceType}`);
117
+ }
104
118
  }
105
119
 
106
120
  const results: Array<any> = await Promise.allSettled(promises);
@@ -314,6 +328,81 @@ export default class PushNotificationService {
314
328
  }
315
329
  }
316
330
 
331
+ private static async sendExpoPushNotification(
332
+ expoPushToken: string,
333
+ message: PushNotificationMessage,
334
+ deviceType: PushDeviceType,
335
+ _options: PushNotificationOptions,
336
+ ): Promise<void> {
337
+ if (!Expo.isExpoPushToken(expoPushToken)) {
338
+ throw new Error(
339
+ `Invalid Expo push token for ${deviceType} device: ${expoPushToken}`,
340
+ );
341
+ }
342
+
343
+ try {
344
+ const dataPayload: { [key: string]: string } = {};
345
+ if (message.data) {
346
+ for (const key of Object.keys(message.data)) {
347
+ dataPayload[key] = String(message.data[key]);
348
+ }
349
+ }
350
+ if (message.url || message.clickAction) {
351
+ dataPayload["url"] = message.url || message.clickAction || "";
352
+ }
353
+
354
+ const channelId: string =
355
+ deviceType === PushDeviceType.Android ? "oncall_high" : "default";
356
+
357
+ const expoPushMessage: ExpoPushMessage = {
358
+ to: expoPushToken,
359
+ title: message.title,
360
+ body: message.body,
361
+ data: dataPayload,
362
+ sound: "default",
363
+ priority: "high",
364
+ channelId: channelId,
365
+ };
366
+
367
+ const tickets: ExpoPushTicket[] =
368
+ await this.expoClient.sendPushNotificationsAsync([expoPushMessage]);
369
+
370
+ const ticket: ExpoPushTicket | undefined = tickets[0];
371
+
372
+ if (ticket && ticket.status === "error") {
373
+ const errorTicket: ExpoPushTicket & {
374
+ message?: string;
375
+ details?: { error?: string };
376
+ } = ticket as ExpoPushTicket & {
377
+ message?: string;
378
+ details?: { error?: string };
379
+ };
380
+ logger.error(
381
+ `Expo push notification error for ${deviceType} device: ${errorTicket.message}`,
382
+ );
383
+
384
+ if (errorTicket.details?.error === "DeviceNotRegistered") {
385
+ logger.info(
386
+ "Expo push token is no longer valid (DeviceNotRegistered)",
387
+ );
388
+ }
389
+
390
+ throw new Error(
391
+ `Expo push notification failed: ${errorTicket.message}`,
392
+ );
393
+ }
394
+
395
+ logger.info(
396
+ `Expo push notification sent successfully to ${deviceType} device`,
397
+ );
398
+ } catch (error: any) {
399
+ logger.error(
400
+ `Failed to send Expo push notification to ${deviceType} device: ${error.message}`,
401
+ );
402
+ throw error;
403
+ }
404
+ }
405
+
317
406
  public static async sendPushNotificationToUser(
318
407
  userId: ObjectID,
319
408
  projectId: ObjectID,
@@ -342,33 +431,46 @@ export default class PushNotificationService {
342
431
 
343
432
  if (userPushDevices.length === 0) {
344
433
  logger.info(
345
- `No verified web push devices found for user ${userId.toString()}`,
434
+ `No verified push devices found for user ${userId.toString()}`,
346
435
  );
347
436
  return;
348
437
  }
349
438
 
350
- // Get web devices with tokens and names
351
- const webDevices: Array<{ token: string; name?: string }> = [];
439
+ // Group devices by type
440
+ const devicesByType: Map<
441
+ string,
442
+ Array<{ token: string; name?: string }>
443
+ > = new Map();
352
444
 
353
445
  for (const device of userPushDevices) {
354
- if (device.deviceType === "web") {
355
- webDevices.push({
356
- token: device.deviceToken!,
357
- name: device.deviceName || "Unknown Device",
358
- });
446
+ const type: string = device.deviceType || PushDeviceType.Web;
447
+ if (!devicesByType.has(type)) {
448
+ devicesByType.set(type, []);
359
449
  }
450
+ devicesByType.get(type)!.push({
451
+ token: device.deviceToken!,
452
+ name: device.deviceName || "Unknown Device",
453
+ });
360
454
  }
361
455
 
362
- // Send notifications to web devices
363
- if (webDevices.length > 0) {
364
- await this.sendPushNotification(
365
- {
366
- devices: webDevices,
367
- message: message,
368
- deviceType: "web",
369
- },
370
- options,
371
- );
456
+ // Send notifications to each device type group
457
+ const sendPromises: Promise<void>[] = [];
458
+
459
+ for (const [deviceType, devices] of devicesByType.entries()) {
460
+ if (devices.length > 0) {
461
+ sendPromises.push(
462
+ this.sendPushNotification(
463
+ {
464
+ devices: devices,
465
+ message: message,
466
+ deviceType: deviceType as PushDeviceType,
467
+ },
468
+ options,
469
+ ),
470
+ );
471
+ }
372
472
  }
473
+
474
+ await Promise.allSettled(sendPromises);
373
475
  }
374
476
  }
@@ -29,6 +29,7 @@ import EmailTemplateType from "../../Types/Email/EmailTemplateType";
29
29
  import BadDataException from "../../Types/Exception/BadDataException";
30
30
  import NotificationRuleType from "../../Types/NotificationRule/NotificationRuleType";
31
31
  import ObjectID from "../../Types/ObjectID";
32
+ import PushDeviceType from "../../Types/PushNotification/PushDeviceType";
32
33
  import Phone from "../../Types/Phone";
33
34
  import SMS from "../../Types/SMS/SMS";
34
35
  import WhatsAppMessage from "../../Types/WhatsApp/WhatsAppMessage";
@@ -1101,6 +1102,8 @@ export class Service extends DatabaseService<Model> {
1101
1102
  ...(alert.alertNumber !== undefined && {
1102
1103
  alertNumber: alert.alertNumber,
1103
1104
  }),
1105
+ alertId: alert.id!.toString(),
1106
+ projectId: alert.projectId!.toString(),
1104
1107
  });
1105
1108
 
1106
1109
  // send push notification.
@@ -1115,7 +1118,8 @@ export class Service extends DatabaseService<Model> {
1115
1118
  },
1116
1119
  ],
1117
1120
  message: pushMessage,
1118
- deviceType: notificationRuleItem.userPush.deviceType!,
1121
+ deviceType: notificationRuleItem.userPush
1122
+ .deviceType! as PushDeviceType,
1119
1123
  },
1120
1124
  {
1121
1125
  projectId: options.projectId,
@@ -1175,6 +1179,8 @@ export class Service extends DatabaseService<Model> {
1175
1179
  ...(incident.incidentNumber !== undefined && {
1176
1180
  incidentNumber: incident.incidentNumber,
1177
1181
  }),
1182
+ incidentId: incident.id!.toString(),
1183
+ projectId: incident.projectId!.toString(),
1178
1184
  });
1179
1185
 
1180
1186
  // send push notification.
@@ -1189,7 +1195,8 @@ export class Service extends DatabaseService<Model> {
1189
1195
  },
1190
1196
  ],
1191
1197
  message: pushMessage,
1192
- deviceType: notificationRuleItem.userPush.deviceType!,
1198
+ deviceType: notificationRuleItem.userPush
1199
+ .deviceType! as PushDeviceType,
1193
1200
  },
1194
1201
  {
1195
1202
  projectId: options.projectId,
@@ -1251,6 +1258,8 @@ export class Service extends DatabaseService<Model> {
1251
1258
  ...(alertEpisode.episodeNumberWithPrefix && {
1252
1259
  episodeNumberWithPrefix: alertEpisode.episodeNumberWithPrefix,
1253
1260
  }),
1261
+ alertEpisodeId: alertEpisode.id!.toString(),
1262
+ projectId: alertEpisode.projectId!.toString(),
1254
1263
  });
1255
1264
 
1256
1265
  PushNotificationService.sendPushNotification(
@@ -1264,7 +1273,8 @@ export class Service extends DatabaseService<Model> {
1264
1273
  },
1265
1274
  ],
1266
1275
  message: pushMessage,
1267
- deviceType: notificationRuleItem.userPush.deviceType!,
1276
+ deviceType: notificationRuleItem.userPush
1277
+ .deviceType! as PushDeviceType,
1268
1278
  },
1269
1279
  {
1270
1280
  projectId: options.projectId,
@@ -4,6 +4,7 @@ import { OnCreate, OnDelete } from "../Types/Database/Hooks";
4
4
  import DatabaseService from "./DatabaseService";
5
5
  import BadDataException from "../../Types/Exception/BadDataException";
6
6
  import PositiveNumber from "../../Types/PositiveNumber";
7
+ import PushDeviceType from "../../Types/PushNotification/PushDeviceType";
7
8
  import UserPush from "../../Models/DatabaseModels/UserPush";
8
9
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
9
10
 
@@ -25,7 +26,7 @@ export class Service extends DatabaseService<UserPush> {
25
26
  }
26
27
 
27
28
  // Validate device type
28
- const validDeviceTypes: string[] = ["web", "android", "ios"];
29
+ const validDeviceTypes: string[] = Object.values(PushDeviceType);
29
30
  if (!validDeviceTypes.includes(createBy.data.deviceType)) {
30
31
  throw new BadDataException(
31
32
  "Device type must be one of: " + validDeviceTypes.join(", "),