From c5a9f8d061df2442eec19bbc0b7186a27d5089b5 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Sat, 19 Feb 2022 16:36:41 -0800 Subject: [PATCH 1/2] add manage FCM tokens sample --- fcm-manage-tokens/README.md | 33 ++++++ fcm-manage-tokens/firebase.json | 1 + fcm-manage-tokens/functions/.eslintrc.json | 122 +++++++++++++++++++++ fcm-manage-tokens/functions/index.js | 58 ++++++++++ fcm-manage-tokens/functions/package.json | 25 +++++ 5 files changed, 239 insertions(+) create mode 100644 fcm-manage-tokens/README.md create mode 100644 fcm-manage-tokens/firebase.json create mode 100644 fcm-manage-tokens/functions/.eslintrc.json create mode 100644 fcm-manage-tokens/functions/index.js create mode 100644 fcm-manage-tokens/functions/package.json diff --git a/fcm-manage-tokens/README.md b/fcm-manage-tokens/README.md new file mode 100644 index 0000000000..babec4d882 --- /dev/null +++ b/fcm-manage-tokens/README.md @@ -0,0 +1,33 @@ +# Manage FCM registration tokens + +This sample demonstrates how FCM registration tokens should be +managed on the server side. + +1. Clients should regularly send valid tokens to the server and +the server updates their corresponding timestamp. +2. The server should periodically prune stale tokens. Unsubscribing +from topics then deleting them. + + +## Functions Code + +See file [functions/index.js](functions/index.js) for the code. + +Storing and pruning the tokens is done using the [Firebase Admin SDK](https://www.npmjs.com/package/firebase-admin). + +The dependencies are listed in [functions/package.json](functions/package.json). + + +## Trigger rules + +- updateToken is triggered... +- pruneTokens is triggered... + + + +## Setup and test this sample section + +To deploy and test the sample: + + - TODO(kroikie): describe usage of sample + diff --git a/fcm-manage-tokens/firebase.json b/fcm-manage-tokens/firebase.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/fcm-manage-tokens/firebase.json @@ -0,0 +1 @@ +{} diff --git a/fcm-manage-tokens/functions/.eslintrc.json b/fcm-manage-tokens/functions/.eslintrc.json new file mode 100644 index 0000000000..e0938b4ad9 --- /dev/null +++ b/fcm-manage-tokens/functions/.eslintrc.json @@ -0,0 +1,122 @@ +{ + "parserOptions": { + // Required for certain syntax usages + "ecmaVersion": 2017 + }, + "plugins": [ + "promise" + ], + "extends": "eslint:recommended", + "rules": { + // Removed rule "disallow the use of console" from recommended eslint rules + "no-console": "off", + + // Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules + "no-regex-spaces": "off", + + // Removed rule "disallow the use of debugger" from recommended eslint rules + "no-debugger": "off", + + // Removed rule "disallow unused variables" from recommended eslint rules + "no-unused-vars": "off", + + // Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules + "no-mixed-spaces-and-tabs": "off", + + // Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules + "no-undef": "off", + + // Warn against template literal placeholder syntax in regular strings + "no-template-curly-in-string": 1, + + // Warn if return statements do not either always or never specify values + "consistent-return": 1, + + // Warn if no return statements in callbacks of array methods + "array-callback-return": 1, + + // Requre the use of === and !== + "eqeqeq": 2, + + // Disallow the use of alert, confirm, and prompt + "no-alert": 2, + + // Disallow the use of arguments.caller or arguments.callee + "no-caller": 2, + + // Disallow null comparisons without type-checking operators + "no-eq-null": 2, + + // Disallow the use of eval() + "no-eval": 2, + + // Warn against extending native types + "no-extend-native": 1, + + // Warn against unnecessary calls to .bind() + "no-extra-bind": 1, + + // Warn against unnecessary labels + "no-extra-label": 1, + + // Disallow leading or trailing decimal points in numeric literals + "no-floating-decimal": 2, + + // Warn against shorthand type conversions + "no-implicit-coercion": 1, + + // Warn against function declarations and expressions inside loop statements + "no-loop-func": 1, + + // Disallow new operators with the Function object + "no-new-func": 2, + + // Warn against new operators with the String, Number, and Boolean objects + "no-new-wrappers": 1, + + // Disallow throwing literals as exceptions + "no-throw-literal": 2, + + // Require using Error objects as Promise rejection reasons + "prefer-promise-reject-errors": 2, + + // Enforce “for” loop update clause moving the counter in the right direction + "for-direction": 2, + + // Enforce return statements in getters + "getter-return": 2, + + // Disallow await inside of loops + "no-await-in-loop": 2, + + // Disallow comparing against -0 + "no-compare-neg-zero": 2, + + // Warn against catch clause parameters from shadowing variables in the outer scope + "no-catch-shadow": 1, + + // Disallow identifiers from shadowing restricted names + "no-shadow-restricted-names": 2, + + // Enforce return statements in callbacks of array methods + "callback-return": 2, + + // Require error handling in callbacks + "handle-callback-err": 2, + + // Warn against string concatenation with __dirname and __filename + "no-path-concat": 1, + + // Prefer using arrow functions for callbacks + "prefer-arrow-callback": 1, + + // Return inside each then() to create readable and reusable Promise chains. + "promise/always-return": 2, + + //Enforces the use of catch() on un-returned promises + "promise/catch-or-return": 2, + + // Warn against nested then() or catch() statements + "promise/no-nesting": 1 + } +} diff --git a/fcm-manage-tokens/functions/index.js b/fcm-manage-tokens/functions/index.js new file mode 100644 index 0000000000..92e3ba45ce --- /dev/null +++ b/fcm-manage-tokens/functions/index.js @@ -0,0 +1,58 @@ +/** + * Copyright 2022 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +const admin = require('firebase-admin'); +const functions = require('firebase-functions'); +const {write} = require("firebase-functions/logger"); +admin.initializeApp(); + +const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 30; // 30 days + +/** + * Callable function that stores/updates the freshness timestamp associated with the + * submitted FCM registration token. + * + * Note: Most FCM registration tokens would be associated with a user. In that case + * there should be some mapping between the user and the token(s) when stored. + */ +exports.updateToken = functions.https.onCall(async (data, context) => { + const registrationToken = data['fcm_token']; + await admin.firestore().collection('tokens').doc(registrationToken).set( + {timestamp: Date.now()}, + {merge: true} + ); +}); + +/** + * Scheduled function that runs once a day. It retrieves all stale tokens then + * unsubscribes them from 'topic1' then deletes them. + */ +exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (context) => { + const staleTokensResult = await admin.firestore().collection('tokens') + .where("timestamp", "<", Date.now() - EXPIRATION_TIME) + .get(); + + const staleTokens = staleTokensResult.docs.map(staleTokenDoc => staleTokenDoc.id); + + await admin.messaging().unsubscribeFromTopic(staleTokens, 'topic1'); + + const deletePromises = []; + for (const staleTokenDoc of staleTokensResult.docs) { + deletePromises.push(staleTokenDoc.ref.delete()); + } + await Promise.all(deletePromises); +}); diff --git a/fcm-manage-tokens/functions/package.json b/fcm-manage-tokens/functions/package.json new file mode 100644 index 0000000000..82b942e37c --- /dev/null +++ b/fcm-manage-tokens/functions/package.json @@ -0,0 +1,25 @@ +{ + "name": "developer-motivator-functions", + "description": "A simple developer motivator using Cloud Function and firebase analytics", + "dependencies": { + "firebase-admin": "^10.0.0", + "firebase-functions": "^3.16.0" + }, + "devDependencies": { + "eslint": "^6.8.0", + "eslint-plugin-promise": "^4.2.1" + }, + "scripts": { + "lint": "./node_modules/.bin/eslint --max-warnings=0 .", + "serve": "firebase emulators:start --only functions", + "shell": "firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log", + "compile": "cp ../../tsconfig.template.json ./tsconfig-compile.json && tsc --project tsconfig-compile.json" + }, + "engines": { + "node": "14" + }, + "private": true +} From 951c36d10e1363de5bba7164d865bcf3af1ca79d Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Sat, 19 Feb 2022 16:41:29 -0800 Subject: [PATCH 2/2] update pruneTokens comment --- fcm-manage-tokens/functions/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fcm-manage-tokens/functions/index.js b/fcm-manage-tokens/functions/index.js index 92e3ba45ce..371ecfce26 100644 --- a/fcm-manage-tokens/functions/index.js +++ b/fcm-manage-tokens/functions/index.js @@ -40,6 +40,9 @@ exports.updateToken = functions.https.onCall(async (data, context) => { /** * Scheduled function that runs once a day. It retrieves all stale tokens then * unsubscribes them from 'topic1' then deletes them. + * + * Note: topic1 is an example topic here. It is up to the developer to unsubscribe + * all topics the token is subscribed to. */ exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (context) => { const staleTokensResult = await admin.firestore().collection('tokens')