-
Notifications
You must be signed in to change notification settings - Fork 136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Firebase for Push notifications #685
base: master
Are you sure you want to change the base?
Changes from all commits
62a813b
3c461b0
f23ab18
a5b618d
17fe743
fbf33c1
cd011b8
494824f
3406339
4b25f58
c782e02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* eslint-disable */ | ||
// Give the service worker access to Firebase Messaging. | ||
// Note that you can only use Firebase Messaging here. Other Firebase libraries | ||
// are not available in the service worker. | ||
importScripts('https://www.gstatic.com/firebasejs/9.1.3/firebase-app-compat.js'); | ||
importScripts('https://www.gstatic.com/firebasejs/9.1.3/firebase-messaging-compat.js'); | ||
|
||
// Initialize the Firebase app in the service worker by passing in | ||
// your app's Firebase config object. | ||
// https://firebase.google.com/docs/web/setup#config-object | ||
firebase.initializeApp({ | ||
apiKey: 'AIzaSyCCW7gmWZli24N61NShh-8ALxVy3WtjqNU', | ||
authDomain: 'simplq-fe712.firebaseapp.com', | ||
projectId: 'simplq-fe712', | ||
storageBucket: 'simplq-fe712.appspot.com', | ||
messagingSenderId: '348531792421', | ||
appId: '1:348531792421:web:c481f1740405522d0f3dcc', | ||
measurementId: 'G-8N2SDV8VF5', | ||
}); | ||
|
||
// Retrieve an instance of Firebase Messaging so that it can handle background | ||
// messages. | ||
const messaging = firebase.messaging(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/** | ||
* Subscribe to backend for notifications | ||
* | ||
* @param {String} deviceId - device token that identifies this device | ||
* @returns {Object} request - partial axios request without baseURL | ||
*/ | ||
export const linkDevice = (deviceId) => ({ | ||
method: 'put', | ||
url: `/owner/link?deviceId=${deviceId}`, | ||
}); | ||
|
||
/** | ||
* Unsubscribe to backend for notifications | ||
* | ||
* @param {String} deviceId - device token that identifies this device | ||
* @returns {Object} request - partial axios request without baseURL | ||
*/ | ||
export const unlinkDevice = (deviceId) => ({ | ||
method: 'patch', | ||
url: `/owner/unlink?deviceId=${deviceId}`, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,8 +3,6 @@ import ReactDOM from 'react-dom'; | |
import './index.css'; | ||
import { createTheme, ThemeProvider } from '@material-ui/core/styles'; | ||
import { Provider } from 'react-redux'; | ||
import * as Sentry from '@sentry/react'; | ||
import { Integrations } from '@sentry/tracing'; | ||
import AOS from 'aos'; | ||
import { Auth0Provider } from '@auth0/auth0-react'; | ||
import { store } from './store'; | ||
|
@@ -13,12 +11,6 @@ import Layout from './components/Layout/Layout'; | |
|
||
AOS.init(); | ||
|
||
Sentry.init({ | ||
dsn: 'https://[email protected]/5420492', | ||
integrations: [new Integrations.BrowserTracing()], | ||
tracesSampleRate: 1.0, | ||
}); | ||
|
||
const theme = createTheme({ | ||
palette: { | ||
primary: { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as Sentry from '@sentry/react'; | ||
import { Integrations } from '@sentry/tracing'; | ||
|
||
Sentry.init({ | ||
dsn: 'https://[email protected]/5420492', | ||
integrations: [new Integrations.BrowserTracing()], | ||
tracesSampleRate: 1.0, | ||
}); | ||
|
||
/** | ||
* Send exception to monitoring framework. | ||
* | ||
* @param {Exception} ex - exception that was captured. | ||
* @param {string} caughtAt - Optional. A tag indicating where the exception was caught. | ||
* @param {object} extras - Optional. Set an object that will be merged sent as extra data with the event. | ||
*/ | ||
export function raiseException(ex, caughtAt, extras) { | ||
Sentry.withScope((scope) => { | ||
if (caughtAt) { | ||
scope.setTag('Caught-at', caughtAt); | ||
} | ||
if (extras) { | ||
scope.setExtras(extras); | ||
} | ||
|
||
const eventId = Sentry.captureException(ex); | ||
|
||
// eslint-disable-next-line no-console | ||
console.log(`Sentry exception captured, event id is ${eventId}`); | ||
// eslint-disable-next-line no-console | ||
console.error(ex); | ||
}); | ||
} | ||
|
||
export default raiseException; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { getMessaging, getToken } from 'firebase/messaging'; | ||
import { initializeApp } from 'firebase/app'; | ||
import { setErrorPopupMessage } from 'store/appSlice'; | ||
import { store } from 'store'; | ||
import { raiseException } from 'services/alerts'; | ||
import { useLinkDevice } from 'store/asyncActions'; | ||
|
||
// Public key generated from firebase console | ||
const vapidKey = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to clarify, it's okay to expose this key, right? |
||
'BCAlBO-AnqZIo_3MEiR5zEwJTFNNWBR6MdmZ5RpStXxTN6vfgUV2mL3c_hz8vQkcQ2bb_a7IMlGUhAnaw3eBZm4'; | ||
// Firebase configuration | ||
const firebaseConfig = { | ||
apiKey: 'AIzaSyCCW7gmWZli24N61NShh-8ALxVy3WtjqNU', | ||
authDomain: 'simplq-fe712.firebaseapp.com', | ||
projectId: 'simplq-fe712', | ||
storageBucket: 'simplq-fe712.appspot.com', | ||
messagingSenderId: '348531792421', | ||
appId: '1:348531792421:web:c481f1740405522d0f3dcc', | ||
measurementId: 'G-8N2SDV8VF5', | ||
}; | ||
|
||
// Initialize Firebase | ||
const firebaseApp = initializeApp(firebaseConfig); | ||
const messaging = getMessaging(firebaseApp); | ||
|
||
/** | ||
* React hook that lets you register for notifications. | ||
*/ | ||
export function useRegisterForNotifications() { | ||
const linkDevice = useLinkDevice(); | ||
|
||
const registerForNotifications = () => { | ||
getToken(messaging, { vapidKey }) | ||
.then((deviceId) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, is deviceId generated internally by firebase? Is it unique for every device? |
||
store.dispatch(linkDevice(deviceId)); | ||
}) | ||
.catch((ex) => { | ||
store.dispatch(setErrorPopupMessage('An error occurred while setting up notifcations.')); | ||
raiseException(ex, 'firebase/registerNotifications'); | ||
}); | ||
}; | ||
|
||
return registerForNotifications; | ||
} | ||
|
||
// TODO | ||
export function useDeregisterNotifications() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
/* eslint-disable no-param-reassign */ | ||
import { createSlice } from '@reduxjs/toolkit'; | ||
import { createQueue, deleteQueue, joinQueue } from 'store/asyncActions'; | ||
import { createQueue, deleteQueue, joinQueue, linkDevice } from 'store/asyncActions'; | ||
|
||
function isRejectedAction(action) { | ||
return action.type.endsWith('rejected'); | ||
|
@@ -11,7 +11,9 @@ const appSlice = createSlice({ | |
initialState: { | ||
errorText: '', | ||
infoText: '', | ||
notificationPermission: null, // This state value is initialised by the notification service. | ||
// This value is initilised at start by services/notification/system.js | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: typo for initialised |
||
notificationPermission: null, | ||
firebaseNotificationDeviceId: null, | ||
}, | ||
reducers: { | ||
setErrorPopupMessage: (state, action) => { | ||
|
@@ -44,6 +46,10 @@ const appSlice = createSlice({ | |
.addCase(deleteQueue.fulfilled, (state, action) => { | ||
state.infoText = `Deleted ${action.payload.queueName}`; | ||
}) | ||
.addCase(linkDevice.fulfilled, (state, action) => { | ||
state.notificationPermission = true; | ||
state.firebaseNotificationDeviceId = action.payload.deviceId; | ||
}) | ||
.addMatcher(isRejectedAction, (state, action) => { | ||
// All failed network calls are handled here | ||
state.errorText = action.error.message; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { createAsyncThunk } from '@reduxjs/toolkit'; | ||
import { useMakeAuthedRequest } from 'api/auth'; | ||
import * as RequestFactory from 'api/requestFactory'; | ||
|
||
const typePrefix = 'linkDevice/action'; | ||
|
||
/** | ||
* A hook to access the linkDevice async action creator. | ||
* | ||
* @returns — linkDevice async action creator | ||
*/ | ||
const useLinkDevice = () => { | ||
const makeAuthedRequest = useMakeAuthedRequest(); | ||
|
||
const linkDevice = createAsyncThunk(typePrefix, async (deviceId) => { | ||
return makeAuthedRequest(RequestFactory.linkDevice(deviceId)); | ||
}); | ||
|
||
return linkDevice; | ||
}; | ||
|
||
const linkDevice = createAsyncThunk(typePrefix); | ||
|
||
export { linkDevice, useLinkDevice }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking this resource should be named something else. I feel that owner doesn't fit its purpose. Do you think
/notifications/subscribe
would fit?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another approach I can think of is simply a
/notifications
endpoint with bodyfor PUT and PATCH.