Add sendNotification api
This commit is contained in:
73
packages/api/src/routers/notification_router.ts
Normal file
73
packages/api/src/routers/notification_router.ts
Normal 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
|
||||
}
|
||||
@ -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())
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user