Sending Notifications with the Web Push Protocol on KaiOS.
The Web Push Protocol
Push Notifications on KaiOS work more or less the same as they do in a web browser like Firefox, with some caveats. They are a great way to provide utility and drive engagement. For instance:
- Chat app with notifications for new messages
- Email app with notifications for new emails
- Podcast app with notifications for new episodes
- Sports app with notifications for game scores
- Bank app with notifications for transactionals
Push Notifications require two components: a client with a registered ServiceWorker to receive notifications, and a web server to store subscriptions and send notifications. This article focuses on KaiOS-specific client-side interactions, as there are many great back-end libraries like web-push. There are also alternatives such as WebSockets and Server-Sent Events (SSE), although these are limited to interactions while the app is active in the foreground, while Web Push allows notifications to be received even if the app isn’t open.
Permissions
In KaiOS 2.5, applications must explicitly request permission to subscribe and receive push notifications by declaring the
"push"
permission in their manifest.webapp
file. In KaiOS 3.0, the
"push"
permission is available by default to all applications.
Similarly, on KaiOS 2.5 (but not KaiOS 3.0), the
"serviceworker"
permission is needed to use register a
ServiceWorker to receive push notifications. This permission was removed in KaiOS 3.0 to align with standard Progressive Web App (PWA) functionality.
Note: it’s impossible to forget the "serviceworker"
permission because without it, on KaiOS 2.5 navigator.serviceWorker
will be undefined
.
Finally, the
"desktop-notification"
is needed on both KaiOS 2.5 and 3.0 to display notifications using the Notification
constructor or ServiceWorkerRegistration.showNotification()
. In KaiOS 2.5, by default the permission is granted for all app types, while in KaiOS 3.0 "pwa"
type apps will display a prompt for the user to confirm. While user interaction is not required to display notifications or request permission in KaiOS 2.5, it is for "pwa"
apps in KaiOS 3.0.
Note: it’s important to check for permission before using the above APIs. Depending on the KaiOS version and app type, users might be able revoke default permissions in Settings > Privacy & Security > App Permissions.
Notification Types and ServiceWorkers
Depending on whether
Notification.requireInteraction
is set, KaiOS will display notifications differently. If requireInteraction
is enabled, notifications will display in the foreground as a modal with actions (by default, “Dismiss”). The user must take an action to proceed. If requireInteraction
is disabled, which it is by default, then the notification will display for several seconds before becoming available in the Notification Center.
There are two ways to display web notifications, using the
Notification
constructor and by calling
ServiceWorkerRegistration.showNotification()
. Using the Notification
constructor creates non-persistent notifications that users can only respond to while your application is open. In contrast, actions are only supported for persistent notifications. In contrast, a notification associated with a ServiceWorker is a
persistent notification that will trigger the the notificationclick
and notificationclose
events.
ServiceWorkers on KaiOS work similarly to Firefox, but on KaioS 2.5 there’s one special function worth mentioning: the
Clients.openApp
function which is part of the
Clients
interface. openApp
launches a web app with the same origin of its service worker scope.
1self.addEventListener('notificationclick', (event) => {
2 let found = false;
3 clients.matchAll().then((clients) => {
4 // Check if the app is already opened
5 for (i = 0; i < clients.length; i++) {
6 if (clients[i].url === event.data.url) {
7 found = true;
8 break;
9 }
10 }
11
12 // If not, launch the app
13 if (!found) {
14 clients.openApp({ msg: 'Data' });
15 }
16 });
17});
The optional msg
property is a String that gets passed to the app via the serviceworker-notification
system message. Apps need to register for this event in manifest.webapp
(KaiOS 2.5).
1{
2 "messages": [
3 { "serviceworker-notification": "/" }
4 ]
5}
These messages can then be received by the application using system message handlers. In KaiOS 2.5, it’s as simple as calling navigator.mozSetMessageHandler
. In KaiOS 3.0 this isn’t necessary because Clients.openApp
was removed in favor of the standard Clients.openWindow
, WindowClient.focus
, and Client.postMessage
, but there is an analagous
SystemMessage API.
1navigator.mozSetMessageHandler("serviceworker-notification", (event) => {
2 console.log(event.msg)
3});
By combining a ServiceWorker, the push
event, Clients.openApp
, and a message handler for the serviceworker-notification
event, it’s now possible to build a KaiOS web app that receives push messages, displays notifications, and opens the appropriate page.
VAPID Keys
Although applicationServerKey
is required in browsers like Chrome and Edge, KaiOS and Firefox allow developers to send push notifications without setting applicationServerKey
during subscription.
1serviceWorkerRegistration.pushManager.subscribe({
2 userVisibleOnly: true,
3 applicationServerKey: null,
4})
However, if Voluntary Application Server Identification for Web Push (VAPID) keys are not configured, only empty push notifications will be allowed. This significantly limits the utility of push notifications. For that reason, it’s best to generate and use VAPID keys. It’s simple with the web-push library:
1web-push generate-vapid-keys --json
This generates a public & private key pair. The public key is that’s provided to applicationServerKey
in the form of a Uint8Array
, while the private key is to send notifications server-side and should not be shared!
1{
2"publicKey":"BF7_KAxbQWoYtHwB7YnL0BlSQ-tvfmWrbp6Z9pUC_8kAdBUDv2QAZ4QScnQjwS982cpV5mqtT6QebWQLP5GpGwM",
3"privateKey":"4cjKnRevSuxLh6KHBEQzG9I3pzM_LFZwyxkqBsW1Kdg"
4}
Converting a Base64-encoded String into a Uint8Array
is simple with a function like urlB64ToUint8Array
, for instance
base64-to-uint8array:
1function urlB64ToUint8Array(base64String) {
2 let padding = '='.repeat((4 - base64String.length % 4) % 4);
3 let base64 = (base64String + padding)
4 .replace(/\-/g, '+')
5 .replace(/_/g, '/');
6
7 let rawData = window.atob(base64);
8 let outputArray = new Uint8Array(rawData.length);
9
10 for (let i = 0, e = rawData.length; i < e; ++i) {
11 outputArray[i] = rawData.charCodeAt(i);
12 }
13 return outputArray;
14};
PushManager.subscribe()
returns a Promise
, with the important details in the response object. Putting it all together:
1const PUBLIC_KEY = "BF7_KAxbQWoYtHwB7YnL0BlSQ-tvfmWrbp6Z9pUC_8kAdBUDv2QAZ4QScnQjwS982cpV5mqtT6QebWQLP5GpGwM";
2
3serviceWorkerRegistration.pushManager.subscribe({
4 userVisibleOnly: true,
5 applicationServerKey: urlB64ToUint8Array(PUBLIC_KEY),
6}).then(
7 (pushSubscriptionObj) => {
8 // Serialized endpoint & keys property
9 const pushSubscription = pushSubscriptionObj.toJSON();
10 saveServerSide(pushSubscription); // TODO
11});
The pushSubscription
object will contain two important properties, endpoint
and keys
. Here’s an example of what that looks like on KaiOS:
1{
2 "endpoint": "https://push.kaiostech.com:8443/wpush/v2/gAAAAABf4QzE-pX31ttCqVfnQQH90dCU9QvwXmWJgcdcHR6BZWMMQ1S_uRfi217k4FAoivLjhJviXJDWF2s7ya47OnfcSjZt2J98HIHFK2UQzZgG5VA7Jagvh-R0SrggsMpSWugCe90Sk9_mqCILmJPe1BN8NF5_jbaDO0U3VwTiF7lMGo9eccI",
3 "keys": {
4 "auth": "SIQaHqu_J-jZfskndcqeYw",
5 "p256dh": "ABykpssKmfXKskWYi6tVwvCZUthXodHLBMJnxUtTym3PCcNse5WFeRbepfXDFhn21jIVxEc_HrFdgKuURbJFh74"
6 }
7}
On the JioPhone, push subscriptions will look similar but resolve to a different domain.
1{
2 "endpoint": "https://update.kai.jiophone.net:8443/wpush/v1/gAAAAABj6Fj8YbiwPc4ydWXSl0lPT_D8lFpddlATEBrtdZ58fPVd7HDKGqo3nZhkuYtu-_-Hx-COEGk-R5NEbX_J8xLJKAt0Z5uqesPeBv5LaqmZmYBNhp8YuojDqAioJD2BN-wM5mZs",
3 "keys": {
4 "auth": "uhvorcFRq-pTnoHZwiN7sw",
5 "p256dh": "BPcvklqnsLZ8n8T_dK71XZQHCp0hZnrNYsY9HJbzNuwRRs0Pyz_Bc48n21whq6Lq8s9_E41pZCVFfZ5xiFII1nU"
6 }
7}
Note: as of the time of writing, the JioStore does not allow third-party applications access to the permissions needed to send push notifications.
Sending Push Notifications
Once you have the endpoint
and keys
for a user, sending push notifications is simple. Push notifications can be send from the command line using web-push
:
1web-push send-notification \
2 --endpoint=$ENDPOINT \
3 --key=$p256dh \
4 --auth="SIQaHqu_J-jZfskndcqeYw" \
5 --vapid-pubkey="BF7_KAxbQWoYtHwB7YnL0BlSQ-tvfmWrbp6Z9pUC_8kAdBUDv2QAZ4QScnQjwS982cpV5mqtT6QebWQLP5GpGwM" \
6 --vapid-pvtkey="4cjKnRevSuxLh6KHBEQzG9I3pzM_LFZwyxkqBsW1Kdg" \
7 --vapid-subject="https://myapp.com" \
8 --ttl=10 \
9 --encoding="aesgcm" \
10 --payload="Hello, World"
Push notifications can also be sent using NodeJS.
1const pushSubscription = { endpoint, keys };
2const message = 'Hello, World';
3const options = {
4 timeout: 1000, // milliseconds
5 TTL: 10,
6 contentEncoding: 'aesgcm',
7};
8
9webpush.setVapidDetails(
10 SUBJECT,
11 PUBLIC_KEY,
12 PRIVATE_KEY,
13);
14
15webpush.sendNotification(
16 pushSubscription,
17 message,
18 options,
19)
Note: the default contentEncoding
for web-push is aes128gcm
, however, for KaiOS only aesgcm
is supported.
Tags, Timestamps & mozbehavior
KaiOS 2.5 & 3.0 both support the
tag
property, allowing developers to replace existing notifications, rather than sending duplicates. Tags are a simple string that identifies the notification during construction. For instance, in
PodLP a tag
is set to the Podcast ID to avoid duplicate new episode notifications.
Unfortunately, neither Firefox nor KaiOS support the
timestamp
. As a result, all notifications will display a timestamp from the moment they are displayed to the user, even if the event happened in the past.
KaiOS also has a custom property,
mozbehavior
, with the following properties defined in Notification.webidl
.
1dictionary NotificationLoopControl {
2 boolean sound;
3 unsigned long soundMaxDuration;
4 boolean vibration;
5 unsigned long vibrationMaxDuration;
6};
7
8dictionary NotificationBehavior {
9 boolean noscreen = false;
10 boolean noclear = false;
11 boolean showOnlyOnce = false;
12 DOMString soundFile = "";
13 sequence<unsigned long> vibrationPattern;
14 NotificationLoopControl loopControl;
15};
Note: NotificationLoopControl
is only available on KaiOS 3.0.
The two most useful properties are soundFile
, while allows for custom notification sounds including via a remote file, and vibrationPattern
which allows for custom
vibration patterns.
Good Vibrations
Since
Notification.vibrate
isn’t available on Firefox or KaiOS, the main use for mozbehavior
is to set the vibrationPattern
to a custom pattern.
1let options = {
2 body: "Body",
3 mozbehavior: {
4 vibrationPattern: [ 30, 200, 30 ]
5 },
6};
7let notification = new Notification("Title", options);
With that, the notification will trigger a vibration when it’s received similar to calling navigator.vibrate
.
Conclusion
Push notifications in KaiOS work similar to other modern browsers, but with a few special features, properties and quirks. Notifications are a great way to drive user engagement and keep users informed. If you’re looking for a partner to ensure the best possible user experience on KaiOS, you can find the author’s contact info on the About page.