4

I am trying to figure out if it is possible to add events to a user's google calendar server side via Firebase.

I have read this and this which seems to be what I am trying to achieve but it explains that the user which I'd like to add events to their calendar should share their calendar with the account I create for my application.

Is that true or am I misunderstanding something?

I also appreciate it if there is any guide for JavaScript/NodeJS.

TheBen
  • 3,410
  • 3
  • 26
  • 51

1 Answers1

11

No. Firebase does not have any built in functionality for adding events to Google calendars. But it's not particularly difficult to connect the two APIs. Below are a couple additional ideas.

Use Functions

One elegant solution would be to use Functions to achieve this by triggering a Functions event by any means (HTTP, a database write, etc) and calling the Calendar API accordingly.

The steps would look something like the following:

  1. When authenticating to Google OAuth on the client, add the calendar scope ('https://www.googleapis.com/auth/calendar')
  2. When triggering the function, send the calendar payload and the Google OAuth token

Inside Cloud Functions for Firebase, your trigger would look something like this:

// Example combined from these docs:
//  https://developers.google.com/calendar/v3/reference/events/insert#examples
//  https://cloud.google.com/solutions/authentication-in-http-cloud-functions#writing_the_cloud_function
//  https://firebase.google.com/docs/functions/http-events

//using another name other than "google" seems to cause error!!
const {google} = require('googleapis');
const calendar = google.calendar('v3');
const functions = require('firebase-functions');

// This example assumes an HTTP call
exports.addToCalendar = functions.https.onRequest((req, res) => {
  const eventData = req.query.eventData;
  const accessToken = getAccessToken(req);
  return addToCalendar(eventData, accessToken).then(() => {
     res.stats(200).send('yay');
  }).catch(e => res.status(e.code).send({error: e.message}));
});

function addEventToGoogleCalendar(eventData, accessToken) {
  const authClient = getOauthClient(accessToken);
  return new Promise((resolve, reject) => {
    calendar.events.insert({
      auth: authClient,
      calendarId: 'primary',
      resource: eventData,
    }, function(err, event) {
      if (err) {
        console.error(err);
        reject(err);
      }
      else {
        resolve();
      }
    });
  });
}

function getOauthClient(accessToken) {
  var oauth = new google.auth.OAuth2();
  oauth.setCredentials({access_token: accessToken});
  return oauth;
}

function getAccessToken(req) {
  const header = req.get('Authorization');
  if (header) {
      var match = header.match(/^Bearer\s+([^\s]+)$/);
      if (match) {
          return match[1];
      }
  }
  return null;
}

And here's some alternative Functions triggers for Realtime Database and Firestore:

// Alternative: Realtime DB trigger
exports.addToCalendar = functions.database.ref('/addToCalendar/{pushId}')
  .onWrite((event) => {
    const data = event.data.val();
    return addToCalendar(data.eventData, data.token)
      // clear from queue after write
      //.then(() => event.ref().remove());
   });

// Alternative: Firestore DB trigger
exports.addToCalendar = functions.firestore.document('addToCalendar/{pushId}')
  .onCreate((event) => {
    const data = event.data.data();
    return addTocalendar(data.eventData, data.token)
      // clear from queue after write
      //.then(() => event.data.ref.remove());
  }); 

An example eventData object would look something like this:

var event = {
  'summary': 'Google I/O 2015',
  'location': '800 Howard St., San Francisco, CA 94103',
  'description': 'A chance to hear more about Google\'s developer products.',
  'start': {
    'dateTime': '2015-05-28T09:00:00-07:00',
    'timeZone': 'America/Los_Angeles',
  },
  'end': {
    'dateTime': '2015-05-28T17:00:00-07:00',
    'timeZone': 'America/Los_Angeles',
  },
  'recurrence': [
    'RRULE:FREQ=DAILY;COUNT=2'
  ],
  'attendees': [
    {'email': 'lpage@example.com'},
    {'email': 'sbrin@example.com'},
  ],
  'reminders': {
    'useDefault': false,
    'overrides': [
      {'method': 'email', 'minutes': 24 * 60},
      {'method': 'popup', 'minutes': 10},
    ],
  },
};

Use Zapier

Zapier provides a trigger for integrating Firebase and Google Calendar: https://zapier.com/apps/firebase/integrations/google-calendar

TheBen
  • 3,410
  • 3
  • 26
  • 51
Kato
  • 40,352
  • 6
  • 119
  • 149
  • I think `getAuthClient` should be `getOauthClient`. – bojeil Mar 08 '18 at 23:17
  • Thanks a lot for the reply. I was mostly wondering about the possibility of adding events in a users Calendar on behalf of them (sort of) while they don't have to add the events manually each time and it's done server side triggered by some events and that is handled by functions then. I'm gonna make my question a bit more clear because I don't expect a built in service. – TheBen Mar 08 '18 at 23:53
  • If I'm not mistaken, your solution is for a scenario when events are added by user themselves. Correct? – TheBen Mar 09 '18 at 00:08
  • It's adding events on behalf of the user. I'm not sure there's any other practical solution. I suppose you could have end users add an owner to the calendar who can also edit (an account you control) but the alternative is to have them pass a token that you can use on their behalf (prob a better security choice since tokens expire) – Kato Mar 12 '18 at 18:30
  • 1
    why I'm getting invalid credentials when getting the access token? – Andrey Solera Feb 25 '19 at 00:52
  • @Kato Will this still work if the access token expires? I belive you need a refresh token in order for this to work. – Hudson Kim Aug 05 '20 at 07:16
  • Access tokens may expire between the time you send and the time the server processes them (an edge case to be sure). That's easily solved with a retry or if you like to over optimize, by checking the expiration before sending and refreshing ahead of time. – Kato Aug 25 '20 at 15:14