-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Resul Avan
authored and
Resul Avan
committed
Jul 16, 2020
1 parent
985f656
commit 84efaeb
Showing
16 changed files
with
415 additions
and
188 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { RequestHandler } from 'express' | ||
import { NO_CONTENT } from 'http-status-codes' | ||
import admin from 'firebase-admin' | ||
import { getDecodedIdTokenFromRequest } from './api-handler' | ||
import { ApiErrorCode, UserDevice } from '../types' | ||
import { deleteUserDevice, getPushNotification, getUserDevices } from '../service/firebase-admin-service' | ||
import { handleApiErrors } from '../service/api-error-service' | ||
import DecodedIdToken = admin.auth.DecodedIdToken | ||
|
||
export const notifyHandler: RequestHandler = async (req, res) => { | ||
console.log(`${req.originalUrl} - notifyHandler called`) | ||
await getDecodedIdTokenFromRequest(req) | ||
.then(async (decodedIdToken: DecodedIdToken) => { | ||
const notificationId = req.params.notificationId | ||
console.log(`notify request from ${decodedIdToken.uid} with notificationId ${notificationId}`) | ||
if (!notificationId) { | ||
throw new Error(ApiErrorCode.BAD_REQUEST) | ||
} | ||
console.log(`getting notification by id ${notificationId}`) | ||
|
||
const notification = await getPushNotification(notificationId) | ||
if (!notification) { | ||
throw new Error(ApiErrorCode.BAD_REQUEST) | ||
} | ||
console.log(`notification goes from ${notification.from} to ${notification.to}`) | ||
|
||
const userDevices: UserDevice[] = await getUserDevices(notification.to) | ||
if (userDevices.length <= 0) { | ||
console.log(`No userDevice for ${notification.to}`) | ||
return res.status(NO_CONTENT).send() | ||
} | ||
console.log(`${userDevices.length} userDevice(s) found for ${notification.to}`) | ||
|
||
const deviceTokens = userDevices.map(value => value.deviceToken) | ||
const payload = { | ||
data: { | ||
type: notification.notificationType | ||
} | ||
} | ||
const removeUserDevicePromises: Promise<any>[] = [] | ||
|
||
await admin.messaging().sendToDevice(deviceTokens, payload) | ||
.then((messagingDevicesResponse) => { | ||
messagingDevicesResponse.results.forEach((result, index) => { | ||
const error = result.error | ||
if (error) { | ||
console.error('Failure sending notification to', userDevices[index], error) | ||
// Cleanup the tokens who are not registered anymore. | ||
if (error.code === 'messaging/invalid-registration-token' || | ||
error.code === 'messaging/registration-token-not-registered') { | ||
removeUserDevicePromises.push(deleteUserDevice(userDevices[index])) | ||
} | ||
} | ||
}) | ||
Promise.all(removeUserDevicePromises).catch(error => console.log(error)) | ||
}).catch(error => console.log(error)) | ||
|
||
console.log(`notification sent from ${notification.from} to ${notification.to}`) | ||
|
||
return res.status(NO_CONTENT).send() | ||
}).catch((error: Error) => handleApiErrors(res, error)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
export const config = { | ||
WEBSITE_URL: 'https://nuxt-ts-firebase-auth-ssr.web.app/' | ||
WEBSITE_URL: 'https://nuxt-ts-firebase-auth-ssr.web.app/' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,83 @@ | ||
import admin from './firebase-admin-init' | ||
import { AuthUser, collection, FirebaseClaimKey, FirebaseClaims, ProviderType, User } from '../types' | ||
import DecodedIdToken = admin.auth.DecodedIdToken; | ||
import { | ||
AuthUser, | ||
collection, | ||
CollectionField, | ||
FirebaseClaimKey, | ||
FirebaseClaims, | ||
FirebaseQueryOperator, | ||
ProviderType, | ||
PushNotification, | ||
User, | ||
UserDevice, | ||
WhereClause | ||
} from '../types' | ||
import { deleteModel, getModelById, getModelsByWhereClauses } from './firestore-admin-collection-service' | ||
import DecodedIdToken = admin.auth.DecodedIdToken | ||
|
||
export const getDecodedIdToken = (idToken: string): Promise<DecodedIdToken> => { | ||
return admin.auth() | ||
.verifyIdToken(idToken) | ||
.then((decodedIdToken: DecodedIdToken) => decodedIdToken) | ||
return admin.auth() | ||
.verifyIdToken(idToken) | ||
.then((decodedIdToken: DecodedIdToken) => decodedIdToken) | ||
} | ||
|
||
export const setCustomClaims = async (uid: string, firebaseClaims: FirebaseClaims): Promise<void> => { | ||
console.log('setCustomClaims', firebaseClaims) | ||
await admin.auth().setCustomUserClaims(uid, firebaseClaims); | ||
console.log('setCustomClaims', firebaseClaims) | ||
await admin.auth().setCustomUserClaims(uid, firebaseClaims); | ||
} | ||
|
||
export const validateClaimsAndGet = async (decodedIdToken: DecodedIdToken) => { | ||
let username = decodedIdToken[FirebaseClaimKey.USERNAME] | ||
if (username) { | ||
return { username } | ||
} | ||
let username = decodedIdToken[FirebaseClaimKey.USERNAME] | ||
if (username) { | ||
return { username } | ||
} | ||
|
||
const user = await getUser(decodedIdToken.sub) | ||
if (!user) { | ||
throw new Error('User not found by id: ' + decodedIdToken.sub) | ||
} | ||
const user = await getUser(decodedIdToken.sub) | ||
if (!user) { | ||
throw new Error('User not found by id: ' + decodedIdToken.sub) | ||
} | ||
|
||
username = user.username || user.id | ||
const firebaseClaims = { username } | ||
username = user.username || user.id | ||
const firebaseClaims = { username } | ||
|
||
await setCustomClaims(decodedIdToken.sub, firebaseClaims) | ||
await setCustomClaims(decodedIdToken.sub, firebaseClaims) | ||
|
||
return firebaseClaims | ||
return firebaseClaims | ||
} | ||
|
||
export const toAuthUser = (decodedIdToken: DecodedIdToken, firebaseClaims: FirebaseClaims): AuthUser => { | ||
return { | ||
name: decodedIdToken.name, | ||
verified: decodedIdToken.email_verified as boolean, | ||
email: decodedIdToken.email as string, | ||
profilePhoto: { | ||
src: decodedIdToken.picture as string, | ||
alt: `Profile photo of ${firebaseClaims[FirebaseClaimKey.USERNAME]}` | ||
}, | ||
userId: decodedIdToken.sub, | ||
username: firebaseClaims[FirebaseClaimKey.USERNAME], | ||
providers: [{ providerType: decodedIdToken.firebase.sign_in_provider as ProviderType }] | ||
}; | ||
} | ||
|
||
export const getUser = async (uid: string): Promise<User> => { | ||
return await admin.firestore() | ||
.collection(collection.USER) | ||
.doc(uid) | ||
.get() | ||
.then((document) => { | ||
return document.data() as User | ||
}) | ||
return { | ||
name: decodedIdToken.name, | ||
verified: decodedIdToken.email_verified as boolean, | ||
email: decodedIdToken.email as string, | ||
profilePhoto: { | ||
src: decodedIdToken.picture as string, | ||
alt: `Profile photo of ${firebaseClaims[FirebaseClaimKey.USERNAME]}` | ||
}, | ||
userId: decodedIdToken.sub, | ||
username: firebaseClaims[FirebaseClaimKey.USERNAME], | ||
providers: [{ providerType: decodedIdToken.firebase.sign_in_provider as ProviderType }] | ||
}; | ||
} | ||
|
||
export const getUser = async (userId: string): Promise<User> => { | ||
return await getModelById(collection.USER, userId) | ||
} | ||
|
||
export const getPushNotification = async (notificationId: string): Promise<PushNotification> => { | ||
return await getModelById(collection.NOTIFICATION, notificationId) | ||
} | ||
|
||
export const getUserDevices = async (userId: string): Promise<UserDevice[]> => { | ||
const userWhereClause: WhereClause = { | ||
field: CollectionField.USER_DEVICE.userId, | ||
operator: FirebaseQueryOperator.EQ, | ||
value: userId | ||
} | ||
|
||
return await getModelsByWhereClauses(collection.USER_DEVICE, userWhereClause) | ||
} | ||
|
||
export const deleteUserDevice = (userDevice: UserDevice) => { | ||
return deleteModel(collection.USER_DEVICE, userDevice) | ||
} |
Oops, something went wrong.