summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorMichal Klocek <michal.klocek@qt.io>2022-09-27 17:41:24 +0200
committerMichal Klocek <michal.klocek@qt.io>2022-11-29 18:32:35 +0100
commit414cc4d81f5aaefd5dc2ad4d14de6c3fcd0730d4 (patch)
treef4e0195706da586a58bebda4be8e8c2a80c806ea /examples
parentc036fa3ead32ab27d600e824e042dad18818b4af (diff)
Add push notification example
Adds simple push service implementation in NodeJS. Note setup for endpoint for qwebenginepage is not mentioned as it has to be decided what to do with it (see bug report). Task-number: QTBUG-107442 Change-Id: Iacfcb2a9d0c33c514b4046d06f866eb82390a240 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'examples')
-rw-r--r--examples/webenginewidgets/CMakeLists.txt1
-rw-r--r--examples/webenginewidgets/push_notifications/CMakeLists.txt7
-rw-r--r--examples/webenginewidgets/push_notifications/content/index.html26
-rw-r--r--examples/webenginewidgets/push_notifications/content/ping.js84
-rw-r--r--examples/webenginewidgets/push_notifications/content/style.css29
-rw-r--r--examples/webenginewidgets/push_notifications/content/worker.js7
-rw-r--r--examples/webenginewidgets/push_notifications/doc/images/notification.pngbin0 -> 1279 bytes
-rw-r--r--examples/webenginewidgets/push_notifications/doc/images/permissions.pngbin0 -> 4749 bytes
-rw-r--r--examples/webenginewidgets/push_notifications/doc/images/push_notifications.pngbin0 -> 10573 bytes
-rw-r--r--examples/webenginewidgets/push_notifications/doc/images/website.pngbin0 -> 7539 bytes
-rw-r--r--examples/webenginewidgets/push_notifications/doc/src/push_notifications.qdoc191
-rw-r--r--examples/webenginewidgets/push_notifications/doc/src/push_notifications.qmodel837
-rw-r--r--examples/webenginewidgets/push_notifications/push_notifications.pro1
-rw-r--r--examples/webenginewidgets/push_notifications/server.js65
-rw-r--r--examples/webenginewidgets/webenginewidgets.pro1
15 files changed, 1249 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
new file mode 100644
index 000000000..ec5c67457
--- /dev/null
+++ b/examples/webenginewidgets/push_notifications/doc/images/notification.png
Binary files differ
diff --git a/examples/webenginewidgets/push_notifications/doc/images/permissions.png b/examples/webenginewidgets/push_notifications/doc/images/permissions.png
new file mode 100644
index 000000000..dedb6e472
--- /dev/null
+++ b/examples/webenginewidgets/push_notifications/doc/images/permissions.png
Binary files differ
diff --git a/examples/webenginewidgets/push_notifications/doc/images/push_notifications.png b/examples/webenginewidgets/push_notifications/doc/images/push_notifications.png
new file mode 100644
index 000000000..c5ba5f589
--- /dev/null
+++ b/examples/webenginewidgets/push_notifications/doc/images/push_notifications.png
Binary files differ
diff --git a/examples/webenginewidgets/push_notifications/doc/images/website.png b/examples/webenginewidgets/push_notifications/doc/images/website.png
new file mode 100644
index 000000000..10a4a6150
--- /dev/null
+++ b/examples/webenginewidgets/push_notifications/doc/images/website.png
Binary files differ
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