push notifs stuff from https://www.digitalocean.com/community/tutorials/how-to-send-web-push-notifications-from-django-applications
This commit is contained in:
parent
b47ee82bda
commit
49148320ee
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,180 @@
|
||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<meta name="vapid-key" content="{{ vapid_key }}">
|
||||||
|
{% if user.id %}
|
||||||
|
<meta name="user_id" content="{{ user.id }}">
|
||||||
|
{% endif %}
|
||||||
|
<title>Web Push</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=PT+Sans:400,700" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<form id="send-push__form">
|
||||||
|
<h3 class="header">Send a push notification</h3>
|
||||||
|
<p class="error"></p>
|
||||||
|
<input type="text" name="head" placeholder="Header: Your favorite airline 😍">
|
||||||
|
<textarea name="body" id="" cols="30" rows="10" placeholder="Body: Your flight has been cancelled 😱😱😱"></textarea>
|
||||||
|
<button>Send Me</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const registerSw = async () => {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
const reg = await navigator.serviceWorker.register('sw.js');
|
||||||
|
initialiseState(reg)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
showNotAllowed("You can't send push notifications ☹️😢")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const initialiseState = (reg) => {
|
||||||
|
if (!reg.showNotification) {
|
||||||
|
showNotAllowed('Showing notifications isn\'t supported ☹️😢');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (Notification.permission === 'denied') {
|
||||||
|
showNotAllowed('You prevented us from showing notifications ☹️🤔');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!'PushManager' in window) {
|
||||||
|
showNotAllowed("Push isn't allowed in your browser 🤔");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subscribe(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const showNotAllowed = (message) => {
|
||||||
|
const button = document.querySelector('form>button');
|
||||||
|
button.innerHTML = `${message}`;
|
||||||
|
button.setAttribute('disabled', 'true');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function urlB64ToUint8Array(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);
|
||||||
|
const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
|
||||||
|
|
||||||
|
return outputData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscribe = async (reg) => {
|
||||||
|
const subscription = await reg.pushManager.getSubscription();
|
||||||
|
if (subscription) {
|
||||||
|
sendSubData(subscription);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vapidMeta = document.querySelector('meta[name="vapid-key"]');
|
||||||
|
const key = vapidMeta.content;
|
||||||
|
const options = {
|
||||||
|
userVisibleOnly: true,
|
||||||
|
// if key exists, create applicationServerKey property
|
||||||
|
...(key && {applicationServerKey: urlB64ToUint8Array(key)})
|
||||||
|
};
|
||||||
|
|
||||||
|
const sub = await reg.pushManager.subscribe(options);
|
||||||
|
sendSubData(sub)
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const sendSubData = async (subscription) => {
|
||||||
|
const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
|
||||||
|
const data = {
|
||||||
|
status_type: 'subscribe',
|
||||||
|
subscription: subscription.toJSON(),
|
||||||
|
browser: browser,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await fetch('/webpush/save_information', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json'
|
||||||
|
},
|
||||||
|
credentials: "include"
|
||||||
|
});
|
||||||
|
|
||||||
|
handleResponse(res);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResponse = (res) => {
|
||||||
|
console.log(res.status);
|
||||||
|
};
|
||||||
|
|
||||||
|
registerSw();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const pushForm = document.getElementById('send-push__form');
|
||||||
|
const errorMsg = document.querySelector('.error');
|
||||||
|
|
||||||
|
pushForm.addEventListener('submit', async function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const input = this[0];
|
||||||
|
const textarea = this[1];
|
||||||
|
const button = this[2];
|
||||||
|
errorMsg.innerText = '';
|
||||||
|
|
||||||
|
const head = input.value;
|
||||||
|
const body = textarea.value;
|
||||||
|
const meta = document.querySelector('meta[name="user_id"]');
|
||||||
|
const id = meta ? meta.content : null;
|
||||||
|
|
||||||
|
if (head && body && id) {
|
||||||
|
button.innerText = 'Sending...';
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
const res = await fetch('/send_push', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({head, body, id}),
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status === 200) {
|
||||||
|
button.innerText = 'Send another 😃!';
|
||||||
|
button.disabled = false;
|
||||||
|
input.value = '';
|
||||||
|
textarea.value = '';
|
||||||
|
} else {
|
||||||
|
errorMsg.innerText = res.message;
|
||||||
|
button.innerText = 'Something broke 😢.. Try again?';
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let error;
|
||||||
|
if (!head || !body){
|
||||||
|
error = 'Please ensure you complete the form 🙏🏾'
|
||||||
|
}
|
||||||
|
else if (!id){
|
||||||
|
error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
|
||||||
|
}
|
||||||
|
errorMsg.innerText = error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Register event listener for the 'push' event.
|
||||||
|
self.addEventListener('push', function (event) {
|
||||||
|
// Retrieve the textual payload from event.data (a PushMessageData object).
|
||||||
|
// Other formats are supported (ArrayBuffer, Blob, JSON), check out the documentation
|
||||||
|
// on https://developer.mozilla.org/en-US/docs/Web/API/PushMessageData.
|
||||||
|
const eventInfo = event.data.text();
|
||||||
|
const data = JSON.parse(eventInfo);
|
||||||
|
const head = data.head || 'New Notification 🕺🕺';
|
||||||
|
const body = data.body || 'This is default content. Your notification didn\'t have one 🙄🙄';
|
||||||
|
|
||||||
|
// Keep the service worker alive until the notification is created.
|
||||||
|
event.waitUntil(
|
||||||
|
self.registration.showNotification(head, {
|
||||||
|
body: body,
|
||||||
|
icon: 'https://i.imgur.com/MZM3K5w.png'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
|
@ -2,10 +2,17 @@ from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from .views import home, send_push
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^", include("djangoldp.urls")),
|
url(r"^", include("djangoldp.urls")),
|
||||||
url(r"^admin/", admin.site.urls),
|
url(r"^admin/", admin.site.urls),
|
||||||
|
path('', home),
|
||||||
|
path('send_push', send_push),
|
||||||
|
path('webpush/', include('webpush.urls')),
|
||||||
|
path('sw.js', TemplateView.as_view(template_name='sw.js', content_type='application/x-javascript')),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
from django.http.response import HttpResponse
|
||||||
|
from django.views.decorators.http import require_GET
|
||||||
|
from django.http.response import JsonResponse, HttpResponse
|
||||||
|
from django.views.decorators.http import require_GET, require_POST
|
||||||
|
from django.shortcuts import render, get_object_or_404
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from webpush import send_user_notification
|
||||||
|
from django.conf import settings
|
||||||
|
import json
|
||||||
|
|
||||||
|
@require_GET
|
||||||
|
def home(request):
|
||||||
|
webpush_settings = getattr(settings, 'WEBPUSH_SETTINGS', {})
|
||||||
|
vapid_key = webpush_settings.get('VAPID_PUBLIC_KEY')
|
||||||
|
user = request.user
|
||||||
|
return render(request, 'home.html', {user: user, 'vapid_key': vapid_key})
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
@csrf_exempt
|
||||||
|
def send_push(request):
|
||||||
|
try:
|
||||||
|
body = request.body
|
||||||
|
data = json.loads(body)
|
||||||
|
|
||||||
|
if 'head' not in data or 'body' not in data or 'id' not in data:
|
||||||
|
return JsonResponse(status=400, data={"message": "Invalid data format"})
|
||||||
|
|
||||||
|
user_id = data['id']
|
||||||
|
user = get_object_or_404(User, pk=user_id)
|
||||||
|
payload = {'head': data['head'], 'body': data['body']}
|
||||||
|
send_user_notification(user=user, payload=payload, ttl=1000)
|
||||||
|
|
||||||
|
return JsonResponse(status=200, data={"message": "Web push successful"})
|
||||||
|
except TypeError:
|
||||||
|
return JsonResponse(status=500, data={"message": "An error occurred"})
|
13
settings.yml
13
settings.yml
|
@ -1,5 +1,5 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
|
djangoldp_notifications
|
||||||
ldppackages:
|
ldppackages:
|
||||||
|
|
||||||
server:
|
server:
|
||||||
|
@ -11,12 +11,19 @@ server:
|
||||||
default:
|
default:
|
||||||
ENGINE: django.db.backends.postgresql_psycopg2
|
ENGINE: django.db.backends.postgresql_psycopg2
|
||||||
NAME: djangoldp
|
NAME: djangoldp
|
||||||
USER: postgres
|
|
||||||
PASSWORD: passw0rd
|
|
||||||
LDP_RDF_CONTEXT: https://cdn.happy-dev.fr/owl/hdcontext.jsonld
|
LDP_RDF_CONTEXT: https://cdn.happy-dev.fr/owl/hdcontext.jsonld
|
||||||
ROOT_URLCONF: server.urls
|
ROOT_URLCONF: server.urls
|
||||||
STATIC_ROOT: static
|
STATIC_ROOT: static
|
||||||
|
# STATIC_URL: = '/static/'
|
||||||
|
# STATICFILES_DIRS: = '/Users/trav/Documents/gitnsurge/autonomic/startinblox/startinblox-startinoff'
|
||||||
MEDIA_ROOT: media
|
MEDIA_ROOT: media
|
||||||
INSTALLED_APPS:
|
INSTALLED_APPS:
|
||||||
- server
|
- server
|
||||||
- djangoldp_crypto # only needed by decentral1se for #236
|
- djangoldp_crypto # only needed by decentral1se for #236
|
||||||
|
- webpush
|
||||||
|
|
||||||
|
|
||||||
|
WEBPUSH_SETTINGS:
|
||||||
|
VAPID_PUBLIC_KEY: "BIDVJ0sd4Cyycf_aGCxhQ_SmXBneWboI3wGO-Iyj3ofeGkvYyNp5o6W9eTf13YkJSz6NlRwiCHA08m8e82n5WXI"
|
||||||
|
VAPID_PRIVATE_KEY: "zQ-Apj3yLGvcq-l_YNFMFgNVBxQ5_JYEsUbZA36Yhes"
|
||||||
|
VAPID_ADMIN_EMAIL: "trav@teafry.me"
|
||||||
|
|
Loading…
Reference in New Issue