I am trying to add email to a firebase cloud function. However, when ever I add the line:
const nodemailer = require('nodemailer');
to index.js, code which previously deployed will no longer deploy. It reports that one of my functions had an error, but this can't be the case cause it works just fine with that function. Here is the full code, runs without the line just fine.
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');
admin.initializeApp();
exports.routineKick = functions.pubsub
.schedule('every 2 minutes')
.timeZone('America/New_York')
.onRun(context => {
//kickMembers();
}
);
Posting the solution as a Community Wiki since this solution was shared by an edit to the question itself by the OP.
The problem is that the app was missing the nodemailer dependency. It can be fixed by doing the following:
In the package.JSON file, located at the functions folder, add under dependencies the following: "nodemailer": "^6.4.3" or whatever version you have.
I'm using node mailer version of 6.7.0, In the "package.JSON" file, downgrade node version from 14 to 10 works for me
"engines": {
"node": "10"
},
"main": "index.js",
"dependencies": {
"firebase-admin": "^9.8.0",
"firebase-functions": "^3.14.1",
"nodemailer": "^6.7.0"
},
Related
I want to test an app that only has Google Oauth Login via AWS Cognito. Lots of guides on how to use cypress to programatically login to Cognito using AWS Amplify with a username and password, but cannot find anything on how to do it with Google Oauth.
Im trying to use cypress to click the buttons to authenticate but I think there is a click forgery protection on Google.
I have also been able to use this cypress documentation to login to Google directly and get a jwt into session storage but not sure if there is a way to pass this to Cognito.
If you're doing end to end testing, then the simplest way would be to have another non prod staging environment without the Google Oauth login in Cognito, and instead the username password login you mentioned that has working examples.
This is also a good idea, as you shouldn't be using your production user system in testing anyway.
I tried many approaches to this including getting oauth token from Google and trying to exchange that manually with Cognito for the token I needed for AWS API Gateway. I also tried cypress-social-logins without success (because of this bug)
Finally I just wrote my own cypress steps to log me into my app with a valid session. NOTE THIS WILL NOT WORK IN A CI/CD because it will probably trigger Google validations and so it is a semi-automated solution.
If in doubt I recommend Tom Roman's answer below about creating a pre-prod environment in cognito that allows username/password login instead of messing about with google login.
// /support/login.js
Cypress.Commands.add('loginByGoogle', () => {
cy.visit('http://localhost:3030')
cy.origin('https://somecognitouserpool.auth.eu-west-1.amazoncognito.com', () => {
cy.contains('button', 'Continue with Google')
.click({force: true})
})
cy.origin('https://accounts.google.com', () => {
const resizeObserverLoopError = /^[^(ResizeObserver loop limit exceeded)]/;
Cypress.on('uncaught:exception', (err) => {
/* returning false here prevents Cypress from failing the test */
if (resizeObserverLoopError.test(err.message)) {
return false;
}
});
cy.get('input#identifierId[type="email"]')
.type(Cypress.env('googleSocialLoginUsername'))
.get('button[type="button"]').contains('Next')
.click()
.get('div#password input[type="password"]')
.type(Cypress.env('googleSocialLoginPassword'))
.get('button[type="button"]').contains('Next')
.click();
});
});
// /e2e/sometest.cy.js
before(() => {
cy.loginByGoogle();
});
describe('E2E testing', () => {
it('should now have a session', () => {
})
});
You also need a .env file (because you don't want to be saving your google credentials into github)
GOOGLE_USERNAME = ''
GOOGLE_PASSWORD = ''
You also need two experimental flags (as of 14th Nov 2022)
// cypress.config.js
const { defineConfig } = require('cypress');
require('dotenv').config()
module.exports = defineConfig({
env: {
googleSocialLoginUsername: process.env.GOOGLE_USERNAME,
googleSocialLoginPassword: process.env.GOOGLE_PASSWORD
},
e2e: {
experimentalSessionAndOrigin: true,
experimentalModifyObstructiveThirdPartyCode: true
}
})
Here is my package.json so that you can see the exact packages I am using.
In particular I added the flags --headed --no-exit in order to complete 2 factor authentication manually as necessary. I have not yet figured out how to stop Google asking for this every time.
{
"name": "docs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "http-server . -p 3030",
"cy:run": "cypress run --headed --no-exit",
"test": "start-server-and-test start http://localhost:3030 cy:run"
},
"author": "",
"license": "ISC",
"devDependencies": {
"cypress": "^11.0.1",
"start-server-and-test": "^1.14.0"
},
"dependencies": {
"dot-env": "^0.0.1",
"dotenv": "^16.0.3"
}
}
Im having an annoying problem which i've spent most of my day trying to solve without any luck - Also tried searching for a solution here and on Google, but without any luck, or at least all solution tried out didn't work.
When running my function, the firebase function log is telling me:
Cannot find module 'mailgun.js'
I have no idea why it won't resolve the module as it exist in the package.json file.
From the package.json:
"dependencies": {
"cors": "^2.8.5",
"firebase-admin": "^9.8.0",
"firebase-functions": "^3.14.1",
"form-data": "^4.0.0",
"mailgun.js": "^4.1.4"
},
I've have done a npm install inside the functions folder, and it deploys without problems, so the problem is when calling the function...
The code is:
import * as functions from "firebase-functions";
import Mailgun from "mailgun.js";
// eslint-disable-next-line #typescript-eslint/no-var-requires
const formData = require("form-data");
const domain = "https://app.mailgun.com/app/sending/domains/sandboxae7cd087b3854d25a6933f1fe489b5e3.mailgun.org";
const mgClient = new Mailgun(formData);
const mg = mgClient.client({
key: "mykey",
username: "api",
public_key: "mypublickey",
});
export const sendWelcomeEmailToClient = functions.https.onCall(
async (email: string) => {
return mg.messages.create(domain, {
from: "Task System",
to: email,
subject: "Testing",
html: "Dette er en test",
});
}
);
Please note, i've also tried doing a require instead of import on mailgun.js like so, but didn't work either:
Also, the mailgun documentation says that is the way to import it: https://www.npmjs.com/package/mailgun.js
const Mailgun = require("mailgun.js");
Solved it by using SendGrid instead of MailGun, that works as a charm...
Firebase Functions onCall not working
I was recently following the Firebase tutorial series by The Net Ninja YouTube channel.
The Net Ninja Firebase Function Playlist
Firebase Functions Tutorial #5 - Callable Functions
And I got stuck in the firebase functions part, first I was not even able to deploy them because billing was enabled in my account, then I put the node version in the package.json to '8', it didn't ask for billing when I deployed the functions.
package.json
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"lint": "eslint .",
"serve": "firebase emulators:start --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "8"
},
"main": "index.js",
"dependencies": {
"firebase-admin": "^8.10.0",
"firebase-functions": "^3.6.1"
},
"devDependencies": {
"eslint": "^5.12.0",
"eslint-plugin-promise": "^4.0.1",
"firebase-functions-test": "^0.2.0"
},
"private": true
}
earlier it was
"node": "10"
After this, I'm able to deploy the functions and even run an onRequest function, but not the onCall function.
Whenever I try to call an onCall function, I don't know what happens, maybe I get an error I'm not sure.
index.js firebase function file
const functions = require('firebase-functions');
exports.randomNumber = functions.https.onRequest((request, response) => {
const number = Math.round(Math.random() * 100);
console.log(number);
response.send(number.toString());
});
exports.sayHello = functions.https.onCall((data, context) => {
console.log('its running');
return 'hello, ninjas';
});
The randomNumber runs perfectly, but sayHello never runs or whatever.
I'm calling the sayHello function from frontend
app.js my web app's javascript file
//sayHello function call
const button = document.querySelector('.call');
button.addEventListener('click', () => {
//get firebase function reference
const sayHello = firebase.functions().httpsCallable('sayHello');
sayHello().then(result => {
console.log(result.data);
}).catch(error => {
console.log(error);
});
});
I'm also initializing firebase properly in the index.html
<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="/__/firebase/7.21.1/firebase-app.js"></script>
<!-- include only the Firebase features as you need -->
<script src="/__/firebase/7.21.1/firebase-auth.js"></script>
<script src="/__/firebase/7.21.1/firebase-firestore.js"></script>
<script src="/__/firebase/7.21.1/firebase-functions.js"></script>
<!-- Initialize Firebase -->
<script src="/__/firebase/init.js"></script>
In the console of my web app, something gets logged
Error: internal
at new y (error.ts:66)
at w (error.ts:175)
at A.<anonymous> (service.ts:245)
at tslib.es6.js:100
at Object.next (tslib.es6.js:81)
at r (tslib.es6.js:71)
Can anyone at least tell what this console log means???
Please help, not able to complete the tutorial series after which I'll move on to some real projects, been stuck at this for a week now.
Thanks in advance, if can solve my problem.
Solution found
Just downgrade the javascript sdk version you are using in your front end to 7.21.0, that's it, it'll work.
The issue was as stated by #DougStevenson below that Firebase callable functions, at the current time (Oct 2020) is not working with javascript sdk 7.21.1 and 7.22.0.
Cannot invoke HttpsCallable functions anymore after upgrading to firebase 7.22.0
<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="/__/firebase/7.21.0/firebase-app.js"></script>
<!-- include only the Firebase features as you need -->
<script src="/__/firebase/7.21.0/firebase-auth.js"></script>
<script src="/__/firebase/7.21.0/firebase-firestore.js"></script>
<script src="/__/firebase/7.21.0/firebase-functions.js"></script>
<!-- Initialize Firebase -->
<script src="/__/firebase/init.js"></script>
I have found that callable functions from javascript web clients are broken with SDK version 7.21.1. If you downgrade to 7.21.0, it should work OK. The latest 7.22.0 still seems broken.
This has been filed on GitHub if you want to track it.
Update Oct 5, 2020: Apparently this has been fixed in 7.22.1.
I have decided to use firebase as a backend for authentication and basic form information with firestore.
In the past I've used this express api in cloud functions to do this, and am basing this new setup off of that. But I'm looking to just use it on a Vultr Centos server instead, to put with the rest of my api, and just make everything easier for now as i don't want to overcomplicate things (until later :P).
Now - I've just cloned this repo onto my server, and I want to test it with postman, and i'm having trouble just accessing it.
I'm not sure how to solve the issue and if anyone could point me in the right direction that would make my life so much easier!
here is the index file and the package json file currently. I've created the server.listen to try and make it work at the moment.
const functions = require("firebase-functions");
const app = require("express")();
const FBAuth = require("./util/fbAuth");
const server = require('http').createServer(app);
const cors = require("cors");
//This was recently added to try and make it all work easier!
server.listen(port, ipaddress, () => {
});
app.use(cors());
const { db } = require("./util/admin");
const {
getAllWorkflows,
...
} = require("./handlers/workflow");
const {
signup,
login,
uploadImage,
addUserDetails,
getAuthenticatedUser,
getUserDetails
} = require("./handlers/users");
// Workflow Routes
app.get("/Workflows", getAllWorkflows);
...
// user route
app.post("/user", FBAuth, addUserDetails);
app.post("/user/image", FBAuth, uploadImage);
...
// cloud functions are better than firebase library because of load time.
exports.api = functions.https.onRequest(app);
here is the package.json file.
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "10"
},
"dependencies": {
"busboy": "^0.3.1",
"cors": "^2.8.5",
"express": "^4.17.1",
"firebase": "^7.21.1",
"firebase-admin": "^8.9.0",
"firebase-functions": "^3.11.0",
"firebase-tools": "^7.11.0"
},
"devDependencies": {
"firebase-functions-test": "^0.1.6",
"node-gyp": "^5.0.7"
},
"private": true
}
With the backend i am fixing up, I use this sort of workthrough (if it helps!), which I am replacing with firebase stuff above - if that makes sense. It works currently, is accessible for signup and login functionality, the key part for me is just using firebase and firestore with it.
const config = require('../../config');
const express = require('express');
const app = express();
const server = require('http').createServer(app);
.....
server.listen(config.serverParams.port, config.serverParams.address, () => {
console.log(`Server running at http://${server.address().address}:${server.address().port}`);
});
....
app.use((req,res,next)=>{
//can reaplce * with website we want to allow access
res.header('Access-Control-Allow-Origin', 'https://moodmap.app/stats');
next();
});
....
io.on('connection', socket => {
socket.use((packet, next) => {
.....
});
});
Really appreciate any guidance on this matter!
Firebase and firestore seems like a nice way to avoid reinventing the wheel, if only i could simply type npm start, and begin testing with postman the underlying functionality :s
The API is based off another side project i did, which is largely here for those interested in exploring it more.
https://github.com/Hewlbern/LightSpeed/tree/master/sigops
Very happy to just use the easiest way forward - I don't want to have any of the libraries on the client side though, as i want to make my front end super efficient. Thanks!
Cloud Functions does not allow you to listen on any ports in order to receive requests. It automatically handles incoming connections using the URL automatically provided during deployment. All incoming requests to that URL are routed to your function callback defined by the exported function built by functions.https.onRequest().
If you require the ability to listen to specific ports, Cloud Functions is not the right product for your use case. Cloud Functions can not be deployed on custom servers - it only works on infrastructure provided by Google.
I created a simple javascript app with mojs, an animation library, and wanted to deploy it to heroku. First, I tried to "heroku create" etc and deploy the original app to heroku - the app was accessible, but the script didn't work. Second, I tried to change an app that I made following the Node.js tutorial from heroku website, by inserting a script bootstrap tag
<script src="http://cdn.jsdelivr.net/mojs/latest/mo.min.js"></script>
<script src="myburstscript.js"></script>
copying the script I made to the folder of this app
var myBurst = new mojs.Burst({
count:10,
radius: {0 :150},
angle: {0 : 180},
children : {
//fill:{'red' : 'blue'},
fill: ['red', 'purple', 'blue', 'violet'],
duration : 1000,
radius: 10,
shape: 'polygon',
delay: 'stagger(50)'
}
});
document.addEventListener('click', function(e) {
myBurst.replay();
});
then running "npm install mojs", and, as usual,
git add .
git commit -m "somedumbsh*t"
git push heroku master
But it didn't play the animation it plays on my localhost. Logs show no errors. The rest, the html part of the page, works fine. Why?
Heroku needs some server, not only the client-side code.
If you cannot start your app with:
PORT=4446 npm start
and then access it on:
http://localhost:4446/
then you won't be able to host it on Heroku.
(I'm assuming that you're using Node as indicated by the tags in your question.) It's important that your app needs to actually use the port number provided in the PORT environment variable, not just a hardcoded port number.
For example if you put all your static files (HTML, client-site JavaScript, CSS, images etc.) in a directory called html then you can use a simple server like this, e.g. called server.js:
'use strict';
const path = require('path');
const express = require('express');
const app = express();
const port = process.env.PORT || 3338;
app.use('/', express.static(path.join(__dirname, 'html')));
app.listen(port, () => console.log(`Listening on http://localhost:${port}/`));
Example package.json:
{
"name": "your-name",
"version": "0.0.0",
"description": "Your description",
"scripts": {
"start": "node server.js",
},
"author": "Your details",
"license": "MIT",
"dependencies": {
"express": "^4.15.2",
}
}
See also this example that I posted on GitHub for a complete solution that even has a Deploy to Heroku button:
https://github.com/rsp/node-live-color
It is an example that was created for this answer:
Getting data from/writing data to localhost with Express
where you can find more details on why it was written like that.