@oneuptime/common 9.5.7 → 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 (38) hide show
  1. package/Models/DatabaseModels/UserPush.ts +2 -1
  2. package/Server/API/UserPushAPI.ts +51 -4
  3. package/Server/Middleware/UserAuthorization.ts +14 -9
  4. package/Server/Services/PushNotificationService.ts +129 -27
  5. package/Server/Services/UserNotificationRuleService.ts +13 -3
  6. package/Server/Services/UserPushService.ts +2 -1
  7. package/Server/Utils/PushNotificationUtil.ts +56 -0
  8. package/Types/PushNotification/PushDeviceType.ts +7 -0
  9. package/Types/PushNotification/PushNotificationRequest.ts +3 -1
  10. package/UI/Components/ModelDelete/ModelDelete.tsx +4 -1
  11. package/UI/Components/ModelDetail/CardModelDetail.tsx +4 -0
  12. package/UI/Components/ModelDetail/ModelDetail.tsx +4 -1
  13. package/UI/Components/Page/ModelPage.tsx +4 -1
  14. package/build/dist/Models/DatabaseModels/UserPush.js +2 -1
  15. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  16. package/build/dist/Server/API/UserPushAPI.js +34 -3
  17. package/build/dist/Server/API/UserPushAPI.js.map +1 -1
  18. package/build/dist/Server/Middleware/UserAuthorization.js +10 -4
  19. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  20. package/build/dist/Server/Services/PushNotificationService.js +77 -21
  21. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  22. package/build/dist/Server/Services/UserNotificationRuleService.js +12 -9
  23. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  24. package/build/dist/Server/Services/UserPushService.js +2 -1
  25. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  26. package/build/dist/Server/Utils/PushNotificationUtil.js +32 -8
  27. package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
  28. package/build/dist/Types/PushNotification/PushDeviceType.js +8 -0
  29. package/build/dist/Types/PushNotification/PushDeviceType.js.map +1 -0
  30. package/build/dist/UI/Components/ModelDelete/ModelDelete.js +2 -1
  31. package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
  32. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
  33. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  34. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +2 -1
  35. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  36. package/build/dist/UI/Components/Page/ModelPage.js +2 -1
  37. package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
  38. package/package.json +2 -1
@@ -16,6 +16,7 @@ import TenantColumn from "../../Types/Database/TenantColumn";
16
16
  import IconProp from "../../Types/Icon/IconProp";
17
17
  import ObjectID from "../../Types/ObjectID";
18
18
  import Permission from "../../Types/Permission";
19
+ import PushDeviceType from "../../Types/PushNotification/PushDeviceType";
19
20
  import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
20
21
 
21
22
  @TenantColumn("projectId")
@@ -122,7 +123,7 @@ class UserPush extends BaseModel {
122
123
  unique: false,
123
124
  nullable: false,
124
125
  })
125
- public deviceType?: "web" = "web" as const; // Only web support for now
126
+ public deviceType?: PushDeviceType = undefined;
126
127
 
127
128
  @ColumnAccessControl({
128
129
  create: [Permission.CurrentUser],
@@ -14,6 +14,7 @@ import Response from "../Utils/Response";
14
14
  import BaseAPI from "./BaseAPI";
15
15
  import BadDataException from "../../Types/Exception/BadDataException";
16
16
  import ObjectID from "../../Types/ObjectID";
17
+ import PushDeviceType from "../../Types/PushNotification/PushDeviceType";
17
18
  import UserPush from "../../Models/DatabaseModels/UserPush";
18
19
  import PushNotificationMessage from "../../Types/PushNotification/PushNotificationMessage";
19
20
 
@@ -39,11 +40,17 @@ export default class UserPushAPI extends BaseAPI<
39
40
  );
40
41
  }
41
42
 
