This code doesn't work. I'm using ReactJS instead of plain js to load the script, this part works (I get the authorization), but then the "url" is somehow mistaken
*what I want to do is to send mails by using the gmail API, searching google and the API docs, so far no luck
componentDidMount() {
this.handleSort('stocks_present', 'total_invested_EUR')();
const script = document.createElement("script");
script.src = "https://apis.google.com/js/client.js";
document.body.appendChild(script);
script.onload = () => this.sendMail(script);
}
wait = ms => new Promise((r, j) => setTimeout(r, ms));
sendMail = async (script) => {
let clientId = 'xxxxxxxx.apps.googleusercontent.com';
let apiKey = 'xxxxxxxxxxxxxxxxxxx5ZDsC3l9TY';
let scopes = 'https://www.googleapis.com/auth/gmail.send';
const google_auth = async () => {
window.gapi.client.setApiKey(apiKey);
await this.wait(1000);
console.log('starting -> authorized')
window.gapi.auth2.authorize({
client_id: clientId,
scope: scopes,
immediate: true
}, () => {
console.log('loading gmail');
window.gapi.client.load('gmail',
{
callback: () => {
console.log('loaded'); // it doesn't get here
let sendRequest = window.gapi.client.gmail.users.messages.send({
'userId': 'me',
'resource': {
'raw': window.btoa(email).replace(/\+/g, '-').replace(/\//g, '_')
}
});
sendRequest.execute();
},
onerror: (e) => console.log('error'),
timeout: 1000,
ontimeout: (e) => console.log('error')
});
});
return false;
}
let counter = 10;
while (counter > 1) {
if (script.getAttribute('gapi_processed')) {
console.log('auth gained');
google_auth();
break;
} else {
await this.wait(1000);
counter--;
console.log('waiting for gapi ' + counter);
}
}
}
I get
waiting for gapi 9
auth gained
starting -> authorized
loading gmail
and then the error
GET https://content.googleapis.com/discovery/v1/apis/gmail/%5Bobject%20Object%5D/rest?fields=kind%2Cname%2Cversion%2CrootUrl%2CservicePath%2Cresources%2Cparameters%2Cmethods%2CbatchPath%2Cid&pp=0&key=xxxxxxxxxxxxxxx3l9TY 404
where if I do it manually I get
{"error":{"errors":[{"domain":"global","reason":"notFound","message":"Not Found"}],"code":404,"message":"Not Found"}}
UPDATE: I can if I use this
window.gapi.client.load('gmail', 'v1', () => {
console.log('loaded');
let sendRequest = window.gapi.client.gmail.users.messages.send({
'userId': 'me',
'resource': {
'raw': window.btoa(email).replace(/\+/g, '-').replace(/\//g, '_')
}
});
sendRequest.execute();
console.log('message sent');
});
But in theory this way is deprecated, so how I make it work?
gapi.client.load(name, version, callback) Deprecated. Please load APIs
with discovery documents. Loads the client library interface to a
particular API. If a callback is not provided, a goog.Thenable is
returned. The loaded API interface will be in the form
gapi.client.api.collection.method. For example, the Moderator API
would create methods like gapi.client.moderator.series.list.
Related
So I am making a React app that accesses the user's Google calendar and fetch their events.
It is working fine, but the problem is that when I refresh the app, the user has to consent again, which I would like to avoid.
Is there a way for me to skip the consent, after the first time the user consent already?
On user click to log in
function handleAuthClick() {
tokenClient.callback = async (resp) => {
if (resp.error !== undefined) {
throw (resp);
}
};
if (gapi.client.getToken() === null) {
// Prompt the user to select a Google Account and ask for consent to share their data
// when establishing a new session.
tokenClient.requestAccessToken({prompt: 'consent', access_type:"offline"});
} else {
// Skip display of account chooser and consent dialog for an existing session.
tokenClient.requestAccessToken({prompt: ''});
}
}
Some other function that is used to log in that I copy from google's documentation
function gapiLoaded() {
try{
gapi.load('client', intializeGapiClient);
} catch {
console.log("Not Logged in");
}
}
/**
* Callback after the API client is loaded. Loads the
* discovery doc to initialize the API.
*/
async function intializeGapiClient() {
var API_KEY = "x";
fetch("http://localhost:5050/id").then(resp => {
// fetch("https://calendar-342103.uc.r.appspot.com/id").then(resp => {
return resp.json();
}).then((data) => {
API_KEY = data.val2;
}).then(async () => {
try{
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC],
});
gapiInited = true;
maybeEnableButtons();
} catch {
console.log("User not logged in")
}
})
}
/**
* Callback after Google Identity Services are loaded.
*/
function gisLoaded() {
var CLIENT_ID = "x";
fetch("http://localhost:5050/id").then(resp => {
// fetch("https://calendar-342103.uc.r.appspot.com/id").then(resp => {
return resp.json();
}).then((data) => {
CLIENT_ID = data.val1;
}).then(() => {
try {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '', // defined later
});
gisInited = true;
maybeEnableButtons();
} catch {
}
})
}
I want to make a call to my backend (registerSanctumFacebook method) after a facebook Graph request to get user profile info (email), however I'm getting the following error:
await is only allowed within async functions
Pretty self explanatory, the problem is, I don't know how to make graph start method to work with async-await...
const getInfoFromToken = async (token) => {
const PROFILE_REQUEST_PARAMS = {
fields: {
string: 'email',
},
};
const profileRequest = new GraphRequest(
'/me',
{token, parameters: PROFILE_REQUEST_PARAMS},
(error, user) => {
if (error) {
console.log('login info has error: ' + error);
} else {
//this.setState({userInfo: user});
console.log('user:', user);
}
},
);
new GraphRequestManager().addRequest(profileRequest).start();
let response = await registerSanctumFacebook(user.email,user.id);
};
How I call getTokenInfo method:
const loginWithFacebook = async () => {
LoginManager.logInWithPermissions(['email']).then(
login => {
if (login.isCancelled) {
console.log('Login cancelled');
} else {
AccessToken.getCurrentAccessToken().then(data => {
const accessToken = data.accessToken.toString();
console.log('accessToken',accessToken);
getInfoFromToken(accessToken);
});
}
},
error => {
console.log('Login fail with error: ' + error);
},
);
};
As per your problem statement, I think you should add await on this line
await new GraphRequestManager().addRequest(profileRequest).start();
await will only work if the function after await keyword is also async.
or declare registerSanctumFacebook as async
everyone, a newbie in programming here. I am currently taking coding camp for my summer project and I am creating a final project to:
create the meal reminder (SMS) every morning (timer trigger has to work first before HTTP trigger) --> then, users will type what ingredient they would like to have in their meal --> Twilio will call the food API to send one random recipe to the user's phone.
The only thing that works in the code is the meal reminder every 6.50 AM but next steps don't seem to work to me, not sure what's wrong.
I am kinda lost what to do since I have debugged for like a week with my mentor. Could you please enlighten me on what I should do so I can get a response whenever I type specific ingredient such as bread or rice? Because I use both HTTP trigger to call my food API and timer triggers to give the user the reminder every morning and afternoon.
Here is my HTTP trigger function:
const fetch = require('node-fetch')
const express = require('express')
const MessagingResponse = require('twilio').twiml.MessagingResponse;
const bodyParser = require('body-parser');
const app = express();
const http = require('http');
module.exports = async function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
const userInputRecipe = (req.query.userInputRecipe || (req.body && req.body.userInputRecipe));
app.post('/sms', (req, res) => {
const twiml = new MessagingResponse();
userInputRecipeFromSMS = req.body.Body;
context.log(req.body.Body);
// const userTag = (req.query.tag || (req.body && req.body.tag));
// const size = (req.query.size || (req.body && req.body.size));
let resultData = await fetch("https://spoonacular-recipe-food-nutrition-v1.p.rapidapi.com/recipes/findByIngredients?ingredients="+ userInputRecipe+"&number=1&ignorePantry=true&ranking=1", {
"method": "GET",
"headers": {
"x-rapidapi-key": process.env.foodKey,
"x-rapidapi-host": process.env.foodHost
}
}).then(response => {
context.log(response);
})
.catch(err => {
console.error(err);
});
let recipeData = await resultData;
context.log(recipeData);
//Show result to response body
context.res = {
// status: 200, /* Defaults to 200 */
//TODO: create objects for needed to show via Twilio API SMS, HTTP Timer trigger etc
body: recipeData
};
// SMS reply to update and fix
if (req.body.Body == userInputRecipeFromSMS) {
twiml.message('recipe please!' + recipeData);
} else if (req.body.Body == 'recipe yes!') {
twiml.message('I need that');
} else if (req.body.Body == 'recipe yes!' + userInputRecipe) {
twiml.message(recipeData);
} else if (req.body.Body == "") {
twiml.message("Oops, this recipe is not available. Try again by sending us another ingredient.");
} else {
twiml.message(
'Sorry, this recipe is not available. Try again by sending us another ingredient.'
);
}
res.writeHead(200, { 'Content-Type': 'text/xml' });
res.end(twiml.toString());
});
http.createServer(app).listen(1337, () => {
console.log('Express server listening on port 1337');
});
context.done();
}.catch(err => {
context.log.error("Twilio Error: " + err.message + " -- " + err.code);
context.res = {
status: 500,
body: `Twilio Error Message: ${err.message}\nTwilio Error code: ${err.code}`
};
context.done();
});
And here is my timer function:
const fetch = require ('node-fetch')
const twiAccountSid = process.env.TWILIO_SID;
const twiAuthToken = process.env.TWILIO_TOKEN;
const client = require('twilio')(twiAccountSid, twiAuthToken);
module.exports = async function (context, myTimer) {
var timeStamp = new Date().toISOString();
if (myTimer.IsPastDue)
{
context.log('JavaScript is running late!');
}
client.messages
.create({ from: process.env.SENDER_NUMBER,
body: "Morning! Time to have breakfast 😋🥘. What ingredient would you like to have?",
to: process.env.RECIPIENT_NUMBER,
mediaUrl: "https://miro.medium.com/max/700/1*OtRzMDGD0qepGUCToZHZ3Q.jpeg",
})
.then(message => {
context.log("Message sent");
context.res = {
body: 'Text successfully sent'
};
context.log('JavaScript timer trigger done!', timeStamp);
context.done();
}).catch(err => {
context.log.error("Twilio Error: " + err.message + " -- " + err.code);
context.res = {
status: 500,
body: `Twilio Error Message: ${err.message}\nTwilio Error code: ${err.code}`
};
context.done();
});
// just try to call my http trigger function
async function myMessage(){
let httpfunc = "https://serverproject1.azurewebsites.net/api/tastyapirecipe"
let resp = await fetch(httpfunc, {
method: 'GET'
});
let data = await resp.json()
return data;
}
let myResp = await myMessage()
context.res = {
myResp: myResp
}
}
This is the error that I get when I test it on Azure portal
the error
I would appreciate if any of you would like to help to figure out what's wrong with it. I will need to present this project in a week later. Thank you!
I have chat app with firebase database and Firebase cloud messaging. I can send firebase notification via console but in real scenario it should be automatic. To make automatic notification,My friend wrote Index.js (Added in cloud functions) file for me but its not sending notifications.
As per our logic function should trigger whenever there is any new entries (in any node or in any room) and fetch these values by firebase function and make post request to FCM server to make notification to receiver device (get value of receiver device from token_To).
Message
Message_From
Time
Type
token_To
Index.js
var functions = require('firebase-functions');
var admin = require('firebase-admin');
var serviceAccount = require('./demofcm-78aad-firebase-adminsdk-4v1ot-2764e7b580.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://demofcm-78aad.firebaseio.com/"
})
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
exports.setUserNode = functions.auth.user().onCreate(event => {
// ...
});
exports.notifyMsg = functions.database.ref('/{chatroom}/{mid}/')
.onWrite(event => {
if (!event.data.val()) {
return console.log('Message Deleted');
}
const getDeviceTokensPromise = admin.database().ref('/{chatroom}/{mid}/token_to').once('value');
return Promise.all([getDeviceTokensPromise]).then(results => {
const tokensSnapshot = results[0];
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
const payload = {
notification: {
title: 'You have a new Message!',
body: event.data.val().Message
}
};
const tokens = Object.keys(tokensSnapshot.val());
return admin.messaging().sendToDevice(tokens, payload).then(response => {
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
});
Firebase function Log
How can i fetch above mentioned values of any newly added node in same room(9810012321-9810012347) or any other room(9810012321-9810012325) from database and send it to FCM to make notification
Thanks in Advance.
What i did is created a Message node and I believe doing this by users key. ie, having the receiver(toId) and sender (fromId) key to send the notification.
Hope it helps.
exports.sendMessageNotification = functions.database.ref('/messages/{pushId}')
.onWrite(event => {
let message = event.data.current.val();
console.log('Fetched message', event.data.current.val());
let senderUid = message.fromId;
let receiverUid = message.toId;
let promises = [];
console.log('message fromId', receiverUid);
console.log('catch me', admin.database().ref(`/users/${receiverUid}`).once('value'));
if (senderUid == receiverUid) {
//if sender is receiver, don't send notification
//promises.push(event.data.current.ref.remove());
return Promise.all(promises);
}
let messageStats = message.messageStatus;
console.log('message Status', messageStats);
if (messageStats == "read") {
return Promise.all(promises);
}
let getInstanceIdPromise = admin.database().ref(`/users/${receiverUid}/pushToken`).once('value');
let getSenderUidPromise = admin.auth().getUser(senderUid);
return Promise.all([getInstanceIdPromise, getSenderUidPromise]).then(results => {
let instanceId = results[0].val();
let sender = results[1];
console.log('notifying ' + receiverUid + ' about ' + message.text + ' from ' + senderUid);
console.log('Sender ', sender);
var badgeCount = 1;
let payload = {
notification: {
uid: sender.uid,
title: 'New message from' + ' ' + sender.displayName,
body: message.text,
sound: 'default',
badge: badgeCount.toString()
},
'data': {
'notificationType': "messaging",
'uid': sender.uid
}
};
badgeCount++;
admin.messaging().sendToDevice(instanceId, payload)
.then(function (response) {
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
});
});
const getDeviceTokensPromise = event.data.child('token_To');
should be there instated of getting data from database reference.
or
with fixed path without wildcard like below
const getDeviceTokensPromise = admin.database().ref('/${chatroom}/${mid}/token_to').once('value');
where chatroom and mid is variable which contain value
Second thing:
if (!tokensSnapshot.exists()) {
should in replace of
if (!tokensSnapshot.hasChildren()) {
third thing:
I am not sure about push notification tokenId but
is it required to do?
const tokens = Object.keys(tokensSnapshot.val());
may be we can use directly like below to send push notification
const tokens = tokensSnapshot.val();
You could store all device tokens in a node called tokens like in my example. Tokens could be an array if you would like one user to be able to get notifications on multiple devices. Anyway, store them by their UID.
This works for both Andriod and iOS.
Here is my code:
function loadUsers() {
let dbRef = admin.database().ref('/tokens/' + recieveId);
console.log(recieveId)
let defer = new Promise((resolve, reject) => {
dbRef.once('value', (snap) => {
let data = snap.val();
console.log("token: " + data.token)
//userToken = data.token
resolve(data.token);
}, (err) => {
reject(err);
});
});
return defer;
}
Next we create the notification. I created a lastMessage node to capture just the last message sent in the chat. It is just updated every time a new message is sent in a chat between two users. Makes it easy to get the value. Also makes it easy to show the message on the Conversations screen where there is a list of users who are in a conversation with the current user.
exports.newMessagePush =
functions.database.ref('/lastMessages/{rcId}/{sendId}').onWrite(event => {
if (!event.data.exists()) {
console.log("deleted message")
return;
}
recieveId = event.params.rcId
//let path = event.data.adminRef.toString();
// let recieveId = path.slice(53, 81);
return loadUsers().then(user => {
console.log("Event " + event.data.child("text").val());
let payload = {
notification: {
title: event.data.child("name").val(),
body: event.data.child("text").val(),
sound: 'default',
priority: "10",
}
};
return admin.messaging().sendToDevice(user , payload);
});
});
To implement this logic on your current data structure, just change this line:
let dbRef = admin.database().ref('/tokens/' + recieveId);
and this line:
exports.newMessagePush =
functions.database.ref('/lastMessages/{rcId}/{sendId}').onWrite(event
=> {
to your token location:
let dbRef =
admin.database().ref('/${chatroom}/${mid}/token_to');
and your conversation location:
exports.notifyMsg = functions.database.ref('/{chatroom}/{mid}/')
.onWrite(event => {
Then just change the notification payload be the message you want to display and throw in your error handling on the end of the sendToDevice function, as you did in your code.
Hopefully you figured all this out already but if not maybe this will help you or others trying to use Cloud Functions for notifications.
let payload = {
notification: {
uid: sender.uid,
title: 'New message from' + ' ' + sender.displayName,
body: message.text,
sound: 'default',
badge: badgeCount.toString()
},
'data': {
'notificationType': "messaging",
'uid': sender.uid
}
};
There are two types of FCMs.
1) Data
2) Notification
For detailed overview : FCM Reference
You have to fix your payload for both FCMS. And for Data FCM you have to extract Data in your FCM Service (Client) and generate a push notification according to your need.
I'm using FCM API to receive push notifications from browser. The firebase-messaging-sw.js works as expected and messaging.setBackgroundMessageHandler fires only once when the web app is in background. However, when the app is in foreground, I'm receiving one notification for each browser tab (if I have the app opened in 3 tabs, I receive 3 notifications). I wonder how should I handle this, since I can't find any reference to this issue. This is the code for FCM messages in the foreground:
import NotificationActionCreators from '../actions/NotificationActionCreators';
import NotificationService from './NotificationService';
import LocalStorageService from './LocalStorageService';
import { FIREBASE_SCRIPT, FCM_URL, FCM_API_KEY, FCM_AUTH_DOMAIN, FCM_PROJECT_ID, FCM_SENDER_ID, PUSH_PUBLIC_KEY } from '../constants/Constants';
class ServiceWorkerService {
constructor() {
this._messaging = null;
this._subscriptionData = null;
}
// This function is called once
init() {
this.loadScript(FIREBASE_SCRIPT, () => this.onFirebaseLoaded());
}
onFirebaseLoaded() {
// Initialize Firebase
let config = {
apiKey: FCM_API_KEY,
authDomain: FCM_AUTH_DOMAIN,
projectId: FCM_PROJECT_ID,
messagingSenderId: FCM_SENDER_ID
};
firebase.initializeApp(config);
this._messaging = firebase.messaging();
this.requestPermission();
// Callback fired if Instance ID token is updated.
this._messaging.onTokenRefresh(() => {
this._messaging.getToken()
.then((refreshedToken) => {
console.log('Token refreshed.');
NotificationActionCreators.unSubscribe(this._subscriptionData).then(() => {
// Indicate that the new Instance ID token has not yet been sent to the
// app server.
this.setTokenSentToServer(false);
// Send Instance ID token to app server.
this.sendTokenToServer(refreshedToken);
}, () => console.log('Error unsubscribing user'));
})
.catch(function(err) {
console.log('Unable to retrieve refreshed token ', err);
});
});
// Handle incoming messages.
// *** THIS IS FIRED ONCE PER TAB ***
this._messaging.onMessage(function(payload) {
console.log("Message received. ", payload);
const data = payload.data;
NotificationActionCreators.notify(data);
});
}
requestPermission() {
console.log('Requesting permission...');
return this._messaging.requestPermission()
.then(() => {
console.log('Notification permission granted.');
this.getToken();
})
.catch(function(err) {
console.log('Unable to get permission to notify.', err);
});
}
getToken() {
// Get Instance ID token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
return this._messaging.getToken()
.then((currentToken) => {
if (currentToken) {
this.sendTokenToServer(currentToken);
} else {
// Show permission request.
console.log('No Instance ID token available. Request permission to generate one.');
this.setTokenSentToServer(false);
}
})
.catch(function(err) {
console.log('An error occurred while retrieving token. ', err);
this.setTokenSentToServer(false);
});
}
sendTokenToServer(currentToken) {
const subscriptionData = {
endpoint: FCM_URL + currentToken,
platform: 'Web'
};
if (!this.isTokenSentToServer()) {
console.log('Sending token to server...');
this.updateSubscriptionOnServer(subscriptionData);
} else {
console.log('Token already sent to server so won\'t send it again ' +
'unless it changes');
}
this._subscriptionData = subscriptionData;
}
isTokenSentToServer() {
return LocalStorageService.get('sentToServer') == 1;
}
setTokenSentToServer(sent) {
LocalStorageService.set('sentToServer', sent ? 1 : 0);
}
updateSubscriptionOnServer(subscriptionData) {
if (subscriptionData) {
NotificationActionCreators.subscribe(subscriptionData);
this.setTokenSentToServer(true);
this._subscriptionData = subscriptionData;
} else {
console.log('Not subscribed');
}
}
unSubscribe() {
this.removeSetTokenSentToServer();
return this._messaging.getToken()
.then((currentToken) => {
return this._messaging.deleteToken(currentToken)
.then(() => {
console.log('Token deleted.');
return NotificationActionCreators.unSubscribe(this._subscriptionData);
})
.catch(function(err) {
console.log('Unable to delete token. ', err);
return new Promise(function(resolve, reject) {
reject(error)
});
});
})
.catch(function(err) {
console.log('Error retrieving Instance ID token. ', err);
return new Promise(function(resolve, reject) {
reject(error)
});
});
}
}
removeSetTokenSentToServer() {
LocalStorageService.remove('sentToServer');
}
loadScript = function (url, callback) {
let head = document.getElementsByTagName('head')[0];
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.onload = callback;
// Fire the loading
head.appendChild(script);
}
}
Is there any way to show the notification just for the first tab found?
The only way I've found to achieve this is to check and set a "notification id" variable in the local storage with a setTimeout with a random time:
this._messaging.onMessage(function(payload) {
const data = payload.data;
// This prevents to show one notification for each tab
setTimeout(() => {
if (localStorage.getItem('lastNotificationId')) != parseInt(data.notId)) {
localStorage.setItem('lastNotificationId', parseInt(data.notId))
NotificationActionCreators.notify(data);
}
}, Math.random() * 1000);
});
The notId is sent within the push notification and is an identifier for the notification.
One way is to create a unique variable per tab, say Math.random() or (new Date()).getMilliseconds() and store that on the server with the token. Now the server can target each tab by attaching the variable to the message, with each tab checking the message variable before acting.
To reduces odds of targeting a closed tab, send the variable up with each request, so the server always targets the latest one.
use document.hidden to detect active tab
if (!document.hidden) {
NotificationActionCreators.notify(data);
}