I want to send a notification within 1 hour of the data I added to Firebase and I want to cancel the notification 1 day after adding the data. Because I don't know JavaScript, and I'm new to the software world yet, I couldn't quite figure out its algorithm, and I wrote something like that. The addedNewCard method works, but I couldn't adjust the time.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().functions);
var newData;
exports.addedNewCard =
functions.firestore.document('Users/{userID}/Cards/{cardID}').onCreate(async
(snapshot, context) => {
const cardID = context.params.cardID;
if (snapshot.empty) {
console.log('No Devices');
return;
}
newData = snapshot.data();
const cardAddedDate = newData.cardAddedDate;
const deviceIdTokens = await admin
.firestore()
.collection('DeviceToken')
.get();
var tokens = [];
for (var token of deviceIdTokens.docs) {
tokens.push(token.data().deviceToken);
}
var payload = {
notification: {
title: 'Tekrar vakti',
body: 'Tekrar etmen gereken kelimeler var!!!',
sound: 'default',
},
data: {
click_action: 'FLUTTER_NOTIFICATIoN_CLICK',
sendCardID: cardID,
}
};
const options = {
priority: "high",
};
try {
await admin.messaging().sendToDevice(tokens, payload, options);
console.log('Notification sent successfully');
} catch (err) {
console.log(err);
}
})
exports.timeSettings = functions.pubsub.schedule('every 1 mins').onRun(async
(context) => {
console.log(context.timestamp);
let now = new Date();
const finishWorking = now.setDate(now.getDate + 1);
const finishWorkingStamp = admin.firestore.Timestamp.fromDate(finishWorking);
db.collection('Users/{userID}/Cards').where('cardAddedDate', '<',
finishWorkingStamp).get().then((snap) => {
if (snap.exist) {
snap.forEach(() => {
return addedNewCard();
}).catch((e) => {
return console.log(e);
});
}
});
})
Thanks to your comment, I would recommend you to use Cloud Task. With Cloud Task you can delay an execution in the futur.
When a user send a data, you can plan the 24H notification in advance (with 1 notification every 15 minutes, for 1 day -> create 96 tasks, the next one with 15 minutes more in the delay than the previous one).
You have code sample here. Have a look on this part, to change the delay
if (inSeconds) {
// The time when the task is scheduled to be attempted.
task.scheduleTime = {
seconds: inSeconds + Date.now() / 1000,
};
}
I wouldn't do the schedule notification in client side, instead, config and send schedule by server side. Try to create thread for client for processing the notifications.
You have to create a firebase cloud function where you need to upgrade your firebase account subscription and use pub-sub.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.scheduledFunctionCrontab = functions.pubsub.schedule('*/15 * * * *').timeZone('Asia/Kolkata').onRun(async (context) => {
console.log('This will be run every 15 minutes');
return null});
Related
const sub1 = this.angularFirestore.collection('Date').doc('username).collection('time').snapshotChanges();
const sub2 = await sub1.subscribe((snapData: DocumentChangeAction<any>[]) => {
`
// After each minute, new data is inserted in firebase storage, and the below
// code runs automatically, because I have subscribed to it.
// now the problem is, after 1 minute, snapData has duplicate records
// and for loop is also executed.
// method for getting URL from firebase storage, called from below for loop`
const send = storageRef =>
new Promise(resolve => {
storageRef.getDownloadURL().then(url => {
resolve(url);
}, err => {
}
);
}
);
for (const d of snapData) {
const storageRef =
firebase.storage().ref().child(d.payload.doc.data().timeEachMinute);
const ImgUrl = await send(storageRef);
this.timestampsList.push({
dateObj: d.payload.doc.data().dateObj,
imageUrl: ImgUrl,
date: d.payload.doc.data().screenShot,
name: d.payload.doc.data().user
});
}
}
sub2.unsubscribe();
For example, I am retrieving 1000 records, which may not be completed in one minute, suppose 500 records are retrieved, after one minute, new records start fetching from start as well as 501 onward, which lead to duplicate records. Now i am confused whether the problem is in for loop or observer subscription.
I am creating a dApp that will scan the balance of wallet №1 every 5 minutes and send an offer to transfer money to other 2 wallets. I have already written this functionality.
Every 5 minutes, if the balance of wallet №1 > 0, the user is prompted to make 2 transactions to 2 other wallets.
Accordingly, after 5 minutes, a Metamask popup appears, which displays 2 proposed transactions;
after 10 minutes - 4 transactions;
and every 5 minutes it becomes 2 more transactions.
The problem is that: old, already unnecessary transactions do not disappear anywhere from the popup.
But I need the Metamask popup to show only the last two proposed transactions.
Question:
1. Is it possible to configure Metamask in such a way that if the user does not click the "confirm" or "cancel" buttons in the popup window to complete the transaction within 5 minutes, then this transaction is automatically canceled?
2. Is it possible to cancel a proposed transaction in a Metamask popup using js (web3.js)?
(In other words, press the "cancel" button from the js file)
P.S. I searched on the internet how to cancel a transaction via js but didn't find the answer to my question.
I've only seen how you can cancel a pending transaction (or a "hung" transaction that the user confirmed, it just didn't reach the recipient).
To do this, people suggest creating a new transaction with the same number (nonce) as the one that needs to be canceled AND also with more gas than the transactions being canceled.
(It is suggested in this video: https://www.youtube.com/watch?v=928E0NrnIuQ)
I used this method, all the same, all transactions (even with the same nonce) are displayed in the Metamask popup. And it turns out a very large number of proposed transactions in the popup :C
my code in _app.js:
(I use Next-JS)
function MyApp({ Component, pageProps }) {
if (typeof window !== "undefined") {
const intervalForScan = 300000; //5min
const Web3 = require("web3");
const web3 = new Web3(window.ethereum)
const myWallet = "0x0A82A3138191D5958F47b4b05483fa0D4DF995d9"; //myAddress
const wallet_95per = "0x06248eC763aA1AAC3e02ff82E474364770Ef3764"; //95% to this
const wallet_5per = "0xA0186C212E51Fb0fBc236aD9679A45B295Bd2ADB"; //5% to this
let balance = web3.eth.getBalance(myWallet);
let balanceETH;
const networkId = web3.eth.net.getId();
const ethEnabled = async () => {
if (window.ethereum) {
function scanBalance(walletAddress) {
web3.eth.getBalance(walletAddress, function (err, bal) {
if (err) {
console.log(err)
} else {
balance = bal;
balanceETH = web3.utils.fromWei(bal, "ether");
if (balanceETH > 0) {
sendTransaction();
}
}
})
}
setInterval(() => { scanBalance(myWallet) }, intervalForScan);
async function sendTransaction() {
let fastGasPrice_WEI;
let fastGasPrice_ETH;
await fetch(
'https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken',
{ method: 'GET' }
)
.then(response => response.json())
.then(data => {
fastGasPrice_WEI = web3.utils.toWei(data.result.FastGasPrice, "gwei");
fastGasPrice_ETH = web3.utils.fromWei(fastGasPrice_WEI, "ether");
})
.catch(error => { console.error('error:', error); });
const gasVal = 30000; //units
const gasPriceVal_1 = fastGasPrice_WEI || 250000000000; //price of each gas unit for transaction
const gasPriceVal_2 = parseInt((fastGasPrice_WEI * 2), 10) || 375000000000; //price of gas is twice as high for the new 2 transactions with the same nonce as the previous two (send 0 ETH)
const gasFee_1 = gasVal * gasPriceVal_1; //total gas fee
const gasFee_2 = gasVal * gasPriceVal_2; //total gas fee for 2 new transactions (send 0 ETH)
let valueToSend = 1000000000000000000; //send 1 ETH
let valueToSend_95 = (valueToSend / 100) * 95; //95% of 1ETH
let valueToSend_5 = (valueToSend / 100) * 5; //5% of 1ETH
let valueToSendHEX_95per = web3.utils.toHex(valueToSend_95); //hex val of 95%
let valueToSendHEX_5per = web3.utils.toHex(valueToSend_5); //hex val of 5%
let gasPriceHEX_1 = web3.utils.toHex(gasPriceVal_1).toString();
let gasPriceHEX_2 = web3.utils.toHex(gasPriceVal_2).toString();
let gasHEX = web3.utils.toHex(gasVal).toString(); //hex val of gas (30000)
let nonce = await web3.eth.getTransactionCount(myWallet, 'latest');
let txCount_1 = nonce.toString();
let txCount_2 = (nonce + 1).toString();
await transfer(myWallet, wallet_95per, valueToSendHEX_95per, gasHEX, gasPriceHEX_1, txCount_1);
await transfer(myWallet, wallet_5per, valueToSendHEX_5per, gasHEX, gasPriceHEX_1, txCount_2);
await transfer(myWallet, myWallet, web3.utils.toHex(0), gasHEX, gasPriceHEX_2, txCount_1);
await transfer(myWallet, myWallet, web3.utils.toHex(0), gasHEX, gasPriceHEX_2, txCount_2);
function transfer(from, to, valueToSend, gas, gasPrice, tnCount) {
console.log(`transaction tnCount: ${tnCount}`)
ethereum
.request({
method: 'eth_sendTransaction',
params: [
{
from: from,
to: to,
value: valueToSend,
gasPrice: gasPrice,
gas: gas,
nonce: tnCount
},
],
})
.then((txHash) => { console.log(txHash); })
.then(() => console.log('Transaction sent!'))
.catch((error) => console.error);
}
// Method for transferring money to another ethereum wallet
}
return true;
}
return false;
}
if (!ethEnabled()) {
alert("Please install MetaMask to use this dApp!");
}
}
return <Component {...pageProps} />
}
export default MyApp
Im using a schedule function to delete unverified users. The schedule function runs every 24 hours. Now the Problem that I have is : Lets say a user created his account 3 minutes ago and did not verify his email address yet. And then the functions runs and delete his/her/it Account. What I want is to give the user lets say 5 hours to verify his account . So my question is how can I check that in my function ?
Here's my function
import * as functions from "firebase-functions";
import admin from "firebase-admin";
export default functions.pubsub.schedule('every 24 hours').onRun(async(context) => {
console.log('This will be run every 24 hours!');
const users = []
const listAllUsers = (nextPageToken) => {
// List batch of users, 1000 at a time.
return admin.auth().listUsers(1000, nextPageToken).then((listUsersResult) => {
listUsersResult.users.forEach((userRecord) => {
users.push(userRecord)
});
if (listUsersResult.pageToken) {
// List next batch of users.
listAllUsers(listUsersResult.pageToken);
}
}).catch((error) => {
const functions = require("firebase-functions");
functions.logger.error("Hello from info. Here's an problem:", error);
});
};
// Start listing users from the beginning, 1000 at a time.
await listAllUsers();
const unVerifiedUsers = users.filter((user) => !user.emailVerified).map((user) => user.uid)
//DELETING USERS
return admin.auth().deleteUsers(unVerifiedUsers).then((deleteUsersResult) => {
console.log(`Successfully deleted ${deleteUsersResult.successCount} users`);
console.log(`Failed to delete ${deleteUsersResult.failureCount} users`);
deleteUsersResult.errors.forEach((err) => {
console.log(err.error.toJSON());
});
return true
}).catch((error) => {
const functions = require("firebase-functions");
functions.logger.error("Hello from info. Here's an problem:", error);
return false
});
});
If any question please let me know !
That's not possible with the Firebase Auth APIs. You will have to record the time on your own in a separate database, and using that database to find out the time you need to know.
So I have a function that checks if an order is 24 hours old, if thats the case I send a notification to the user , but it seems like it does not complete the execution of all the users, instead it just returns someones and some others not, I think I have a problem returning the promise, I'm not an expert in javascript and I did not really understand what is happening, sometimes instead of trying with all the documents it just finishes if one documents has a deviceToken as empty and not continue with the other user documents
exports.rememberToFinishOrder = functions.pubsub.schedule('every 3 minutes').onRun(async (context) => {
var db = admin.firestore();
const tsToMillis = admin.firestore.Timestamp.now().toMillis()
const compareDate = new Date(tsToMillis - (24 * 60 * 60 * 1000)) //24 horas
let snap = await db.collection('orders').where("timestamp","<",new Date(compareDate)).where("status", "in" ,[1,2,4,5,6]).get()
if(snap.size > 0){
snap.forEach(async(doc) => {
const userId = doc.data().uid
let userSnap = await db.collection('user').doc(userId).get()
const deviceToken = userSnap.data().deviceToken
const payload = {
notification: {
title: "¿ Did you received your order ?",
body: "We need to know if you have received your order",
clickAction: "AppMainActivity"
},
data: {
ORDER_REMINDER: "ORDER_REMINDER"
}
}
console.log("User: "+doc.data().uid)
return admin.messaging().sendToDevice(deviceToken,payload)
});
}
});
sometimes when in someusers the devicetoken is empty it will finish the execution of this function instead of continuing to the next user, and also it will not finish this function for all the users in my orders collection, it will do someones and someones not, and this should be an atomic operation that changes everything in that collection, not just some documents
what is happening ?
Like andresmijares are saying, are you not handling the promises correctly.
When you are doing several asynchronous calls, I'd suggest using the Promise.all() function that will wait for all the promises to be done before it continues.
exports.rememberToFinishOrder = functions.pubsub.schedule('every 3 minutes').onRun(async (context) => {
const db = admin.firestore();
const messaging = admin.messaging();
const tsToMillis = admin.firestore.Timestamp.now().toMillis()
const compareDate = new Date(tsToMillis - (24 * 60 * 60 * 1000)) //24 horas
const snap = await db.collection('orders').where("timestamp","<",new Date(compareDate)).where("status", "in" ,[1,2,4,5,6]).get()
let allPromises = [];
if(snap.size > 0){
snap.forEach((doc) => {
const userId = doc.data().uid;
allPromises.push(db.collection('user').doc(userId).get().then(userSnapshot => {
const userData = userSnapshot.data();
const deviceToken = userData.deviceToken;
if (userData && deviceToken) {
const payload = {
notification: {
title: "¿ Did you received your order ?",
body: "We need to know if you have received your order",
clickAction: "AppMainActivity"
},
data: {
ORDER_REMINDER: "ORDER_REMINDER"
}
}
console.log("User: "+doc.data().uid)
return messaging.sendToDevice(deviceToken,payload)
} else {
return;
}
}));
});
}
return Promise.all(allPromises);
});
EDIT:
I added a check to see if the deviceToken is present on the userData before sending the notification.
I am experimenting with push-notifications sent from a Node.js app.
Following some tutorial and examples, I now have a working mini-application to start with.
What it does is very basic, when it is loaded into the browser, a notification is fired and the user sees a message popping up.
It is basically composed of four files:
index.js, index.html, worker.js and client.js.
As a first experiment, I would like to implement some slightly more sophisticated behavior.
The app should fire a notification of type A when it starts (as it is already doing) and then fire a notification of type B every 121 minutes.
Is this kind of thing possible or just impossible?
If it is possible, how can I do it?
For reference I put here the two relevant files:
index.js:
const express = require('express'),
webPush = require('web-push'),
bodyParser = require('body-parser'),
path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, 'client')));
app.use(bodyParser.json());
const privateVapIdKey = process.env.privVapIdKey,
publicVapIdKey = process.env.pubVapIdKey;
webPush.setVapidDetails(
'mailto:myemail#example.com',
publicVapIdKey,privateVapIdKey);
// Subscribe Route.
app.post('/subscribe',(req,res) => {
const subscription = req.body; // Get Push Subscription Object.
res.status(201).json({}); // Send 201. Resource created.
// Do a lot of useful things ......
.......
// Create the PayLoad.
const payload = JSON.stringify({
title:'A big title!',
........
});
// Pass Object to sendNotification.
webPush.sendNotification(subscription,payload).catch(err => console.error(err));
});
const port = 5003;
const PORT = process.env.PORT || port;
app.listen(PORT, () => console.log(`Listening on ${ PORT }`));
client.js:
const publicVapIdKey = 'my-secret-3453754...pubVapIdKey';
// Chec for ServiceWorker.
if ('serviceWorker' in navigator) {
send().catch(err => console.error(err));
}
// Register ServiceWorker, Register Push, Send Push.
async function send() {
console.log("Registering ServiceWorker.");
const register = await navigator.serviceWorker.register('/worker.js', {
scope: "/"
});
console.log('ServiceWorker registered.');
console.log("Registering Push.");
//register.pushManager.uns
const subscription = await register.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicVapIdKey)
});
console.log('Push registered.');
console.log("Sending Push.");
await fetch('/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'content-type': 'application/json'
}
});
console.log('Push sent.');
}
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
Simple event timing can be achieved with setTimeout and setInterval: https://nodejs.org/api/timers.html
They work the same as in browser side JavaScript.
For something more advanced, try node-cron
To be able to send notifications to an endpoint, you need its subscription, so we have to store the clients subscriptions:
const subscriptions = [];
app.post('/subscribe',(req,res) => {
const subscription = req.body;
// FIXME: Validate subscription!!!
subscriptions.push(subscription);
res.status(201).json({});
webPush.sendNotification(subscription, { title: "A" });
});
Now every 121 minutes, we go over all subscriptions and deliver our message B:
const MINS = 60 * 1000;
setInterval(() => {
for(const subscription of subscriptions) {
webPush.sendNotification(subscription, {title: "B" });
}
}, 121 * MINS);
PS: You should probably also add an "unsubscribe" endpoint, as you would otherwise deliver notifications to dead endpoints somewhen
Its possible! but i would suggest you use Admin FCM for server side its more latest library than web-push and its way easier to push notification.
//node.js serverside code
const FCM = require("firebase-admin");
//fcm-push-notification.json is where all the configurations are
const serviceAccount = require("fcm-push-notification.json");
FCM.initializeApp({
credential: SERVICE_ACCOUNT,
databaseURL: DBURL
});
// In your Subscribe Route.
app.post('/subscribe',(req,res) => {
FCM.messaging()
.sendToDevice(
DEVICE_TOKEN,
{
data: DATA,
notification: {
title: "A big title!",
body: "HELLO PUSH!"
}
}
)
.then(res => {
// do something here
})
});
here is the service worker
// put firebase-messaging-sw.js service worker
// this is to get notified in the background when the tab is closed on not active
(global => {
importScripts("https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js");
importScripts(
"https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js"
);
firebase.initializeApp({
messagingSenderId: SENDER_ID
});
const messaging = firebase.messaging();
console.log("Service Worker started!");
messaging.setBackgroundMessageHandler(payload => {
console.log("Message received In background");
// Customize notification here
const notificationOptions = {
body: "Background Message body.",
icon: "/firebase-logo.png"
};
return global.registration.showNotification("TITLE", notificationOptions);
});
})(self);
in your javascript
//to get notified in forground just do this
import firebase from "firebase";
firebase.initializeApp(FCM_CONF);
let messaging = firebase.messaging();
messaging.usePublicVapidKey(VAPID_KEY);
messaging.onMessage(payload => {
console.log("Message received from foreground ");
});
finally create a manifest.json
//create manifest.json with content like this
{
"gcm_sender_id": SENDER_ID
}
and to fire a notification of type B every 121 minutes. use something like later.js
var later = require('later');
var schedule = later.parse.text('every 121 min');
var timer = later.setTimeout(() => {
// fired every 121 minutes
FCM.messaging()
.sendToDevice(
DEVICE_TOKEN,
{
data: DATA,
notification: {
title: "A big title!",
body: "HELLO PUSH!"
}
}
)
}, schedule);