42
- if (!req.body.deviceType || req.body.deviceType !== "web") {
43
+ const validDeviceTypes: string[] = Object.values(PushDeviceType);
44
+ if (
45
+ !req.body.deviceType ||
46
+ !validDeviceTypes.includes(req.body.deviceType)
47
+ ) {
43
48
  return Response.sendErrorResponse(
44
49
  req,
45
50
  res,
46
- new BadDataException("Only web device type is supported"),
51
+ new BadDataException(
52
+ "Device type must be one of: " + validDeviceTypes.join(", "),
53
+ ),
47
54
  );
48
55
  }
49
56
 
@@ -86,7 +93,7 @@ export default class UserPushAPI extends BaseAPI<
86
93
  userPush.deviceToken = req.body.deviceToken;
87
94
  userPush.deviceType = req.body.deviceType;
88
95
  userPush.deviceName = req.body.deviceName || "Unknown Device";
89
- userPush.isVerified = true; // For web push, we consider it verified immediately
96
+ userPush.isVerified = true; // Web, iOS, and Android devices are verified immediately
90
97
 
91
98
  const savedDevice: UserPush = await this.service.create({
92
99
  data: userPush,
@@ -105,6 +112,46 @@ export default class UserPushAPI extends BaseAPI<
105
112
  },
106
113
  );
107
114
 
115
+ this.router.post(
116
+ `/user-push/unregister`,
117
+ UserMiddleware.getUserMiddleware,
118
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
119
+ try {
120
+ req = req as OneUptimeRequest;
121
+
122
+ if (!req.body.deviceToken) {
123
+ return Response.sendErrorResponse(
124
+ req,
125
+ res,
126
+ new BadDataException("Device token is required"),
127
+ );
128
+ }
129
+
130
+ const userId: ObjectID = (req as OneUptimeRequest).userAuthorization!
131
+ .userId!;
132
+
133
+ await this.service.deleteBy({
134
+ query: {
135
+ userId: userId,
136
+ deviceToken: req.body.deviceToken,
137
+ },
138
+ limit: 100,
139
+ skip: 0,
140
+ props: {
141
+ isRoot: true,
142
+ },
143
+ });
144
+
145
+ return Response.sendJsonObjectResponse(req, res, {
146
+ success: true,
147
+ message: "Device unregistered successfully",
148
+ });
149
+ } catch (error) {
150
+ return next(error);
151
+ }
152
+ },
153
+ );
154
+
108
155
  this.router.post(
109
156
  `/user-push/:deviceId/test-notification`,
110
157
  UserMiddleware.getUserMiddleware,
@@ -186,7 +233,7 @@ export default class UserPushAPI extends BaseAPI<
186
233
  },
187
234
  ],
188
235
  message: testMessage,
189
- deviceType: device.deviceType!,
236
+ deviceType: device.deviceType! as PushDeviceType,
190
237
  },
