0

I am trying to use a Firebase callable function to create a Twilio token for a React project. The project should allow video calls using Twilio's webRTC service. The code is based on the example here https://www.twilio.com/blog/video-chat-react-hooks. I am attempting to move the server code to obtain the Twilio token for the video service into the Firebase callable function.

My console gives the following output and errors:

error check 1: handleSubmit called
error check 2: getToken function retrieved from functions ƒ (r){return n.call(e,r,t||{})} 
error check 4: token set 
POST https://us-central1-vid-chat-app.cloudfunctions.net/getToken 500 
Uncaught (in promise) Error: INTERNAL
            at new m (error.ts:66)
            at b (error.ts:175)
            at P.<anonymous> (service.ts:244)
            at tslib.es6.js:100
            at Object.next (tslib.es6.js:82)
            at a (tslib.es6.js:71)

Here is the error from the Firebase console

Unhandled error Error: accountSid is required
    at new AccessToken (/srv/node_modules/twilio/lib/jwt/AccessToken.js:213:28)
    at generateToken (/srv/index.js:9:12)
    at videoToken (/srv/index.js:23:19)
    at exports.getToken.functions.https.onCall (/srv/index.js:42:19)
    at func (/srv/node_modules/firebase-functions/lib/providers/https.js:273:32)
    at corsHandler (/srv/node_modules/firebase-functions/lib/providers/https.js:293:44)
    at cors (/srv/node_modules/cors/lib/index.js:188:7)
    at /srv/node_modules/cors/lib/index.js:224:17
    at originCallback (/srv/node_modules/cors/lib/index.js:214:15)
    at /srv/node_modules/cors/lib/index.js:219:13

These are the two main files I believe are relevant

React file

import React, { useState, useCallback } from 'react';
import firebase from './firebase'
import Lobby from './Lobby';
import Room from './Room';

const VideoChat = () => {
    const [username, setUsername] = useState('');
    const [roomName, setRoomName] = useState('');
    const [token, setToken] = useState(null);

const handleUsernameChange = useCallback(event => {
    setUsername(event.target.value);
}, []);

const handleRoomNameChange = useCallback(event => {
    setRoomName(event.target.value);
}, []);

const handleSubmit = useCallback(async event => {
  console.log("error check 1: handleSubmit called")
  event.preventDefault();
  const getToken = firebase.functions().httpsCallable('getToken')
  console.log("error check 2: getToken function retrieved from functions", getToken)
  getToken({ identity: username, room: roomName })
  .then(result => {
      console.log("error check 3: calling .then")         
      setToken(result.data)
  })
  console.log("error check 4: token set")
}, [username, roomName]);

const handleLogout = useCallback(event => {
    setToken(null);
}, []);

let render;
if (token) {
  render = (
    <Room roomName={roomName} token={token} handleLogout={handleLogout} />
  );
} else {
  render = (
    <Lobby
      username={username}
      roomName={roomName}
      handleUsernameChange={handleUsernameChange}
      handleRoomNameChange={handleRoomNameChange}
      handleSubmit={handleSubmit}
    />
  );
}
return render;
};

export default VideoChat;

Firebase function

const twilio = require("twilio");
const functions = require('firebase-functions');
const config = require('./config');

const AccessToken = twilio.jwt.AccessToken;
const { VideoGrant } = AccessToken;

const generateToken = config => {
    return new AccessToken(
        config.twilio.accountSid,
        config.twilio.apiKey,
        config.twilio.apiSecret
    );
};

const videoToken = (identity, room, config) => {
    let videoGrant;
    if (typeof room !== "undefined") {
        videoGrant = new VideoGrant({ room });
    } else {
        videoGrant = new VideoGrant();
    }
    const token = generateToken(config);
    token.addGrant(videoGrant);
    token.identity = identity;
    return token;
};

