diff options
Diffstat (limited to 'examples/webenginewidgets/push-notifications/doc/src')
-rw-r--r-- | examples/webenginewidgets/push-notifications/doc/src/push-notifications.qdoc | 203 | ||||
-rw-r--r-- | examples/webenginewidgets/push-notifications/doc/src/push-notifications.qmodel | 837 |
2 files changed, 1040 insertions, 0 deletions
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..05ccf3e8b --- /dev/null +++ b/examples/webenginewidgets/push-notifications/doc/src/push-notifications.qdoc @@ -0,0 +1,203 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + +\example webenginewidgets/push-notifications +\examplecategory {Web Technologies} +\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 look into the \e push-notification browser application, which is based on +\l {WebEngine Notifications Example}: + +\quotefromfile webenginewidgets/push-notifications/main.cpp +\skipto main +\printuntil app.exec +\printuntil } + +This application simply opens the page at \c http:\\localhost:5000. We are not going into detail +about how to open a notification as it is documented \l {WebEngine Notifications Example}{here}. +However, you need to modify the application in two ways. First, \c QWebEngineProfile cannot be +set to \e off-the-record because push messaging would be disabled. Therefore, as you can see +above, \c QWebEngineProfile is initialized with the name. Second, you need to enable push +messaging with the call QWebEngineProfile::setPushServiceEnabled for the created \c profile. + +When the application runs it displays: + +\image website.png + +After granting the permission we can send our ping request: + +\image permissions.png + +We should see the coming push notification: + +\image notification.png + +*/ 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> |