@@ -13,9 +13,12 @@ import {sendNewMessageEmail} from 'email/functions/helpers'
1313import dayjs from 'dayjs'
1414import utc from 'dayjs/plugin/utc'
1515import timezone from 'dayjs/plugin/timezone'
16- import webPush from 'web-push' ;
17- import { parseJsonContentToText } from "common/util/parse" ;
18- import { encryptMessage } from "shared/encryption" ;
16+ import webPush from 'web-push'
17+ import { parseJsonContentToText } from "common/util/parse"
18+ import { encryptMessage } from "shared/encryption"
19+ import * as admin from 'firebase-admin'
20+
21+ const fcm = admin . messaging ( )
1922
2023dayjs . extend ( utc )
2124dayjs . extend ( timezone )
@@ -29,17 +32,17 @@ export const leaveChatContent = (userName: string) => ({
2932 } ,
3033 ] ,
3134} )
32- export const joinChatContent = ( userName : string ) => {
33- return {
34- type : 'doc' ,
35- content : [
36- {
37- type : 'paragraph' ,
38- content : [ { text : `${ userName } joined the chat!` , type : 'text' } ] ,
39- } ,
40- ] ,
41- }
42- }
35+ // export const joinChatContent = (userName: string) => {
36+ // return {
37+ // type: 'doc',
38+ // content: [
39+ // {
40+ // type: 'paragraph',
41+ // content: [{text: `${userName} joined the chat!`, type: 'text' }],
42+ // },
43+ // ],
44+ // }
45+ // }
4346
4447export const insertPrivateMessage = async (
4548 content : Json ,
@@ -48,8 +51,8 @@ export const insertPrivateMessage = async (
4851 visibility : ChatVisibility ,
4952 pg : SupabaseDirectClient
5053) => {
51- const plaintext = JSON . stringify ( content ) ;
52- const { ciphertext, iv, tag} = encryptMessage ( plaintext ) ;
54+ const plaintext = JSON . stringify ( content )
55+ const { ciphertext, iv, tag} = encryptMessage ( plaintext )
5356 const lastMessage = await pg . one (
5457 `insert into private_user_messages (ciphertext, iv, tag, channel_id, user_id, visibility)
5558 values ($1, $2, $3, $4, $5, $6)
@@ -134,7 +137,7 @@ export const createPrivateUserMessageMain = async (
134137 void notifyOtherUserInChannelIfInactive ( channelId , creator , content , pg )
135138 . catch ( ( err ) => {
136139 console . error ( 'notifyOtherUserInChannelIfInactive failed' , err )
137- } ) ;
140+ } )
138141
139142 track ( creator . id , 'send private message' , {
140143 channelId,
@@ -162,49 +165,24 @@ const notifyOtherUserInChannelIfInactive = async (
162165 // We're only sending notifs for 1:1 channels
163166 if ( ! otherUserIds || otherUserIds . length > 1 ) return
164167
165- const otherUserId = first ( otherUserIds )
166- if ( ! otherUserId ) return
168+ const receiverId = first ( otherUserIds ) ?. user_id
169+ if ( ! receiverId ) return
167170
168171 // TODO: notification only for active user
169172
170- const otherUser = await getUser ( otherUserId . user_id )
171- console . debug ( 'otherUser :' , otherUser )
172- if ( ! otherUser ) return
173+ const receiver = await getUser ( receiverId )
174+ console . debug ( 'receiver :' , receiver )
175+ if ( ! receiver ) return
173176
174- // Push notif
175- webPush . setVapidDetails (
176- 177- process . env . VAPID_PUBLIC_KEY ! ,
178- process . env . VAPID_PRIVATE_KEY !
179- ) ;
177+ // Push notifs
180178 const textContent = parseJsonContentToText ( content )
181- // Retrieve subscription from the database
182- const subscriptions = await getSubscriptionsFromDB ( otherUser . id , pg ) ;
183- for ( const subscription of subscriptions ) {
184- try {
185- const payload = JSON . stringify ( {
186- title : `${ creator . name } ` ,
187- body : textContent ,
188- url : `/messages/${ channelId } ` ,
189- } )
190- console . log ( 'Sending notification to:' , subscription . endpoint , payload ) ;
191- await webPush . sendNotification ( subscription , payload ) ;
192- } catch ( err : any ) {
193- console . log ( 'Failed to send notification' , err ) ;
194- if ( err . statusCode === 410 || err . statusCode === 404 ) {
195- console . warn ( 'Removing expired subscription' , subscription . endpoint ) ;
196- await pg . none (
197- `DELETE
198- FROM push_subscriptions
199- WHERE endpoint = $1
200- AND user_id = $2` ,
201- [ subscription . endpoint , otherUser . id ]
202- ) ;
203- } else {
204- console . error ( 'Push failed' , err ) ;
205- }
206- }
179+ const payload = {
180+ title : `${ creator . name } ` ,
181+ body : textContent ,
182+ url : `/messages/${ channelId } ` ,
207183 }
184+ await sendWebNotifications ( pg , receiverId , JSON . stringify ( payload ) )
185+ await sendMobileNotifications ( pg , receiverId , payload )
208186
209187 const startOfDay = dayjs ( )
210188 . tz ( 'America/Los_Angeles' )
@@ -222,7 +200,7 @@ const notifyOtherUserInChannelIfInactive = async (
222200 log ( 'previous messages this day' , previousMessagesThisDayBetweenTheseUsers )
223201 if ( previousMessagesThisDayBetweenTheseUsers . count > 1 ) return
224202
225- await createNewMessageNotification ( creator , otherUser , channelId )
203+ await createNewMessageNotification ( creator , receiver , channelId )
226204}
227205
228206const createNewMessageNotification = async (
@@ -237,24 +215,126 @@ const createNewMessageNotification = async (
237215}
238216
239217
218+ async function sendWebNotifications (
219+ pg : SupabaseDirectClient ,
220+ userId : string ,
221+ payload : string ,
222+ ) {
223+ webPush . setVapidDetails (
224+ 225+ process . env . VAPID_PUBLIC_KEY ! ,
226+ process . env . VAPID_PRIVATE_KEY !
227+ )
228+ // Retrieve subscription from the database
229+ const subscriptions = await getSubscriptionsFromDB ( pg , userId )
230+ for ( const subscription of subscriptions ) {
231+ try {
232+ console . log ( 'Sending notification to:' , subscription . endpoint , payload )
233+ await webPush . sendNotification ( subscription , payload )
234+ } catch ( err : any ) {
235+ console . log ( 'Failed to send notification' , err )
236+ if ( err . statusCode === 410 || err . statusCode === 404 ) {
237+ console . warn ( 'Removing expired subscription' , subscription . endpoint )
238+ await removeSubscription ( pg , subscription . endpoint , userId )
239+ } else {
240+ console . error ( 'Push failed' , err )
241+ }
242+ }
243+ }
244+ }
245+
246+
240247export async function getSubscriptionsFromDB (
248+ pg : SupabaseDirectClient ,
241249 userId : string ,
242- pg : SupabaseDirectClient
243250) {
244251 try {
245252 const subscriptions = await pg . manyOrNone ( `
246253 select endpoint, keys
247254 from push_subscriptions
248255 where user_id = $1
249256 ` , [ userId ]
250- ) ;
257+ )
251258
252259 return subscriptions . map ( sub => ( {
253260 endpoint : sub . endpoint ,
254261 keys : sub . keys ,
255- } ) ) ;
262+ } ) )
263+ } catch ( err ) {
264+ console . error ( 'Error fetching subscriptions' , err )
265+ return [ ]
266+ }
267+ }
268+
269+ async function removeSubscription (
270+ pg : SupabaseDirectClient ,
271+ endpoint : any ,
272+ userId : string ,
273+ ) {
274+ await pg . none (
275+ `DELETE
276+ FROM push_subscriptions
277+ WHERE endpoint = $1
278+ AND user_id = $2` ,
279+ [ endpoint , userId ]
280+ )
281+ }
282+
283+
284+ async function sendMobileNotifications (
285+ pg : SupabaseDirectClient ,
286+ userId : string ,
287+ payload : PushPayload ,
288+ ) {
289+ const subscriptions = await getMobileSubscriptionsFromDB ( pg , userId )
290+ for ( const subscription of subscriptions ) {
291+ await sendPushToToken ( subscription . token , payload )
292+ }
293+ }
294+
295+ interface PushPayload {
296+ title : string
297+ body : string
298+ data ?: Record < string , string >
299+ }
300+
301+ export async function sendPushToToken ( token : string , payload : PushPayload ) {
302+ const message = {
303+ token,
304+ notification : {
305+ title : payload . title ,
306+ body : payload . body ,
307+ } ,
308+ data : payload . data , // optional custom key-value pairs
309+ }
310+
311+ try {
312+ console . log ( 'Sending notification to:' , token , payload )
313+ const response = await fcm . send ( message )
314+ console . log ( 'Push sent successfully:' , response )
315+ return response
316+ } catch ( err ) {
317+ console . error ( 'Error sending push:' , err )
318+ }
319+ return
320+ }
321+
322+
323+ export async function getMobileSubscriptionsFromDB (
324+ pg : SupabaseDirectClient ,
325+ userId : string ,
326+ ) {
327+ try {
328+ const subscriptions = await pg . manyOrNone ( `
329+ select token
330+ from push_subscriptions_mobile
331+ where user_id = $1
332+ ` , [ userId ]
333+ )
334+
335+ return subscriptions
256336 } catch ( err ) {
257- console . error ( 'Error fetching subscriptions' , err ) ;
258- return [ ] ;
337+ console . error ( 'Error fetching subscriptions' , err )
338+ return [ ]
259339 }
260340}
0 commit comments