mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
refactor: enhance push notification handling with PushNotificationService integration
This commit is contained in:
@@ -7,9 +7,7 @@ import Express, {
|
||||
import Response from "Common/Server/Utils/Response";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import { Expo, ExpoPushMessage, ExpoPushTicket } from "expo-server-sdk";
|
||||
import { ExpoAccessToken } from "Common/Server/EnvironmentConfig";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import PushNotificationService from "Common/Server/Services/PushNotificationService";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
@@ -44,11 +42,6 @@ setInterval(() => {
|
||||
}
|
||||
}, 5 * 60 * 1000);
|
||||
|
||||
// Expo client for sending push notifications (only available when EXPO_ACCESS_TOKEN is set)
|
||||
const expoClient: Expo | null = ExpoAccessToken
|
||||
? new Expo({ accessToken: ExpoAccessToken })
|
||||
: null;
|
||||
|
||||
router.post(
|
||||
"/send",
|
||||
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
||||
@@ -65,7 +58,7 @@ router.post(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expoClient) {
|
||||
if (!PushNotificationService.hasExpoAccessToken()) {
|
||||
throw new BadDataException(
|
||||
"Push relay is not configured. EXPO_ACCESS_TOKEN is not set on this server.",
|
||||
);
|
||||
@@ -75,7 +68,7 @@ router.post(
|
||||
|
||||
const to: string | undefined = body["to"] as string | undefined;
|
||||
|
||||
if (!to || !Expo.isExpoPushToken(to)) {
|
||||
if (!to || !PushNotificationService.isValidExpoPushToken(to)) {
|
||||
throw new BadDataException(
|
||||
"Invalid or missing push token. Must be a valid Expo push token.",
|
||||
);
|
||||
@@ -92,40 +85,15 @@ router.post(
|
||||
);
|
||||
}
|
||||
|
||||
const expoPushMessage: ExpoPushMessage = {
|
||||
await PushNotificationService.sendRelayPushNotification({
|
||||
to: to,
|
||||
title: title,
|
||||
body: messageBody,
|
||||
data: (body["data"] as { [key: string]: string }) || {},
|
||||
sound: (body["sound"] as "default" | null) || "default",
|
||||
priority: (body["priority"] as "default" | "normal" | "high") || "high",
|
||||
sound: (body["sound"] as string) || "default",
|
||||
priority: (body["priority"] as string) || "high",
|
||||
channelId: (body["channelId"] as string) || "default",
|
||||
};
|
||||
|
||||
const tickets: ExpoPushTicket[] =
|
||||
await expoClient.sendPushNotificationsAsync([expoPushMessage]);
|
||||
|
||||
const ticket: ExpoPushTicket | undefined = tickets[0];
|
||||
|
||||
if (ticket && ticket.status === "error") {
|
||||
const errorTicket: ExpoPushTicket & {
|
||||
message?: string;
|
||||
details?: { error?: string };
|
||||
} = ticket as ExpoPushTicket & {
|
||||
message?: string;
|
||||
details?: { error?: string };
|
||||
};
|
||||
|
||||
logger.error(
|
||||
`Push relay: Expo push notification error: ${errorTicket.message}`,
|
||||
);
|
||||
|
||||
throw new BadDataException(
|
||||
`Failed to send push notification: ${errorTicket.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Push relay: notification sent successfully to ${to}`);
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, { success: true });
|
||||
} catch (err) {
|
||||
|
||||
58
App/package-lock.json
generated
58
App/package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"@sendgrid/mail": "^8.1.0",
|
||||
"Common": "file:../Common",
|
||||
"ejs": "^3.1.9",
|
||||
"expo-server-sdk": "^5.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"nodemailer": "^6.9.7",
|
||||
"ts-node": "^10.9.1",
|
||||
@@ -2166,6 +2167,12 @@
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/err-code": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
|
||||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
@@ -2299,6 +2306,20 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-server-sdk": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/expo-server-sdk/-/expo-server-sdk-5.0.0.tgz",
|
||||
"integrity": "sha512-GEp1XYLU80iS/hdRo3c2n092E8TgTXcHSuw6Lw68dSoWaAgiLPI2R+e5hp5+hGF1TtJZOi2nxtJX63+XA3iz9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"promise-limit": "^2.7.0",
|
||||
"promise-retry": "^2.0.1",
|
||||
"undici": "^7.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
@@ -4167,6 +4188,25 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/promise-limit": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz",
|
||||
"integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/promise-retry": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
|
||||
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"err-code": "^2.0.2",
|
||||
"retry": "^0.12.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/prompts": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||
@@ -4290,6 +4330,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
@@ -4801,6 +4850,15 @@
|
||||
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.22.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
|
||||
"integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"@sendgrid/mail": "^8.1.0",
|
||||
"Common": "file:../Common",
|
||||
"ejs": "^3.1.9",
|
||||
"expo-server-sdk": "^5.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"nodemailer": "^6.9.7",
|
||||
"ts-node": "^10.9.1",
|
||||
|
||||
@@ -468,6 +468,66 @@ export default class PushNotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
public static isValidExpoPushToken(token: string): boolean {
|
||||
return Expo.isExpoPushToken(token);
|
||||
}
|
||||
|
||||
public static hasExpoAccessToken(): boolean {
|
||||
return Boolean(ExpoAccessToken);
|
||||
}
|
||||
|
||||
public static async sendRelayPushNotification(data: {
|
||||
to: string;
|
||||
title?: string;
|
||||
body?: string;
|
||||
data?: { [key: string]: string };
|
||||
sound?: string;
|
||||
priority?: string;
|
||||
channelId?: string;
|
||||
}): Promise<void> {
|
||||
if (!ExpoAccessToken) {
|
||||
throw new Error(
|
||||
"Push relay is not configured. EXPO_ACCESS_TOKEN is not set on this server.",
|
||||
);
|
||||
}
|
||||
|
||||
const expoPushMessage: ExpoPushMessage = {
|
||||
to: data.to,
|
||||
title: data.title,
|
||||
body: data.body,
|
||||
data: data.data || {},
|
||||
sound: (data.sound as "default" | null) || "default",
|
||||
priority:
|
||||
(data.priority as "default" | "normal" | "high") || "high",
|
||||
channelId: data.channelId || "default",
|
||||
};
|
||||
|
||||
const tickets: ExpoPushTicket[] =
|
||||
await this.expoClient.sendPushNotificationsAsync([expoPushMessage]);
|
||||
|
||||
const ticket: ExpoPushTicket | undefined = tickets[0];
|
||||
|
||||
if (ticket && ticket.status === "error") {
|
||||
const errorTicket: ExpoPushTicket & {
|
||||
message?: string;
|
||||
details?: { error?: string };
|
||||
} = ticket as ExpoPushTicket & {
|
||||
message?: string;
|
||||
details?: { error?: string };
|
||||
};
|
||||
|
||||
logger.error(
|
||||
`Push relay: Expo push notification error: ${errorTicket.message}`,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Failed to send push notification: ${errorTicket.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Push relay: notification sent successfully to ${data.to}`);
|
||||
}
|
||||
|
||||
public static async sendPushNotificationToUser(
|
||||
userId: ObjectID,
|
||||
projectId: ObjectID,
|
||||
|
||||
Reference in New Issue
Block a user