191
238
  {
192
239
  isSensitive: false,
@@ -64,18 +64,23 @@ export default class UserMiddleware {
64
64
  public static getAccessTokenFromExpressRequest(
65
65
  req: ExpressRequest,
66
66
  ): string | undefined {
67
- let accessToken: string | undefined = undefined;
67
+ // 1. Try cookie (existing web dashboard flow)
68
+ const cookieToken: string | undefined =
69
+ CookieUtil.getCookieFromExpressRequest(req, CookieUtil.getUserTokenKey());
68
70
 
69
- if (
70
- CookieUtil.getCookieFromExpressRequest(req, CookieUtil.getUserTokenKey())
71
- ) {
72
- accessToken = CookieUtil.getCookieFromExpressRequest(
73
- req,
74
- CookieUtil.getUserTokenKey(),
75
- );
71
+ if (cookieToken) {
72
+ return cookieToken;
76
73
  }
77
74
 
78
- return accessToken;
75
+ // 2. Fallback: Check Authorization: Bearer <token> header (mobile app flow)
76
+ const authHeader: string | undefined = req.headers["authorization"] as
77
+ | string
78
+ | undefined;
79
+ if (authHeader && authHeader.startsWith("Bearer ")) {
80
+ return authHeader.substring(7);
81
+ }
82
+
83
+ return undefined;
79
84
  }
80
85
 
81
86
  @CaptureSpan()
@@ -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(", "),
@@ -21,6 +21,8 @@ export default class PushNotificationUtil {
21
21
  incidentViewLink: string;
22
22
  incidentNumber?: number;
23
23
  incidentNumberWithPrefix?: string;
24
+ incidentId?: string;
25
+ projectId?: string;
24
26
  }): PushNotificationMessage {
25
27
  const {
26
28
  incidentTitle,
@@ -28,6 +30,8 @@ export default class PushNotificationUtil {
28
30
  incidentViewLink,
29
31
  incidentNumber,
30
32
  incidentNumberWithPrefix,
33
+ incidentId,
34
+ projectId,
31
35
  } = params;
32
36
  const displayNumber: string =
33
37
  incidentNumberWithPrefix || (incidentNumber ? `#${incidentNumber}` : "");
@@ -43,6 +47,9 @@ export default class PushNotificationUtil {
43
47
  requireInteraction: true,
44
48
  data: {
45
49
  type: "incident-created",
50
+ entityType: "incident",
51
+ entityId: incidentId,
52
+ projectId: projectId,
46
53
  incidentTitle: incidentTitle,
47
54
  projectName: projectName,
48
55
  url: incidentViewLink,
@@ -58,6 +65,8 @@ export default class PushNotificationUtil {
58
65
  incidentViewLink: string;
59
66
  incidentNumber?: number;
60
67
  incidentNumberWithPrefix?: string;
68
+ incidentId?: string;
69
+ projectId?: string;
61
70
  }): PushNotificationMessage {
62
71
  const {
63
72
  incidentTitle,
@@ -67,6 +76,8 @@ export default class PushNotificationUtil {
67
76
  incidentViewLink,
68
77
  incidentNumber,
69
78
  incidentNumberWithPrefix,
79
+ incidentId,
80
+ projectId,
70
81
  } = params;
71
82
  const displayNumber: string =
72
83
  incidentNumberWithPrefix || (incidentNumber ? `#${incidentNumber}` : "");
@@ -85,6 +96,9 @@ export default class PushNotificationUtil {
85
96
  requireInteraction: true,
86
97
  data: {
87
98
  type: "incident-state-changed",
99
+ entityType: "incident",
100
+ entityId: incidentId,
101
+ projectId: projectId,
88
102
  incidentTitle: incidentTitle,
89
103
  projectName: projectName,
90
104
  newState: newState,
@@ -101,6 +115,8 @@ export default class PushNotificationUtil {
101
115
  incidentViewLink: string;
102
116
  incidentNumber?: number;
103
117
  incidentNumberWithPrefix?: string;
118
+ incidentId?: string;
119
+ projectId?: string;
104
120
  }): PushNotificationMessage {
105
121
  const {
106
122
  incidentTitle,
@@ -109,6 +125,8 @@ export default class PushNotificationUtil {
109
125
  incidentViewLink,
110
126
  incidentNumber,
111
127
  incidentNumberWithPrefix,
128
+ incidentId,
129
+ projectId,
112
130
  } = params;
113
131
  const noteType: string = isPrivateNote ? "Private" : "Public";
114
132
  const displayNumber: string =
@@ -125,6 +143,9 @@ export default class PushNotificationUtil {
125
143
  requireInteraction: true,
126
144
  data: {
127
145
  type: "incident-note-posted",
146
+ entityType: "incident",
147
+ entityId: incidentId,
148
+ projectId: projectId,
128
149
  incidentTitle: incidentTitle,
129
150
  projectName: projectName,
130
151
  isPrivateNote: isPrivateNote,
@@ -139,6 +160,8 @@ export default class PushNotificationUtil {
139
160
  alertViewLink: string;
140
161
  alertNumber?: number;
141
162
  alertNumberWithPrefix?: string;
163
+ alertId?: string;
164
+ projectId?: string;
142
165
  }): PushNotificationMessage {
143
166
  const {
144
167
  alertTitle,
@@ -146,6 +169,8 @@ export default class PushNotificationUtil {
146
169
  alertViewLink,
147
170
  alertNumber,
148
171
  alertNumberWithPrefix,
172
+ alertId,
173
+ projectId,
149
174
  } = params;
150
175
  const displayNumber: string =
151
176
  alertNumberWithPrefix || (alertNumber ? `#${alertNumber}` : "");
@@ -161,6 +186,9 @@ export default class PushNotificationUtil {
161
186
  requireInteraction: true,
162
187
  data: {
163
188
  type: "alert-created",
189
+ entityType: "alert",
190
+ entityId: alertId,
191
+ projectId: projectId,
164
192
  alertTitle: alertTitle,
165
193
  projectName: projectName,
166
194
  url: alertViewLink,
@@ -174,6 +202,8 @@ export default class PushNotificationUtil {
174
202
  alertEpisodeViewLink: string;
175
203
  episodeNumber?: number;
176
204
  episodeNumberWithPrefix?: string;
205
+ alertEpisodeId?: string;
206
+ projectId?: string;
177
207
  }): PushNotificationMessage {
178
208
  const {
179
209
  alertEpisodeTitle,
@@ -181,6 +211,8 @@ export default class PushNotificationUtil {
181
211
  alertEpisodeViewLink,
182
212
  episodeNumber,
183
213
  episodeNumberWithPrefix,
214
+ alertEpisodeId,
215
+ projectId,
184
216
  } = params;
185
217
  const displayNumber: string =
186
218
  episodeNumberWithPrefix || (episodeNumber ? `#${episodeNumber}` : "");
@@ -196,6 +228,9 @@ export default class PushNotificationUtil {
196
228
  requireInteraction: true,
197
229
  data: {
198
230
  type: "alert-episode-created",
231
+ entityType: "alert-episode",
232
+ entityId: alertEpisodeId,
233
+ projectId: projectId,
199
234
  alertEpisodeTitle: alertEpisodeTitle,
200
235
  projectName: projectName,
201
236
  url: alertEpisodeViewLink,
@@ -209,6 +244,8 @@ export default class PushNotificationUtil {
209
244
  incidentEpisodeViewLink: string;
210
245
  episodeNumber?: number;
211
246
  episodeNumberWithPrefix?: string;
247
+ incidentEpisodeId?: string;
248
+ projectId?: string;
212
249
  }): PushNotificationMessage {
213
250
  const {
214
251
  incidentEpisodeTitle,
@@ -216,6 +253,8 @@ export default class PushNotificationUtil {
216
253
  incidentEpisodeViewLink,
217
254
  episodeNumber,
218
255
  episodeNumberWithPrefix,
256
+ incidentEpisodeId,
257
+ projectId,
219
258
  } = params;
220
259
  const displayNumber: string =
221
260
  episodeNumberWithPrefix || (episodeNumber ? `#${episodeNumber}` : "");
@@ -231,6 +270,9 @@ export default class PushNotificationUtil {
231
270
  requireInteraction: true,
232
271
  data: {
233
272
  type: "incident-episode-created",
273
+ entityType: "incident-episode",
274
+ entityId: incidentEpisodeId,
275
+ projectId: projectId,
234
276
  incidentEpisodeTitle: incidentEpisodeTitle,
235
277
  projectName: projectName,
236
278
  url: incidentEpisodeViewLink,
@@ -244,6 +286,8 @@ export default class PushNotificationUtil {
244
286
  newStatus: string;
245
287
  previousStatus?: string;
246
288
  monitorViewLink: string;
289
+ monitorId?: string;
290
+ projectId?: string;
247
291
  }): PushNotificationMessage {
248
292
  const {
249
293
  monitorName,
@@ -251,6 +295,8 @@ export default class PushNotificationUtil {
251
295
  newStatus,
252
296
  previousStatus,
253
297
  monitorViewLink,
298
+ monitorId,
299
+ projectId,
254
300
  } = params;
255
301
  const statusChangeText: string = previousStatus
256
302
  ? `Monitor status changed from ${previousStatus} to ${newStatus}`
@@ -264,6 +310,9 @@ export default class PushNotificationUtil {
264
310
  requireInteraction: true,
265
311
  data: {
266
312
  type: "monitor-status-changed",
313
+ entityType: "monitor",
314
+ entityId: monitorId,
315
+ projectId: projectId,
267
316
  monitorName: monitorName,
268
317
  projectName: projectName,
269
318
  newStatus: newStatus,
@@ -280,6 +329,8 @@ export default class PushNotificationUtil {
280
329
  viewLink: string;
281
330
  scheduledMaintenanceNumber?: number;
282
331
  scheduledMaintenanceNumberWithPrefix?: string;
332
+ scheduledMaintenanceId?: string;
333
+ projectId?: string;
283
334
  }): PushNotificationMessage {
284
335
  const {
285
336
  title,
@@ -288,6 +339,8 @@ export default class PushNotificationUtil {
288
339
  viewLink,
289
340
  scheduledMaintenanceNumber,
290
341
  scheduledMaintenanceNumberWithPrefix,
342
+ scheduledMaintenanceId,
343
+ projectId,
291
344
  } = params;
292
345
  const displayNumber: string =
293
346
  scheduledMaintenanceNumberWithPrefix ||
@@ -304,6 +357,9 @@ export default class PushNotificationUtil {
304
357
  requireInteraction: false,
305
358
  data: {
306
359
  type: "scheduled-maintenance",
360
+ entityType: "scheduled-maintenance",
361
+ entityId: scheduledMaintenanceId,
362
+ projectId: projectId,
307
363
  title: title,
308
364
  projectName: projectName,
309
365
  state: state,
@@ -0,0 +1,7 @@
1
+ enum PushDeviceType {
2
+ Web = "web",
3
+ iOS = "ios",
4
+ Android = "android",
5
+ }
6
+
7
+ export default PushDeviceType;
@@ -1,3 +1,5 @@
1
+ import PushDeviceType from "./PushDeviceType";
2
+
1
3
  interface PushNotificationRequest {
2
4
  devices: Array<{
3
5
  token: string;
@@ -19,7 +21,7 @@ interface PushNotificationRequest {
19
21
  clickAction?: string;
20
22
  url?: string;
21
23
  };
22
- deviceType: "web";
24
+ deviceType: PushDeviceType;
23
25
  }
24
26
 
25
27
  export default PushNotificationRequest;