Guruprasad J Rao
Guruprasad J Rao

Reputation: 29683

Cloud functions throws error after migrating Angular Universal firebase

I have a SPA which is hosted in Firebase and I have been using Firestore to store the data. I am also making use of cloud functions for few of my https operations and certain other database read and write.

Recently I updated my rendering logic from client side to server side with angular universal which is pretty successful. Here's the link I followed: https://fireship.io/lessons/angular-universal-firebase/

Basically, I had created a https function to render ssr in cloud functions.

const universal = require(`${process.cwd()}/dist/server`).app;
exports.api = functions.https.onRequest(app); //Application related endpoint line makepayment, createorder etc.,
exports.ssr = functions.https.onRequest(universal);

Now, once I deploy this function, all the api https function, where I used to access db.collection or db.doc started throwing error. Below is the same call for db.doc.

db.doc("samplecollection/docid")
.get()
.then(function (doc) {
   console.log('doc.data', doc.data());
})
.catch(function (error) {
   console.log('Error in fetching samplecollection doc', error);
});

Now when I try to do above I get the below error.

Error in autogenerated TypeError [ERR_INVALID_ARG_TYPE]: The "path"
 argument must be of type string. Received type object
      at validateString (internal/validators.js:125:11)
      at Object.basename (path.js:744:5)
      at GrpcClient.loadProto (sampleproject\functions\node_modules\google-gax\build\src\grpc.js:133:29)
      at new FirestoreClient (sampleproject\functions\node_modules\@google-cloud\firestore\build\src\v1\firestore_client.js:121:32)
      at ClientPool.Firestore._clientPool.pool_1.ClientPool [as clientFactory]
 (sampleproject\functions\node_modules\@google-cloud\firestore\build\src\index.js:302:26)
      at ClientPool.acquire (sampleproject\functions\node_modules\@google-cloud\firestore\build\src\pool.js:67:35)
      at ClientPool.run (sampleproject\functions\node_modules\@google-cloud\firestore\build\src\pool.js:124:29)
      at Firestore.readStream (sampleproject\functions\node_modules\@google-cloud\firestore\build\src\index.js:947:26)
      at Firestore.getAll_ (sampleproject\functions\node_modules\@google-cloud\firestore\build\src\index.js:680:14)
      at initializeIfNeeded.then (sampleproject\functions\node_modules\@google-cloud\firestore\build\src\index.js:650:61)
      at ZoneDelegate.invoke (sampleproject\functions\dist\server.js:5715:30)
      at Zone.run (sampleproject\functions\dist\server.js:5472:47)
      at sampleproject\functions\dist\server.js:6213:38
      at ZoneDelegate.invokeTask (sampleproject\functions\dist\server.js:5750:35)
      at Zone.runTask (sampleproject\functions\dist\server.js:5517:51)
      at drainMicroTaskQueue (sampleproject\functions\dist\server.js:5930:39)
      at process._tickCallback (internal/process/next_tick.js:68:7)

I am not really sure why the error says The "path" argument must be of type string. Received type object. I tried to assign path of document to a variable and check its type with typeof, it still says as string.

Key point to note is - if I remove ssr, function and dist folder copied into functions directory, everything with same line of code works properly. So, I strongly suspect, this has something to do with SSR.

I made a lot of google search for this but none of them had this set-up of SSR. Could someone please point me in right direction or let me know if anyone has faced this issue with Angular Universal SSR and found a solution?


Update - 1

I have the below option set in webpack.server.config.js file. Will that be a problem?

node: {
    __dirname: false
},

Update - 2

Here's the firebase.json configured for development and production environment.

{
  "hosting": [
    {
      "public": "dist/browser",
      "target": "staging",
      "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
      "rewrites": [
        {
          "source": "**",
          "function": "ssr"
        }
      ],
      "headers": [
        {
          "source": "**/*.@(jpg|jpeg|gif|png|svg|js|css|css?*|eot|otf|ttf|ttc|woff|woff2)",
          "headers": [
            {
              "key": "Access-Control-Allow-Origin",
              "value": "*"
            },
            {
              "key": "Cache-Control",
              "value": "max-age=31536000"
            }
          ]
        },
        {
          "source": "404",
          "headers": [
            {
              "key": "Cache-Control",
              "value": "max-age=7200"
            }
          ]
        }
      ]
    },
    {
      "public": "dist",
      "target": "production",
      "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
      "rewrites": [
        {
          "source": "**",
          "destination": "/index.html"
        }
      ]
    }
  ],
  "functions": {
    "include": ["**/views/**"]
  }
}

