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.
Related
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.
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}`);
},
},
I currently have a project set up through VueCLI and firebase-tools and can't seem to be able to attach the Firebase Auth emulator to my project locally.
My Firebase Set-up file:
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
import 'firebase/storage';
const configOptions = {
apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
authDomain: process.env.VUE_APP_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.VUE_APP_FIREBASE_DB_URL,
projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.VUE_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.VUE_APP_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.VUE_APP_FIREBASE_APP_ID,
measurementId: process.env.VUE_APP_FIREBASE_MEASUREMENT_ID
};
firebase.initializeApp(configOptions);
if (process.env.NODE_ENV === "development"){
firebase.firestore().settings({ host: 'localhost:8080', ssl: false });
firebase.auth().useEmulator('http://localhost:9099/');
}
export const firebaseauth = firebase.auth();
export const firestore = firebase.firestore();
export const firebasestorage = firebase.storage();
export default firebase;
My .env.development file
VUE_APP_I18N_LOCALE=en
VUE_APP_I18N_FALLBACK_LOCALE=en
VUE_APP_FIREBASE_API_KEY="xx"
VUE_APP_FIREBASE_AUTH_DOMAIN="localhost:9099"
VUE_APP_FIREBASE_DB_URL="http://localhost:4000"
VUE_APP_FIREBASE_PROJECT_ID="xx"
VUE_APP_FIREBASE_STORAGE_BUCKET="xx"
VUE_APP_FIREBASE_MESSAGING_SENDER_ID="xx"
VUE_APP_FIREBASE_APP_ID="xx"
VUE_APP_FIREBASE_MEASUREMENT_ID="xx"
When navigating to localhost:5000 (emulated hosting), I get the error:
Uncaught TypeError: firebase_app__WEBPACK_IMPORTED_MODULE_0___default.a.auth(...).useEmulator is not a function
useEmulator comes directly from Google's Firebase Documentation so I'm unsure what I'm doing incorrectly.
It may be that you're still using a firebase version older than version 8.0.0, in that case the method you'd want to call is the .useFunctionsEmulator method (deprecated since v8.0.0):
firebase.functions().useFunctionsEmulator('http://localhost:5001');
If you are using the v8 SDK, however, here is how you can connect your app to the emulator:
firebase.auth().useEmulator("http://localhost:9099"); // Connect to the Authentication emulator
firebase.functions().useEmulator("localhost", 5001); // Connect to the Cloud Functions emulator
With the JS SDK v9, you'll need to use connectFirestoreEmulator function.
See docs https://firebase.google.com/docs/emulator-suite/connect_firestore
import { initializeApp } from "firebase/app";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";
const config = {} //your config object here
const app = initializeApp(config);
// firebaseApps previously initialized using initializeApp()
const db = getFirestore();
connectFirestoreEmulator(db, 'localhost', 8080);
ufmemo's solution is correct.
A more detailed answer would be as follows.
// import 'firebase/auth';
import { connectAuthEmulator } from "firebase/auth";
if (process.env.NODE_ENV === "development"){
firebase.firestore().settings({ host: 'localhost:8080', ssl: false });
// firebase.auth().useEmulator('http://localhost:9099/');
connectAuthEmulator(firebase, 'http://localhost:9099/');
}
I'm trying to implement Google's Firebase Cloud Messaging (FCM) into my Nuxt.js APP.
So far I've installed firebase, created a firebase.js plugin inside ./plugins folder, imported and initialized firebase and the messaging service, everything seems to be working fine.
Now I'm not sure how or where to go from here..
The idea is to handle everything inside vuex, in notifications module.
I want to handle both background and foreground notifications. Background gets handled by service-worker, for the foreground I've made a simple notification component that I want to show everytime I receive a push notification from FCM.
The question:
How would I go about registering a service worker, requesting permission and handling the foreground/background notifications? I mean the exact location/file/way specific to Nuxt.js? Should I make another plugin just for that, use middleware folder or just handle everything in my default layout file?
Whats the cleanest way to go about it?
Thanks in advance!
Step 1) Install dependencies
npm install firebase
npm install #nuxtjs/firebase
Step 2) Create a file serviceWorker.js on your project's root folder.
self.addEventListener('push', function (e) {
data = e.data.json();
var options = {
body: data.notification.body,
icon: data.notification.icon,
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: '2'
},
};
Step 3) Config your nuxt.config.js as follows.
Add this line to the top of your file.
const fs = require('fs')
Update your modules array with firebase credentials.
[
'#nuxtjs/firebase',
{
config: {
apiKey: "<yourKey>",
authDomain: "<yourAuthDomain>",
projectId: "<yourProjectId>",
storageBucket: "<yourStorageBucket>",
messagingSenderId: "<yourMessagingSenderId>",
appId: "<yourAppId>",
measurementId: ",<yourMeasurementId>"
},
onFirebaseHosting: false,
services: {
messaging: {
createServiceWorker: true,
fcmPublicVapidKey: '<yourFCMPublicVapidKey>',
inject: fs.readFileSync('./serviceWorker.js')
}
}
}
]
Step 4 > Finally.. to your index.js or layout file.
async mounted() {
const currentToken = await this.$fire.messaging.getToken()
const data = JSON.stringify({
notification: {
title: 'firebase',
body: 'firebase is awesome',
click_action: 'http://localhost:3000/',
icon: 'http://localhost:3000/assets/images/brand-logo.png'
},
to: currentToken
})
const config = {
method: 'post',
url: 'https://fcm.googleapis.com/fcm/send',
headers: {
'Content-Type': 'application/json',
'Authorization': 'key=<yourServerKey>'
},
data
};
const response = await axios(config)
this.$fire.messaging.onMessage((payload) => {
console.info('Message received: ', payload)
})
this.$fire.messaging.onTokenRefresh(async () => {
const refreshToken = await this.$fire.messaging.getToken()
console.log('Token Refreshed',refreshToken)
})
}
For more details, to understand the steps, you can visit this article.
you can check the Nuxt Module Nuxt Firebase https://firebase.nuxtjs.org
the documentation is very good https://firebase.nuxtjs.org/service-options/messaging
Once you have installed #nuxtjs/firebase module and insert following code into your nuxt.config.js where you can get those data from firebase console. I placed them as a dotenv module because it is easier to manage configuration templates for different projects.
firebase: {
config: {
apiKey: dotenv.parsed.apiKey,
authDomain: dotenv.parsed.authDomain,
databaseURL: dotenv.parsed.databaseURL,
projectId: dotenv.parsed.projectId,
storageBucket: dotenv.parsed.storageBucket,
messagingSenderId: dotenv.parsed.messagingSenderId,
appId: dotenv.parsed.appId,
measurementId: dotenv.parsed.measurementId
},
onFirebaseHosting: false,
services: {
messaging: {
createServiceWorker: true,
fcmPublicVapidKey: dotenv.parsed.vapidKey // OPTIONAL : Sets vapid key for FCM after initialization
}
Once you have implemented it, the module will generate service workers by itself and you can view them on your inspect element console.
All done and said,
In your vuex store, just call this.$fire.messaging.getToken() to ask users for permission to receive notification.
You can initiate your receiving message with this function below where you call this.$fire.messaging.getToken()
messaging.onMessage(function (payload) {
context.dispatch('yourDesireDispatchFunction', payload)
})
I'm using Nuxt with Firebase and Vue. However, I'm not getting past this error:
Nuxt.js Error:
TypeError: WEBPACK_IMPORTED_MODULE_0_firebase___default.a.storage is not a function
at Object.61 (assets/firebase-config.js:15:28)
at __webpack_require (webpack:/webpack/bootstrap f85942abf3210b58a241:25:0)
at Object.63 (0.server-bundle.js:70:85)
at webpack_require (webpack:/webpack/bootstrap f85942abf3210b58a241:25:0)
at Object.60 (pages/index.vue:7:0)
at webpack_require (webpack:/webpack/bootstrap f85942abf3210b58a241:25:0)
at
EDIT: Also get this error when refreshing now:
Nuxt.js Error:
TypeError: WEBPACK_IMPORTED_MODULE_2__assets_firebase_config_js.b.auth is not a function
at VueComponent.isAuthenticated (2.server-bundle.js:1277:82)
The setup is straight out of the vue-cli with two pages in the pages directory and firebase-config.js in assets:
-assets
|- firebase-config.js
-pages
|- index.vue
|- businesses.vue
Here is the contents of firebase-config.js:
import firebase from 'firebase'
if (firebase.apps.length === 0) {
firebase.initializeApp({
apiKey: '...',
authDomain: '...',
databaseURL: '...',
storageBucket: '...',
messagingSenderId: '...'
})
}
const db = firebase.database()
const sphinx = firebase.auth()
const storehouse = firebase.storage()
export { db, sphinx, storehouse }
And how this ties into businesses.vue:
import Vue from 'vue'
import VueFire from 'vuefire'
import { db, sphinx, storehouse } from '~assets/firebase-config.js'
import _ from 'lodash'
Vue.use(VueFire)
Any insight into why this is occurring?