Skip to content

Commit

Permalink
Add WebPush support for Safari
Browse files Browse the repository at this point in the history
  • Loading branch information
James Bligh committed Nov 20, 2022
1 parent a208192 commit 4fb64be
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 247 deletions.
229 changes: 1 addition & 228 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,238 +122,11 @@ For WNS, you need both the ``WNS_PACKAGE_SECURITY_KEY`` and the ``WNS_SECRET_KEY

**WP settings**

- Install:

.. code-block:: python
pip install pywebpush
pip install py-vapid (Only for generating key)
- Getting keys:

- Create file (claim.json) like this:

.. code-block:: bash
{
"sub": "mailto: [email protected]",
"aud": "https://android.googleapis.com"
}
- Generate public and private keys:
.. code-block:: bash
vapid --sign claim.json
No private_key.pem file found.
Do you want me to create one for you? (Y/n)Y
Do you want me to create one for you? (Y/n)Y
Generating private_key.pem
Generating public_key.pem
Include the following headers in your request:
Crypto-Key: p256ecdsa=BEFuGfKKEFp-kEBMxAIw7ng8HeH_QwnH5_h55ijKD4FRvgdJU1GVlDo8K5U5ak4cMZdQTUJlkA34llWF0xHya70
Authorization: WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2FuZHJvaWQuZ29vZ2xlYXBpcy5jb20iLCJleHAiOiIxNTA4NDkwODM2Iiwic3ViIjoibWFpbHRvOiBkZXZlbG9wbWVudEBleGFtcGxlLmNvbSJ9.r5CYMs86X3JZ4AEs76pXY5PxsnEhIFJ-0ckbibmFHZuyzfIpf1ZGIJbSI7knA4ufu7Hm8RFfEg5wWN1Yf-dR2A
- Generate client public key (applicationServerKey)
.. code-block:: bash
vapid --applicationServerKey
Application Server Key = BEFuGfKKEFp-kEBMxAIw7ng8HeH_QwnH5_h55ijKD4FRvgdJU1GVlDo8K5U5ak4cMZdQTUJlkA34llWF0xHya70
- Configure settings:

- ``WP_PRIVATE_KEY``: Absolute path to your private certificate file: os.path.join(BASE_DIR, "private_key.pem")
- ``WP_CLAIMS``: Dictionary with the same sub info like claims file: {'sub': "mailto: [email protected]"}
- ``WP_ERROR_TIMEOUT``: The timeout on WebPush POSTs. (Optional)
- ``WP_POST_URL``: A dictionary (key per browser supported) with the full url that webpush notifications will be POSTed to. (Optional)


- Configure client (javascript):

.. code-block:: javascript
// Utils functions:
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;
}
function loadVersionBrowser () {
if ("userAgentData" in navigator) {
// navigator.userAgentData is not available in
// Firefox and Safari
const uaData = navigator.userAgentData;
// Outputs of navigator.userAgentData.brands[n].brand are e.g.
// Chrome: 'Google Chrome'
// Edge: 'Microsoft Edge'
// Opera: 'Opera'
let browsername;
let browserversion;
let chromeVersion = null;
for (var i = 0; i < uaData.brands.length; i++) {
let brand = uaData.brands[i].brand;
browserversion = uaData.brands[i].version;
if (brand.match(/opera|chrome|edge|safari|firefox|msie|trident/i) !== null) {
// If we have a chrome match, save the match, but try to find another match
// E.g. Edge can also produce a false Chrome match.
if (brand.match(/chrome/i) !== null) {
chromeVersion = browserversion;
}
// If this is not a chrome match return immediately
else {
browsername = brand.substr(brand.indexOf(' ')+1);
return {
name: browsername,
version: browserversion
}
}
}
}
// No non-Chrome match was found. If we have a chrome match, return it.
if (chromeVersion !== null) {
return {
name: "chrome",
version: chromeVersion
}
}
}
// If no userAgentData is not present, or if no match via userAgentData was found,
// try to extract the browser name and version from userAgent
const userAgent = navigator.userAgent;
var ua = userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(M[1])) {
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
return {name: 'IE', version: (tem[1] || '')};
}
if (M[1] === 'Chrome') {
tem = ua.match(/\bOPR\/(\d+)/);
if (tem != null) {
return {name: 'Opera', version: tem[1]};
}
}
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
if ((tem = ua.match(/version\/(\d+)/i)) != null) {
M.splice(1, 1, tem[1]);
}
return {
name: M[0],
version: M[1]
};
};
var applicationServerKey = "BEFuGfKKEFp-kEBMxAIw7ng8HeH_QwnH5_h55ijKD4FRvgdJU1GVlDo8K5U5ak4cMZdQTUJlkA34llWF0xHya70";
....
// In your ready listener
if ('serviceWorker' in navigator) {
// The service worker has to store in the root of the app
// http://stackoverflow.com/questions/29874068/navigator-serviceworker-is-never-ready
var browser = loadVersionBrowser();
navigator.serviceWorker.register('navigatorPush.service.js?version=1.0.0').then(function (reg) {
reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(applicationServerKey)
}).then(function (sub) {
var endpointParts = sub.endpoint.split('/');
var registration_id = endpointParts[endpointParts.length - 1];
var data = {
'browser': browser.name.toUpperCase(),
'p256dh': btoa(String.fromCharCode.apply(null, new Uint8Array(sub.getKey('p256dh')))),
'auth': btoa(String.fromCharCode.apply(null, new Uint8Array(sub.getKey('auth')))),
'name': 'XXXXX',
'registration_id': registration_id
};
requestPOSTToServer(data);
})
}).catch(function (err) {
console.log(':^(', err);
});
// Example navigatorPush.service.js file
var getTitle = function (title) {
if (title === "") {
title = "TITLE DEFAULT";
}
return title;
};
var getNotificationOptions = function (message, message_tag) {
var options = {
body: message,
icon: '/img/icon_120.png',
tag: message_tag,
vibrate: [200, 100, 200, 100, 200, 100, 200]
};
return options;
};
self.addEventListener('install', function (event) {
self.skipWaiting();
});
self.addEventListener('push', function(event) {
try {
// Push is a JSON
var response_json = event.data.json();
var title = response_json.title;
var message = response_json.message;
var message_tag = response_json.tag;
} catch (err) {
// Push is a simple text
var title = "";
var message = event.data.text();
var message_tag = "";
}
self.registration.showNotification(getTitle(title), getNotificationOptions(message, message_tag));
// Optional: Comunicating with our js application. Send a signal
self.clients.matchAll({includeUncontrolled: true, type: 'window'}).then(function (clients) {
clients.forEach(function (client) {
client.postMessage({
"data": message_tag,
"data_title": title,
"data_body": message});
});
});
});
// Optional: Added to that the browser opens when you click on the notification push web.
self.addEventListener('notificationclick', function(event) {
// Android doesn't close the notification when you click it
// See http://crbug.com/463146
event.notification.close();
// Check if there's already a tab open with this URL.
// If yes: focus on the tab.
// If no: open a tab with the URL.
event.waitUntil(clients.matchAll({type: 'window', includeUncontrolled: true}).then(function(windowClients) {
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
if ('focus' in client) {
return client.focus();
}
}
})
);
});

For more information about how to configure WebPush, see `docs/WebPush <https://github.com/jazzband/django-push-notifications/blob/master/docs/WebPush.rst>`_.


Sending messages
Expand Down
Loading

0 comments on commit 4fb64be

Please sign in to comment.