I'm trying to do web push notifications that are clickable and when user click on it he should be redirected to some url.
I'm using Laravel 5.4 with package:
https://github.com/laravel-notification-channels/webpush
Here is some of the code:
app.blade.php
<script>
var _registration = null;
function registerServiceWorker() {
return navigator.serviceWorker.register('{{ asset('assets/js/service-worker.js') }}')
.then(function(registration) {
console.log('Service worker successfully registered.');
_registration = registration;
return registration;
})
.catch(function(err) {
console.error('Unable to register service worker.', err);
});
}
function askPermission() {
return new Promise(function(resolve, reject) {
const permissionResult = Notification.requestPermission(function(result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
})
.then(function(permissionResult) {
if (permissionResult !== 'granted') {
throw new Error('We weren\'t granted permission.');
}
else{
subscribeUserToPush();
}
});
}
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;
}
function getSWRegistration(){
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (_registration != null) {
resolve(_registration);
}
else {
reject(Error("It broke"));
}
});
return promise;
}
function subscribeUserToPush() {
getSWRegistration()
.then(function(registration) {
console.log(registration);
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
"{{env('VAPID_PUBLIC_KEY')}}"
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
sendSubscriptionToBackEnd(pushSubscription);
return pushSubscription;
});
}
function sendSubscriptionToBackEnd(subscription) {
return fetch('/api/save-subscription/{{Auth::user()->id}}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
})
.then(function(response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function(responseData) {
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
function enableNotifications(){
//register service worker
//check permission for notification/ask
askPermission();
}
registerServiceWorker();
</script>
service-worker.js
self.addEventListener('push', function(event) {
if (event.data) {
var data = event.data.json();
self.registration.showNotification(data.title, {
body: data.body,
icon: data.icon
});
console.log('This push event has data: ', event.data.text());
} else {
console.log('This push event has no data.');
}
});
self.addEventListener('notificationclick', function(event) {
console.log('Notification click: tag ', event.notification.tag);
event.notification.close();
var url = 'https://google.com';
console.log('kliknieto');
event.waitUntil(
clients.matchAll({
type: 'window'
})
.then(function(windowClients) {
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});
Notification:
public function toWebPush($notifiable, $notification)
{
return (new WebPushMessage)
->title('Some title')
->icon(asset("img/img.jpg"))
->body('Some body')
->action('View action', 'view_action')
->data(['id' => $notification->id, 'link' => $this->link]);
}
Generally it display the notification, but when I am trying to click on it I want to be redirected to https://google.com, but nothing happens. Do you have any idea how to fix it?
The problem has been solved after reset Google Chrome settings to default.
Found the solution. The action method accepts title and action, but the action is not an url. You should add notificationclick event to your service-worker.js file.
self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(
self.clients.openWindow('https://www.example.com/')
);
});
Reference - https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/notificationclick_event
Related
I have written three files which are: home-flatfull.jsp, settings-social-prefs.html and
google-js-api-wrapper.js files.
In home-flatfull.jsp file, I have written as below:
head.js('jscore/lib/base64.js', 'jscore/lib/google-js-api.js', 'jscore/lib/google-js-api-wrapper.js', function () {
var config = {
apiKey: 'AIzaSyCa52K8J68kr5b4S7Afu1FQzeleCfvzOFs',
clientId: '492662354647-877atvgj1a0pu82nrutsm50rcmg0lufh.apps.googleusercontent.com',
discoveryDocs: ["https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest"],
scopes: 'https://www.googleapis.com/auth/gmail.readonly',
listener: function(response){
console.log(' Check google ');
console.log(response);
}
};
googleJSAPI = GoogleJSAPI.getInstance(config);
});
In settings-social-prefs.html file I have defined as below:
<a onclick="googleJSAPI.signIn()" class="btn btn-sm btn-default">
{{agile_lng_translate 'prefs-email' 'enable'}}
</a>
In google-js-api-wrapper.js file, I have defined as below:
class GoogleJSAPI {
emailRegx = /^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
instance;
isActive = false;
constructor(config) {
console.log(' google code loaded ');
gapi.load('client:auth2', () => {
gapi.client.init({
apiKey: config.apiKey,
clientId: config.clientId,
discoveryDocs: config.discoveryDocs,
scope: config.scopes
}).then(() => {
this.isActive = true;
console.log(' config loaded ');
gapi.auth2.getAuthInstance().isSignedIn.listen(config.listener);
}, (error) => {
this.isActive = false;
console.log(JSON.stringify(error, null, 2));
});
});
}
static getInstance(config) {
if (!this.instance) {
this.instance = new GoogleJSAPI(config);
}
return this.instance;
}
isActive() {
return this.isActive;
}
isUserLoggedIn() {
return gapi.auth2.getAuthInstance().isSignedIn.get();
}
signIn = () => {
gapi.auth2.getAuthInstance().signIn();
}
signOut() {
gapi.auth2.getAuthInstance().signOut();
}
getSorted(a, b) {
return new Date(b.date).getTime() - new Date(a.date).getTime();
}
getMailList(queryObject) {
return new Promise((resolve, reject) => {
gapi.client.gmail.users.messages.list(queryObject).then(function (response) {
resolve(response.result);
});
});
}
getMailContentById(id) {
return new Promise((resolve, reject) => {
gapi.client.gmail.users.messages.get({
'userId': 'me', 'id': id
}).then((response) => {
let message = {};
let headers = response.result.payload.headers;
headers.forEach((header) => {
if (header.name === "From") {
message['from'] = header.value;
} else if (header.name === "Subject") {
message['subject'] = header.value;
} else if (header.name === "To") {
message['to'] = theader.value;
} else if (header.name === "Date") {
message['date'] = header.value;
} else if (header.name === "Cc") {
message['cc'] = header.value;
}
});
try {
if (response.result.payload) {
let body = "";
if (response.result.payload.body.size > 0) {
body = response.result.payload.body.data;
} else {
let bodyParts = response.result.payload.parts;
bodyParts.forEach((part, index) => {
if (part.type = "text/html") {
//console.log(index);
body = part.body.data;
return;
}
});
}
message['message'] = Base64.decode(body);
// console.log(message['body']);
}
} catch (e) {
//console.log(index);
//console.log(response.result);
//console.log(e);
}
resolve(message);
});
});
}
getInboxMailsWithContent(nextPageToken, fromEmail) {
var qData = '';
var queryObject = {
'userId': 'me',
'labelIds': ['INBOX']
};
if (nextPageToken) {
queryObject['pageToken'] = nextPageToken;
}
if (fromEmail) {
qData += 'from:' + fromEmail;
}
queryObject['q'] = qData;
return new Promise((resolve, reject) => {
gapi.client.gmail.users.messages.list(queryObject).then((response) => {
let resultObject = {
nextPageToken: response.result.nextPageToken
};
let messages = new Array();
let rawMessages = response.result.messages;
rawMessages.forEach((rawMessage, index) => {
gapi.client.gmail.users.messages.get({
'userId': 'me', 'id': rawMessage.id
}).then((response) => {
let message = {
id: rawMessage.id
};
let headers = response.result.payload.headers;
headers.forEach((header) => {
if (header.name === "From") {
message['from'] = header.value;
} else if (header.name === "Subject") {
message['subject'] = header.value;
} else if (header.name === "To") {
message['to'] = header.value;
} else if (header.name === "Date") {
message['date'] = header.value;
} else if (header.name === "Cc") {
message['cc'] = header.value;
}
});
try {
if (response.result.payload) {
let body = "";
if (response.result.payload.body.size > 0) {
body = response.result.payload.body.data;
} else {
let bodyParts = response.result.payload.parts;
bodyParts.forEach((part, index) => {
if (part.type = "text/html") {
f //console.log(index);
body = part.body.data;
return;
}
});
}
message['message'] = Base64.decode(body);
// console.log(message['body']);
}
} catch (e) {
//console.log(index);
//console.log(response.result);
//console.log(e);
}
messages[index] = message;
});
});
// resultObject.messages = messages.sort(this.getSorted);
resultObject.messages = messages;
resolve(resultObject);
});
});
}
}
function getInboxMailsWithContent(nextPageToken, fromEmail, callback) {
googleJSAPI.getInboxMailsWithContent(nextPageToken, fromEmail).then((response) => {
setTimeout(() => {
if (callback && typeof (callback) == "function") {
callback(response);
}
}, 3000);
});
}
When I clicked on enable button in settings-social-prefs.html file, I am just getting the gmail login page and gmail password page once I have provided gmail username and password, I got the consent screen which asks to allow access to user's email then I am getting the blank screen without getting the Gmail Read-Only mails of a specified user who has logged in. Can you please help me on this to get Gmail Read-Only mails when I click on enable button.
you may turn off two factor authentication (if on) and also "allow low secure apps to connect" in google my account settings
Ps: Displaying API in public should be avoided :-)
I currently have a script that checks for an incoming email (in a mailbox) every 30 seconds, using a recursion.
The package I'm using for this testing is imap-simple.
The below script currently does this as required;
var imaps = require('imap-simple');
const { connect } = require('net');
var config = {
imap: {
user: 'qatestspecialist#outlook.com',
password: 'specialistQa',
host: 'imap-mail.outlook.com',
port: 993,
tls: true,
authTimeout: 30000
}
};
module.exports = {
'delete any existing emails...': function () {
imaps.connect(config).then(function (connection) {
connection.openBox('INBOX').then(function () {
var searchCriteria = ['ALL'];
var fetchOptions = { bodies: ['TEXT'], struct: true };
return connection.search(searchCriteria, fetchOptions);
//Loop over each message
}).then(function (messages) {
let taskList = messages.map(function (message) {
return new Promise((res, rej) => {
var parts = imaps.getParts(message.attributes.struct);
parts.map(function (part) {
return connection.getPartData(message, part)
.then(function (partData) {
//Display e-mail body
if (part.disposition == null && part.encoding != "base64"){
console.log(partData);
}
//Mark message for deletion
connection.addFlags(message.attributes.uid, "\Deleted", (err) => {
if (err){
console.log('Problem marking message for deletion');
rej(err);
}
res(); //Final resolve
});
});
});
});
});
return Promise.all(taskList).then(() => {
connection.imap.closeBox(true, (err) => { //Pass in false to avoid delete-flagged messages being removed
if (err){
console.log(err);
}
});
connection.end();
});
});
});
},
'send email to seller and wait for mailbox notification': function (browser) {
// script to send an email to the mailbox...
},
'get new email info': function(browser) {
const createPromise = ms => new Promise((resolve, reject) => {
setTimeout(() => resolve(ms), ms)
});
function findUnseenEmails(connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = ['UNSEEN'];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
markSeen: false
};
return connection.search(searchCriteria, fetchOptions).then(function (results) {
var subjects = results.map(function (res) {
return res.parts.filter(function (part) {
return part.which === 'HEADER';
})
[0].body.subject[0];
});
return subjects.length > 0 ? subjects : createPromise(30000).then(function() { return findUnseenEmails(connection);
});
});
});
}
imaps.connect(config).then(function (connection) {
return findUnseenEmails(connection)
})
.then((subjects) => console.log(JSON.stringify(subjects)));
},
'Closing the browser': function (browser) {
browser.browserEnd();
}
};
This waits for an email and then displays the email 'header'.
However, the imap connection does not close, and stays open which is stopping my test suite from completing as the associated test never actually finishes.
I've tried adding the imap-simple command connection.end() in several places after the
imaps.connect(config).then(function (connection) {
return findUnseenEmails(connection)
})
part of the script, but it doesn't work.
So I'm just wondering if anyone knows where I should be adding this connection.end() command in order for the connection to be closed once an email has been received?
Any help would be greatly appreciated.
This has now been resolved in another post, using the following;
if (subjects.length > 0) {
connection.end();
return subjects;
} else {
return createPromise(5000).then(function() { return findUnseenEmails(connection)});
}
I'm working with Webpush notification in Laravel. It shows permission denied when i click on send notification button, and i want to update the count as soon as i press that send notification button. I'm following this link : https://medium.com/#sagarmaheshwary31/push-notifications-with-laravel-and-webpush-446884265aaa
I'm using following code:
enable-push.js
'use strict';
const swReady = navigator.serviceWorker.ready;
document.addEventListener('DOMContentLoaded', function () {
initSW();
});
function initSW() {
if (!"serviceWorker" in navigator) {
//service worker isn't supported
return;
}
//don't use it here if you use service worker
//for other stuff.
if (!"PushManager" in window) {
//push isn't supported
return;
}
//register the service worker
navigator.serviceWorker.register('../sw.js')
.then(() => {
console.log('serviceWorker installed!')
initPush();
})
.catch((err) => {
console.log(err)
});
}
function initPush() {
if (!swReady) {
return;
}
new Promise(function (resolve, reject) {
const permissionResult = Notification.requestPermission(function (result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
})
.then((permissionResult) => {
console.log(permissionResult);
if (permissionResult !== 'granted') {
throw new Error('We weren\'t granted permission.');
}
subscribeUser();
});
}
/**
* Subscribe the user to push
*/
function subscribeUser() {
swReady
.then((registration) => {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
"BIHpWZJBO9Ilqq0UG4+zKmOcYFZBbYIf2cC/sNnEOeA9DCihcDCBhwSVO+ELV9UN/ktv0ksAgoOF53zFT+h9vh="
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then((pushSubscription) => {
console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
storePushSubscription(pushSubscription);
});
}
/**
* send PushSubscription to server with AJAX.
* #param {object} pushSubscription
*/
function storePushSubscription(pushSubscription) {
const token = document.querySelector('meta[name=csrf-token]').getAttribute('content');
fetch('/push', {
method: 'POST',
body: JSON.stringify(pushSubscription),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': token
}
})
.then((res) => {
return res.json();
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
});
}
/**
* urlBase64ToUint8Array
*
* #param {string} base64String a public vapid key
*/
function urlBase64ToUint8Array(base64String) {
var padding = '='.repeat((4 - base64String.length % 4) % 4);
var base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
var rawData = window.atob(base64);
var outputArray = new Uint8Array(rawData.length);
for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
Please help to sort out my issue,
I'll throw the http request because I'm calling the refresh token when it returns 401. After the refresh token response, I need to throw the previous request
SAMPLE
Logın -> — 1 hours later— —> call product —> 401 —> call refresh token —> call product
I try this link a link and look this link a link but doesn't work.
Catch the 401 error
setInterceptors = () => {
axios.interceptors.response.use(
response => {
return response;
},
err => {
return new Promise((resolve, reject) => {
if (err.response.status === 401 && err.config && !err.config.__isRetryRequest) {
const originalRequest = err.config;
this.emit('onAutoLogin', originalRequest);
}
// throw err;
});
}
);
};
Call my action
jwtService.on('onAutoLogin', originalRequest => {
jwtService
.signInWithToken()
.then(res => {
if (res.access_token) {
originalRequest.headers['Authorization'] = 'Bearer ' + res.access_token;
Axios.request(originalRequest).then(response => {
store.dispatch({
type: ** MY PROBLEM İS HERE **
payload: response.data
});
});
}
})
.catch(err => {
jwtService.setSession(null);
});
using this link I was able to solve the problem without triggering the redux store.
let isRefreshing = false;
let failedQueue = [];
const processQueue = (error, token = null) => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
axios.interceptors.response.use(
response => {
return response;
},
err => {
const originalRequest = err.config;
if (err.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function(resolve, reject) {
failedQueue.push({ resolve, reject });
})
.then(token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token;
return axios(originalRequest);
})
.catch(err => {
return Promise.reject(err);
});
}
originalRequest._retry = true;
isRefreshing = true;
return new Promise(function(resolve, reject) {
axios
.post('/fooUrl/refreshToken', {
refreshToken: "fooToken"})
.then(({ data }) => {
axios.defaults.headers.common['Authorization'] = 'Bearer ' + data.fooToken;
originalRequest.headers['Authorization'] = 'Bearer ' + data.fooToken;
processQueue(null, data.fooToken);
resolve(axios(originalRequest));
})
.catch(err => {
processQueue(err, null);
store.dispatch(showMessage({ message: 'Expired Token' }));
reject(err);
})
.then(() => {
isRefreshing = false;
});
});
}
return Promise.reject(err);
}
);
I want to display drive file metadata
"iconLink",
"thumbnailLink"
of each of the files in drive, but getting output for fields like kind, id, name, mimeType only. While other fields are not displayed.
function loadDriveApi() {
gapi.client.load('drive', 'v3', listFiles);
}
function listFiles() {
var request = gapi.client.drive.files.list({});
request.execute(function(resp) {
var files = resp.files;
if (files && files.length > 0) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
appendPre(file.iconLink);
}
} else {
appendPre('No files found.');
}
});
}
at least you will need the scope: https://www.googleapis.com/auth/drive.metadata.readonly + OAuth (API-Key & Client-ID)
you can test it at: https://developers.google.com/drive/v3/reference/files/list
in the "fields" input add: files(iconLink,thumbnailLink)
if you use https://apis.google.com/js/api.js, be sure to add your domain to API-Key -> HTTP-Referrer & Client-ID -> JavaScript-Source & Forwarding-URI (# https://console.developers.google.com/apis/credentials)
you can find a basic gapi usage sample here: https://github.com/google/google-api-javascript-client/blob/51aa25bed4f6c36d8e76fd3b9f7e280ded945c98/samples/loadedDiscovery.html
I promisified the gapi client a bit some time ago, because I disliked the mix of callbacks and thenables in the methods.. this worked for me (assuming api.js was already loaded), but only hold 100 file entries in the response.
window.gapiPromisified = {
apiKey: 'XXXXXXXXXXX',
clientId: 'XXXXX-XXXXXX.apps.googleusercontent.com'
}
window.gapiPromisified.init = function init () {
return new Promise(resolve => {
gapi.load('client:auth2', () => {
if (!document.getElementById('gapiAuthButton')) {
let authButton = document.createElement('button')
authButton.id = 'gapiAuthButton'
authButton.style.display = 'none'
authButton.style.marginLeft = 'auto'
authButton.style.marginRight = 0
document.body.insertBefore(authButton, document.body.firstChild)
authButton.addEventListener('click', () => {
let GoogleAuth = gapi.auth2.getAuthInstance()
if (GoogleAuth.isSignedIn.get()) {
GoogleAuth.signOut()
} else {
GoogleAuth.signIn()
}
})
}
gapi.client.setApiKey(this.apiKey)
gapi.auth2.init({ client_id: this.clientId })
.then(() => resolve())
})
})
}
window.gapiPromisified.signIn = function signIn () {
return new Promise(resolve => {
let GoogleAuth = gapi.auth2.getAuthInstance()
// Listen for sign-in state changes
GoogleAuth.isSignedIn.listen(isSignedIn => {
let authButton = document.getElementById('gapiAuthButton')
if (isSignedIn) {
authButton.textContent = 'Sign-out'
resolve()
} else {
authButton.textContent = 'Sign-in'
}
})
// Handle the initial sign-in state
let authButton = document.getElementById('gapiAuthButton')
let isSignedIn = GoogleAuth.isSignedIn.get()
authButton.textContent = (isSignedIn) ? 'Sign-out' : 'Sign-in'
document.getElementById('gapiAuthButton').style.display = 'block'
if (isSignedIn) {
resolve()
} else {
GoogleAuth.signIn()
}
})
}
window.gapiPromisified.getReady = function getReady () {
if (!gapi.hasOwnProperty('auth2')) {
return this.init()
.then(() => this.signIn())
} else {
if (gapi.auth2.getAuthInstance().isSignedIn.get()) {
return Promise.resolve()
} else {
return this.signIn()
}
}
}
window.gapiPromisified.getScopes = function getScopes (scopes) {
return new Promise((resolve, reject) => {
let GoogleUser = gapi.auth2.getAuthInstance().currentUser.get()
if (GoogleUser.hasGrantedScopes(scopes)) {
resolve()
} else {
// method returns goog.Thenable
GoogleUser.grant({ 'scope': scopes })
.then(onFulfilled => {
resolve(onFulfilled)
}, onRejected => {
reject(onRejected)
})
}
})
}
window.gapiPromisified.loadAPI = function loadAPI (urlOrObject) {
return new Promise((resolve, reject) => {
// method returns goog.Thenable
gapi.client.load(urlOrObject)
.then(onFulfilled => {
resolve(onFulfilled)
}, onRejected => {
reject(onRejected)
})
})
}
window.gapiPromisified.metadata = function metadata () {
return new Promise((resolve, reject) => {
this.getReady()
.then(() => this.getScopes('https://www.googleapis.com/auth/drive.metadata.readonly'))
.then(() => this.loadAPI('https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'))
.then(() => {
gapi.client.drive.files.list({
fields: 'files(iconLink,thumbnailLink)'
})
.then(onFulfilled => {
resolve(onFulfilled)
}, onRejected => {
reject(onRejected)
})
})
})
}
window.gapiPromisified.metadata()
.then(res => {
res.result.files.forEach(file => {
console.log(file.iconLink)
console.log(file.thumbnailLink)
})
})
In the v3 you need to specify which fields you want included in the metadata response. See the fields= parameter