Add sendNotification api

This commit is contained in:
Hongbo Wu
2022-11-22 12:48:17 +08:00
parent f7b0981b7d
commit 2ae3226bdb
5 changed files with 98 additions and 112 deletions

View File

@ -0,0 +1,73 @@
import express from 'express'
import { getDeviceTokensByUserId } from '../services/user_device_tokens'
import {
PushNotificationType,
sendMulticastPushNotifications,
} from '../utils/sendNotification'
import cors from 'cors'
import { corsConfig } from '../utils/corsConfig'
import * as jwt from 'jsonwebtoken'
import { env } from '../env'
import { Claims } from '../resolvers/types'
interface Notification {
body: string
title?: string
data?: Record<string, string>
image?: string
notificationType?: PushNotificationType
}
export function notificationRouter() {
const router = express.Router()
router.options('/send', cors<express.Request>({ ...corsConfig, maxAge: 600 }))
router.post('/send', cors<express.Request>(corsConfig), async (req, res) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const token = (req.cookies?.auth || req.headers?.authorization) as string
if (!token || !jwt.verify(token, env.server.jwtSecret)) {
return res.status(401).send({ errorCode: 'UNAUTHORIZED' })
}
const claims = jwt.decode(token) as Claims
const { uid: userId } = claims
const {
body,
title,
data,
image: imageUrl,
notificationType,
} = req.body as Notification
if (!userId || !body) {
return res.status(400).send({ errorCode: 'BAD_DATA' })
}
const tokens = await getDeviceTokensByUserId(userId)
if (tokens.length === 0) {
return res.status(400).send({ errorCode: 'NO_DEVICE_TOKENS' })
}
const message = {
notification: {
title,
body,
imageUrl,
},
data,
tokens: tokens.map((token) => token.token),
}
const result = await sendMulticastPushNotifications(
userId,
message,
notificationType || 'rule'
)
if (!result) {
return res.status(400).send({ errorCode: 'SEND_NOTIFICATION_FAILED' })
}
res.send('OK')
})
return router
}

View File

@ -47,6 +47,7 @@ import { webhooksServiceRouter } from './routers/svc/webhooks'
import { integrationsServiceRouter } from './routers/svc/integrations'
import { textToSpeechRouter } from './routers/text_to_speech'
import * as httpContext from 'express-http-context'
import { notificationRouter } from './routers/notification_router'
const PORT = process.env.PORT || 4000
@ -131,6 +132,7 @@ export const createApp = (): {
app.use('/api/article', articleRouter())
app.use('/api/mobile-auth', mobileAuthRouter())
app.use('/api/text-to-speech', textToSpeechRouter())
app.use('/api/notification', notificationRouter())
app.use('/svc/pubsub/content', contentServiceRouter())
app.use('/svc/pubsub/links', linkServiceRouter())
app.use('/svc/pubsub/newsletters', newsletterServiceRouter())

View File

@ -8,7 +8,7 @@ import {
import { env } from '../env'
import { analytics } from './analytics'
type PushNotificationType = 'newsletter' | 'reminder'
export type PushNotificationType = 'newsletter' | 'reminder' | 'rule'
// getting credentials from App Engine
initializeApp()

View File

@ -1,97 +1,35 @@
import { applicationDefault, initializeApp } from 'firebase-admin/app'
import {
BatchResponse,
getMessaging,
Message,
MulticastMessage,
} from 'firebase-admin/messaging'
import axios from 'axios'
import { getAuthToken } from './index'
export interface DeviceToken {
id: string
token: string
userId: string
createdAt: Date
interface NotificationData {
body: string
title?: string
data?: Record<string, string>
image?: string
notificationType?: string
}
// getting credentials from App Engine
initializeApp({
credential: applicationDefault(),
})
export const getDeviceTokens = async (
export const sendNotification = async (
userId: string,
apiEndpoint: string,
jwtSecret: string
): Promise<DeviceToken[]> => {
jwtSecret: string,
message: string,
title?: string,
image?: string
) => {
const auth = await getAuthToken(userId, jwtSecret)
const data = JSON.stringify({
query: `query {
deviceTokens {
... on DeviceTokensError {
errorCodes
}
... on DeviceTokensSuccess {
deviceTokens {
id
token
createdAt
}
}
}
}`,
})
const data: NotificationData = {
body: message,
title: title || message,
image,
notificationType: 'rule',
}
const response = await axios.post(`${apiEndpoint}/graphql`, data, {
await axios.post(`${apiEndpoint}/notification/send`, data, {
headers: {
Cookie: `auth=${auth};`,
'Content-Type': 'application/json',
},
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return response.data.data.deviceTokens.deviceTokens as DeviceToken[]
}
export const getBatchMessages = (
messages: string[],
tokens: string[],
title?: string,
imageUrl?: string
): Message[] => {
const batchMessages: Message[] = []
messages.forEach((message) => {
tokens.forEach((token) => {
batchMessages.push({
token,
notification: {
title,
body: message,
imageUrl,
},
})
})
})
return batchMessages
}
export const sendPushNotification = async (
message: Message
): Promise<string | undefined> => {
return getMessaging().send(message)
}
export const sendMulticastPushNotifications = async (
message: MulticastMessage
): Promise<BatchResponse | undefined> => {
return getMessaging().sendMulticast(message)
}
export const sendBatchPushNotifications = async (
messages: Message[]
): Promise<BatchResponse | undefined> => {
return getMessaging().sendAll(messages)
}

View File

@ -1,8 +1,4 @@
import {
getBatchMessages,
getDeviceTokens,
sendBatchPushNotifications,
} from './notification'
import { sendNotification } from './notification'
import { getAuthToken, PubSubData } from './index'
import axios from 'axios'
import { parse, SearchParserKeyWordOffset } from 'search-query-parser'
@ -142,33 +138,10 @@ export const triggerActions = async (
case RuleActionType.MarkAsRead:
continue
case RuleActionType.SendNotification:
if (action.params.length === 0) {
console.log('No notification messages provided')
continue
for (const message of action.params) {
await sendNotification(userId, apiEndpoint, jwtSecret, message)
}
await sendNotification(userId, action.params, apiEndpoint, jwtSecret)
}
}
}
}
export const sendNotification = async (
userId: string,
messages: string[],
apiEndpoint: string,
jwtSecret: string,
title?: string,
image?: string
) => {
// get device tokens by calling api
const tokens = await getDeviceTokens(userId, apiEndpoint, jwtSecret)
const batchMessages = getBatchMessages(
messages,
tokens.map((t) => t.token),
title,
image
)
return sendBatchPushNotifications(batchMessages)
}