denoland / fresh

The next-gen web framework.
https://fresh.deno.dev
MIT License
12.55k stars 648 forks source link

Firebase Admin RTDB Works Locally But Not in Deno Deploy #2653

Open azroberts8 opened 1 month ago

azroberts8 commented 1 month ago

Context / Setup

I am using Firebase Admin in Fresh 1.6.8 to fetch and render values from a Firebase realtime database server-side. My project is deployed on Deno Deploy with the Fresh (with Build step) preset and the contents of my Firebase service account file are stored in the FIREBASE_ADMIN_KEY environment variable in Deno Deploy.

Goal

I would like to use Firebase Admin server-side at runtime but do not want the Firebase initialization or related code executed at build time.

Problem

My project works locally with the code provided below however when I deploy, the build process fails with the following error:

warning: Packages contained npm lifecycle scripts (preinstall/install/postinstall) that were not executed.
    This may cause the packages to not work correctly. To run them, use the `--allow-scripts` flag with `deno cache`
    (e.g. `deno cache --allow-scripts=pkg1,pkg2 <entrypoint>`):
      npm:protobufjs@7.4.0
error: Uncaught (in promise) Error: Service account object must contain a string "project_id" property.
    at new ServiceAccount (file:///home/runner/work/Stampede-v2/Stampede-v2/node_modules/.deno/firebase-admin@12.5.0/node_modules/firebase-admin/lib/app/credential-internal.js:145:19)
    at new ServiceAccountCredential (file:///home/runner/work/Stampede-v2/Stampede-v2/node_modules/.deno/firebase-admin@12.5.0/node_modules/firebase-admin/lib/app/credential-internal.js:70:15)
    at cert (file:///home/runner/work/Stampede-v2/Stampede-v2/node_modules/.deno/firebase-admin@12.5.0/node_modules/firebase-admin/lib/app/credential-factory.js:103:54)
    at file:///home/runner/work/Stampede-v2/Stampede-v2/utilities/firebase.ts:6:15
Error: Process completed with exit code 1.

This issue occurs as a result of FIREBASE_ADMIN_KEY not being accessible in the build environment but this also signifies that the build process is attempting to initialize a connection to Firebase. I only want the connection to initialize in the deployed runtime environment not in the build environment.

Code

./firebase.ts

import { initializeApp, cert, ServiceAccount } from "npm:firebase-admin/app";
import { getDatabase } from 'npm:firebase-admin/database';

const firebaseCreds: ServiceAccount = JSON.parse(Deno.env.get("FIREBASE_ADMIN_KEY") || "{}");
const firebase = initializeApp({
  credential: cert(firebaseCreds),
  databaseURL: "https://my-database-default-rtdb.firebaseio.com/"
});
const database = getDatabase(firebase);

export { database };

./routes/index.tsx

import { database } from "../firebase.js";

export const handler: Handlers = {
  async GET(_req, ctx) {
    const ref = database.ref("/test");
    const value = (await ref.get()).val();
    return ctx.render(value);
  }
}

export default function Home(props: PageProps) {
  return (
    <div>
      { props.data }
    </div>
  );
}
marvinhagemeister commented 1 month ago

To exempt code from being run during the build process, wrap it with an if statement:

if (!Deno.args.includes("build")) {
  somethingThatShouldNotRunDuringBuild();
}

Related https://github.com/denoland/fresh/issues/2240

azroberts8 commented 1 month ago

@marvinhagemeister Thank you for your reply! This solution does allow my build to pass however it presents another issue that seems to be more specific to the Deno Deploy runtime environment.

For reference, firebase.ts now looks like:

import { initializeApp, cert, ServiceAccount } from "npm:firebase-admin/app";
import { getDatabase, Database } from 'npm:firebase-admin/database';

let database: Database;
if (!Deno.args.includes("build")) {
  const firebaseCreds: ServiceAccount = JSON.parse(Deno.env.get("FIREBASE_ADMIN_KEY") || "{}");
  const firebase = initializeApp({
    credential: cert(firebaseCreds),
    databaseURL: "https://my-database-default-rtdb.firebaseio.com/"
  });
  database = getDatabase(firebase);
}

export { database };

This code still works as intended on my local machine and resolves the build issue in Deno Deploy. However, hitting this route in production causes the code to hang at the const value = (await ref.get()).val(); line in index.tsx. I've added console.log(database) just before this line to compare the output in the deployed environment and my local environment and from what I can tell they are the same which is perplexing why one environment works and the other doesn't.

I'm having trouble finding documentation using Firebase or Firebase admin in Fresh or Deno Deploy. The best resource I've found is this document which mentions importing XHR into the project to allow Firebase to work under Deno Deploy however this does not seem to resolve the issue.

dasmeet commented 2 weeks ago

Am facing the same issue.

@azroberts8 Were you able to find any solution?

dasmeet commented 2 weeks ago

It appears that Deno Deploy is preventing Firebase from utilizing WebSockets.

[2024-10-21T15:36:42.514Z]  @firebase/database: c:0:0: Shutting down all connections 
[2024-10-21T15:36:42.514Z]  @firebase/database: c:0:0: Closing realtime connection. 
[2024-10-21T15:36:42.513Z]  @firebase/database: c:0:0: Realtime connection failed. 
[2024-10-21T15:36:42.513Z]  @firebase/database: c:0:0:0 Websocket connection was disconnected. 
[2024-10-21T15:36:42.511Z]  @firebase/database: c:0:0:0 WebSocket is closing itself 
[2024-10-21T15:36:42.511Z]  @firebase/database: c:0:0:0 Network error: wss://******.firebaseio.com/.ws?v=5: connect ECONNREFUSED 35.190.***.113:443 - Local (undefined:undefined) 
[2024-10-21T15:36:42.511Z]  @firebase/database: c:0:0:0 WebSocket error.  Closing connection. 
[2024-10-21T15:36:42.468Z]  @firebase/database: c:0:0:0 Websocket connecting to wss://*******.firebaseio.com/.ws?v=5
azroberts8 commented 2 weeks ago

@dasmeet I have not found a solution yet. Is there a way to enable the use of WebSocket connections in Deno Deploy or is there a specific security or performance reason that it is blocked?