const sendTokenResponse = (token, res) => {
    res.set('Content-Type', 'application/json');
    res.send(
        JSON.stringify({
            token: token.toJwt()
        })
    );
};

exports.getToken = functions.https.onCall((data, context)=>{
    console.log('Peter error check getToken function called')
    const identity = data.identity;
    const room = data.room;
    const token = videoToken(identity, room, config);
    sendTokenResponse(token, res);
    console.log('here is the token', token)
    return token
});

Config file also contained in Firebase cloud function folder

module.exports = {
  twilio: {
    accountSid: process.env.TWILIO_ACCOUNT_SID,
    apiKey: process.env.TWILIO_API_KEY,
    apiSecret: process.env.TWILIO_API_SECRET,
    chatService: process.env.TWILIO_CHAT_SERVICE_SID,
    outgoingApplicationSid: process.env.TWILIO_TWIML_APP_SID,
    incomingAllow: process.env.TWILIO_ALLOW_INCOMING_CALLS === "true"
  }
};
4
  • You are getting a 500 status according to the error message which is an Internal Server Error. Can you check the logs of your cloud functions to check and share what it shows? If there are no error logs, which should not be the case, check if the value of the token variable is what you expect. You can view the logs for the cloud functions following the instructions in this link Commented Sep 24, 2020 at 11:52
  • Hi @RafaelLemos, thanks for the comment. I have included more information on the error. I am still at a loss as to how to fix it. Is the issue related to having the cloud function in one folder and the env file in another? More than happy to provide any additional information that might help. Commented Sep 25, 2020 at 17:08
  • I double checked I had installed dotenv in the project. It's definitely there now. The error has changed to "Failed to load resource: the server responded with a status of 500 ()" When I click on us-central1-vid-chat-app.cloudfunctions.net/getToken I get "{"error":{"message":"Bad Request","status":"INVALID_ARGUMENT"}}" Commented Sep 25, 2020 at 17:20
  • It very likely that the placement of the enviroment variable in a different folder is causing this, since what is causing the error is the accountSid not having a proper value per the error hat is being showned in the firebase functions logs, you could try logging the environment variables to confirm that. Let me know if placing the env file the same folder solves the issue. Commented Sep 29, 2020 at 16:52

1 Answer 1

1

my approach to do this is:

1) Environment Configuration: store your environment data.

firebase functions:config:set twilio.sid="The Account SID" twilio.apikey="The api key" twilio.apisecret="The api secret"

2) Firebase Function: use https.onCall

const functions = require('firebase-functions');  
const AccessToken = require('twilio').jwt.AccessToken;  
const VideoGrant = AccessToken.VideoGrant;  

*//call environment data*  
const accountSid = functions.config().twilio.accountsid;
const apiKey = functions.config().twilio.apikey;
const apiSecret = functions.config().twilio.secret;

*//function to generate token*  
const generateToken = () => {  
    return new AccessToken(accountSid, apiKey, apiSecret);  
};  

*//function to generate video grant*  
const videoToken = (identity, room) => {  
  videoGrant = new VideoGrant({ room });  
  const token = generateToken();  
  token.addGrant(videoGrant);  
  token.identity = identity;  
  return token;  
};  

*//firebase function*  
exports.twilio = functions.https.onCall((data, context) => {  
  const identity = data.identity;  
  const room = data.room;  
  const token = videoToken(identity, room);  
  return token.toJwt();  
}});  

3) React code:

const handleSubmit = useCallback( async () => {  
   try {
     const twilioRequest = firebase
        .functions()
        .httpsCallable('twilio');  
     const response = await twilioRequest({  
        identity: username,  
        room: roomName,  
      });  
      setToken(response.data);  
   } catch (err) {  
        console.log(err);  
   }  
},[username, roomName]);  

4) Allowing unauthenticated HTTP function invocation

Be sure you have permissions granted https://cloud.google.com/functions/docs/securing/managing-access-iam#allowing_unauthenticated_function_invocation

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.