As I explained earlier, isitblackfridayyet.app product needed a way to tweet once a day if it is black friday or not as an image (that makes it more challenging than just text.). @isitblackfriday was created.
Setup Firebase
On the project is set up, install Firebase CLI on your machine:
$ npm install -g firebase-tools
Sign in to Google to have access to your Firebase account.
$ firebase login
Initiate your Firebase project locally and select Functions
.
$ firebase init
? Which Firebase CLI features do you want to set up for this folder?
◯ Database: Deploy Firebase Realtime Database Rules
◯ Firestore: Deploy rules and create indexes for Firestore
❯◉ Functions: Configure and deploy Cloud Functions
◯ Hosting: Configure and deploy Firebase Hosting sites
◯ Storage: Deploy Cloud Storage security rules
Selecte whether you want to use JavaScript or TypeScript to write functions (please select TypeScript), then folow the rest of the wizard.
? What language would you like to use to write Cloud Functions?
JavaScript
❯ TypeScript
once the installation is over, you will get the following functions
folder:
Run the hello world function locally
uncomment the following from functions/src/index.ts
export const helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!");
});
run npm run serve
, you should have the hello world functions available on http://localhost:5000/<Firebase-project-id>/us-central1/helloWorld
Deploy the hello world function on the cloud
$ npm run deploy
now your function runs on Firebase servers on https://us-central1-<Firebase-project-id>.cloudfunctions.net/helloWorld
!
Let’s create something fancier now shall we?
Create an image from a Web page
Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium.
We are going to use it to create an image (png or jpg) from an online page.
Install Puppeteer
npm i puppeteer @types/puppeteer
Create a function to take a screenshot
We want that function to open https://isitblackfridayyet.app, take a screenshot of the page, save it and return the image path.
Here is the code with comments:
import * as puppeteer from "puppeteer";
import * as os from "os";
import * as fs from "fs";
import * as path from "path";
async function takeScreenshot(): Promise<string> {
// #1 create a browser instance
const browser = await puppeteer.launch({
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox"]
});
// #2 create a browser page with the required viewport
const page = await browser.newPage();
await page.setViewport({ width: 512, height: 512 });
await page.goto("https://isitblackfridayyet.app/", {
// consider navigation to be finished when there are
// no more than 0 network connections for at least 500 ms.
waitUntil: "networkidle0"
});
// #3 create a new file path
const localFile = path.join(os.tmpdir(), `${Date.now()}.png`);
// #4 generate the screenshot using this path
await page.screenshot({
type: "png",
path: localFile
});
return localFile;
}
Create a function to download the image
we can reuse the helloWorld
function to download the isitblackfridayyet.app
screenshot.
export const helloWorld = functions
.runWith({ memory: '1GB', timeoutSeconds: 60 })
.https.onRequest(async (request, response) => {
try {
// #1 call takeScreenshot function
const localFile = await takeScreenshot();
// #2 download the image
return response.download(localFile, () => {
// #3 once the image is downloaded we can erase it.
fs.unlinkSync(localFile);
});
} catch (error) {
return response.json({ error });
}
}
);
now re-run the functions locally and open http://localhost:5000/<Firebase-project-id>/us-central1/helloWorld
function. You should be able to download the image.
The real functions for isitblackfridayyet.app
are open sourced here
Trigger functions via a CRON job
We can now create images from any webpage but we still need to trigger the function manually! To fix that problem on a unix system, we would use a Cron job. But how to use a Cron job on Firebase functions ?
Firebase supports scheduling function calls via the Cloud Scheduler and pubsub.
The following two functions will run every 2 minutes. As you can see you can use the crontab way */2 * * * *
or jsut plain english.
export const scheduledFunction = functions.pubsub
.schedule("*/2 * * * *")
.onRun(context => {
console.log("This will be run every 2 minutes!");
});
export const scheduledFunctionPlainEnglish = functions.pubsub
.schedule("every 2 minutes")
.onRun(context => {
console.log("This will be run every 2 minutes!");
});
When you will try to deploy that you will get the following error message: Error: HTTP Error: 400, Billing must be enabled for activation of service '[cloudscheduler.googleapis.com]' in project 'XXXXXXXX' to proceed.
Your project must be on the Blaze payment plan, as Cloud Scheduler and Pub/Sub require billing information. Each Cloud Scheduler job costs $0.10 (USD) per month.
To enable Blaze payment plan
open the Firebase console of your project and go check the bottom left corner of the page:
Click upgrade
and select Pay as you go
on next screen:
Re-deploy your code and open GCP Cloud Scheduler page to see the two jobs:
Now go to the Firebase functions logs page to see that the functions are already running!
We now have everything that we need to schedule image creation on Firebase functions. To know exactly how @isitblackfriday does it, the code is open sourced here!