it is showing Unexpected value for STRIPE_SIGNING_SECRET error even after checking it many times in the env file
the terminal shows everything created but it does not reach firebase database I am thinking there is a error in the code
the stripe dashboard also says connected
I am using the forward to local host line in git terminal
webhook code
import { buffer } from "micro";
import * as admin from 'firebase-admin'
//secure a connection to Firebase from backend
const serviceAccount = require('../../../permissions.json');
const app = !admin.apps.length ? admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
})
: admin.app();
// establish connection to stripe
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_SIGNING_SECRET;
if (typeof endpointSecret !== "string") {
console.error("Unexpected value for STRIPE_SIGNING_SECRET");
// potentially throw an error here
}
const fulfillOrder = async (session) => {
//console.log('Fulfilling order', session)
return app
.firestore()
.collection("user")
.doc(session.metadata.email)
.collection("orders")
.doc(session.id)
.set({
amount: session.amount_total / 100,
amount_shipping: session.amount_total_details.amount_shipping / 100,
images: JSON.parse(session.metadata.images),
timestamp: admin.firestore.FieldValue.serverTimestamp(),
})
.then(() => {
console.log(`success: order ${session.id} had been added to db`);
});
};
export default async (req, res) =>{
if(req.method === 'post'){
const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
const sig = req.headers["stripe-signature"];
let event;
// verify that the event posted came from stripe
try{
event = stripe.webhooks.constructEvent(
payload,
sig,
endpointSecret);
} catch (err) {
console.log('ERROR', err.message)
return res.status(400).send(`Webhook error: ${err.message}`)
}
//handle the checkout event
if (event.type === 'checkout.session.completed') {
const session = event .data.object;
//fulfill the order...
return fulfillOrder(session)
.then(() => res.status(200))
.catch((err) => res.status(400).send(`Webhook error: ${err.message}`));
}
}
};
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
firebase rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow write: if false;
allow read: if true;
}
}
}
const endpointSecret = process.env.STRIPE_SIGNNING_SECRET;
Typo: STRIPE_SIGNNING_SECRET
To avoid the next issue, fix the other typo:
const sig = req.headers["stripe-signatur"];
stripe-signature
Related
I am trying to let the user only read and write their own data. My rules are as follow(from the docs)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
The uid for my user matches my document id but i still get the error:
Uncaught Error in snapshot listener:
FirebaseError: [code=permission-denied]: Missing or
insufficient permissions.
My code for getting uid to document id
const handleSignUp = async () => {
auth
.createUserWithEmailAndPassword(email, password)
.then(async (UserCredentials) => {
const user = UserCredentials.user;
console.log("Registered with: ", user.email);
try {
const uidRef = doc(db, 'users', user.uid);
const docRef = await setDoc(uidRef, {
name: name,
age: age,
currentWeight: currentWeight,
goalWeight: goalWeight,
});
} catch (e) {
console.error("Error adding document: ", e);
}
I am really lost as I have tried many different ways and all docs / answers on here do not work for me. I am guessing the error comes when i call snapshot in this code
const getUser = async() => {
const subscriber = onSnapshot(usersRef, (snapshot) => {
let user = []
snapshot.docs.forEach((doc) => {
user.push({...doc.data(), key: doc.id })
})
setUser(user);
console.log(user);
})
return () => subscriber();
};
I am just unsure as to what is exactly wrong here. Is it my rules? My snapshot?
Given that you get a QuerySnapshot result, I suspect that your code is reading the entire users collection. But as the documentation says rules are not filters, but instead merely ensure that your code only tries to access data that it is permitted to.
So your code should only try to read the document of the currently signed in user.
const getUser = async() => {
if (getAuth().currentUser) {
const uidRef = doc(db, 'users', getAuth().currentUser.uid);
const subscriber = onSnapshot(uidRef, (doc) => {
setUser({...doc.data(), key: doc.id })
})
...
}
};
I am referencing this tutorial for Firestore security rules. I have extracted the code from the repository and it matches that of the video.
I changed the setup code to run the firestore.rules instead of firestore-test.rules, and tried running firebase emulators:start and jest ./spec following the same directory structure, I fail the tests of "should allow delete when user is admin" and "should not allow delete for normal user" and the reason it is failing is due to the write rule in the wildcard. Does anyone know what is wrong?
collections.spec.js
const { setup, teardown } = require("./helpers");
describe("General Safety Rules", () => {
afterEach(async () => {
await teardown();
});
test("should deny a read to the posts collection", async () => {
const db = await setup();
const postsRef = db.collection("posts");
await expect(postsRef.get()).toDeny();
});
test("should deny a write to users even when logged in", async () => {
const db = await setup({
uid: "danefilled"
});
const usersRef = db.collection("users");
await expect(usersRef.add({ data: "something" })).toDeny();
});
});
describe("Posts Rules", () => {
afterEach(async () => {
await teardown();
});
test("should allow update when user owns post", async () => {
const mockData = {
"posts/id1": {
userId: "danefilled"
},
"posts/id2": {
userId: "not_filledstacks"
}
};
const mockUser = {
uid: "danefilled"
};
const db = await setup(mockUser, mockData);
const postsRef = db.collection("posts");
await expect(
postsRef.doc("id1").update({ updated: "new_value" })
).toAllow();
await expect(postsRef.doc("id2").update({ updated: "new_value" })).toDeny();
});
test("should allow delete when user owns post", async () => {
const mockData = {
"posts/id1": {
userId: "danefilled"
},
"posts/id2": {
userId: "not_filledstacks"
}
};
const mockUser = {
uid: "danefilled"
};
const db = await setup(mockUser, mockData);
const postsRef = db.collection("posts");
await expect(postsRef.doc("id1").delete()).toAllow();
await expect(postsRef.doc("id2").delete()).toDeny();
});
test("should allow delete when user is admin", async () => {
const mockData = {
"users/filledstacks": {
userRole: "Admin"
},
"posts/id1": {
userId: "not_matching1"
},
"posts/id2": {
userId: "not_matching2"
}
};
const mockUser = {
uid: "filledstacks"
};
const db = await setup(mockUser, mockData);
const postsRef = db.collection("posts");
await expect(postsRef.doc("id1").delete()).toAllow();
});
test("should not allow delete for normal user", async () => {
const mockData = {
"users/filledstacks": {
userRole: "User"
},
"posts/id1": {
userId: "not_matching1"
},
"posts/id2": {
userId: "not_matching2"
}
};
const mockUser = {
uid: "filledstacks"
};
const db = await setup(mockUser, mockData);
const postsRef = db.collection("posts");
await expect(postsRef.doc("id1").delete()).toDeny();
});
test("should allow adding a post when logged in", async () => {
const db = await setup({
uid: "userId"
});
const postsRef = db.collection("posts");
await expect(postsRef.add({ title: "new_post" })).toAllow();
});
test("should deny adding a post when not logged in", async () => {
const db = await setup();
const postsRef = db.collection("posts");
await expect(postsRef.add({ title: "new post" })).toDeny();
});
});
firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// lock down the db
match /{document=**} {
allow read: if false;
allow write: if false;
}
match /posts/{postId} {
allow update: if userOwnsPost();
allow delete: if userOwnsPost() || userIsAdmin();
allow create: if loggedIn();
}
function loggedIn() {
return request.auth.uid != null;
}
function userIsAdmin() {
return getUserData().userRole == 'Admin';
}
function getUserData() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data
}
function userOwnsPost() {
return resource.data.userId == request.auth.uid;
}
}
}
Error trace from terminal
FirebaseError: 7 PERMISSION_DENIED:
false for 'create' # L10
● Posts Rules › should not allow delete for normal user
FirebaseError: 7 PERMISSION_DENIED:
false for 'create' # L10
at new FirestoreError (/Users/../../../../../../../../../Resources/rules/node_modules/#firebase/firestore/src/util/error.ts:166:5)
at ClientDuplexStream.<anonymous> (/Users/../../../../../../../../../Resources/rules/node_modules/#firebase/firestore/src/platform_node/grpc_connection.ts:240:13)
at ClientDuplexStream._emitStatusIfDone (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client.js:234:12)
at ClientDuplexStream._receiveStatus (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client.js:211:8)
at Object.onReceiveStatus (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:1311:15)
at InterceptingListener._callNext (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:568:42)
at InterceptingListener.onReceiveStatus (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:618:8)
at /Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:1127:18
I actually followed the same tutorial to get started with the firebase emulator and got the same kind of error messages. The problem for me was that when you start the simulator it automatically looks for your firestore.rules file and loads the rules. So, when you then add your mockData the rules already apply.
In order to make your test code work either change the setting for your firestore rules file in your firebase.json to a non-existing file (or rules file that allows all read/write) or add the mockData as an admin in your setup function, e.g.:
module.exports.setup = async (auth, data) => {
const projectId = `rules-spec-${Date.now()}`;
const app = firebase.initializeTestApp({
projectId,
auth
});
const db = app.firestore();
// Initialize admin app
const adminApp = firebase.initializeAdminApp({
projectId
});
const adminDB = adminApp.firestore();
// Write mock documents before rules using adminApp
if (data) {
for (const key in data) {
const ref = adminDB.doc(key);
await ref.set(data[key]);
}
}
// Apply rules
await firebase.loadFirestoreRules({
projectId,
rules: fs.readFileSync('firestore.rules', 'utf8')
});
return db;
};
Hope this helps.
Also see this question
For those that are currently having this issue firestore 8.6.1 (or equivalent), there is a bug discussed here:
https://github.com/firebase/firebase-tools/issues/3258#issuecomment-814402977
The fix is to downgrade to firestore 8.3.1, or if you are reading this in the future and firestore >= 9.9.0 has been released, upgrade to that version.
I have a HTTP POST request I'm making from the frontend using Axios to the Firebase Functions backend. I want to be able to send two requests at the same time to call two functions, createEmaileList and zohoCrmHook. The problem is, when I make a request to both functions at the same time, it gives me the CORS error. When I make a request to individual function, they work perfectly fine. How do I make a request to multiple functions at the same time?
Following is the frontend:
const handleSubmit = e => {
setLoading(true)
e.preventDefault()
axios.all([
axios.post(`${ROOT_URL}/createEmailList`, {
email,
firstName,
lastName
}),
axios.post(`${ROOT_URL}/zohoCrmHook`, {
email,
firstName,
lastName
})
])
.then(axios.spread((emailRes, crmRes) => {
if(emailRes.status===200 || emailRes.status===204 || crmRes.status===200 || crmRes.status===204 || crmRes.status===201){
setLoading(false)
closeModal()
}
}))
.catch(err=> console.log(err));
}
The backend index.js is as follows:
const functions = require('firebase-functions');
const admin = require("firebase-admin")
const serviceAccount = require("./service_account.json");
const createEmailList = require('./createEmailList')
const zohoCrmHook = require('./zohoCrmHook')
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://landing-page.firebaseio.com"
})
exports.zohoCrmHook = functions.https.onRequest(zohoCrmHook)
exports.createEmailList = functions.https.onRequest(createEmailList)
I've imported the cors module and implemented the function as following, but it still only works individually and not both at the same time
createEmailList.js
const admin = require('firebase-admin')
const cors = require('cors')({ origin: true })
module.exports = (req, res) => {
cors(req, res, () => {
if (!req.body.email) {
return res.status(422).send({ error: 'Bad Input'})
}
const email = String(req.body.email)
const firstName = String(req.body.firstName)
const lastName = String(req.body.lastName)
const data = {
email,
firstName,
lastName
}
const db = admin.firestore()
const docRef = db.collection('users')
.doc(email)
.set(data, { merge: false })
.catch(err => res.status(422).send({ error: err }))
return res.status(204).end();
})
}
zohoCrmHook.js
const axios = require('axios');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true })
// zoho
const clientId = functions.config().zoho.client_id;
const clientSecret = functions.config().zoho.client_secret;
const refreshToken = functions.config().zoho.refresh_token;
const baseURL = 'https://accounts.zoho.com';
module.exports = (req, res) => {
cors(req, res, async () => {
const newLead = {
'data': [
{
'Email': String(req.body.email),
'Last_Name': String(req.body.lastName),
'First_Name': String(req.body.firstName),
}
],
'trigger': [
'approval',
'workflow',
'blueprint'
]
};
const { data } = await getAccessToken();
const accessToken = data.access_token;
const leads = await getLeads(accessToken);
const result = checkLeads(leads.data.data, newLead.data[0].Email);
if (result.length < 1) {
try {
return res.json(await createLead(accessToken, newLead));
} catch (e) {
console.log("createLead error", e);
}
} else {
return res.json({ message: 'Lead already in CRM' })
}
})
}
Update
I've also tried combining the two Firebase Functions into one as following:
exports.myWebHook = functions.https.onRequest(async (req, res) => {
createEmailList(req, res)
zohoCrmHook(req, res)
})
and converting the frontend axios request into one:
const handleSubmit = e => {
setLoading(true)
e.preventDefault()
axios.post(`${ROOT_URL}/myWebHook`, {
email,
firstName,
lastName
})
.then(res => {
if(res.status===200 || res.status===204){
setLoading(false)
closeModal()
}
})
.catch(err=> console.log(err));
}
But, it still gives the same CORS error:
Access to XMLHttpRequest at
'https://us-landing-page.cloudfunctions.net/myWebHook'
from origin 'https://www.website.com' has been blocked by CORS
policy: Response to preflight request doesn't pass access control
check: Redirect is not allowed for a preflight request.
Update2
I've tried to incorporating the CORS module in index.js as following and removed the CORS module from both of the functions.
exports.myWebHook = functions.https.onRequest((req, res) => {
cors(req, res, async () => {
zohoCrmHook(req, res)
createEmailList(req, res)
})
})
Now, the axios request to the server doesn't incur any CORS errors and the myWebHook function gets invoked with no issues, but the neither of zohoCrmHook nor createEmailList function gets invoked.
If we look at the code we see that CORS in not only imported but also invoked with an options object. So I think it is instantiating CORS twice and setting multiple times the same CORS headers, which is known to cause issues.
const cors = require('cors')({ origin: true })
My suggestion is, to instantiate CORS once in the entry point and add the functions as resources to the same CORS instance.
How can I set session expiration for a Firebase auth session?
By default the session never expires.
I wish for the session to expire after 8 hours of inactivity.
I have read the documentation but cannot figure out how to set session expiration.
My code for signing in the user and performing tasks on sign in and sign out
firebase.auth().signInWithEmailAndPassword(data.email, data.password)
firebase.auth().onAuthStateChanged((user) => {
if (user) {
//Signed in
}else{
//Signed out
}
}
Thanks for all replies!
I have tried but cannot seem to get Firebase-admin to work.
Firebase-db.js
const admin = require('firebase-admin')
const databaseConnection = {
serviceAccountFile: './serviceAccount.json',
databaseURL: 'https://myProject.firebaseio.com/'
}
const serviceAccount = require(databaseConnection.serviceAccountFile)
const app = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: databaseConnection.databaseURL
}, 'test')
const database = admin.database(app)
module.exports = database
sessionSignout.js
const functions = require('firebase-functions')
const database = require('../../firebase-db')
const admin = database.admin
exports.sessionSignout = functions
.region('europe-west1')
.pubsub
.schedule('*/15 * * * *')
.timeZone('Europe/Stockholm')
.onRun(async (event) => {
database.ref(`users`)
.once('value', (usersSnapshots) => {
usersSnapshots.forEach((snapshot) => {
const uid = snapshot.key
admin.auth().revokeRefreshTokens(uid)
})
})
}
I get error
Error: function execution failed. Details: Cannot read property 'auth' of undefined
The documentation you linked says that you can use the Firebase Admin SDK to revoke a user's refresh tokens in order to terminate their session. This code must run on a backend you control, which means that you won't be able to do it in the client app. The backend will need to know when the user became "inactive", by whatever definition of that you choose. Wiring this all up is non-trivial, but possible.
Thanks for all the answers!
I just wanted to share my code for others to use.
I already had code in place to accommodate presence awareness.
index.js
import database from './firebase/firebase' //Firebase setup for client
firebase.auth().onAuthStateChanged((user) => {
//Handle login and redirect
if (user) {
//We are logged in
addPresenceAwarenessListener()
}else{
...
}
}
const addPresenceAwarenessListener = () => {
// Create a reference to the special '.info/connected' path in
// Realtime Database. This path returns `true` when connected
// and `false` when disconnected.
database.ref('.info/connected').on('value', (snapshot) => {
// If we're not currently connected, don't do anything.
if (snapshot.val() == false) {
return
}
const uid = firebase.auth().currentUser.uid
//Push last login/logout to user profile
const userLastLoginOutRef = database.ref(`users/${uid}`)
userLastLoginOutRef.onDisconnect().update({lastLoginOut: firebase.database.ServerValue.TIMESTAMP})
.then(() => { userLastLoginOutRef.update({lastLoginOut: firebase.database.ServerValue.TIMESTAMP}) })
})
}
Session handling - expire sessions after n hours (setting "sessExp" in database)
firebase-db.js - Basic Firebase setup for cloud functions
const admin = require('firebase-admin')
const databaseConnection = {
serviceAccountFile: './my-project.json',
databaseURL: 'https://my-project.firebaseio.com/'
}
const serviceAccount = require(databaseConnection.serviceAccountFile)
const app = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: databaseConnection.databaseURL
}, 'remindMiNotifier')
const database = admin.database(app)
module.exports = database
sessionSignout.js - Signout user after a specific time period - if specified. Default to eternal session.
const functions = require('firebase-functions')
const moment = require('moment')
const database = require('../../firebase-db')
const admin = database.app
//Import enviroment variable config (.env)
require('dotenv').config()
//Export cron job - deploy: firebase deploy --only functions:sessionSignout
exports.sessionSignout = functions
.region('europe-west1')
.pubsub
.schedule('*/15 * * * *')
.timeZone('Europe/Stockholm')
.onRun(async (event) => {
//Start execution
const now = moment()
const defaultSessionTime = 0 //Eternal session
//Get all users and calculate inactive time - time since last login
let logoutUsersArray = []
await database.ref(`users`)
.once('value', (usersSnapshots) => {
usersSnapshots.forEach((snapshot) => {
const userData = snapshot.val()
const lastLoginOut = (userData.lastLoginOut) ? userData.lastLoginOut : 0
//Only process users that has a login/out time stamp
if(lastLoginOut > 0){
const userSessionTime = (userData.sessExp) ? userData.sessExp : defaultSessionTime
const hoursSinceLastLoginOut = now.diff(lastLoginOut, 'hours')
const logoutUser = ( userSessionTime > 0 && (hoursSinceLastLoginOut > userSessionTime) )
if(logoutUser){
const userId = snapshot.key
const userName = (userData.alias) ? userData.alias : userData.displayName
const email = (userData.email) ? userData.email : ''
const userObject = {
userId,
userName,
email,
lastLoginOut,
diffHours: now.diff(lastLoginOut, 'hours')
}
logoutUsersArray.push(userObject)
}
}
})
})
console.log('logoutUsersArray', logoutUsersArray)
//Collect all promises to carry out
let myPromises = []
// Revoke all refresh tokens for each user
logoutUsersArray.forEach((logoutUser) => {
const uid = logoutUser.userId
myPromises.push(
admin.auth().revokeRefreshTokens(uid)
.then(() => {
return admin.auth().getUser(uid)
})
.then((userRecord) => {
return new Date(userRecord.tokensValidAfterTime).getTime() / 1000
})
.then((timestamp) => {
// Retrieve the timestamp of the revocation, in seconds since the epoch.
console.log('Tokens revoked at: ', timestamp)
return Promise.resolve(true)
})
.catch((err) => {
console.error('Error', err)
return Promise.reject(err)
})
)
})
//Execute promises
console.log('Execute promises')
return Promise.all(myPromises)
.then(() => Promise.resolve(true))
.catch((err) => {
console.error('Error', err)
return Promise.reject(err)
})
})//End sessionSignout
Documentation on firebase-admin can be found here.
I am working on solutions using which i can send desktop push notification to subscribed clients.
I have created basic solution in where whenever user click on button i ask user for whether they want to allow notifications for my app or not!
I am getting an error of "Registration failed - permission denied" whenever i click on button for first time.
So that i am not able to get required endpoints to save at backend
Here is my code
index.html
<html>
<head>
<title>PUSH NOT</title>
<script src="index.js"></script>
</head>
<body>
<button onclick="main()">Ask Permission</button>
</body>
</html>
index.js
const check = () => {
if (!("serviceWorker" in navigator)) {
throw new Error("No Service Worker support!");
} else {
console.log("service worker supported")
}
if (!("PushManager" in window)) {
throw new Error("No Push API Support!");
} else {
console.log("PushManager worker supported")
}
};
const registerServiceWorker = async () => {
const swRegistration = await navigator.serviceWorker.register("/service.js?"+Math.random());
return swRegistration;
};
const requestNotificationPermission = async () => {
const permission = await window.Notification.requestPermission();
// value of permission can be 'granted', 'default', 'denied'
// granted: user has accepted the request
// default: user has dismissed the notification permission popup by clicking on x
// denied: user has denied the request.
if (permission !== "granted") {
throw new Error("Permission not granted for Notification");
}
};
const main = async () => {
check();
const swRegistration = await registerServiceWorker();
const permission = await requestNotificationPermission();
};
// main(); we will not call main in the beginning.
service.js
// urlB64ToUint8Array is a magic function that will encode the base64 public key
// to Array buffer which is needed by the subscription option
const urlB64ToUint8Array = base64String => {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, "+")
.replace(/_/g, "/");
const rawData = atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};
const saveSubscription = async subscription => {
console.log("Save Sub")
const SERVER_URL = "http://localhost:4000/save-subscription";
const response = await fetch(SERVER_URL, {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(subscription)
});
return response.json();
};
self.addEventListener("activate", async () => {
try {
const applicationServerKey = urlB64ToUint8Array(
"BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM"
);
const options = { applicationServerKey, userVisibleOnly: true };
const subscription = await self.registration.pushManager.subscribe(options);
console.log(JSON.stringify(subscription))
const response = await saveSubscription(subscription);
} catch (err) {
console.log(err.code)
console.log(err.message)
console.log(err.name)
console.log('Error', err)
}
});
self.addEventListener("push", function(event) {
if (event.data) {
console.log("Push event!! ", event.data.text());
} else {
console.log("Push event but no data");
}
});
Also i have created a bit of backend as well
const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const webpush = require('web-push')
const app = express();
app.use(cors());
app.use(bodyParser.json());
const port = 4000;
app.get("/", (req, res) => res.send("Hello World!"));
const dummyDb = { subscription: null }; //dummy in memory store
const saveToDatabase = async subscription => {
// Since this is a demo app, I am going to save this in a dummy in memory store. Do not do this in your apps.
// Here you should be writing your db logic to save it.
dummyDb.subscription = subscription;
};
// The new /save-subscription endpoint
app.post("/save-subscription", async (req, res) => {
const subscription = req.body;
await saveToDatabase(subscription); //Method to save the subscription to Database
res.json({ message: "success" });
});
const vapidKeys = {
publicKey:
'BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM',
privateKey: 'mHSKS-uwqAiaiOgt4NMbzYUb7bseXydmKObi4v4bN6U',
}
webpush.setVapidDetails(
'mailto:janakprajapati90#email.com',
vapidKeys.publicKey,
vapidKeys.privateKey
)
const sendNotification = (subscription, dataToSend='') => {
webpush.sendNotification(subscription, dataToSend)
}
app.get('/send-notification', (req, res) => {
const subscription = {endpoint:"https://fcm.googleapis.com/fcm/send/dLjyDYvI8yo:APA91bErM4sn_wRIW6xCievhRZeJcIxTiH4r_oa58JG9PHUaHwX7hQlhMqp32xEKUrMFJpBTi14DeOlECrTsYduvHTTnb8lHVUv3DkS1FOT41hMK6zwMvlRvgWU_QDDS_GBYIMRbzjhg",expirationTime:null,keys:{"p256dh":"BE6kUQ4WTx6v8H-wtChgKAxh3hTiZhpfi4DqACBgNRoJHt44XymOWFkQTvRPnS_S9kmcOoDSgOVD4Wo8qDQzsS0",auth:"CfO4rOsisyA6axdxeFgI_g"}} //get subscription from your databse here.
const message = 'Hello World'
sendNotification(subscription, message)
res.json({ message: 'message sent' })
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
Please help me
Try the following code:
index.js
const check = () => {
if (!("serviceWorker" in navigator)) {
throw new Error("No Service Worker support!");
} else {
console.log("service worker supported")
}
if (!("PushManager" in window)) {
throw new Error("No Push API Support!");
} else {
console.log("PushManager worker supported")
}
};
const saveSubscription = async subscription => {
console.log("Save Sub")
const SERVER_URL = "http://localhost:4000/save-subscription";
const response = await fetch(SERVER_URL, {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(subscription)
});
return response.json();
};
const urlB64ToUint8Array = base64String => {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, "+")
.replace(/_/g, "/");
const rawData = atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};
const registerServiceWorker = async () => {
return navigator.serviceWorker.register("service.js?"+Math.random()).then((swRegistration) => {
console.log(swRegistration);
return swRegistration;
});
};
const requestNotificationPermission = async (swRegistration) => {
return window.Notification.requestPermission().then(() => {
const applicationServerKey = urlB64ToUint8Array(
"BFPtpIVOcn2y25il322-bHQIqXXm-OACBtFLdo0EnzGfs-jIGXgAzjY6vNapPb4MM1Z1WuTBUo0wcIpQznLhVGM"
);
const options = { applicationServerKey, userVisibleOnly: true };
return swRegistration.pushManager.subscribe(options).then((pushSubscription) => {
console.log(pushSubscription);
return pushSubscription;
});
});
};
const main = async () => {
check();
const swRegistration = await registerServiceWorker();
const subscription = await requestNotificationPermission(swRegistration);
// saveSubscription(subscription);
};
service.js
self.addEventListener("push", function(event) {
if (event.data) {
console.log("Push event!! ", event.data.text());
} else {
console.log("Push event but no data");
}
});
I can think of three reasons that the permission is denied
1) your site is not on https (including localhost that is not on https), the default behaviour from chrome as far as i know is to block notifications on http sites. If that's the case, click on the info icon near the url, then click on site settings, then change notifications to ask
2) if you are on Safari, then safari is using the deprecated interface of the Request permission, that is to say the value is not returned through the promise but through a callback so instead of
Notification.requestPermission().then(res => console.log(res))
it is
Notification.requestPermission(res => console.log(res))
3) Your browser settings are blocking the notifications request globally, to ensure that this is not your problem run the following code in the console (on a secured https site)
Notification.requestPermission().then(res => console.log(res))
if you receive the alert box then the problem is something else, if you don't then make sure that the browser is not blocking notifications requests