Skip to content

Commit

Permalink
feat: implement token sync logic in client code
Browse files Browse the repository at this point in the history
  • Loading branch information
czabaj committed Oct 30, 2024
1 parent cd7a507 commit 5756ae6
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 21 deletions.
28 changes: 21 additions & 7 deletions src/App.res
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,27 @@ let make = () => {
| list{"misto", ..._} =>
<FirebaseAuthProvider>
<SignInWrapper>
{switch List.tail(url.path) {
| Some(list{}) => <MyPlaces />
| Some(list{placeId}) => <Place placeId />
| Some(list{placeId, "nastaveni"}) => <PlaceSetting placeId />
| Some(list{placeId, "nastaveni", "osob"}) => <PlacePersonsSetting placeId />
| _ => <PageNotFound />
}}
{
let maybePlaceSubPath = List.tail(url.path)
switch maybePlaceSubPath {
| None
| Some(list{}) =>
<MyPlaces />
| Some(placeSubPath) => {
let placeId = List.headExn(placeSubPath)
let placeIdSub = List.tail(placeSubPath)
<>
<FcmTokenSync placeId />
{switch placeIdSub {
| Some(list{}) => <Place placeId />
| Some(list{"nastaveni"}) => <PlaceSetting placeId />
| Some(list{"nastaveni", "osob"}) => <PlacePersonsSetting placeId />
| _ => <PageNotFound />
}}
</>
}
}
}
</SignInWrapper>
</FirebaseAuthProvider>
| list{"s", linkId} =>
Expand Down
17 changes: 12 additions & 5 deletions src/backend/Firebase.res
Original file line number Diff line number Diff line change
Expand Up @@ -437,14 +437,21 @@ module AppCheckProvider = {
module Messaging = {
type t

type getTokenOptions = { vapidKey: string }
@module("firebase/messaging")
external getMessaging: FirebaseApp.t => t = "getMessaging"

type getTokenOptions = {vapidKey: string}

// Subscribes the Messaging instance to push notifications. Returns a Firebase Cloud Messaging registration token that
// can be used to send push messages to that Messaging instance. If notification permission isn't already granted,
// this method asks the user for permission. The returned promise rejects if the user does not allow the app to show
// notifications.
@module("firebase/messaging")
external getToken: (t, getTokenOptions) => unit = "getToken"
}
external _getToken: (t, getTokenOptions) => promise<string> = "getToken"

@module("firebase/messaging")
external getMessaging: FirebaseApp.t => Messaging.t = "getMessaging"
let getToken = (messaging: t) =>
_getToken(messaging, {vapidKey: %raw(`import.meta.env.VITE_FIREBASE_VAPID_KEY`)})
}

module Timestamp = {
@genType.import("firebase/firestore") @genType.as("Timestamp")
Expand Down
8 changes: 0 additions & 8 deletions src/backend/NotificationEvents.res
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,6 @@ type notificationEvent =
@module("./NotificationEvents.ts")
external notificationEvent: notificationEvent = "NotificationEvent"

let notificationEventFromInt = (notificationEvent: int) =>
switch notificationEvent {
| 0 => Some(Unsubscribed)
| 1 => Some(FreeTable)
| 2 => Some(FreshKeg)
| _ => None
}

let roleI18n = (notificationEvent: notificationEvent) =>
switch notificationEvent {
| Unsubscribed => "Nepřihlášen"
Expand Down
8 changes: 8 additions & 0 deletions src/backend/Reactfire.res
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ module FunctionsProvider = {
"FunctionsProvider"
}

let messagingContext = React.createContext((None: option<Firebase.Messaging.t>))

module MessagingProvider = {
let make = React.Context.provider(messagingContext)
}

let useMessaging = () => React.useContext(messagingContext)->Option.getExn

type observableStatus<'a> = {
data: option<'a>,
error: option<Js.Exn.t>,
Expand Down
50 changes: 50 additions & 0 deletions src/components/FcmTokenSync/FcmTokenSync.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
let isSubscribedToNotificationsRx = (auth, firestore, placeId) => {
open Rxjs
let currentUserRx = Rxfire.user(auth)->op(keepMap(Null.toOption))
let placeRef = Db.placeDocument(firestore, placeId)
let placeRx = Rxfire.docData(placeRef)
combineLatest2(currentUserRx, placeRx)->op(
map(((currentUser: Firebase.User.t, place: option<FirestoreModels.place>), _) => {
switch (currentUser, place) {
| (user, Some(place)) =>
place.accounts
->Dict.get(user.uid)
->Option.map(((_, notificationSetting)) => notificationSetting > 0)
->Option.getOr(false)
| _ => false
}
}),
)
}

@react.component
let make = (~placeId) => {
let auth = Reactfire.useAuth()
let firestore = Reactfire.useFirestore()
let messaging = Reactfire.useMessaging()
let updateNotificationToken = NotificationEvents.useUpdateNotificationToken()
let isStandaloneModeStatus = DomUtils.useIsStandaloneMode()
let isSubscribedToNotifications = Reactfire.useObservable(
~observableId="isSubscribedToNotifications",
~source=isSubscribedToNotificationsRx(auth, firestore, placeId),
)
React.useEffect2(() => {
switch (isSubscribedToNotifications.data, isStandaloneModeStatus.data) {
| (Some(true), Some(true)) =>
messaging
->Firebase.Messaging.getToken
->Promise.then(updateNotificationToken)
->Promise.then(_ => Promise.resolve())
->Promise.catch(error => {
let exn = Js.Exn.asJsExn(error)->Option.getExn
LogUtils.captureException(exn)
Promise.resolve()
})
->ignore
| _ => ()
}
None
}, (isSubscribedToNotifications.data, isStandaloneModeStatus.data))

React.null
}
5 changes: 4 additions & 1 deletion src/components/FirebaseAuthProvider/FirebaseAuthProvider.res
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ let make = (~children) => {

let analytics = app->Analytics.getAnalytics
let functions = app->Functions.getFunctions
let messaging = app->Messaging.getMessaging

<AppCheckProvider sdk=appCheck>
<AnalyticsProvider sdk=analytics>
<AuthProvider sdk=auth>
<FunctionsProvider sdk=functions> {children} </FunctionsProvider>
<FunctionsProvider sdk=functions>
<MessagingProvider value=Some(messaging)> {children} </MessagingProvider>
</FunctionsProvider>
</AuthProvider>
</AnalyticsProvider>
</AppCheckProvider>
Expand Down

0 comments on commit 5756ae6

Please sign in to comment.