The user story:
On a browser, I have my Progressive Web App opened on any URL
https://mypwa.com/xxxx
I receive a mail, with a link to a page of my PWA
https://mypwa.com/post/<postId>
Instead of opening a new tab in the browser, I want my existing PWA tab to get the focus and go on the URL. I don't want a new tab nor a new PWA startup (it costs a lot in records download)
With postMessage, Service Worker can tell existing tab to get focus and go on the /post/<postId> path. Supposed to work but I get a "DOM Exception" on client.focus() in the service worker, from a fetch event, in short:
self.addEventListener("fetch", function(event) {
var requestUrl = new URL(event.request.url);
var pathname = requestUrl.pathname;
if (
event.request.mode === "navigate" &&
requestUrl.origin === location.origin
) {
const rootUrl = new URL("/", location).href;
const mg = pathname.match(regexpPost);
const postId = mg[1];
event.waitUntil(
clients.matchAll().then(matchedClients => {
for (let client of matchedClients) {
if (client.url.indexOf(rootUrl) >= 0) {
return client
.focus() // ***** CRASHING HERE *****
.then(() => {
return sendMessageClient(client, { postId }).then(resp => {
other;
});
});
}
}
})
);
}
});
What I'm missing also is how to close the newly opened tab. I don't want to keep it: dead tabs can accumulate and this is not a good user experience.
Tried javascript window.close(); but it has to respond to a user action...
I have used Flutter Blue for a college work, where I need to create an application to fetch and pass information to an equipment. The passing of this data must be automatic, as in any application (after all the end user should not look for the services and characteristics necessary to carry out the process). The problem is that I am not being able to perform the data passing soon after connecting with the device.
I'm using the App example I downloaded at https://github.com/pauldemarco/flutter_blue, so the basic idea is that as soon as I connect to my bluetooth device I send a message to a certain device. There is already an answered question that has the interest of setting notifications when connecting at Flutter Blue Setting Notifications
I followed the same example but instead of using _setNotification (c) I used the _writeCharacteristic (c), but it does not work.
_connect(BluetoothDevice d) async {
device = d;
// Connect to device
deviceConnection = _flutterBlue
.connect(device, timeout: const Duration(seconds: 4))
.listen(
null,
onDone: _disconnect,
);
// Update the connection state immediately
device.state.then((s) {
setState(() {
deviceState = s;
});
});
// Subscribe to connection changes
deviceStateSubscription = device.onStateChanged().listen((s) {
setState(() {
deviceState = s;
});
if (s == BluetoothDeviceState.connected) {
device.discoverServices().then((s) {
services = s;
for(BluetoothService service in services) {
for(BluetoothCharacteristic c in service.characteristics) {
if(c.uuid == new Guid("06d1e5e7-79ad-4a71-8faa-373789f7d93c")) {
_writeCharacteristic(c);
} else {
print("Nope");
}
}
}
setState(() {
services = s;
});
});
}
});
}
I have changed the original code so that it prints me the notifications as soon as I perform the writing method. The notifications should show me a standard message that is in the firmware of the device, but instead it is printing me the Local Name of the bluetooth chip, being that if I select the service and characteristic manually the return is the correct message.
You'd need to elaborate how you're executing writes on the descriptor - inside _writeCharacteristic(c).
BluetoothDescriptor.write() is a Future per docs, you should be able to catch any errors thrown during write.
I am using service worker to achieve background sync functionality. Following is my code:
importScripts( 'https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js' )
const queue = new workbox.backgroundSync.Queue('registerQueue', { callbacks: {
queueDidReplay: function(requestArray) {
let requestSynced = 0
requestArray.forEach(item => {
if (!item.error) {
requestSynced++
}
})
if (requestSynced > 0) {
new BroadcastChannel('backgroundSynBroadCastChannel').postMessage(
requestSynced
)
}
} } }) const GraphQLMatch = /graphql(\S+)?/
self.addEventListener('fetch', event => { if (
null !== event.request.url.match(GraphQLMatch) &&
navigator.onLine === false ) {
const promiseChain = fetch(event.request.clone()).catch(err => {
return queue.addRequest(event.request)
})
event.waitUntil(promiseChain) } })
self.addEventListener('message', event => { if (!event.data) {
return }
switch (event.data) {
case 'skipWaiting':
self.skipWaiting()
break
default:
break } })
workbox.precaching.precacheAndRoute([])
/* * Alternate for navigateFallback & navigateFallbackBlacklist */ workbox.routing.registerNavigationRoute('/index.html', { blacklist: [/^\/__.*$/] })
On internet disconnect, the requests are queued on the indexed DB. But the problem is After acquiring the connection back, the background sync is made at least 5-10 mins later. Is there any way to do the background sync immediately upon internet re-connection or at least reduce the time for syncing.
Thanks in advance.
You could manually trigger a replay of a queue as soon as your connection is back by sending an event to the service worker.
In your service worker:
self.addEventListener('message', (event) => {
if (event.data.type === 'replayQueue') {
queue.replayRequests();
}
});
In your app (using workbox-window):
if ('serviceWorker' in navigator) this.wb = new Workbox('/service-worker.js');
window.addEventListener(‘online’, function(event){
this.wb.messageSW({type: 'replayQueue'});
});
Unfortunately, doesn't look like it's possible right now to change the timing for the sync. According to Google's Workbox documentation:
Browsers that support the BackgroundSync API will automatically replay
failed requests on your behalf at an interval managed by the browser,
likely using exponential backoff between replay attempts.
If Google's documentation is correct (at least for Chrome) that also means that the longer the user has been offline, the probability of a longer wait for the sync event increases.
#cyril-hanquez's idea is good as long as the user is still utilizing your site when they come back online. You might also want to add a "fetchDidFail" callback to handle more network outage edge cases. Along those lines: you might want to avoid relying on the status of "navigator.onLine", since it doesn't always do what one would expect.
I am trying to handle push notifications on iOS.
My simple code looks something similar to this:
var Cloud = require("ti.cloud");
var deviceToken = null;
var deviceToken = Ti.App.Properties.getString('deviceToken');
Ti.App.iOS.registerUserNotificationSettings({
types: [
Ti.App.iOS.USER_NOTIFICATION_TYPE_ALERT,
Ti.App.iOS.USER_NOTIFICATION_TYPE_SOUND,
Ti.App.iOS.USER_NOTIFICATION_TYPE_BADGE
]
});
Ti.App.iOS.addEventListener('usernotificationsettings', function registerForPush() {
Ti.App.iOS.removeEventListener('usernotificationsettings', registerForPush);
Ti.Network.registerForPushNotifications({
success: function(e) {
if (e.deviceToken !== Ti.App.Properties.getString('deviceToken', null)) {
deviceToken = e.deviceToken;
Ti.App.Properties.setString('deviceToken', deviceToken)
subscribeToChannel();
} else {
Ti.API.info('Already registered for push notifications!');
}
},
error: function(e) {
Ti.API.error('Failed to register for push notifications: ' + e.error);
},
callback: receivePush
});
});
function subscribeToChannel () {
Cloud.PushNotifications.subscribeToken({
device_token: deviceToken,
channel: 'general',
type: Ti.Platform.name == 'android' ? 'android' : 'ios'
}, function (e) {
alert(e.success === true ? 'Subscribed' : 'Error!');
});
}
// When receieve interactive remote notification
Ti.App.iOS.addEventListener('remotenotificationaction', function(e) {
alert('remotenotificationaction: ' + JSON.stringify(e));
});
// When receieve interactive notification in the background
Ti.App.iOS.addEventListener('localnotificationaction', function(e) {
alert('localnotificationaction');
});
// When receieve interactive notification in the foreground
Ti.App.iOS.addEventListener('notification', function(e) {
alert('notification');
});
function receivePush(e) {
alert('receivePush');
}
For the most part everything works fine. The following happens when I send a remote push notification:
When the app is in the background, a notification appears. Upon clicking the notification, I get the "receivePush" message, as expected
When the app is in the foreground, a notification does not appear, but I still get the "receivePush" message, as expected.
However, when I receive a notification while the app is in the background, and then click on the app directly (i.e. not clicking the notification), none of the above events is triggered!
How can I make sure an event is triggered for the last case.
I don't think this is possible since your callback function is assigned with a notification behavior, not app starting. This is not a Titanium problem but a workflow misunderstanding if you know what I mean.
I think for you is best to always check something when the app starts, not related to notifications.
I have spent lot of time to find correct cordova plugin for parse push notifications for both Android & iOS platforms.
My requirements are:
To receive parse push notification (in both android & iOS)
Able to store all the incoming push notifications in mobile local storage Sqlite.
I have tried all the below parse push cordova plugins for both Android & iOS platforms.
https://github.com/avivais/phonegap-parse-plugin
https://github.com/taivo/parse-push-plugin
https://github.com/campers/parse-push-plugin
https://github.com/manishiitg/parse-push-plugin
For Android: All the above plugins are working perfectly to fulfill my above mentioned requirements.
For iOS: Only 1st plugin i.e https://github.com/avivais/phonegap-parse-plugin is working. And that too i was not able to save the notifications in local storage sqlite. That means only my 1st requirement is fulfilled but not my 2nd requirement.
All the github pages of remaining plugins (i.e 2nd, 3rd, 4th) states that:
"Please note that I've only worked on the Android aspect of this fork. The iOS side is not yet up to date."
Is there any plugin which will work for both Android & iOS platforms to fulfill my 2 requirements?
(or)
If there is no common plugin for both the platforms, then how can I store the incoming plugins in iOS sqlite?
Please help me. Thanks in advance.
I happen to maintain https://github.com/taivo/parse-push-plugin
It looks like you caught my fork at its infancy. I picked it up when the upstream fork seemed stagnant for a while and at that time I was only addressing the Android aspect. Since then I've provided full iOS support. And it works for parse-server as well as the out-going parse.com. I also did one better and made installation just a matter of
cordova add https://github.com/taivo/parse-push-plugin
and writing a few config.xml tags to indicate server url, and app id.
That should take out the big pain of manually messing with Android Manifest, Java, and Objective C when setting up the plugin.
It should now meet or exceed your requirement. To receive push notification and store in sqlite, all you have to do is set an event handler in javascript. Be sure to wrap it with some sort of device ready or platform ready event handler to ensure the plugin has properly loaded.
$ionicPlatform.ready(function(){
if(window.ParsePushPlugin){
ParsePushPlugin.on('receivePN', function(pn){
console.log('yo i got this notif:' + JSON.stringify(pn) );
//
// do your sqlite storage here
//
});
}
});
You just might be interested in the Azure Push Notifications. It combines both Push notification services so you can send messages to both devices from one central point.
I quote:
Notification Hubs A scalable, cross-platform solution for sending push
notifications to mobile devices, Notification Hubs works well with
Cordova apps. Notification Hubs manages the registrations with each
PNS. More important, Notification Hubs lets you create template
registrations so you can send messages to all registered devices,
regardless of platform, with only a single line of code. You can also
use tags to send targeted notifications only to devices with specific
registrations. For more information about Notification Hubs, see the
Azure Web site at aka.ms/nkn4n4.
Here i have a helper class for registering your device with the pushnotification service. For sending push notifications, you can use an azure portal and send styled push notifications in json format.
var Pushman = {
Initialize: function (hubConnString, hubName, gcmSenderId, callbackRegistered, callbackUnRegistered, callbackInlineNotification, callbackBackgroundNotification, callbackError) {
//store connection and callback information on app startup for Push Registration later
Pushman.HubConnectionString = hubConnString;
Pushman.HubName = hubName;
Pushman.GcmSenderId = gcmSenderId;
//callbacks
Pushman.RegisteredCallback = callbackRegistered;
Pushman.UnRegisteredCallback = callbackUnRegistered;
Pushman.NotificationForegroundCallback = callbackInlineNotification;
Pushman.NotificationBackgroundCallback = callbackBackgroundNotification;
Pushman.ErrorCallback = callbackError;
},
RegisterForPushNotifications: function (tags) {
//setup Azure Notification Hub registration
Pushman.Hub = new WindowsAzure.Messaging.NotificationHub(Pushman.HubName, Pushman.HubConnectionString, Pushman.GcmSenderId);
Pushman.Hub.registerApplicationAsync(tags).then(Pushman.onRegistered, Pushman.onError);
//setup PushPlugin registration
Pushman.Push = window.PushNotification;
var push;
//register depending on device being run
if (device.platform == 'android' || device.platform == 'Android' || device.platform == "amazon-fireos") {
//android
push = Pushman.Push.init(
{ "android": { "senderID": Pushman.GcmSenderId } }
);
push.on('registration', Pushman.onRegistered);
push.on('notification', Pushman.onAndroidNotification);
push.on('error', Pushman.onError);
} else {
//iOS
push = Pushman.Push.init(
{ "ios": { "alert": "true", "badge": "true", "sound": "true" } }
);
push.on('registration', Pushman.onRegistered);
push.on('notification', Pushman.onIOSNotification);
push.on('error', Pushman.onError);
}
},
UnRegisterForPushNotifications: function () {
if (Pushman.Hub != null) {
//dont pass through error handler
//unreg azure
Pushman.Hub.unregisterApplicationAsync()
.then(Pushman.onUnRegistered, null);
//unreg native
Pushman.Push.unregister(Pushman.onUnRegistered, null);
}
},
onRegistered: function (msg) {
Pushman.log("Registered: " + msg.registrationId);
//only call callback if registrationId actually set
if (msg.registrationId.length > 0 && Pushman.RegisteredCallback != null) {
Pushman.RegisteredCallback(msg);
}
},
onUnRegistered: function () {
Pushman.log("UnRegistered");
if (Pushman.UnRegisteredCallback != null) {
Pushman.UnRegisteredCallback();
}
},
onInlineNotification: function (msg) {
Pushman.log("OnInlineNotification: " + msg);
if (Pushman.NotificationForegroundCallback != null) {
Pushman.NotificationForegroundCallback(msg);
}
},
onBackgroundNotification: function (msg) {
Pushman.log("OnBackgroundNotification: " + msg);
if (Pushman.NotificationBackgroundCallback != null) {
Pushman.NotificationBackgroundCallback(msg);
}
},
onColdStartNotification: function (msg) {
Pushman.log("OnColdStartNotification: " + msg);
if (Pushman.NotificationBackgroundCallback != null) {
Pushman.NotificationBackgroundCallback(msg);
}
},
onError: function (error) {
Pushman.log("Error: " + error);
if (Pushman.ErrorCallback != null) {
Pushman.ErrorCallback(error);
}
},
onAndroidNotification: function (e) {
switch (e.event) {
case 'registered':
if (e.regid.length > 0) {
Pushman.onRegistered("Registered");
}
break;
case 'message':
if (e.foreground) {
//if this flag is set, this notification happened while app in foreground
Pushman.onInlineNotification(e.payload.message);
} else {
//otherwise app launched because the user touched a notification in the notification tray.
if (e.coldstart) {
//app was closed
Pushman.onColdStartNotification(e.payload.message);
}
else {
//app was minimized
Pushman.onBackgroundNotification(e.payload.message);
}
}
break;
case 'error':
Pushman.onError(e.msg);
break;
default:
Pushman.onError("Unknown message");
break;
}
},
onIOSNotification: function (event) {
//TODO: not sure how ios works re cold start vs inline msg types?
if (event.alert) {
navigator.notification.alert(event.alert);
}
if (event.badge) {
Push.setApplicationIconBadgeNumber(app.successHandler, app.errorHandler, event.badge);
}
},
tokenHandler: function (result) {
// iOS - not sure its use though appears somewhat important
// Your iOS push server needs to know the token before it can push to this device
// here is where you might want to send it the token for later use.
alert('device token = ' + result);
},
log: function (msg) {
console.log(msg);
},
}
///"class" variables - not sure how to put them into the js "class"
Pushman.Push = null;
Pushman.Hub = null;
Pushman.HubConnectionString = null;
Pushman.HubName = null;
Pushman.GcmSenderId = null;
Pushman.NotificationForegroundCallback = null;
Pushman.NotificationBackgroundCallback = null;
Pushman.RegisteredCallback = null;
Pushman.UnRegisteredCallback = null;
Pushman.ErrorCallback = null;
I did not write this myself, all credit goes to this guy.
Then you just need to initialize the plugin when the application starts:
//azure notificationshub connection information
notificationHubPath = "notificationhub name";
connectionString = "notificatin hub connectionstring";
//sender id for google cloud services
var senderIdGCM = "sender id from google gcm";
//tag registration (csv string), can be empty but not undefined
var registrationTagsCsv = ""; //test1, test2
var app = {
Initialize: function () {
//reg for onload event
this.AppStart();
},
AppStart: function () {
"use strict";
document.addEventListener('deviceready', app.onLoad, false);
document.addEventListener('deviceready', onDeviceReady.bind(this), false);
function onDeviceReady() {
// Handle the Cordova pause and resume events
document.addEventListener('pause', onPause.bind(this), false);
document.addEventListener('resume', onResume.bind(this), false);
// TODO: Cordova has been loaded. Perform any initialization that requires Cordova here.
};
function onPause() {
// TODO: This application has been suspended. Save application state here.
};
function onResume() {
// TODO: This application has been reactivated. Restore application state here.
};
},
onLoad: function () {
app.log("Initializing...");
//setup push notifications
Pushman.Initialize(connectionString, notificationHubPath, senderIdGCM,
app.onNotificationRegistered, app.onNotificationUnRegistered,
app.onNotificationInline, app.onNotificationBackground, app.onNotificationError);
//hookup cmd buttons
app.registerForPush();
//$("#register").click(app.registerForPush);
//$("#unregister").click(app.unRegisterForPush);
app.onAppReady();
},
registerForPush: function (a, c) {
app.log("Registering...");
//register for tags
Pushman.RegisterForPushNotifications(registrationTagsCsv);
},
unRegisterForPush: function (a, c) {
app.log("UnRegistering...");
//register for tags
Pushman.UnRegisterForPushNotifications();
},
onAppReady: function () {
app.log("Ready");
},
onNotificationRegistered: function (msg) {
app.log("Registered: " + msg.registrationId);
},
onNotificationUnRegistered: function () {
app.log("UnRegistered");
},
onNotificationInline: function (data) {
app.log("Inline Notification: " + data);
},
onNotificationBackground: function (data) {
app.log("Background Notification: " + data);
},
onNotificationError: function (error) {
app.log("Error: " + error);
},
log: function (msg) {
console.log(msg);
},
};
If you want to store the messages then you just need to add your code for storing to sql where the messages get received. You'll need an azure account to make this work, here you can get a free trail. It will allow you to send up to 1 million push notifications a month free of charge.
I think this article may be of use, it has more of a direct native workaround for your hybrid app to work
http://www.hiddentao.com/archives/2015/04/10/parse-push-notifications-for-your-android-and-ios-cordova-app/.
I'm working on a Cordova android app, and this seems to be a working solution