Skip to content

Instantly share code, notes, and snippets.

@mplatts
Last active October 31, 2023 22:15
Show Gist options
  • Save mplatts/711490535fde55c97e69766cba082db8 to your computer and use it in GitHub Desktop.
Save mplatts/711490535fde55c97e69766cba082db8 to your computer and use it in GitHub Desktop.
Firebase functions cheatsheet
// Core
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.database();
admin.auth().createUser({
uid: uid,
displayName: displayName,
photoURL: photoURL
});
admin.auth().getUser(uid)
// Triggers
functions.database.ref('/queues/images/tasks/{taskId}').onWrite((event) => {})
event.timestamp // current time
event.params.userId
event.data == DeltaSnapshot
event.data.adminRef // ref that you can do anything with (rules don't apply)
event.data.current // snap()
event.data.key
event.data.previous // snap() before the write
event.data.ref
event.data.changed() // bool - has something changed in this write?
event.data.exists() // does data exist here?
event.data.exportVal() // returns JSON value
event.data.forEach((childSnap) => {}) // enumerates over children snaps
event.data.hasChild(path) // does that path have a child?
event.data.hasChildren() // bool
event.data.numChildren() // count children
event.data.toJSON() // not sure how this is different to exportVa()
event.data.val()
// Auth
functions.auth.user().onCreate((event) => {})
functions.auth.user().onDelete((event) => {})
event.data == UserRecord
event.data.uid
event.data.displayName
// HTTP
exports.date = functions.https.onRequest((req, res) => {
const formattedDate = moment().format(format);
console.log('Sending Formatted date:', formattedDate);
res.status(200).send(formattedDate);
})
// Add env vars
firebase functions:config:set someservice.key="THE API KEY" someservice.id="THE CLIENT ID"
exports.countlikechange = functions.database.ref('/posts/{postid}/likes/{likeid}').onWrite(event => {
const collectionRef = event.data.ref.parent;
const countRef = collectionRef.parent.child('likes_count');
// Return the promise from countRef.transaction() so our function
// waits for this async event to complete before it exits.
return countRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
}
else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
});
});
// If the number of likes gets deleted, recount the number of likes
exports.recountlikes = functions.database.ref('/posts/{postid}/likes_count').onWrite(event => {
if (!event.data.exists()) {
const counterRef = event.data.ref;
const collectionRef = counterRef.parent.child('likes');
// Return the promise from counterRef.set() so our function
// waits for this async event to complete before it exits.
return collectionRef.once('value')
.then(messagesData => counterRef.set(messagesData.numChildren()));
}
});
const functions = require('firebase-functions'),
admin = require('firebase-admin'),
logging = require('@google-cloud/logging')();
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')(functions.config().stripe.token),
currency = functions.config().stripe.currency || 'USD';
// [START chargecustomer]
// Charge the Stripe customer whenever an amount is written to the Realtime database
exports.createStripeCharge = functions.database.ref('/stripe_customers/{userId}/charges/{id}').onWrite(event => {
const val = event.data.val();
// This onWrite will trigger whenever anything is written to the path, so
// noop if the charge was deleted, errored out, or the Stripe API returned a result (id exists)
if (val === null || val.id || val.error) return null;
// Look up the Stripe customer id written in createStripeCustomer
return admin.database().ref(`/stripe_customers/${event.params.userId}/customer_id`).once('value').then(snapshot => {
return snapshot.val();
}).then(customer => {
// Create a charge using the pushId as the idempotency key, protecting against double charges
const amount = val.amount;
const idempotency_key = event.params.id;
let charge = {amount, currency, customer};
if (val.source !== null) charge.source = val.source;
return stripe.charges.create(charge, {idempotency_key});
}).then(response => {
// If the result is seccessful, write it back to the database
return event.data.adminRef.set(response);
}, error => {
// We want to capture errors and render them in a user-friendly way, while
// still logging an exception with Stackdriver
return event.data.adminRef.child('error').set(userFacingMessage(error)).then(() => {
return reportError(error, {user: event.params.userId});
});
}
);
});
// [END chargecustomer]]
// When a user is created, register them with Stripe
exports.createStripeCustomer = functions.auth.user().onCreate(event => {
const data = event.data;
return stripe.customers.create({
email: data.email
}).then(customer => {
return admin.database().ref(`/stripe_customers/${data.uid}/customer_id`).set(customer.id);
});
});
// Add a payment source (card) for a user by writing a stripe payment source token to Realtime database
exports.addPaymentSource = functions.database.ref('/stripe_customers/{userId}/sources/{pushId}/token').onWrite(event => {
const source = event.data.val();
if (source === null) return null;
return admin.database().ref(`/stripe_customers/${event.params.userId}/customer_id`).once('value').then(snapshot => {
return snapshot.val();
}).then(customer => {
return stripe.customers.createSource(customer, {source});
}).then(response => {
return event.data.adminRef.parent.set(response);
}, error => {
return event.data.adminRef.parent.child('error').set(userFacingMessage(error)).then(() => {
return reportError(error, {user: user});
});
});
});
// When a user deletes there account, clean up after them
exports.cleanupUser = functions.auth.user().onDelete(event => {
return admin.database().ref(`/stripe_customers/${event.data.uid}`).once('value').then(snapshot => {
return snapshot.val();
}).then(customer => {
return stripe.customers.del(customer);
}).then(() => {
return admin.database().ref(`/stripe_customers/${event.data.uid}`).remove();
});
});
// To keep on top of errors, we should raise a verbose error report with Stackdriver rather
// than simply relying on console.error. This will calculate users affected + send you email
// alerts, if you've opted into receiving them.
// [START reporterror]
function reportError(err, context = {}) {
// This is the name of the StackDriver log stream that will receive the log
// entry. This name can be any valid log stream name, but must contain "err"
// in order for the error to be picked up by StackDriver Error Reporting.
const logName = 'errors';
const log = logging.log(logName);
// https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource
const metadata = {
resource: {
type: 'cloud_function',
labels: { function_name: process.env.FUNCTION_NAME }
}
};
// https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent
const errorEvent = {
message: err.stack,
serviceContext: {
service: process.env.FUNCTION_NAME,
resourceType: 'cloud_function'
},
context: context
};
// Write the error log entry
return new Promise((resolve, reject) => {
log.write(log.entry(metadata, errorEvent), error => {
if (error) { reject(error); }
resolve();
});
});
}
// [END reporterror]
// Sanitize the error message for the user
function userFacingMessage(error) {
return error.type ? error.message : 'An error occurred, developers have been alerted';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment