diff --git a/.gitignore b/.gitignore index 485acd7..5898eb0 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,9 @@ static/img/davecat.jpg static/yoyo-medium.gif static/leo.goldfish.fyi.png + +private_key.txt + +public_key.txt + +vapid_private.pem diff --git a/smsproj.py b/smsproj.py index c9d97de..f5542b3 100644 --- a/smsproj.py +++ b/smsproj.py @@ -6,9 +6,12 @@ import time import pprint import configparser import json -# import re +import os import flask +# import re +from flask import request, Response, render_template, jsonify, Flask, session +from pywebpush import webpush, WebPushException import appdb import appsms @@ -20,8 +23,18 @@ config = configparser.ConfigParser() config.read('config.ini') app_debug = config.get("app", "debug") -app = flask.Flask(__name__) +app = Flask(__name__) app.secret_key = config.get("auth", "FN_FLASK_SECRET_KEY") +app.config['SECRET_KEY'] = config.get("auth", "FN_FLASK_SECRET_KEY") +DER_BASE64_ENCODED_PRIVATE_KEY_FILE_PATH = os.path.join(os.getcwd(),"private_key.txt") +DER_BASE64_ENCODED_PUBLIC_KEY_FILE_PATH = os.path.join(os.getcwd(),"public_key.txt") + +VAPID_PRIVATE_KEY = open(DER_BASE64_ENCODED_PRIVATE_KEY_FILE_PATH, "r+").readline().strip("\n") +VAPID_PUBLIC_KEY = open(DER_BASE64_ENCODED_PUBLIC_KEY_FILE_PATH, "r+").read().strip("\n") + +VAPID_CLAIMS = { +"sub": "mailto:support@athnex.com" +} app.register_blueprint(callback_sms.app) app.register_blueprint(app_settings.app) @@ -34,6 +47,13 @@ if app_debug == '1': else: app.debug = False +def send_web_push(subscription_information, message_body): + return webpush( + subscription_info=subscription_information, + data=message_body, + vapid_private_key=VAPID_PRIVATE_KEY, + vapid_claims=VAPID_CLAIMS + ) @app.route('/') def index(): @@ -105,7 +125,7 @@ def getNumMessages(number): '''Return the messages from a single DID in json form''' # This gets the mssages based on the provided from or two DID if not app_auth.is_logged_in(): - return json.dumps({'error': 'Unable to send SMS, you are not logged in'}) + return json.dumps({'error': 'You are not logged in.'}) # We need to take and compare the authIDforDID, gotta add use id # to getNumSMSLog and pull the id from result. @@ -174,6 +194,20 @@ def markallunread(): return json.dumps({'status': 'success'}) return False +@app.route("/subscription/", methods=["GET", "POST"]) +def subscription(): + """ + POST creates a subscription + GET returns vapid public key which clients uses to send around push notification + """ + + if request.method == "GET": + return Response(response=json.dumps({"public_key": VAPID_PUBLIC_KEY}), + headers={"Access-Control-Allow-Origin": "*"}, content_type="application/json") + + subscription_token = request.get_json("subscription_token") + return Response(status=201, mimetype="application/json") + @app.route('/submitMessage', methods=['POST']) def submitMessage(): @@ -215,6 +249,24 @@ def submitMessage(): "targetdid": targetDid}) return returndata +@app.route("/push_v1/",methods=['POST']) +def push_v1(): + message = "Push Test v1" + print("is_json",request.is_json) + + if not request.json or not request.json.get('sub_token'): + return jsonify({'failed':1}) + + print("request.json",request.json) + + token = request.json.get('sub_token') + try: + token = json.loads(token) + send_web_push(token, message) + return jsonify({'success':1}) + except Exception as e: + print("error",e) + return jsonify({'failed':str(e)}) @app.route('/testAjax') def testAjax(): diff --git a/static/main.js b/static/main.js new file mode 100644 index 0000000..3248200 --- /dev/null +++ b/static/main.js @@ -0,0 +1,171 @@ +// main.js + +'use strict'; + +// const applicationServerPublicKey = "BNbxGYNMhEIi9zrneh7mqV4oUanjLUK3m+mYZBc62frMKrEoMk88r3Lk596T0ck9xlT+aok0fO1KXBLV4+XqxYM="; +const pushButton = document.querySelector('.js-push-btn'); + +let isSubscribed = false; +let swRegistration = null; + +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); + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; +} + +function updateBtn() { + if (Notification.permission === 'denied') { + pushButton.textContent = 'Push Messaging Blocked.'; + pushButton.disabled = true; + updateSubscriptionOnServer(null); + return; + } + + if (isSubscribed) { + pushButton.textContent = 'Disable Push Messaging'; + } else { + pushButton.textContent = 'Enable Push Messaging'; + } + + pushButton.disabled = false; +} + +function updateSubscriptionOnServer(subscription) { + // TODO: Send subscription to application server + + const subscriptionJson = document.querySelector('.js-subscription-json'); + const subscriptionDetails = + document.querySelector('.js-subscription-details'); + + if (subscription) { + subscriptionJson.textContent = JSON.stringify(subscription); + subscriptionDetails.classList.remove('is-invisible'); + } else { + subscriptionDetails.classList.add('is-invisible'); + } +} + +function subscribeUser() { + const applicationServerPublicKey = localStorage.getItem('applicationServerPublicKey'); + const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey); + swRegistration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: applicationServerKey + }) + .then(function(subscription) { + console.log('User is subscribed.'); + + updateSubscriptionOnServer(subscription); + localStorage.setItem('sub_token',JSON.stringify(subscription)); + isSubscribed = true; + + updateBtn(); + }) + .catch(function(err) { + console.log('Failed to subscribe the user: ', err); + updateBtn(); + }); +} + +function unsubscribeUser() { + swRegistration.pushManager.getSubscription() + .then(function(subscription) { + if (subscription) { + return subscription.unsubscribe(); + } + }) + .catch(function(error) { + console.log('Error unsubscribing', error); + }) + .then(function() { + updateSubscriptionOnServer(null); + + console.log('User is unsubscribed.'); + isSubscribed = false; + + updateBtn(); + }); +} + +function initializeUI() { + pushButton.addEventListener('click', function() { + pushButton.disabled = true; + if (isSubscribed) { + unsubscribeUser(); + } else { + subscribeUser(); + } + }); + + // Set the initial subscription value + swRegistration.pushManager.getSubscription() + .then(function(subscription) { + isSubscribed = !(subscription === null); + + updateSubscriptionOnServer(subscription); + + if (isSubscribed) { + console.log('User IS subscribed.'); + } else { + console.log('User is NOT subscribed.'); + } + + updateBtn(); + }); +} + +if ('serviceWorker' in navigator && 'PushManager' in window) { + console.log('Service Worker and Push is supported'); + + navigator.serviceWorker.register("/static/sw.js") + .then(function(swReg) { + console.log('Service Worker is registered', swReg); + + swRegistration = swReg; + initializeUI(); + }) + .catch(function(error) { + console.error('Service Worker Error', error); + }); +} else { + console.warn('Push meapplicationServerPublicKeyssaging is not supported'); + pushButton.textContent = 'Push Not Supported'; +} + +function push_message() { + console.log("sub_token", localStorage.getItem('sub_token')); + $.ajax({ + type: "POST", + url: "/push_v1/", + contentType: 'application/json; charset=utf-8', + dataType:'json', + data: JSON.stringify({'sub_token':localStorage.getItem('sub_token')}), + success: function( data ){ + console.log("success",data); + }, + error: function( jqXhr, textStatus, errorThrown ){ + console.log("error",errorThrown); + } + }); +} + +$(document).ready(function(){ + $.ajax({ + type:"GET", + url:'/subscription/', + success:function(response){ + console.log("response",response); + localStorage.setItem('applicationServerPublicKey',response.public_key); + } + }) +}); diff --git a/static/sw.js b/static/sw.js new file mode 100644 index 0000000..544bcf2 --- /dev/null +++ b/static/sw.js @@ -0,0 +1,64 @@ +// sw.js + +'use strict'; + +/* eslint-disable max-len */ + +// const applicationServerPublicKey = "BNbxGYNMhEIi9zrneh7mqV4oUanjLUK3m+mYZBc62frMKrEoMk88r3Lk596T0ck9xlT+aok0fO1KXBLV4+XqxYM="; + +/* eslint-enable max-len */ + +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); + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; +} + +self.addEventListener('push', function(event) { + console.log('[Service Worker] Push Received.'); + console.log(`[Service Worker] Push had this data: "${event.data.text()}"`); + + const title = 'Push Codelab'; + const options = { + body: `"${event.data.text()}"`, + icon: 'images/icon.png', + badge: 'images/badge.png' + }; + + event.waitUntil(self.registration.showNotification(title, options)); +}); + +self.addEventListener('notificationclick', function(event) { + console.log('[Service Worker] Notification click Received.'); + + event.notification.close(); + + event.waitUntil( + clients.openWindow('https://developers.google.com/web/') + ); +}); + +self.addEventListener('pushsubscriptionchange', function(event) { + console.log('[Service Worker]: \'pushsubscriptionchange\' event fired.'); + const applicationServerPublicKey = localStorage.getItem('applicationServerPublicKey'); + const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey); + event.waitUntil( + self.registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: applicationServerKey + }) + .then(function(newSubscription) { + // TODO: Send to application server + console.log('[Service Worker] New subscription: ', newSubscription); + }) + ); +}); diff --git a/templates/inbox.html b/templates/inbox.html index 3487848..da6e10f 100644 --- a/templates/inbox.html +++ b/templates/inbox.html @@ -52,6 +52,7 @@ $("document").ready(function() { alert("FAIL"); }); } + getInboxData(); }); diff --git a/templates/include/header.html b/templates/include/header.html index a6be53d..f943305 100644 --- a/templates/include/header.html +++ b/templates/include/header.html @@ -4,6 +4,8 @@ + +