Last active
May 22, 2025 01:48
-
-
Save tatelax/2535637b79d2241464d72934d312842a to your computer and use it in GitHub Desktop.
Emulate Firebase Functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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