Right now, I have just configured this in staging i.e. [development environment] for testing, but this issue affects even production environment since cloud functions will remain same for both.

Update - 3

Upon further investigation, I noticed that the firestore_client.js present within node_modules of functions dependencies, under the path node_modules/@google-cloud/firestore/build/src/v1 has below piece of code to identify whether its a browser or not.

const isBrowser = typeof window !== 'undefined';
if (isBrowser) {
    opts.fallback = true;
}
.....
.....
.....
// Load the applicable protos.
// For Node.js, pass the path to JSON proto file.
// For browsers, pass the JSON content.
const nodejsProtoPath = path.join(__dirname, '..', '..', 'protos', 'protos.json');
const protos = gaxGrpc.loadProto(opts.fallback ? require('../../protos/protos.json') : nodejsProtoPath);

So the firestore_client.js identifies whether this is a browser or not by checking whether the typeof window !== undefined. But, in my server.ts of my SPA, I have defined global['window'] = win from domino library. This was added because it would throw window is not defined ReferenceError.

Basically, that means, the firestore_client.js is able to determine window object via the server.js, generated and kept within functions folder and hence it passes protos.json file content which is an object, instead of the path. Reading the comments above written in the said file, I feel that a path to the file should be passed here instead of the object for the nodejs environment.

Any idea, how I can overcome this now?

Upvotes: 5

Views: 1078

Answers (3)

Robin Dijkhof
Robin Dijkhof

Reputation: 19288

Not the answer you are hoping for, but posting it anyway.

A while ago I tried basically the same. I used Firebase authentication and Realtime Database instead of Firestore. Also, I used AngularFire2. I'm not sure if you are using this as well. The build was fine, however I'd always get runtime exceptions.

I found multiple statements claiming Firebase authentication and realtime datebase could not work with ssr since it depends on some browser functionality. Unfortunately I can't find them now. Not sure if this is firebase related or AngularFire related.

I search for multiple tutorial/posts about ssr combined with realtime database. I couldn't find any but one. This guide explains how to use Firestore with SSR. The difference is they are using AngularFireLite instead of AngularFire.

Upvotes: 4

Guruprasad J Rao
Guruprasad J Rao

Reputation: 29683

So, after days of investigation, I had to follow a dirty approach for the SPA to work in cloud functions as an SSR application.

As I mentioned in my last update on the post, I had to make opts.fallback as false for all the cases. I did this on being sure that @google-cloud will be there only for functions in my application. Now it looks something like below:

const isBrowser = typeof window !== 'undefined';
if (isBrowser) {
    opts.fallback = true;
}

opts.fallback = false; //Added this line.

Now, everything works fine. But, ultimately, this is the dirtiest approach.

I am still confused on certain things under @google/cloud as I noticed that there is also firestore_admin_client.js file and not really sure when this will get called. Also, I am worried if this approach causes any security loopholes in the future.

Anyways, I'll keep posted if anything I find in the future, upon debugging this.

Well, the above approach seems to fail when I deploy the function to cloud. This will only work in local with firebase serve

Upvotes: 0

Shlomo Koppel
Shlomo Koppel

Reputation: 945

So I've got 2 options that I'm gonna throw out there, hoping one of them will work.

  1. try an older version: uninstall current installation:
npm uninstall @angular/fire firebase --save

then install the older version, I did below:

npm install @angular/[email protected] [email protected] --save
  1. Not sure if you have this in your webpack.server.config:
module.exports = {
...
 resolve: {
    alias: {
      ['firebase/app']: path.resolve(__dirname, 'node_modules/firebase/app/dist/index.cjs.js')
    }
  }
...
}

Upvotes: 2

Related Questions