1- import { Json } from 'common/supabase/schema'
2- import { SupabaseDirectClient } from 'shared/supabase/init'
3- import { ChatVisibility } from 'common/chat-message'
4- import { User } from 'common/user'
5- import { first } from 'lodash'
6- import { log } from 'shared/monitoring/log'
7- import { getPrivateUser , getUser } from 'shared/utils'
8- import { type JSONContent } from '@tiptap/core'
9- import { APIError } from 'common/api/utils'
10- import { broadcast } from 'shared/websockets/server'
11- import { track } from 'shared/analytics'
12- import { sendNewMessageEmail } from 'email/functions/helpers'
1+ import { Json } from 'common/supabase/schema'
2+ import { SupabaseDirectClient } from 'shared/supabase/init'
3+ import { ChatVisibility } from 'common/chat-message'
4+ import { User } from 'common/user'
5+ import { first } from 'lodash'
6+ import { log } from 'shared/monitoring/log'
7+ import { getPrivateUser , getUser } from 'shared/utils'
8+ import { type JSONContent } from '@tiptap/core'
9+ import { APIError } from 'common/api/utils'
10+ import { broadcast } from 'shared/websockets/server'
11+ import { track } from 'shared/analytics'
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' ;
1617
1718dayjs . extend ( utc )
1819dayjs . extend ( timezone )
@@ -22,7 +23,7 @@ export const leaveChatContent = (userName: string) => ({
2223 content : [
2324 {
2425 type : 'paragraph' ,
25- content : [ { text : `${ userName } left the chat` , type : 'text' } ] ,
26+ content : [ { text : `${ userName } left the chat` , type : 'text' } ] ,
2627 } ,
2728 ] ,
2829} )
@@ -32,7 +33,7 @@ export const joinChatContent = (userName: string) => {
3233 content : [
3334 {
3435 type : 'paragraph' ,
35- content : [ { text : `${ userName } joined the chat!` , type : 'text' } ] ,
36+ content : [ { text : `${ userName } joined the chat!` , type : 'text' } ] ,
3637 } ,
3738 ] ,
3839 }
@@ -47,11 +48,14 @@ export const insertPrivateMessage = async (
4748) => {
4849 const lastMessage = await pg . one (
4950 `insert into private_user_messages (content, channel_id, user_id, visibility)
50- values ($1, $2, $3, $4) returning created_time` ,
51+ values ($1, $2, $3, $4)
52+ returning created_time` ,
5153 [ content , channelId , userId , visibility ]
5254 )
5355 await pg . none (
54- `update private_user_message_channels set last_updated_time = $1 where id = $2` ,
56+ `update private_user_message_channels
57+ set last_updated_time = $1
58+ where id = $2` ,
5559 [ lastMessage . created_time , channelId ]
5660 )
5761}
@@ -65,16 +69,17 @@ export const addUsersToPrivateMessageChannel = async (
6569 userIds . map ( ( id ) =>
6670 pg . none (
6771 `insert into private_user_message_channel_members (channel_id, user_id, role, status)
68- values
69- ($1, $2, 'member', 'proposed')
70- on conflict do nothing
71- ` ,
72+ values ($1, $2, 'member', 'proposed')
73+ on conflict do nothing
74+ ` ,
7275 [ channelId , id ]
7376 )
7477 )
7578 )
7679 await pg . none (
77- `update private_user_message_channels set last_updated_time = now() where id = $1` ,
80+ `update private_user_message_channels
81+ set last_updated_time = now()
82+ where id = $1` ,
7883 [ channelId ]
7984 )
8085}
@@ -90,9 +95,9 @@ export const createPrivateUserMessageMain = async (
9095 // Normally, users can only submit messages to channels that they are members of
9196 const authorized = await pg . oneOrNone (
9297 `select 1
93- from private_user_message_channel_members
94- where channel_id = $1
95- and user_id = $2` ,
98+ from private_user_message_channel_members
99+ where channel_id = $1
100+ and user_id = $2` ,
96101 [ channelId , creator . id ]
97102 )
98103 if ( ! authorized )
@@ -108,10 +113,12 @@ export const createPrivateUserMessageMain = async (
108113 }
109114
110115 const otherUserIds = await pg . map < string > (
111- `select user_id from private_user_message_channel_members
112- where channel_id = $1 and user_id != $2
113- and status != 'left'
114- ` ,
116+ `select user_id
117+ from private_user_message_channel_members
118+ where channel_id = $1
119+ and user_id != $2
120+ and status != 'left'
121+ ` ,
115122 [ channelId , creator . id ] ,
116123 ( r ) => r . user_id
117124 )
@@ -133,10 +140,12 @@ const notifyOtherUserInChannelIfInactive = async (
133140 pg : SupabaseDirectClient
134141) => {
135142 const otherUserIds = await pg . manyOrNone < { user_id : string } > (
136- `select user_id from private_user_message_channel_members
137- where channel_id = $1 and user_id != $2
138- and status != 'left'
139- ` ,
143+ `select user_id
144+ from private_user_message_channel_members
145+ where channel_id = $1
146+ and user_id != $2
147+ and status != 'left'
148+ ` ,
140149 [ channelId , creator . id ]
141150 )
142151 // We're only sending notifs for 1:1 channels
@@ -150,11 +159,12 @@ const notifyOtherUserInChannelIfInactive = async (
150159 . startOf ( 'day' )
151160 . toISOString ( )
152161 const previousMessagesThisDayBetweenTheseUsers = await pg . one (
153- `select count(*) from private_user_messages
154- where channel_id = $1
155- and user_id = $2
156- and created_time > $3
157- ` ,
162+ `select count(*)
163+ from private_user_messages
164+ where channel_id = $1
165+ and user_id = $2
166+ and created_time > $3
167+ ` ,
158168 [ channelId , creator . id , startOfDay ]
159169 )
160170 log ( 'previous messages this day' , previousMessagesThisDayBetweenTheseUsers )
@@ -166,16 +176,63 @@ const notifyOtherUserInChannelIfInactive = async (
166176 console . debug ( 'otherUser:' , otherUser )
167177 if ( ! otherUser ) return
168178
169- await createNewMessageNotification ( creator , otherUser , channelId )
179+ await createNewMessageNotification ( creator , otherUser , channelId , pg )
170180}
171181
172182const createNewMessageNotification = async (
173183 fromUser : User ,
174184 toUser : User ,
175- channelId : number
185+ channelId : number ,
186+ pg : SupabaseDirectClient
176187) => {
177188 const privateUser = await getPrivateUser ( toUser . id )
178189 console . debug ( 'privateUser:' , privateUser )
179190 if ( ! privateUser ) return
191+
192+ webPush . setVapidDetails (
193+ 194+ process . env . VAPID_PUBLIC_KEY ! ,
195+ process . env . VAPID_PRIVATE_KEY !
196+ ) ;
197+
198+ // Retrieve subscription from your database
199+ const subscriptions = await getSubscriptionsFromDB ( toUser . id , pg ) ;
200+
201+ for ( const subscription of subscriptions ) {
202+ try {
203+ console . log ( 'Sending notification to:' , subscription . endpoint ) ;
204+ await webPush . sendNotification ( subscription , JSON . stringify ( {
205+ title : `Message from ${ fromUser . name } ` ,
206+ body : 'You have a new message!' ,
207+ url : `/messages/${ channelId } ` ,
208+ } ) ) ;
209+ } catch ( err ) {
210+ console . error ( 'Failed to send notification' , err ) ;
211+ // optionally remove invalid subscription from DB
212+ }
213+ }
214+
180215 await sendNewMessageEmail ( privateUser , fromUser , toUser , channelId )
181216}
217+
218+
219+ export async function getSubscriptionsFromDB (
220+ userId : string ,
221+ pg : SupabaseDirectClient
222+ ) {
223+ try {
224+ const subscriptions = await pg . manyOrNone ( `
225+ select endpoint, keys from push_subscriptions
226+ where user_id = $1
227+ ` , [ userId ]
228+ ) ;
229+
230+ return subscriptions . map ( sub => ( {
231+ endpoint : sub . endpoint ,
232+ keys : sub . keys ,
233+ } ) ) ;
234+ } catch ( err ) {
235+ console . error ( 'Error fetching subscriptions' , err ) ;
236+ return [ ] ;
237+ }
238+ }
0 commit comments