pwa: add url, views and javascript for webpush (#25462)
This commit is contained in:
parent
947f479949
commit
1b4edd9a52
|
@ -0,0 +1,255 @@
|
|||
/* globals window, navigator, Uint8Array, $, console, Notification */
|
||||
"use strict";
|
||||
// Webpush Application controller for Combo
|
||||
// Copyright (C) 2018 Entr'ouvert
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it
|
||||
// under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Utils functions:
|
||||
function loadVersionBrowser(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]
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$(window)
|
||||
.load(function () {
|
||||
var gruPushSubscribeButton,
|
||||
gruPushMessageElt,
|
||||
gruSW;
|
||||
|
||||
var activateTextMessage = 'Activez les notifications';
|
||||
var stopTextMessage = 'Stoppez les notifications';
|
||||
var incompatibleMessage = 'Ce navigateur n'est pas compatible avec les notifications push.';
|
||||
|
||||
gruPushSubscribeButton = $('#webpush-subscribe-checkbox');
|
||||
gruPushMessageElt = $('#webpush-subscribe-message');
|
||||
|
||||
console.log("user has got webpush subscriptions :", gruPushSubscribeButton.data('userDevices'));
|
||||
|
||||
function uncheckSubscribeButton() {
|
||||
gruPushSubscribeButton.attr('disabled', false);
|
||||
gruPushSubscribeButton.attr('checked', false);
|
||||
gruPushSubscribeButton.removeClass("checked");
|
||||
gruPushMessageElt.text(activateTextMessage);
|
||||
}
|
||||
|
||||
function checkSubscribeButton() {
|
||||
gruPushSubscribeButton.attr('disabled', false);
|
||||
gruPushSubscribeButton.attr('checked', true);
|
||||
gruPushSubscribeButton.addClass("checked");
|
||||
gruPushMessageElt.text(stopTextMessage);
|
||||
}
|
||||
// disable if not supported
|
||||
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
|
||||
gruPushMessageElt.text(incompatibleMessage);
|
||||
gruPushSubscribeButton.attr('checked', false);
|
||||
gruPushSubscribeButton.attr('disabled', true);
|
||||
return;
|
||||
}
|
||||
// Get the initial subscription and refresh state from server
|
||||
if ('serviceWorker' in navigator) {
|
||||
var serviceWorkerSrc = '/service-worker.js';
|
||||
navigator.serviceWorker.register(serviceWorkerSrc)
|
||||
.then(function (reg) {
|
||||
gruSW = reg;
|
||||
// Get the initial Subscription
|
||||
gruSW.pushManager.getSubscription()
|
||||
.then(function (subscription) {
|
||||
// Check we have a subscription to unsubscribe
|
||||
if (!subscription) {
|
||||
// No subscription object, so we uncheck
|
||||
uncheckSubscribeButton();
|
||||
} else {
|
||||
// existing subscription object, so we check
|
||||
checkSubscribeButton();
|
||||
}
|
||||
refreshSubscription(reg);
|
||||
});
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.log('error registering service worker : ', err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
gruPushSubscribeButton.click(
|
||||
function () {
|
||||
gruPushMessageElt.text('Connexion au serveur en cours...');
|
||||
gruPushSubscribeButton.attr('disabled', true);
|
||||
refreshSubscription(gruSW);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// Once the service worker is registered set the initial state
|
||||
function refreshSubscription(reg) {
|
||||
// If its denied, it's a permanent block until the
|
||||
if (Notification.permission === 'denied') {
|
||||
// Show a message and uncheck the button
|
||||
uncheckSubscribeButton();
|
||||
return;
|
||||
}
|
||||
// based on ":checked" being set before by pushManager.getSubscription()
|
||||
if (gruPushSubscribeButton.filter(':checked').length > 0) {
|
||||
return subscribe(reg);
|
||||
} else {
|
||||
return unsubscribe(reg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get the Subscription or register one new to POST to our server
|
||||
function subscribe(reg) {
|
||||
getOrCreateSubscription(reg)
|
||||
.then(function (subscription) {
|
||||
postSubscribeObj(true, subscription);
|
||||
})
|
||||
.catch(function (error) {
|
||||
gruPushMessageElt.text('Impossible de communiquer avec le serveur, veuillez retenter dans quelques minutes (Debug = ' + error + ')');
|
||||
});
|
||||
}
|
||||
|
||||
function getOrCreateSubscription(reg) {
|
||||
return reg.pushManager.getSubscription()
|
||||
.then(function (subscription) {
|
||||
var applicationServerKey, options;
|
||||
// Check if a subscription is available
|
||||
if (subscription) {
|
||||
return subscription;
|
||||
}
|
||||
applicationServerKey = gruPushSubscribeButton.data('applicationServerKey');
|
||||
options = {
|
||||
userVisibleOnly: true, // required by chrome
|
||||
applicationServerKey: urlBase64ToUint8Array(applicationServerKey)
|
||||
};
|
||||
// If not, register one
|
||||
return reg.pushManager.subscribe(options)
|
||||
})
|
||||
}
|
||||
|
||||
function unsubscribe() {
|
||||
// Get the Subscription to unregister
|
||||
gruSW.pushManager.getSubscription()
|
||||
.then(function (subscription) {
|
||||
// Check we have a subscription to unsubscribe
|
||||
if (!subscription) {
|
||||
// No subscription object, so set the state
|
||||
// to allow the user to subscribe to push
|
||||
uncheckSubscribeButton();
|
||||
return;
|
||||
}
|
||||
postSubscribeObj(false, subscription);
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Send the parameter to the server
|
||||
* the type of the request, the name of the user subscribing,
|
||||
* and the push subscription endpoint + key the server needs
|
||||
* Each subscription is different on different browsers
|
||||
*/
|
||||
function postSubscribeObj(active, subscription) {
|
||||
subscription = subscription.toJSON()
|
||||
var browser = loadVersionBrowser(navigator.userAgent);
|
||||
var endpointParts = subscription.endpoint.split('/');
|
||||
var registrationId = endpointParts[endpointParts.length - 1];
|
||||
var data = {
|
||||
'browser': browser.name.toUpperCase(),
|
||||
'p256dh': subscription.keys.p256dh,
|
||||
'auth': subscription.keys.auth,
|
||||
'name': 'gru-notificationcell-subscription',
|
||||
'registration_id': registrationId,
|
||||
'active': active
|
||||
};
|
||||
$.ajax({
|
||||
url: gruPushSubscribeButton.data('comboApiUrl'),
|
||||
method: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
dataType: 'json',
|
||||
crossDomain: true,
|
||||
cache: false,
|
||||
contentType: 'application/json; charset=UTF-8',
|
||||
xhrFields: { withCredentials: true }
|
||||
})
|
||||
.done(function (response) {
|
||||
// Check if the parameter is saved on the server
|
||||
if (response.active) {
|
||||
// Show the unsubscribe button
|
||||
checkSubscribeButton();
|
||||
}
|
||||
// Check if the information is deleted from server
|
||||
else if (!response.active) {
|
||||
// Get the Subscription
|
||||
getOrCreateSubscription(gruSW)
|
||||
.then(function (subscription) {
|
||||
// Remove the subscription
|
||||
subscription
|
||||
.unsubscribe()
|
||||
.then(function () {
|
||||
// Show the subscribe button
|
||||
uncheckSubscribeButton();
|
||||
});
|
||||
})
|
||||
.catch(function (error) {
|
||||
gruPushSubscribeButton.attr('disabled', false);
|
||||
gruPushSubscribeButton.attr('checked', false);
|
||||
gruPushSubscribeButton.removeClass("checked");
|
||||
gruPushMessageElt.text('Erreur lors de la requête, veuillez réessayer dans quelques minutes : ', error);
|
||||
});
|
||||
}
|
||||
})
|
||||
.fail(function (error) {
|
||||
gruPushMessageElt.text('Erreur lors de la requête, veuillez réessayer dans quelques minutes : ', error);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,117 +1,210 @@
|
|||
{% load gadjo %}
|
||||
{% load gadjo static %}
|
||||
/* global self, caches, fetch, URL, Response */
|
||||
'use strict';
|
||||
|
||||
var config = {
|
||||
version: 'v{% start_timestamp %}',
|
||||
staticCacheItems: [
|
||||
'/offline/'
|
||||
],
|
||||
cachePathPattern: /^\/static\/.*/,
|
||||
handleFetchPathPattern: /.*/,
|
||||
offlinePage: '/offline/'
|
||||
version: 'v{% start_timestamp %}',
|
||||
staticCacheItems: [], // putting 404 items fail its registration (will never be "installed")
|
||||
cachePathPattern: /^\/static\/.*/,
|
||||
handleFetchPathPattern: /.*/,
|
||||
offlinePage: '/offline/'
|
||||
};
|
||||
|
||||
function cacheName (key, opts) {
|
||||
return `${opts.version}-${key}`;
|
||||
return `${opts.version}-${key}`;
|
||||
}
|
||||
|
||||
function addToCache (cacheKey, request, response) {
|
||||
if (response.ok && cacheKey !== null) {
|
||||
var copy = response.clone();
|
||||
caches.open(cacheKey).then( cache => {
|
||||
cache.put(request, copy);
|
||||
});
|
||||
}
|
||||
return response;
|
||||
if (response.ok && cacheKey !== null) {
|
||||
var copy = response.clone();
|
||||
caches.open(cacheKey).then( cache => {
|
||||
cache.put(request, copy);
|
||||
});
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
function fetchFromCache (event) {
|
||||
return caches.match(event.request).then(response => {
|
||||
if (!response) {
|
||||
throw Error(`${event.request.url} not found in cache`);
|
||||
}
|
||||
return response;
|
||||
});
|
||||
return caches.match(event.request).then(response => {
|
||||
if (!response) {
|
||||
throw Error(`${event.request.url} not found in cache`);
|
||||
}
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
function offlineResponse (resourceType, opts) {
|
||||
if (resourceType === 'content') {
|
||||
return caches.match(opts.offlinePage);
|
||||
}
|
||||
return undefined;
|
||||
if (resourceType === 'content') {
|
||||
return caches.match(opts.offlinePage);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
self.addEventListener('install', event => {
|
||||
function onInstall (event, opts) {
|
||||
var cacheKey = cacheName('static', opts);
|
||||
return caches.open(cacheKey)
|
||||
.then(cache => cache.addAll(opts.staticCacheItems));
|
||||
}
|
||||
function onInstall (event, opts) {
|
||||
var cacheKey = cacheName('static', opts);
|
||||
return caches.open(cacheKey)
|
||||
.then(cache => cache.addAll(opts.staticCacheItems));
|
||||
}
|
||||
|
||||
event.waitUntil(
|
||||
onInstall(event, config).then( () => self.skipWaiting() )
|
||||
);
|
||||
event.waitUntil(
|
||||
onInstall(event, config).then( () => self.skipWaiting() )
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', event => {
|
||||
function onActivate (event, opts) {
|
||||
return caches.keys()
|
||||
.then(cacheKeys => {
|
||||
var oldCacheKeys = cacheKeys.filter(key => key.indexOf(opts.version) !== 0);
|
||||
var deletePromises = oldCacheKeys.map(oldKey => caches.delete(oldKey));
|
||||
return Promise.all(deletePromises);
|
||||
});
|
||||
}
|
||||
function onActivate (event, opts) {
|
||||
return caches.keys()
|
||||
.then(cacheKeys => {
|
||||
var oldCacheKeys = cacheKeys.filter(key => key.indexOf(opts.version) !== 0);
|
||||
var deletePromises = oldCacheKeys.map(oldKey => caches.delete(oldKey));
|
||||
return Promise.all(deletePromises);
|
||||
});
|
||||
}
|
||||
|
||||
event.waitUntil(
|
||||
onActivate(event, config)
|
||||
.then( () => self.clients.claim() )
|
||||
);
|
||||
event.waitUntil(
|
||||
onActivate(event, config)
|
||||
.then( () => self.clients.claim() )
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
|
||||
function shouldHandleFetch (event, opts) {
|
||||
var request = event.request;
|
||||
var url = new URL(request.url);
|
||||
var criteria = {
|
||||
matchesPathPattern: opts.handleFetchPathPattern.test(url.pathname),
|
||||
isGETRequest : request.method === 'GET',
|
||||
isFromMyOrigin : url.origin === self.location.origin
|
||||
};
|
||||
var failingCriteria = Object.keys(criteria)
|
||||
.filter(criteriaKey => !criteria[criteriaKey]);
|
||||
return !failingCriteria.length;
|
||||
}
|
||||
function shouldHandleFetch (event, opts) {
|
||||
var request = event.request;
|
||||
var url = new URL(request.url);
|
||||
var criteria = {
|
||||
matchesPathPattern: opts.handleFetchPathPattern.test(url.pathname),
|
||||
isGETRequest : request.method === 'GET',
|
||||
isFromMyOrigin : url.origin === self.location.origin
|
||||
};
|
||||
var failingCriteria = Object.keys(criteria)
|
||||
.filter(criteriaKey => !criteria[criteriaKey]);
|
||||
return !failingCriteria.length;
|
||||
}
|
||||
|
||||
function onFetch (event, opts) {
|
||||
var request = event.request;
|
||||
var url = new URL(request.url);
|
||||
var acceptHeader = request.headers.get('Accept');
|
||||
var resourceType = 'static';
|
||||
var cacheKey;
|
||||
function onFetch (event, opts) {
|
||||
var request = event.request;
|
||||
var url = new URL(request.url);
|
||||
var acceptHeader = request.headers.get('Accept');
|
||||
var resourceType = 'static';
|
||||
var cacheKey;
|
||||
|
||||
if (acceptHeader.indexOf('text/html') !== -1) {
|
||||
resourceType = 'content';
|
||||
} else if (acceptHeader.indexOf('image') !== -1) {
|
||||
resourceType = 'image';
|
||||
}
|
||||
if (acceptHeader.indexOf('text/html') !== -1) {
|
||||
resourceType = 'content';
|
||||
} else if (acceptHeader.indexOf('image') !== -1) {
|
||||
resourceType = 'image';
|
||||
}
|
||||
|
||||
cacheKey = null;
|
||||
if (opts.cachePathPattern.test(url.pathname)) {
|
||||
cacheKey = cacheName(resourceType, opts);
|
||||
}
|
||||
cacheKey = null;
|
||||
if (opts.cachePathPattern.test(url.pathname)) {
|
||||
cacheKey = cacheName(resourceType, opts);
|
||||
}
|
||||
|
||||
/* always network first */
|
||||
event.respondWith(
|
||||
fetch(request)
|
||||
.then(response => addToCache(cacheKey, request, response))
|
||||
.catch(() => fetchFromCache(event))
|
||||
.catch(() => offlineResponse(resourceType, opts))
|
||||
);
|
||||
}
|
||||
if (shouldHandleFetch(event, config)) {
|
||||
onFetch(event, config);
|
||||
}
|
||||
/* always network first */
|
||||
event.respondWith(
|
||||
fetch(request)
|
||||
.then(response => addToCache(cacheKey, request, response))
|
||||
.catch(() => fetchFromCache(event))
|
||||
.catch(() => offlineResponse(resourceType, opts))
|
||||
);
|
||||
}
|
||||
if (shouldHandleFetch(event, config)) {
|
||||
onFetch(event, config);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Return the options paramter for showNotification
|
||||
* Only Chrome has extended support for extra features like actions, badge, icon, etc
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification
|
||||
*/
|
||||
var getNotificationOptions = function (responseJson) {
|
||||
var body = responseJson.body,
|
||||
icon = responseJson.icon,
|
||||
vibrate = responseJson.vibrate,
|
||||
data = responseJson.data,
|
||||
actions = responseJson.actions;
|
||||
/* default icon is configured in the theme 'css_variant' */
|
||||
if (!icon ) icon = '{{ site_base }}{% static "" %}{{ css_variant }}/{{ icon_prefix }}512px.png';
|
||||
var options = {
|
||||
body: body,
|
||||
icon: icon,
|
||||
requireInteraction: 'true',
|
||||
data: data,
|
||||
actions: actions
|
||||
};
|
||||
/* optional vibration */
|
||||
if (vibrate) options.vibrate = vibrate;
|
||||
return options;
|
||||
};
|
||||
|
||||
/*
|
||||
* Push event handler
|
||||
* documentation at https://developers.google.com/web/fundamentals/push-notifications/display-a-notification
|
||||
*/
|
||||
self.addEventListener('push', function (event) {
|
||||
try {
|
||||
// Push is a JSON
|
||||
var responseJson = event.data.json();
|
||||
var title = responseJson.title;
|
||||
var options = getNotificationOptions(responseJson)
|
||||
} catch (err) {
|
||||
// Push is a simple text (usually debugging)
|
||||
console.log('Push is a simple text');
|
||||
var options = {
|
||||
'body': event.data.text()
|
||||
};
|
||||
var title = '';
|
||||
}
|
||||
event.waitUntil(self.registration.showNotification(title, options));
|
||||
});
|
||||
|
||||
self.addEventListener('notificationclick', function (event) {
|
||||
if (!event.action) {
|
||||
// Was a normal notification click
|
||||
console.log('No actions');
|
||||
}
|
||||
var urlToOpen = event.notification.data.open_url;
|
||||
switch (event.action) {
|
||||
case 'ack':
|
||||
break;
|
||||
case 'forget':
|
||||
break;
|
||||
}
|
||||
if (event.action) {
|
||||
// ack or forget
|
||||
fetch(event.notification.data.callback_url + '?action=' + event.action, {
|
||||
method: 'GET',
|
||||
mode: 'cors', // no-cors, cors, *same-origin
|
||||
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
|
||||
})
|
||||
.catch(function() {
|
||||
console.log("error on GET ", event.notification.data.callback_url + '?action=' + event.action);
|
||||
});
|
||||
}
|
||||
// Check if there's already a tab open with this urlToOpen.
|
||||
event.waitUntil(self.clients.matchAll({
|
||||
type: 'window',
|
||||
includeUncontrolled: true
|
||||
})
|
||||
.then(function (windowClients) {
|
||||
let matchingClient = null;
|
||||
for (let i = 0; i < windowClients.length; i++) {
|
||||
const windowClient = windowClients[i];
|
||||
if (windowClient.url === urlToOpen) {
|
||||
matchingClient = windowClient;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matchingClient) {
|
||||
return matchingClient.focus();
|
||||
} else {
|
||||
return clients.openWindow(urlToOpen);
|
||||
}
|
||||
})
|
||||
);
|
||||
// Android doesn't close the notification when you click it
|
||||
// See http://crbug.com/463146
|
||||
event.notification.close();
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2015-2018 Entr'ouvert
|
||||
# -*- coding: utf-8 -*-
|
||||
# combo - Combo PWA App
|
||||
# Copyright (C) 2018 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
|
@ -16,9 +17,12 @@
|
|||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import manifest_json, service_worker_js
|
||||
from .views import manifest_json, service_worker_js, WebPushDeviceAuthorizedViewSetNoCsrf, NotificationCallback
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url('^manifest.json$', manifest_json),
|
||||
url('^service-worker.js$', service_worker_js),
|
||||
]
|
||||
url('^webpush/subscribe/$', WebPushDeviceAuthorizedViewSetNoCsrf.as_view({'post': 'create'}), name='create_webpush_subscription'),
|
||||
url(r'^webpush/notification/(\w+)/(\w+)/(\S+)/$', NotificationCallback.as_view(), name='webpush-notification-callback'),
|
||||
]
|
||||
|
|
|
@ -1,5 +1,22 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2015-2018 Entr'ouvert
|
||||
# -*- coding: utf-8 -*-
|
||||
# combo - Combo PWA App
|
||||
# Copyright (C) 2018 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# combo - Combo PWA App
|
||||
# Copyright (C) 2018 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
|
@ -17,6 +34,16 @@
|
|||
from django.http import HttpResponse, Http404
|
||||
from django.template.loader import get_template, TemplateDoesNotExist
|
||||
|
||||
from rest_framework import permissions, status
|
||||
from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
|
||||
from push_notifications.models import WebPushDevice
|
||||
from push_notifications.api.rest_framework import WebPushDeviceAuthorizedViewSet
|
||||
|
||||
from combo.apps.notifications.models import Notification
|
||||
|
||||
|
||||
def manifest_json(request, *args, **kwargs):
|
||||
try:
|
||||
|
@ -29,4 +56,38 @@ def manifest_json(request, *args, **kwargs):
|
|||
def service_worker_js(request, *args, **kwargs):
|
||||
template = get_template('combo/service-worker.js')
|
||||
return HttpResponse(template.render({}, request),
|
||||
content_type='application/javascript; charset=utf-8')
|
||||
content_type='application/javascript; charset=utf-8')
|
||||
|
||||
|
||||
class CsrfExemptSessionAuthentication(SessionAuthentication):
|
||||
def enforce_csrf(self, request):
|
||||
return # To not perform the csrf check previously happening
|
||||
|
||||
|
||||
class WebPushDeviceAuthorizedViewSetNoCsrf(WebPushDeviceAuthorizedViewSet):
|
||||
authentication_classes = (CsrfExemptSessionAuthentication,)
|
||||
|
||||
|
||||
class NotificationCallback(GenericAPIView):
|
||||
'''
|
||||
Ack or forget a Notification object
|
||||
Anonymously but with a check on the user's public webpush registration key
|
||||
'''
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
authentication_classes = (CsrfExemptSessionAuthentication,)
|
||||
|
||||
def get(self, request, notification_id, user, key, *args, **kwargs):
|
||||
action = request.GET['action']
|
||||
try:
|
||||
WebPushDevice.objects.get(p256dh=key)
|
||||
qs = Notification.objects.find(user, notification_id)
|
||||
if action == 'ack':
|
||||
qs.ack()
|
||||
if action == 'forget':
|
||||
qs.forget()
|
||||
return Response({'err': 0})
|
||||
except WebPushDevice.DoesNotExist:
|
||||
return Response({'err': 1}, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
notification_callback = NotificationCallback.as_view()
|
||||
|
|
Loading…
Reference in New Issue