diff options
16 files changed, 1268 insertions, 0 deletions
diff --git a/examples/webenginewidgets/CMakeLists.txt b/examples/webenginewidgets/CMakeLists.txt index ef4708ec7..5b0a56d23 100644 --- a/examples/webenginewidgets/CMakeLists.txt +++ b/examples/webenginewidgets/CMakeLists.txt @@ -7,6 +7,7 @@ qt_internal_add_example(cookiebrowser) qt_internal_add_example(notifications) qt_internal_add_example(simplebrowser) qt_internal_add_example(stylesheetbrowser) +qt_internal_add_example(push_notifications) qt_internal_add_example(videoplayer) qt_internal_add_example(webui) if(QT_FEATURE_webengine_geolocation) diff --git a/examples/webenginewidgets/push_notifications/CMakeLists.txt b/examples/webenginewidgets/push_notifications/CMakeLists.txt new file mode 100644 index 000000000..bbb585c9b --- /dev/null +++ b/examples/webenginewidgets/push_notifications/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(simplebrowser LANGUAGES CXX) + +message("\nPlease use simplebrowser to see notifiactions.\n") diff --git a/examples/webenginewidgets/push_notifications/content/index.html b/examples/webenginewidgets/push_notifications/content/index.html new file mode 100644 index 000000000..cce3055cd --- /dev/null +++ b/examples/webenginewidgets/push_notifications/content/index.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<head> + <title>Push Notification Using Node and FCM</title> + <link rel="stylesheet" href="style.css"> +</head> +<body> + <h1>Push Notification Using NodeJS and QtWebEngine</h1> + <div id="app"> + <div id="ping-setup"> + <form> + <div> + Ping Me Every [sec]: + </div> + <div class="ping-input"> + <input type="number" name="seconds" min="0" max="3600" required=""> + </div><button>Ping Me</button> + </form> + </div> + <div id="ping-clear"> + <div id="ping-text"></div><button id="ping-clear-button">Clear</button> + </div> + </div> + <script src="ping.js"></script> +</body> +</html> diff --git a/examples/webenginewidgets/push_notifications/content/ping.js b/examples/webenginewidgets/push_notifications/content/ping.js new file mode 100644 index 000000000..285a57019 --- /dev/null +++ b/examples/webenginewidgets/push_notifications/content/ping.js @@ -0,0 +1,84 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +const publicVapidKey = + "BNO4fIv439RpvbReeABNlDNiiBD2Maykn7EVnwsPseH7-P5hjnzZLEfnejXVP7Zt6MFoKqKeHm4nV9BHvbgoRPg"; + +async function setup(delay) +{ + console.log('>> register service worker...'); + const register = await navigator.serviceWorker.register('/worker.js', { scope : '/' }); + console.log('>> service worker registered...'); + + console.log('>> subscribe push subscription to FCM'); + var subscription = await register.pushManager.subscribe( + { userVisibleOnly : true, applicationServerKey : publicVapidKey }); + console.log('>> subscription created...'); + + console.log('>> subscribe to push service...'); + await fetch('/subscribe', { + method : 'POST', + body : JSON.stringify(subscription), + headers : { 'content-type' : 'application/json', 'ping-time' : delay } + }); + console.log('>> push subscription created...') +} + +async function clear() +{ + const register = await navigator.serviceWorker.getRegistration(); + var subscription = await register.pushManager.getSubscription(); + console.log('>> unsubscribe to push service...'); + await fetch('/unsubscribe', { + method : 'POST', + body : JSON.stringify(subscription), + headers : { 'content-type' : 'application/json' } + }); + console.log('>> push unsubscription removed...') + + console.log('>> unsubscribe push subscription to FCM'); + await subscription.unsubscribe(); + console.log('>> subscription removed...'); +} + +function displaySetup(delay = null) +{ + const pingSetup = document.getElementById('ping-setup'); + const pingClear = document.getElementById('ping-clear'); + const pingText = document.getElementById('ping-text'); + if (delay) { + pingClear.style.display = 'block'; + pingSetup.style.display = 'none'; + pingText.innerHTML = 'Ping Me Every ' + delay + ' seconds'; + } else { + pingClear.style.display = 'none'; + pingSetup.style.display = 'block'; + pingText.innerHTML = ""; + } +} + +function handleSetupPing(event) +{ + event.preventDefault(); + + const seconds = document.forms[0].seconds.value; + document.forms[0].reset(); + + // check for service worker support + if ('serviceWorker' in navigator) { + setup(seconds).catch(err => console.error(err)); + } + + displaySetup(seconds); +}; + +function handleClearPing(event) +{ + event.preventDefault(); + clear(); + displaySetup(); +}; + +document.forms[0].addEventListener('submit', handleSetupPing); +const clearButton = document.getElementById('ping-clear-button'); +clearButton.addEventListener('click', handleClearPing); diff --git a/examples/webenginewidgets/push_notifications/content/style.css b/examples/webenginewidgets/push_notifications/content/style.css new file mode 100644 index 000000000..8176462a2 --- /dev/null +++ b/examples/webenginewidgets/push_notifications/content/style.css @@ -0,0 +1,29 @@ +body { + font-family: monaco; + height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; + display: flex; +} +#ping-clear, +#ping-setup { + font-size: 32px; + text-align: center; +} +.form-inputs{ + margin-top: 20px; +} +input { + width: 100px; + height: 30px; +} +button { + font-size: 16px; +} +#ping-text { + margin-bottom: 30px; +} +#ping-clear { + display: none; +} diff --git a/examples/webenginewidgets/push_notifications/content/worker.js b/examples/webenginewidgets/push_notifications/content/worker.js new file mode 100644 index 000000000..ed660a6e9 --- /dev/null +++ b/examples/webenginewidgets/push_notifications/content/worker.js @@ -0,0 +1,7 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +self.addEventListener('push', event => { + const data = event.data.json(); + self.registration.showNotification(data.title, { body : data.text }); +}); diff --git a/examples/webenginewidgets/push_notifications/doc/images/notification.png b/examples/webenginewidgets/push_notifications/doc/images/notification.png Binary files differnew file mode 100644 index 000000000..ec5c67457 --- /dev/null +++ b/examples/webenginewidgets/push_notifications/doc/images/notification.png diff --git a/examples/webenginewidgets/push_notifications/doc/images/permissions.png b/examples/webenginewidgets/push_notifications/doc/images/permissions.png Binary files differnew file mode 100644 index 000000000..dedb6e472 --- /dev/null +++ b/examples/webenginewidgets/push_notifications/doc/images/permissions.png diff --git a/examples/webenginewidgets/push_notifications/doc/images/push_notifications.png b/examples/webenginewidgets/push_notifications/doc/images/push_notifications.png Binary files differnew file mode 100644 index 000000000..c5ba5f589 --- /dev/null +++ b/examples/webenginewidgets/push_notifications/doc/images/push_notifications.png diff --git a/examples/webenginewidgets/push_notifications/doc/images/website.png b/examples/webenginewidgets/push_notifications/doc/images/website.png Binary files differnew file mode 100644 index 000000000..10a4a6150 --- /dev/null +++ b/examples/webenginewidgets/push_notifications/doc/images/website.png diff --git a/examples/webenginewidgets/push_notifications/doc/src/push_notifications.qdoc b/examples/webenginewidgets/push_notifications/doc/src/push_notifications.qdoc new file mode 100644 index 000000000..1d12a9cc1 --- /dev/null +++ b/examples/webenginewidgets/push_notifications/doc/src/push_notifications.qdoc @@ -0,0 +1,191 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + +\example webenginewidgets/push-notifications +\title WebEngine Push Notifications Example +\ingroup webengine-widgetexamples +\brief Demonstrates how to subscribe to and unsubscribe from push notifications. + +In this example we are going to send push notifications from a web push service to the user. +This is the typical scenario where messages are sent from the application server i.e. website +back-end through a 3rd-party push service, to finally arrive at the user's browser in form of +notifications. To demonstrate this flow, we will implement a simple push service server +application, to which the user can subscribe to receive \e ping messages. + +As already mentioned, in such a workflow there are three different parties involved: + +\list + \li the user's web browser where they receive push notifications + \li 3rd-party push service, which is defined by a subscription endpoint and is a part + of a browser's push service implementation + \li application server, which will store user's subscriptions and initiate push messages + using a subscription endpoint +\endlist + +The user visits a website, where a JavaScript web application uses the JavaScript Push API +to create a push notification subscription. The user is then asked to grant a permission +to receive and display push notifications. Once accepted, the Push API establishes a push channel +with a 3rd-party push service, which in case of QtWebEngine is \l {https://firebase.google.com} +{Firebase Cloud Messaging (FCM)}. The unique push subscription is created that +includes the subscription endpoint URL. The browser then sends a subscription message +to the application server forwarding the endpoint setup. The application server can now +use the subscription endpoint to send notifications to the browser. The browser push service +implementation will deliver a push message. However, to show it, a service worker must +be registered. As the service worker runs in the background, it allows displaying notifications +even if a website, which has installed it, is no longer opened. + +\image push_notifications.png + +Let's go more into implementation details. We start with implementing our custom +push service using NodeJS with two modules: + +\list + \li \l {https://github.com/web-push-libs/web-push} {web-push} - provides the web-push protocol + implementation + \li \l {https://github.com/expressjs/express} {express} - provides the web application framework +\endlist + +Let's initialize a new project and install the required modules in the root directory of this +example: + +\snippet /push_notifications/commands 0 + +These commands should create package.js, which defines the start command: + +\snippet /push_notifications/commands 1 + +Now let's move on to the push service back-end implementation in server.js. + +We start by including the required modules and doing basic \e express framework setup, which +we use to create our custom push server. For simplicity we are going to handle only one +subscription at a time. To do that we need to create \e VAPID keys which we are going to +generate with \e web-push libs. The public key is going to be used by the front-end and authenticate +to the service. + +\quotefromfile webenginewidgets/push_notifications/server.js +\skipto const express +\printto add subscribe route + +\note We are not going to cover the encryption of messages in this example. + +To generate keys, we can use the tool shipped with \e web-push lib, that is installed by +\c npm in our example's root directory. + +\snippet /push_notifications/commands 2 + +Now we add two \c routes to the push server. One to \c subscribe and one to \c unsubscribe, +so that our front-end can send an HTTP POST request to handle the push subscription. +In the subscribe request we will get \c subscription in the request body and we also retrieve +the custom header \c ping-time that defines how often the ping messages should be pushed to +the user. We keep around the \c subscription to be able to send push notifications later. +As a confirmation, we send the 201 status code and schedule the first push notification based on +the \c ping-time value. The \c unsubscribe request simply removes a subscription. + +\quotefromfile webenginewidgets/push_notifications/server.js +\skipto add subscribe route +\printto function sendNotification + +The \c sendNotication() function sends push messages using the web-push lib. We create the payload +with the message we want to present to a user and schedule the next push message. + +\quotefromfile webenginewidgets/push_notifications/server.js +\skipto function sendNotification +\printto server.listen + +In the end we start the server to listen on the given port. + +\quotefromfile webenginewidgets/push_notifications/server.js +\skipto server.listen +\printline started + +Let's move now to our front-end. We create a simple page index.html, where the user can enter how +often they want to receive ping notification messages. We will have two buttons: +\e {Ping Me} to subscribe for push notifications and \e Clear to unsubscribe. +In the end we load ping.js, which we cover next. + +\quotefromfile webenginewidgets/push_notifications/content/index.html +\skipto <body> +\printuntil </body> + +The last piece is creating the logic for the push subscription within the front-end. Here we have two +functions, \c setup and \c clear, to handle subscriptions. When the user clicks on the \e {Ping Me} +button, \c setup is called. To be able to receive notifications, the service worker is needed. +This way the user can leave the website and still get notified as the service worker works +in the background and handles incoming messages. To achieve that, we have to first register +one with: + +\quotefromfile webenginewidgets/push_notifications/content/ping.js +\skipto const register +\printline worker.js + +The call to \c cpushManager.subscribe() will trigger a permission prompt, which is displayed to the +user. If the permission is granted, the push subscription is returned. It includes a URL endpoint +that allows sending notifications to the browser, where the registered service worker waits for +push messages. + +\quotefromfile webenginewidgets/push_notifications/content/ping.js +\skipto var subscription +\printto console.log + +As mentioned the subscription is created for FCM and should be now sent to our custom server +with an HTTP POST request. In addition, we add to the post request the HTTP header with the +\c ping-time the user entered on our website. + +\quotefromfile webenginewidgets/push_notifications/content/ping.js +\skipto await fetch +\printto console.log + +The function \c clear call unsubscribes first from our push server by sending an HTTP POST request +and later from the 3rd-party push service (FCM). + +\quotefromfile webenginewidgets/push_notifications/content/ping.js +\skipuntil async function clear() +\skipuntil { +\printto console.log +\dots +\skipto await fetch +\printto console.log +\dots +\skipto await subscription +\printto console.log + +The rest of code in ping.js is just boilerplate code to read a user provided value +and call \c setup() or \c clear(). + +As the last part of the front-end let's look inside a service worker script, where we simply +register an event listener for \e push events. + +\quotefromfile webenginewidgets/push_notifications/content/worker.js +\skipto self +\printuntil }); +\printuntil }); + +When a push event comes we simply use the Notification JavaScript API to display a notification. + +\note QtWebEngine Notification Example shows how to provide your own handler and customize +the look and feel of a notification message. + +Having the implementation in place, we can start the server on localhost at the port 5000. +To do that, we can simply enter in the console in the project's root directory: + +\snippet /push_notifications/commands 3 + +Now we can fire up \e simplebrowser and enter \c http:\\localhost:5000 as a URL. + +\image website.png + +\note \c QWebEngineProfile cannot be set to \e off-the-record, because push messages would be +disabled. + +After granting the permission we can send our ping request: + +\image permissions.png + +We should see the coming push notification: + +\image notification.png + +\note To troubleshoot errors, open \e {Developer Tools} from Tools menu. +*/ diff --git a/examples/webenginewidgets/push_notifications/doc/src/push_notifications.qmodel b/examples/webenginewidgets/push_notifications/doc/src/push_notifications.qmodel new file mode 100644 index 000000000..048a55911 --- /dev/null +++ b/examples/webenginewidgets/push_notifications/doc/src/push_notifications.qmodel @@ -0,0 +1,837 @@ +<?xml version="1.0" encoding="UTF-8"?> +<qmt> + <project> + <uid>{9fd14f7e-7b67-4e13-9980-ef8fbe24b780}</uid> + <root-package> + <instance> + <MPackage> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{a26e0efd-1b21-4fba-96ce-a607bbcdb4f7}</uid> + </MElement> + </base-MElement> + <name>push_notifications</name> + <children> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{6b1a06e9-960b-4a10-872e-5504d8c0b127}</uid> + <target> + <instance type="MCanvasDiagram"> + <MCanvasDiagram> + <base-MDiagram> + <MDiagram> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{6b1a06e9-960b-4a10-872e-5504d8c0b127}</uid> + </MElement> + </base-MElement> + <name>push_notifications</name> + </MObject> + </base-MObject> + <elements> + <qlist> + <item> + <instance type="DBoundary"> + <DBoundary> + <base-DElement> + <DElement> + <uid>{d50762f2-c7f4-4d52-9592-8bcc07274ba1}</uid> + </DElement> + </base-DElement> + <text>Browser / User Agent</text> + <pos>x:415;y:390</pos> + <rect>x:-105;y:-125;w:210;h:250</rect> + </DBoundary> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{cc80cad6-2e47-4913-aeb1-0c6f41de44bb}</uid> + </DElement> + </base-DElement> + <object>{121189af-36df-4efa-b451-3f1f85ef339f}</object> + <name>JavaScript Web Application</name> + <pos>x:410;y:395</pos> + <rect>x:-75;y:-15;w:150;h:30</rect> + <visual-role>0</visual-role> + </DObject> + </base-DObject> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{8bde1815-304a-41f6-8528-c754dc3d1eda}</uid> + </DElement> + </base-DElement> + <object>{13c69399-5587-4169-a012-8d86216139fd}</object> + <name>Push Manager API</name> + <pos>x:410;y:320</pos> + <rect>x:-75;y:-15;w:150;h:30</rect> + <auto-sized>false</auto-sized> + <visual-role>0</visual-role> + </DObject> + </base-DObject> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{75431119-9e8e-4e3e-a539-9d7bad6dbf81}</uid> + </DElement> + </base-DElement> + <object>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</object> + <name>Service Worker</name> + <pos>x:410;y:470</pos> + <rect>x:-75;y:-15;w:150;h:30</rect> + <auto-sized>false</auto-sized> + <visual-role>0</visual-role> + </DObject> + </base-DObject> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{0a7b0146-d34b-4c39-a1af-df15a1b4108d}</uid> + </DElement> + </base-DElement> + <object>{648fd8d0-6f61-4cc9-b5ce-6c5b9057fe5d}</object> + <a>{cc80cad6-2e47-4913-aeb1-0c6f41de44bb}</a> + <b>{8bde1815-304a-41f6-8528-c754dc3d1eda}</b> + <name>subscribe</name> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{e7d4a675-ddbd-41aa-a802-32221a56dae2}</uid> + </DElement> + </base-DElement> + <object>{afae2401-ea81-4514-aa80-da53d61a8516}</object> + <a>{cc80cad6-2e47-4913-aeb1-0c6f41de44bb}</a> + <b>{75431119-9e8e-4e3e-a539-9d7bad6dbf81}</b> + <name>register</name> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{998e7d36-3416-4b2f-843a-a437449f09f1}</uid> + </DElement> + </base-DElement> + <object>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</object> + <name>Firebase Cloud Messaging </name> + <pos>x:770;y:320</pos> + <rect>x:-75;y:-15;w:150;h:30</rect> + <visual-role>6</visual-role> + </DObject> + </base-DObject> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{28c1fccd-c50e-458c-865d-4ba41d55690b}</uid> + </DElement> + </base-DElement> + <object>{d9959769-74a4-40ad-bf00-904c12a67309}</object> + <a>{8bde1815-304a-41f6-8528-c754dc3d1eda}</a> + <b>{998e7d36-3416-4b2f-843a-a437449f09f1}</b> + <name>create subscription</name> + </DRelation> + </base-DRelation> + <direction>2</direction> + </DDependency> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{ba581e41-cc00-4129-adae-0a1208bb3222}</uid> + </DElement> + </base-DElement> + <object>{13d79379-1727-46a3-947c-e3f2f6eb3a87}</object> + <name>Push Application Server</name> + <pos>x:770;y:390</pos> + <rect>x:-75;y:-15;w:150;h:30</rect> + <auto-sized>false</auto-sized> + <visual-role>6</visual-role> + </DObject> + </base-DObject> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{e796d1cc-f574-45db-a440-7e47d7560d71}</uid> + </DElement> + </base-DElement> + <object>{373c492c-0b9a-4032-9ff4-b3a7cc442883}</object> + <a>{cc80cad6-2e47-4913-aeb1-0c6f41de44bb}</a> + <b>{ba581e41-cc00-4129-adae-0a1208bb3222}</b> + <name>subscribe with endpoint</name> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{6332f8ae-ef9d-4c15-9fbf-d6ee06ba3f84}</uid> + </DElement> + </base-DElement> + <object>{94241d64-4e88-4855-8e78-365be237108b}</object> + <a>{ba581e41-cc00-4129-adae-0a1208bb3222}</a> + <b>{998e7d36-3416-4b2f-843a-a437449f09f1}</b> + <name>push notification</name> + <points> + <qlist> + <item> + <DRelation--IntermediatePoint> + <pos>x:770;y:335</pos> + </DRelation--IntermediatePoint> + </item> + </qlist> + </points> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{582af542-5f2d-4660-a6d0-a3bd983d9682}</uid> + </DElement> + </base-DElement> + <object>{00b8f12d-e08b-426a-83b8-83fea8907fab}</object> + <name>User</name> + <pos>x:185;y:395</pos> + <rect>x:-10;y:-20;w:20;h:40</rect> + <visual-role>0</visual-role> + </DObject> + </base-DObject> + <variety>actor</variety> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{66ca12e1-84a1-4247-82e5-ae6891f3f376}</uid> + </DElement> + </base-DElement> + <object>{6d0ce78f-28e1-4a82-a443-59f4efcb8e66}</object> + <a>{582af542-5f2d-4660-a6d0-a3bd983d9682}</a> + <b>{cc80cad6-2e47-4913-aeb1-0c6f41de44bb}</b> + <name>subscribe</name> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{7657479a-a855-4d97-8c61-098690da9023}</uid> + </DElement> + </base-DElement> + <object>{eef542f5-e2cf-40a8-92c9-9892f3d06e1f}</object> + <a>{75431119-9e8e-4e3e-a539-9d7bad6dbf81}</a> + <b>{582af542-5f2d-4660-a6d0-a3bd983d9682}</b> + <name>notify</name> + <points> + <qlist> + <item> + <DRelation--IntermediatePoint> + <pos>x:340;y:430</pos> + </DRelation--IntermediatePoint> + </item> + </qlist> + </points> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{97f3e5b0-93ca-4351-98d6-ad88567979f7}</uid> + </DElement> + </base-DElement> + <object>{963ad9f9-ecad-430f-8233-b7897fa630bd}</object> + <a>{75431119-9e8e-4e3e-a539-9d7bad6dbf81}</a> + <b>{998e7d36-3416-4b2f-843a-a437449f09f1}</b> + <name>push notification</name> + <points> + <qlist> + <item> + <DRelation--IntermediatePoint> + <pos>x:535;y:470</pos> + </DRelation--IntermediatePoint> + </item> + <item> + <DRelation--IntermediatePoint> + <pos>x:590;y:470</pos> + </DRelation--IntermediatePoint> + </item> + <item> + <DRelation--IntermediatePoint> + <pos>x:615;y:470</pos> + </DRelation--IntermediatePoint> + </item> + <item> + <DRelation--IntermediatePoint> + <pos>x:670;y:470</pos> + </DRelation--IntermediatePoint> + </item> + <item> + <DRelation--IntermediatePoint> + <pos>x:670;y:365</pos> + </DRelation--IntermediatePoint> + </item> + </qlist> + </points> + </DRelation> + </base-DRelation> + <direction>1</direction> + </DDependency> + </instance> + </item> + </qlist> + </elements> + <last-modified>1665083804306</last-modified> + <toolbarid>UseCases</toolbarid> + </MDiagram> + </base-MDiagram> + </MCanvasDiagram> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{121189af-36df-4efa-b451-3f1f85ef339f}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{121189af-36df-4efa-b451-3f1f85ef339f}</uid> + </MElement> + </base-MElement> + <name>JavaScript Web Application</name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{648fd8d0-6f61-4cc9-b5ce-6c5b9057fe5d}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{648fd8d0-6f61-4cc9-b5ce-6c5b9057fe5d}</uid> + </MElement> + </base-MElement> + <name>subscribe</name> + <a>{121189af-36df-4efa-b451-3f1f85ef339f}</a> + <b>{13c69399-5587-4169-a012-8d86216139fd}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{afae2401-ea81-4514-aa80-da53d61a8516}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{afae2401-ea81-4514-aa80-da53d61a8516}</uid> + </MElement> + </base-MElement> + <name>register</name> + <a>{121189af-36df-4efa-b451-3f1f85ef339f}</a> + <b>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{373c492c-0b9a-4032-9ff4-b3a7cc442883}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{373c492c-0b9a-4032-9ff4-b3a7cc442883}</uid> + </MElement> + </base-MElement> + <name>subscribe with endpoint</name> + <a>{121189af-36df-4efa-b451-3f1f85ef339f}</a> + <b>{13d79379-1727-46a3-947c-e3f2f6eb3a87}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + </MItem> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{13c69399-5587-4169-a012-8d86216139fd}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{13c69399-5587-4169-a012-8d86216139fd}</uid> + </MElement> + </base-MElement> + <name>Push Manager API</name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{d9959769-74a4-40ad-bf00-904c12a67309}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{d9959769-74a4-40ad-bf00-904c12a67309}</uid> + </MElement> + </base-MElement> + <name>create subscription</name> + <a>{13c69399-5587-4169-a012-8d86216139fd}</a> + <b>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</b> + </MRelation> + </base-MRelation> + <direction>2</direction> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + </MItem> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</uid> + </MElement> + </base-MElement> + <name>Service Worker</name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{bfebbc32-3541-4c1a-b3c6-a3652f700767}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{bfebbc32-3541-4c1a-b3c6-a3652f700767}</uid> + </MElement> + </base-MElement> + <name>push notification</name> + <a>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</a> + <b>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</b> + </MRelation> + </base-MRelation> + <direction>1</direction> + </MDependency> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{89d5f886-1913-4bb7-be32-cdd5cb61eebc}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{89d5f886-1913-4bb7-be32-cdd5cb61eebc}</uid> + </MElement> + </base-MElement> + <name>notify</name> + <a>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</a> + <b>{00b8f12d-e08b-426a-83b8-83fea8907fab}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{eef542f5-e2cf-40a8-92c9-9892f3d06e1f}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{eef542f5-e2cf-40a8-92c9-9892f3d06e1f}</uid> + </MElement> + </base-MElement> + <name>notify</name> + <a>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</a> + <b>{00b8f12d-e08b-426a-83b8-83fea8907fab}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{963ad9f9-ecad-430f-8233-b7897fa630bd}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{963ad9f9-ecad-430f-8233-b7897fa630bd}</uid> + </MElement> + </base-MElement> + <name>push notification</name> + <a>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</a> + <b>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</b> + </MRelation> + </base-MRelation> + <direction>1</direction> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + </MItem> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</uid> + </MElement> + </base-MElement> + <name>Firebase Cloud Messaging </name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{560fb941-261e-4f45-8909-2106283fdb3e}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{560fb941-261e-4f45-8909-2106283fdb3e}</uid> + </MElement> + </base-MElement> + <a>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</a> + <b>{13c69399-5587-4169-a012-8d86216139fd}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + </MItem> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{13d79379-1727-46a3-947c-e3f2f6eb3a87}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{13d79379-1727-46a3-947c-e3f2f6eb3a87}</uid> + </MElement> + </base-MElement> + <name>Push Application Server</name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{94241d64-4e88-4855-8e78-365be237108b}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{94241d64-4e88-4855-8e78-365be237108b}</uid> + </MElement> + </base-MElement> + <name>push notification</name> + <a>{13d79379-1727-46a3-947c-e3f2f6eb3a87}</a> + <b>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + </MItem> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{00b8f12d-e08b-426a-83b8-83fea8907fab}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{00b8f12d-e08b-426a-83b8-83fea8907fab}</uid> + </MElement> + </base-MElement> + <name>User</name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{6d0ce78f-28e1-4a82-a443-59f4efcb8e66}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{6d0ce78f-28e1-4a82-a443-59f4efcb8e66}</uid> + </MElement> + </base-MElement> + <name>subscribe</name> + <a>{00b8f12d-e08b-426a-83b8-83fea8907fab}</a> + <b>{121189af-36df-4efa-b451-3f1f85ef339f}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + <variety-editable>false</variety-editable> + <variety>actor</variety> + </MItem> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </children> + </MObject> + </base-MObject> + </MPackage> + </instance> + </root-package> + </project> +</qmt> diff --git a/examples/webenginewidgets/push_notifications/push_notifications.pro b/examples/webenginewidgets/push_notifications/push_notifications.pro new file mode 100644 index 000000000..f88018da2 --- /dev/null +++ b/examples/webenginewidgets/push_notifications/push_notifications.pro @@ -0,0 +1 @@ +message("Please use simplebrowser example to see notificationsi.") diff --git a/examples/webenginewidgets/push_notifications/server.js b/examples/webenginewidgets/push_notifications/server.js new file mode 100644 index 000000000..fc7deb08a --- /dev/null +++ b/examples/webenginewidgets/push_notifications/server.js @@ -0,0 +1,65 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +const express = require('express'); +const webpush = require('web-push'); + +// setup server +const port = 5000; +const server = express(); +server.use(express.json()); +server.use(express.static('content')); + +// we support only one subscription at the time +var subscription = null; + +// setup vapid keys +const vapidKeys = { + publicKey : + "BNO4fIv439RpvbReeABNlDNiiBD2Maykn7EVnwsPseH7-P5hjnzZLEfnejXVP7Zt6MFoKqKeHm4nV9BHvbgoRPg", + privateKey : "HqhrzsRfG5-SB3j45lyUmV7cYZuy-71r2Bb0tgaOefk" +}; + +// set vapid keys for webpush libs +webpush.setVapidDetails('mailto:push@qt.io', vapidKeys.publicKey, vapidKeys.privateKey); + +// add subscribe route +server.post('/subscribe', (req, res) => { + + // subscription request + subscription = req.body; + const delay = req.headers['ping-time']; + console.log('Got subscription with endpoint: ' + subscription.endpoint); + console.log('Ping delay is at: ' + delay); + + // confirm resource created + res.status(201).json({}); + + // schedule notification + setTimeout(() => { sendNotification(delay) }, delay * 1000); +}); + +// add unsubscribe route +server.post('/unsubscribe', (req, res) => { + console.log('Got unsubscribe with endpoint: ' + req.body.endpoint); + subscription = null; + res.status(201).json({}); +}); + +function sendNotification(delay) +{ + if (!subscription) + return; + + // create payload text + const payload = JSON.stringify({ title : 'Ping !', text : 'Visit qt.io', url : 'www.qt.io' }); + + // send notification + console.log('Sending notification !'); + webpush.sendNotification(subscription, payload).catch(err => console.error(err)); + + // schedule next notification + setTimeout(() => { sendNotification(delay) }, delay * 1000); +} + +server.listen(port, () => console.log(`Push server started at port ${port}`)); diff --git a/examples/webenginewidgets/webenginewidgets.pro b/examples/webenginewidgets/webenginewidgets.pro index c88b7874e..ad11d3f5c 100644 --- a/examples/webenginewidgets/webenginewidgets.pro +++ b/examples/webenginewidgets/webenginewidgets.pro @@ -9,6 +9,7 @@ SUBDIRS += \ notifications \ simplebrowser \ stylesheetbrowser \ + push_notifications \ videoplayer \ webui diff --git a/src/webenginewidgets/doc/snippets/push_notifications/commands b/src/webenginewidgets/doc/snippets/push_notifications/commands new file mode 100644 index 000000000..aee9761c1 --- /dev/null +++ b/src/webenginewidgets/doc/snippets/push_notifications/commands @@ -0,0 +1,19 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] +npm init -y +npm install web-push express +//! [0] + +//! [1] +"start": "node server.js" +//! [1] + +//! [2] +./node_odules/.bin/web-push generate-vapid-keys +//! [2] + +//! [3] +npm start +//! [3] |