Skip to content

Commit

Permalink
Upgrade Firebase FCM to v1 (latest Firebase api)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchdowney committed Sep 10, 2024
1 parent ac17233 commit e73640b
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .example.env
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ RATE_LIMITER_MAX_OVERRIDE=50
PODPING_HIVE_ACCOUNT=
PODPING_HIVE_POSTING_KEY=

FCM_GOOGLE_API_AUTH_TOKEN=
FCM_GOOGLE_API_PATH_TO_AUTH_JSON=

MAINTENANCE_MODE_ENABLED= # lowercase true to enable
MAINTENANCE_MODE_DOWNTIME_EXPECTED= # in minutes
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "podverse-api",
"version": "4.16.21",
"description": "Data API, database migration scripts, and backend services for all Podverse models.",
"private": true,
"contributors": [
"Mitch Downey"
],
Expand Down Expand Up @@ -176,6 +177,7 @@
"eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "1.x",
"follow-redirects": "^1.15.6",
"google-auth-library": "^9.14.1",
"googleapis": "45.0.0",
"http-errors": "1.7.3",
"husky": "3.1.0",
Expand Down
6 changes: 3 additions & 3 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export interface Config {
rateLimiterMaxOverride: any
manticore: any
twitterAPIBearerToken: string
fcmGoogleApiAuthToken: string
fcmGoogleApiPathToAuthJson: string
minimumMobileVersion: string
podping: {
hiveAccount: string
Expand Down Expand Up @@ -124,7 +124,7 @@ const podcastIndexBaseUrl = process.env.PODCAST_INDEX_BASE_URL || ''
// default 1 hour (3600000 milliseconds)
const podcastIndexRecentlyUpdatedSinceTime = process.env.PODCAST_INDEX_RECENTLY_UPDATED_SINCE_TIME || '3600000' // 1 hour
const podcastIndexNewFeedsSinceTime = process.env.PODCAST_INDEX_NEW_FEEDS_SINCE_TIME || '43200000' // half a day
const fcmGoogleApiAuthToken = process.env.FCM_GOOGLE_API_AUTH_TOKEN || ''
const fcmGoogleApiPathToAuthJson = process.env.FCM_GOOGLE_PATH_TO_AUTH_JSON || ''

const bitpayConfig = {
apiKeyPath: process.env.BITPAY_API_KEY_PATH || '/',
Expand Down Expand Up @@ -249,7 +249,7 @@ const config: Config = {
},
twitterAPIBearerToken,
minimumMobileVersion,
fcmGoogleApiAuthToken,
fcmGoogleApiPathToAuthJson,
podping,
maintenanceMode: {
isEnabled: process.env.MAINTENANCE_MODE_ENABLED === 'true' || false,
Expand Down
61 changes: 27 additions & 34 deletions src/lib/notifications/fcmGoogleApi.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
/* eslint-disable @typescript-eslint/camelcase */
import { request } from '../request'
import { config } from '~/config'
import { getFCMTokensForPodcastId } from '~/controllers/fcmDevice'
import { generateAccessToken } from './firebaseGenerateAccessToken'
import { SendNotificationOptions } from './sendNotificationOptions'
const { fcmGoogleApiAuthToken } = config
import { config } from '~/config';
const fs = require('fs');

const fcmGoogleApiPath = 'https://fcm.googleapis.com/fcm/send'
const keyFilePath = config.fcmGoogleApiPathToAuthJson;
const key = JSON.parse(fs.readFileSync(keyFilePath, 'utf8'));

const fcmGoogleApiPath = `https://fcm.googleapis.com/v1/projects/${key.project_id}/messages:send`

export const sendFcmNewEpisodeDetectedNotification = async (options: SendNotificationOptions) => {
const { podcastId, podcastShrunkImageUrl, podcastFullImageUrl, episodeFullImageUrl, episodeId } = options
Expand Down Expand Up @@ -69,36 +73,25 @@ export const sendFCMGoogleApiNotification = async (
episodeImage?: string,
episodeId?: string
) => {
if (!fcmTokens || fcmTokens.length === 0) return
const accessToken = await generateAccessToken()

const fcmTokenBatches: any[] = []
const size = 1000
for (let i = 0; i < fcmTokens.length; i += size) {
fcmTokenBatches.push(fcmTokens.slice(i, i + size))
}

for (const fcmTokenBatch of fcmTokenBatches) {
if (fcmTokenBatch?.length > 0) {
const imageUrl = episodeImage || podcastImage
if (!fcmTokens || fcmTokens.length === 0) return

try {
await request(fcmGoogleApiPath, {
method: 'POST',
headers: {
Authorization: `key=${fcmGoogleApiAuthToken}`,
'Content-Type': 'application/json'
},
body: {
registration_ids: fcmTokenBatch || [],
for (const fcmToken of fcmTokens) {
const imageUrl = episodeImage || podcastImage
try {
await request(fcmGoogleApiPath, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: {
message: {
token: fcmToken,
notification: {
body,
title,
podcastId,
episodeId,
podcastTitle: podcastTitle,
episodeTitle: episodeTitle,
notificationType,
timeSent: new Date(),
image: imageUrl
},
data: {
Expand Down Expand Up @@ -131,12 +124,12 @@ export const sendFCMGoogleApiNotification = async (
image: imageUrl
}
}
},
json: true
})
} catch (error) {
console.log('sendFCMGoogleApiNotification error', error)
}
}
},
json: true
})
} catch (error) {
console.log('sendFCMGoogleApiNotification error', error)
}
}
}
18 changes: 18 additions & 0 deletions src/lib/notifications/firebaseGenerateAccessToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { config } from '~/config';
import { JWT } from 'google-auth-library';
const fs = require('fs');

const keyFilePath = config.fcmGoogleApiPathToAuthJson;
const key = JSON.parse(fs.readFileSync(keyFilePath, 'utf8'));

const client = new JWT(
key.client_email,
'',
key.private_key,
['https://www.googleapis.com/auth/firebase.messaging'],
);

export async function generateAccessToken() {
const token = await client.authorize();
return token.access_token;
}
61 changes: 61 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4514,6 +4514,17 @@ gaxios@^2.0.1, gaxios@^2.1.0:
is-stream "^2.0.0"
node-fetch "^2.3.0"

gaxios@^6.0.0, gaxios@^6.1.1:
version "6.7.1"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.7.1.tgz#ebd9f7093ede3ba502685e73390248bb5b7f71fb"
integrity sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==
dependencies:
extend "^3.0.2"
https-proxy-agent "^7.0.1"
is-stream "^2.0.0"
node-fetch "^2.6.9"
uuid "^9.0.1"

gcp-metadata@^3.4.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-3.5.0.tgz#6d28343f65a6bbf8449886a0c0e4a71c77577055"
Expand All @@ -4522,6 +4533,14 @@ gcp-metadata@^3.4.0:
gaxios "^2.1.0"
json-bigint "^0.3.0"

gcp-metadata@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.0.tgz#9b0dd2b2445258e7597f2024332d20611cbd6b8c"
integrity sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==
dependencies:
gaxios "^6.0.0"
json-bigint "^1.0.0"

[email protected]:
version "3.8.2"
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.8.2.tgz#aab4f280adb522fdfbdc5e5b64d718d3683f04e9"
Expand Down Expand Up @@ -4732,6 +4751,18 @@ google-auth-library@^5.2.0, google-auth-library@^5.6.1:
jws "^4.0.0"
lru-cache "^5.0.0"

google-auth-library@^9.14.1:
version "9.14.1"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.14.1.tgz#4c6f535f474b01847ea1a60ef1d56dbd6a0aad2f"
integrity sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==
dependencies:
base64-js "^1.3.0"
ecdsa-sig-formatter "^1.0.11"
gaxios "^6.1.1"
gcp-metadata "^6.1.0"
gtoken "^7.0.0"
jws "^4.0.0"

google-p12-pem@^2.0.0:
version "2.0.5"
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-2.0.5.tgz#b1c44164d567ae894f7a19b4ff362a06be5b793b"
Expand Down Expand Up @@ -4796,6 +4827,14 @@ gtoken@^4.1.0:
jws "^4.0.0"
mime "^2.2.0"

gtoken@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26"
integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==
dependencies:
gaxios "^6.0.0"
jws "^4.0.0"

handlebars@^4.4.3:
version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
Expand Down Expand Up @@ -5121,6 +5160,14 @@ https-proxy-agent@^7.0.0:
agent-base "^7.0.2"
debug "4"

https-proxy-agent@^7.0.1:
version "7.0.5"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2"
integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==
dependencies:
agent-base "^7.0.2"
debug "4"

human-signals@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
Expand Down Expand Up @@ -6301,6 +6348,13 @@ json-bigint@^0.3.0:
dependencies:
bignumber.js "^9.0.0"

json-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
dependencies:
bignumber.js "^9.0.0"

json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
Expand Down Expand Up @@ -7412,6 +7466,13 @@ [email protected], node-fetch@^2.3.0, node-fetch@^2.6.1:
dependencies:
whatwg-url "^5.0.0"

node-fetch@^2.6.9:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"

node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
Expand Down

0 comments on commit e73640b

Please sign in to comment.