Skip to content

Instantly share code, notes, and snippets.

@tatelax
Last active May 22, 2025 01:48
Show Gist options
  • Save tatelax/2535637b79d2241464d72934d312842a to your computer and use it in GitHub Desktop.
Save tatelax/2535637b79d2241464d72934d312842a to your computer and use it in GitHub Desktop.
Emulate Firebase Functions
// Provide database URL and database name where specified
// Provide file location for your function
const express = require("express");
const admin = require("firebase-admin");
const functions = require("firebase-functions/v2");
const cron = require("node-cron");
const app = express();
// Set emulator configuration before initializing
process.env.FUNCTIONS_EMULATOR = "true";
process.env.FIRESTORE_EMULATOR_HOST = "0.0.0.0:8082";
process.env.FIREBASE_DATABASE_EMULATOR_HOST = "0.0.0.0:9001";
process.env.FIREBASE_AUTH_EMULATOR_HOST = "0.0.0.0:9099";
// PROVIDE YOUR PROJECT ID HERE
process.env.GCLOUD_PROJECT = "PROJECT_ID_HERE";
process.env.FIREBASE_EMULATOR_HUB = "127.0.0.1:4400";
// Initialize Firebase Admin
admin.initializeApp({
projectId: "PROJECT_ID_HERE",
databaseURL: "http://localhost:9001/?ns=DATABASE_NAME_HERE", // PROVIDE YOUR DATABASE NAME HERE
});
// Verify emulator connections
async function verifyEmulators() {
try {
// Test Firestore connection
await firestore.collection("test").get();
console.log("✓ Connected to Firestore emulator");
// Test RTDB connection
await rtdb.ref(".info/connected").once("value");
console.log("✓ Connected to RTDB emulator");
return true;
} catch (error) {
console.error("Error connecting to emulators:", error);
console.log("\nPlease ensure Firebase emulators are running:");
console.log("firebase emulators:start");
return false;
}
}
// Get database instances
const firestore = admin.firestore();
const rtdb = admin.database();
// Store for scheduled functions
const scheduledFunctions = new Map();
// Helper to convert Firebase scheduler syntax to cron (testing intervals)
function convertScheduleToCron(schedule) {
if (schedule === "every 1 hours") return "*/10 * * * * *"; // Every 1 minute
if (schedule === "every 24 hours") return "*/3 * * * *"; // Every 3 minutes
if (schedule === "every 5 minutes") return "*/1 * * * *"; // Every 1 minute
return schedule;
}
// Mock Firebase Functions v2 scheduler context
function createSchedulerContext() {
return {
timestamp: new Date().toISOString(),
eventId: Math.random().toString(36).substring(2),
schedule: {
retry: {
count: 3,
},
},
};
}
// Mock HTTP request context
function createMockRequest() {
return {
body: {},
query: {},
params: {},
headers: {},
get: function (header) {
return this.headers[header];
},
header: function (header) {
return this.headers[header];
},
};
}
// Mock HTTP response context
function createMockResponse() {
return {
set: () => {},
status: () => ({ send: () => {} }),
send: () => {},
json: () => {},
};
}
// Register a scheduled function
function registerScheduledFunction(schedulerConfig, handler) {
const { schedule, timeZone, retryCount } = schedulerConfig;
const cronSchedule = convertScheduleToCron(schedule);
const functionName = handler.name || "scheduledFunction";
console.log(
`Setting up ${functionName} to run with schedule: ${cronSchedule}`
);
const job = cron.schedule(
cronSchedule,
async () => {
try {
console.log(`\n🚀 Executing scheduled function: ${functionName}`);
console.log("----------------------------------------");
const context = createSchedulerContext();
const req = createMockRequest();
const res = createMockResponse();
// Store original console methods
const originalLog = console.log;
const originalError = console.error;
const originalWarn = console.warn;
const originalInfo = console.info;
// Override console methods to add prefix
console.log = (...args) => originalLog(`[${functionName}]`, ...args);
console.error = (...args) =>
originalError(`[${functionName}]`, ...args);
console.warn = (...args) => originalWarn(`[${functionName}]`, ...args);
console.info = (...args) => originalInfo(`[${functionName}]`, ...args);
// Get the raw handler from the function
const rawHandler = handler.run ? handler.run : handler;
// Execute the function
await rawHandler(req, res, context);
// Restore original console methods
console.log = originalLog;
console.error = originalError;
console.warn = originalWarn;
console.info = originalInfo;
console.log("----------------------------------------");
console.log(`✅ Successfully executed ${functionName}\n`);
} catch (error) {
console.error(`❌ Error executing ${functionName}:`, error);
if (retryCount > 0) {
console.log(`⟳ Will retry ${retryCount} more times`);
}
}
},
{
timezone: timeZone,
}
);
scheduledFunctions.set(functionName, job);
console.log(`Registered scheduled function: ${functionName}`);
return job;
}
// Add current directory to module search paths
require("module").Module._initPaths();
// PROVIDE THE FILE PATH FOR YOUR FUNCTION HERE
const someFunction = require("./src/FUNCTION_NAME_HERE");
// SPECIFY THE SCHEDULE HERE
const validatorJob = registerScheduledFunction(
{
schedule: "every 1 hours",
timeZone: "America/Los_Angeles",
retryCount: 3,
},
someFunction
);
// Endpoints to manage scheduled functions
app.get("/scheduled-functions", (req, res) => {
const functions = Array.from(scheduledFunctions.keys()).map((name) => ({
name,
status: "running",
}));
res.json(functions);
});
// Endpoint to manually trigger a function
app.post("/trigger/:functionName", async (req, res) => {
const { functionName } = req.params;
const func = scheduledFunctions.get(functionName);
if (!func) {
return res.status(404).json({ error: "Function not found" });
}
try {
await func.execute();
res.json({ message: `Manually triggered ${functionName}` });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Start the server after verifying emulators
const PORT = process.env.PORT || 6969;
async function startServer() {
console.log("Verifying emulator connections...");
const emulatorsReady = await verifyEmulators();
if (!emulatorsReady) {
console.error("Failed to connect to emulators. Exiting...");
process.exit(1);
}
app.listen(PORT, () => {
console.log("\n🚀 Local scheduler running!");
console.log(`\nEndpoints:`);
console.log(
`- View functions: http://localhost:${PORT}/scheduled-functions`
);
console.log(
`- Trigger function: curl -X POST http://localhost:${PORT}/trigger/:functionName`
);
});
}
startServer();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment