Deploy Nuxt sitemap with Firebase - javascript

I have a sitemap created with nuxt and firebase but when I deploy the site to firebase the sitemap throws and error since I am referencing my fireInit file but my nuxt.config.file has been moved to my firebase functions folder so I can deploy it. When I try to add the fireInit file in my firebase functions folder I then get an error because I am importing outside a module. Also when I take out my dynamic routes by commenting out my async routes function the site map doesn't show my custom domain url. It shows the https://us-central1-PROJECT_NAME.cloudfunctions.net.
File Structure
functions/
- nuxt.config (prod)
- index.js
public/
- _nuxt/ (client)
src/
- nuxt.config (dev)
firebase.json
Nuxt Config
module.exports = {
modules: ['#nuxtjs/sitemap'],
sitemap: {
hostname: process.env.BASE_URL,
exclude: ['/listings/create', '/listings/edit', '/profile/**'],
async routes() {
const { db } = require('./plugins/fireInit');
const snapshots = await db.collection('listings').get();
return snapshots.docs.map((doc) => `/listings/${doc.id}`);
},
},
}
./plugins/fireInit
import firebase from 'firebase/app';
import 'firebase/firestore';
const firebaseConfig = {
apiKey: 'KEY',
authDomain: 'KEY',
projectId: 'KEY',
appId: 'KEY',
measurementId: 'KEY',
};
// init firebase
!firebase.apps.length ? firebase.initializeApp(firebaseConfig) : '';
// init services
export const db = firebase.firestore();

You can't use the firebase web in functions and import it outside a module.
You have to read Firestore via firebase-admin on your index.js
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp()
And in your sitemap config:
sitemap: {
hostname: 'YOUR HOST',
exclude: ['/listings/create', '/listings/edit', '/profile/**'],
async routes() {
const snapshot = await admin.firestore().collection('listings').get()
return snapshot.docs.map((doc) => `/listings/${doc.id}`);
},
},

Related

Webpack error when running cypress tests with cypress-firebase

