trav 2021-04-01 10:24:39 -04:00
parent b47ee82bda
commit 49148320ee
15 changed files with 251 additions and 3 deletions

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.

180
server/templates/home.html Normal file
View File

@ -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>

18
server/templates/sw.js Normal file
View File

@ -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'
})
);
});

View File

@ -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:

36
server/views.py Normal file
View File

@ -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"})

View File

@ -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"