5
votes

I am working on an app based of Expo - I decided to use firebase as a backend but had come up to a blocker, I need to send push notifications to the users in the app - but Expo doesn't work with Firebase Cloud Messaging (FCM) unless I eject the Expo project and work independently with Android and ios.

To overcome this I decided to store the Expo Notification Tokens in firebase and then write a firebase cloud function in firebase to get all these tokens from the database and trigger a sendPushNotificationAsync() using the expo-server-sdk.

I understand that to do this approach my version of firebase cannot be on the Spark plan - and it isn't.

I decided to implement the solution in TypeScript so I can conform to ES6/ES7 coding standards.

The approach is - I trigger a get request to my firebase function url and this triggers the function to execute. With the code below - when I do a get request against the provided google function URL I get an error:

@firebase/database: FIREBASE WARNING: Exception was thrown by user callback. TypeError: expo.isExpoPushToken is not a function at admin.database.ref.once (/user_code/lib/index.js:24:18) at onceCallback (/user_code/node_modules/firebase-

Is there something I am doing incorrectly it doesn't seem like it is able to initialise and use functions from the Expo import, although I have included this in the package.json to be installed, imported and used.

index.ts - generated by firebase

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as Expo from 'expo-server-sdk';

admin.initializeApp();
// Create a new Expo SDK client
let expo = new Expo();

exports.pushNotifications = functions.https.onRequest((req,res) => {
  if(req.method !== 'GET'){
    return res.status(403).send('Forbidden!')
  }

  let allTokens = [];
  let messages = [];

  admin.database().ref('/all_user_push_tokens/').once('value', (snapshot) => {
    if(expo.isExpoPushToken(snapshot.val())){
      allTokens.push(snapshot.val())
    } else {
      console.log(snapshot.val())
    }
  })
  .then(() => {
    for (var token in allTokens){
      messages.push({
        to: token,
        sound: 'default',
        title: 'NXET',
        body: ' NEW ACTIVITIES ADDED!'
      })
    }
  })
  .then(() => {
    let chunks = expo.chunkPushNotifications(messages)

    async (chunks) => {
      for (let chunk of chunks) {
        try {
          await expo.sendPushNotificationsAsync(chunk);
        } catch (error){
          console.error(error);
        }
      }
    }

    return res.status(200).send('SUCCESS - NOTIFICATIONS SENT!!')

  })
  .catch(() => {
    return res.status(403).send('Forbidden!')
  });

  return res.status(200).send('Function Executing')

})

package.json

{

"name": "functions",
  "scripts": {
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "main": "lib/index.js",
  "dependencies": {
    "expo-server-sdk": "^2.3.3",
    "firebase-admin": "~5.12.0",
    "firebase-functions": "^1.0.1"
  },
  "devDependencies": {
    "typescript": "^2.5.3"
  },
  "private": true
}

Any help would be much appreciated.

1

1 Answers

2
votes

Looking quickly at the Expo server sdk, it looks like isExpoPushToken is a class method rather than an instance method, thus you should be calling the method on the class Expo than the instance expo (notice uppercase vs. lowercase).

From the Expo server sdk github page:

// Check that all your push tokens appear to be valid Expo push tokens
if (!Expo.isExpoPushToken(pushToken)) {
  console.error(`Push token ${pushToken} is not a valid Expo push token`);
  continue;
}