I am writing cypress e2e tests for my nextjs web app, which uses firebase on the back end. I have followed the guide in the docs for setting it up using the cypress-firebase package (https://www.npmjs.com/package/cypress-firebase), but I am getting an error relating to webpack:
Webpack Compilation Error
./node_modules/cypress-firebase/lib-esm/plugin.js 18:27
Module parse failed: Unexpected token (18:27)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| export default function pluginWithTasks(cypressOnFunc, cypressConfig, adminInstance, overrideConfig) {
| // Only initialize admin instance if it hasn't already been initialized
> if (adminInstance.apps?.length === 0) {
| initializeFirebase(adminInstance, overrideConfig);
| }
Here is the contents of my cypress/support/commands.js file (I am using firebase version 9.15.0, hence the imports from firebase/compat/*):
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/database';
import 'firebase/compat/firestore';
import { attachCustomCommands } from 'cypress-firebase';
const fbConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_APP_ID
};
firebase.initializeApp(fbConfig);
attachCustomCommands({ Cypress, cy, firebase });
Here is the contents of my cypress.config.js file:
const { defineConfig } = require('cypress');
const cypressFirebasePlugin = require('cypress-firebase').plugin;
const admin = require('firebase-admin');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
chromeWebSecurity: false,
setupNodeEvents(on, config) {
return cypressFirebasePlugin(on, config, admin, { projectId: process.env.NEXT_PUBLIC_PROJECT_ID });
},
},
});
I have a CYPRESS_TEST_UID in my .env file and have downloaded the serviceAccount.json file required by firebase-admin. I am not sure what is causing this error; any help would be much appreciated.
I also have the same problem just in 3.0.1; I am not sure if it is a plugin bug.

How to use Firebase Emulator to write to Firestore, Storage?

My Firebase Function runs in the emulator and writes to the same collection and document:
import * as functions from "firebase-functions";
export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
try {
const original = snap.data().original;
const uppercase = original.toUpperCase();
return snap.ref.set({ uppercase }, { merge: true });
} catch (error) {
console.error(error);
}
});
That works great. Now I want to write to a different collection in the Firestore emulator:
import { initializeApp } from "firebase/app";
import * as functions from "firebase-functions";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";
const firebaseConfig = {
apiKey: "...",
authDomain: "triggerable-functions-project.firebaseapp.com",
projectId: "triggerable-functions-project",
storageBucket: "triggerable-functions-project.appspot.com",
messagingSenderId: "...",
appId: "..."
};
const app = initializeApp(firebaseConfig);
const db = getFirestore();
connectFirestoreEmulator(db, 'localhost', 8080);
export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
try {
const original = snap.data().original;
const uppercase = original.toUpperCase();
return db.firestore().collection('AnotherCollection').doc(context.params.docId).set({ uppercase }, { merge: true });
} catch (error) {
console.error(error);
}
});
That throws this error:
TypeError: db.firestore is not a function
I either have an incorrect Firestore DocumentReference or I didn't import a necessary module.
Let's write to Cloud Firestore. Same code, new module, different DocumentReference:
import * as admin from "firebase-admin";
...
return admin.firestore().collection('AnotherCollection').doc(context.params.docId).set({ uppercase }, { merge: true });
Same error:
TypeError: admin.firestore is not a function
Let's write to the Storage emulator:
import { getStorage, connectStorageEmulator, ref, uploadString } from "firebase/storage";
...
const storage = getStorage();
connectStorageEmulator(storage, "localhost", 9199);
...
return storage.collection('AnotherCollection').doc(context.params.docId).set({ uppercase }, { merge: true });
That throws the same error:
TypeError: storage.collection is not a function
Let's write to Cloud Storage:
const storageRef = ref(storage, 'Messages');
...
return uploadString(storageRef, uppercase);
The log says that the function executed without errors but when I go to Firebase Console I don't see the file in Cloud Storage.
Here's my final code. Why is app declared but never used?
import { initializeApp } from "firebase/app";
import * as functions from "firebase-functions";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";
import { getStorage, connectStorageEmulator, ref, uploadString } from "firebase/storage";
import * as admin from "firebase-admin";
const firebaseConfig = {
apiKey: "...",
authDomain: "triggerable-functions-project.firebaseapp.com",
projectId: "triggerable-functions-project",
storageBucket: "triggerable-functions-project.appspot.com",
messagingSenderId: "...",
appId: "..."
};
const app = initializeApp(firebaseConfig);
const db = getFirestore();
connectFirestoreEmulator(db, 'localhost', 8080);
const storage = getStorage();
const storageRef = ref(storage, 'Messages');
connectStorageEmulator(storage, "localhost", 9199);
export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
try {
const original = snap.data().original;
const uppercase = original.toUpperCase();
return uploadString(storageRef, uppercase);
} catch (error) {
console.error(error); // emulator always throws an "unhandled error": "Your function timed out after ~60s."
}
});
You're using the new(ish) modular syntax of the v9 and later SDKs when you call getFirestore. With that new API, most calls are no longer namespaced, but are top-level functions. So there are no longer any public methods on the db object you have (as the error message says), but instead you have to pass the db object when calling those top-level functions.
The equivalent of this code:
db.firestore().collection('AnotherCollection').doc(context.params.docId).set({ uppercase }, { merge: true });
Would be:
setDoc(
doc(db, 'AnotherCollection', context.params.docId),
{ uppercase }, { merge: true })
)
Which is pretty close to the code sample in the documentation on setting a document with the v9 syntax, so I recommend keeping that handy while converting the rest of the code to the new API. The Upgrade from version 8 to the modular Web SDK guide is also a good place to get started, as are the blog posts Introducing the new Firebase JS SDK and The new Firebase JS SDK is now GA
Thanks, #Frank van Puffelen! Here's my Firebase version 9 code for writing to a different directory in the emulator:
import { initializeApp } from "firebase/app";
import * as functions from "firebase-functions";
import { getFirestore, connectFirestoreEmulator, setDoc, doc } from "firebase/firestore";
const firebaseConfig = {
apiKey: "...",
authDomain: "triggerable-functions-project.firebaseapp.com",
projectId: "triggerable-functions-project",
storageBucket: "triggerable-functions-project.appspot.com",
messagingSenderId: "...",
appId: "..."
};
const firebaseApp = initializeApp(firebaseConfig);
const db = getFirestore(firebaseApp);
connectFirestoreEmulator(db, 'localhost', 8080);
export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
try {
const original = snap.data().original;
const uppercase = original.toUpperCase();
return setDoc(
doc(db, 'AnotherCollection', context.params.docId),
{ uppercase }, { merge: true }
);
} catch (error) {
console.error(error);
}
});
Commenting out the emulator
connectFirestoreEmulator(db, 'localhost', 8080);
writes to Cloud Firestore. This threw an error:
Connection GRPC stream error. Code: 7 Message: 7 PERMISSION_DENIED: Missing or insufficient permissions.
I'm not going to worry about that, I presume that the emulator isn't intended to write to the Cloud.
As for Storage, my original code executes without errors but nothing writes to Storage in the emulator. Trying Cloud Storage, my Firebase Console refuses to set up Storage.
The documentation explains why I kept getting "not a function" errors. Instead of thinking of a Firestore location as a place with an address (in the namespace), in version 9 Firestore locations are functions with parameters.
While the version 8 APIs are based on a dot-chained namespace and
service pattern, version 9's modular approach means that your code
will be organized principally around functions.
David East explained in a blog post that the old (version 8) Firebase managed namespaces on the window but version 9 uses ES modules:
Historically libraries have been loaded and managed via a namespace on
the window, such as window.firebase. This technique does not allow for
tree shaking and lacks other benefits of the JavaScript module system.

It looks like you're using the development build of the Firebase JS SDK. When deploying Firebase apps to production,

I am trying to integrate firebase with ReactJs like this.
Here is my code
import firebase from "firebase";
var firebaseConfig = {
apiKey: "", // Add API Key
databaseURL: "" // Add databaseURL
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
export const db = firebase;
var firepadRef = firebase.database().ref();
export const userName = localStorage.getItem('auth_name');
const urlparams = new URLSearchParams(window.location.search);
const roomId = urlparams.get("id");
if (roomId) {
firepadRef = firepadRef.child(roomId);
} else {
firepadRef = firepadRef.push();
}
export default firepadRef;
And now I get this warning:
It looks like you're using the development build of the Firebase JS
SDK. When deploying Firebase apps to production, it is advisable to
only import the individual SDK components you intend to use.
For the module builds, these are available in the following manner
(replace with the name of a component - i.e. auth, database,
etc):
CommonJS Modules: const firebase = require('firebase/app');
require('firebase/');
ES Modules: import firebase from 'firebase/app'; import
'firebase/';
Typescript: import firebase from 'firebase
After a new firebase.initializeApp, (assuming you are using v9 of firebase), you need to instantiate the db differently. Check your docs. I'd write your code like this instead:
// firebase file which you export the initialization
import firebase from "firebase";
var firebaseConfig = {
apiKey: "", // Add API Key
databaseURL: "" // Add databaseURL
};
// Initialize Firebase
var fire = firebase.initializeApp(firebaseConfig);
export fire.
//second js file
import firebase from ./firebase.js
import {getDatabase, ref, set} from "firebase/database";
var db = getDatabase(Firebase);
export const userName = localStorage.getItem('auth_name');
const urlparams = new URLSearchParams(window.location.search);
const roomId = urlparams.get("id");
//if you want to push to the db or something, you call the method
set(ref(db, 'users/' + userId), {
username: name,
email: email,
profile_picture : imageUrl
});
//more code here
All in all check the docs one more time. and use version 9, its implementation is the best.
https://firebase.google.com/docs/database/web/read-and-write#web-version-9_1

'Cross-Origin Request Blocked' when testing Firebase Cloud Functions using local emulator

I'm getting the following CORS error when trying to invoke my callable function locally using the firebase cloud function emulator with my react app:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://us-central1-xxxx-xxxxx.cloudfunctions.net/getSignedUrls. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 302.
Here is my cloud function:
/functions/index.js
const functions = require('firebase-functions')
const AWS = require('aws-sdk')
const fs = require('fs')
const path = require('path')
const admin = require('firebase-admin')
admin.initializeApp()
exports.getSignedUrls = functions.https.onCall((data, context) => {
const cloudfrontAccessKeyId = "XXXXXXXXX"
let cloudfrontPrivateKey = fs.readFileSync(path.join(__dirname, 'private_key.pem'))
const signer = new AWS.CloudFront.Signer(cloudfrontAccessKeyId, cloudfrontPrivateKey)
const distName = "xxxxxxxxx.cloudfront.net"
const s3Path = data.path
let cfObjectUrl = 'https://' + distName + '/' + s3Path
const twoDays = 2*24*60*60*1000
const signedUrl = signer.getSignedUrl({
url: cfObjectUrl,
expires: Math.floor((Date.now() + twoDays)/1000)
})
return {url: signedUrl}
})
Here is where I create a reference to my apps cloud functions:
/src/firebase.js
import firebase from 'firebase/compat/app'
import { getAuth } from "firebase/auth";
import { getDatabase } from "firebase/database"
import {getFunctions} from 'firebase/functions'
const app = firebase.initializeApp({
apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "xxxx-xxxx.firebaseapp.com",
databaseURL: "https://xxx-xxxx-default-rtdb.firebaseio.com",
projectId: "xxxx-xxxx",
storageBucket: "xxxx-xxxx.appspot.com",
messagingSenderId: "xxxxxxxxxxx",
appId: "1:938967916081:web:xxxxxxxxxxxxxxxxxxx",
measurementId: "G-xxxxxxxx"
})
export const auth = getAuth(app)
export const db = getDatabase(app)
export const functions = getFunctions(app)
export default app
And here is the component in which I'm attempting to call my cloud function:
/src/components/SignedUrlTest.js
import React, { Component } from 'react';
import {httpsCallable} from 'firebase/functions'
import {functions} from '../firebase'
class SignedUrlTest extends Component {
constructor(props){
super(props)
this.state = { url: null}
this.getUrl = this.getUrl.bind(this)
}
getUrl(){
var getSignedUrls = httpsCallable(functions, 'getSignedUrls');
getSignedUrls({path: "Melissa-OShaughnessy-6-1024x683.jpg"
}).then((result) => { console.log(result.data) })
.catch((error) => {
console.log(error)
})
}
render() {
return (
<div>
<button onClick={this.getUrl}>geturl</button>
</div>
);
}
}
export default SignedUrlTest;
I've poured over the docs (https://firebase.google.com/docs/functions/callable), but can't seem to figure out why I'm unable to call my function using the client SDK. I have tested a locally emulated 'onRequest' cloud function in the same environment and was able to get an appropriate response using my web browser, but I can't figure out how to invoke an 'onCall' function from my app.
Any help would be greatly appreciated :)
Did you try the solutions mentioned in this thread:
Enabling CORS in Cloud Functions for Firebase
Especially the one, going to the GCP console and setting the cloud function invoker setting to "allUsers".

initialize firebase app in multiple files with nodejs

I want to Initialize Firebase App in multiple files to organize my methods properly, but I not sure which is the best way to do so.
Below is the file system structure:
/functions
|-keys
|-methods
| |-email.js
| |-logger.js
|-node_modules
|-index.js
|-package.json
|-package-lock.json
In my index.js, I initialize 2 projects where 1 is for production and another is for OTE.:
const functions = require('firebase-functions');
var firebaseAdmin = require('firebase-admin');
var productionServiceAccount = require('./keys/production-key.json');
var oteServiceAccount = require("./keys/ote-key.json");
var prodServer = firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(productionServiceAccount),
databaseURL: 'https://production-panel.firebaseio.com'
}, "prod");
var oteServer = firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(oteServiceAccount),
databaseURL: "https://ote-panel.firebaseio.com"
}, "ote");
console.log("prodServer: ", prodServer.name, "oteServer: ", oteServer.name)
var mailer = require('./methods/email.js') //import code from method folder
var logger = require('./methods/logger.js') //import code from method folder
Below is how i handle the request whether use prod or OTE project:
let admin = req.headers.env == 'prod' ? prodServer : oteServer
Now the problem is my ./methods/logger.js want to read/write log into DB as well, but I don't know how/what to do.
Below is the `logger.js` code:
var exports = {}
exports.log = function(item, ref, env) {
let admin = env == 'prod' ? prodServer : oteServer //<--problem here
return admin.database().ref(ref).push(item)
}
module.exports = exports
Should I initialize firebase project again or import it from index.js?
-If initialize firebase project again it will say the project "name" has been used.
-If import it from index.js, then I have to export it from index.js, which when I deploy it to Firebase Function, it will become an onCall Methods..?
-If I move the initialize to another file (./method/initFirebase.js) and import it to index.js when I deploy it to Firebase Function, will it automatically initialize the firebase app?
Please advise. Thank you.
You can create one additional file like you said initFirebase.js and put your initilization and export code there.
const prodServer = firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(productionServiceAccount),
databaseURL: 'https://production-panel.firebaseio.com',
}, 'prod');
const oteServer = firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(oteServiceAccount),
databaseURL: 'https://ote-panel.firebaseio.com',
}, 'ote');
module.exports = {
firebaseApp: env === 'prod' ? prodServer : oteServer,
};
And from all other file import firebase app
const firebaseApp = require('../YOUR_initFirebase.js')
So you dont need to worry about environment in each of the files and it is working for me on google cloud functions.
Here is how you can manage multiple apps with firebase-admin on nodejs to ensure the apps are not reinitialised.
const admin = require('firebase-admin');
const service1 = require('./app1-service.json');
const service2 = require('./app2-service.json');
const apps = {
app1: null,
app2: null,
};
void init() {
// check and init app1
const initialisedApps = admin.apps.map((item) => item.name);
if (!initialisedApps.includes('app1')) {
apps.app1 = admin.initializeApp({
credential: admin.credential.cert(service1),
}, 'app1');
} else apps.app1 = admin.app('app1');
// check and init app2
if (!initialisedApps.includes('app2')) {
apps.app2 = admin.initializeApp({
credential: admin.credential.cert(service2),
}, 'app2');
} else apps.app2 = admin.app('app2');
}
The way I solved this for ts is:
index.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp(functions.config().firebase);
exports.auth = require('./methods/email.ts');
...
email.ts
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
// Get emails
exports.getEMail = functions.https.onCall(async (data, context) => {
const uid = context?.auth?.uid ? context.auth.uid : '';
const email = await admin.firestore().collection('emails').doc(uid).get();
return email;
...

Categories

Resources