Reputation: 29683
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
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
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
Reputation: 945
So I've got 2 options that I'm gonna throw out there, hoping one of them will work.
npm uninstall @angular/fire firebase --save
then install the older version, I did below:
npm install @angular/[email protected] [email protected] --save
module.exports = { ... resolve: { alias: { ['firebase/app']: path.resolve(__dirname, 'node_modules/firebase/app/dist/index.cjs.js') } } ... }
Upvotes: 2