This repository has been archived by the owner on Sep 30, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 21
/
Index.cshtml
340 lines (330 loc) · 27.2 KB
/
Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<title>Web Push Notifications Demo | Microsoft Edge Demos</title>
<meta name="viewport" content="width=device-width">
<meta name="og:title" content="Push Notifications">
<meta name="description" content="Push notifications can help you achieve what apps have long been able to do. Join us as we explore how to build the front-end and back-end of a push notification">
<meta name="keywords" content="PWAS, push notifications, service worker">
<meta name="author" content="ststimac, aliams, molant">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="msedgedev">
<meta name="twitter:title" content="Push Notifiations Demo">
<meta name="twitter:description" content="Push notifications can help you achieve what apps have long been able to do. Join us as we explore how to build the front-end and back-end of a push notification">
<meta name="twitter:image" content="images/social-image.jpg">
<meta property="og:title" content="Push Notifications Demo">
<meta property="og:description" content="Push notifications can help you achieve what apps have long been able to do. Join us as we explore how to build the front-end and back-end of a push notification">
<meta property="og:site_name" content="Microsoft Edge Demos">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="images/social-image.jpg">
<link rel="icon" href="images/favicon.png">
<link rel="stylesheet" href="https://az813057.vo.msecnd.net/styles/fonts-full-20180102.css">
<link rel="stylesheet" href="css/demo-template.css">
<link rel="stylesheet" href="css/push-notifications.css">
</head>
<body>
<header class="c-nav-bar">
<div class="l-contain c-nav-bar__contain" data-menu>
<nav class="c-nav-bar__breadcrumb" aria-label="breadcrumbs">
<ul class="u-simple-list">
<li class="c-nav-bar__index"><a href="https://developer.microsoft.com/en-us/microsoft-edge/testdrive/" class="c-nav-bar__more">Microsoft Edge Demos</a></li>
<li class="c-nav-bar__title"><span class="u-sr-only">Current demo:</span>Push Notifications with ASP.NET Core</li>
</ul>
</nav>
<nav class="c-toc">
<button class="c-toc__btn" id="js-nav-btn" aria-haspopup="true" aria-expanded="false" aria-controls="js-nav-items" aria-label="Demo contents">
Contents <svg class="c-toc__arrow" xmlns="http://www.w3.org/2000/svg" width="12" height="12"><path fill="none" stroke="#FFF" stroke-miterlimit="10" d="M12 3L6 9 0 3" /></svg>
</button>
<ul class="c-toc__items" id="js-nav-items" role="group" aria-hidden="true"></ul>
</nav>
</div>
</header>
<main role="main">
<div class="intro l-section--banner--dark-black">
<section class="intro__section l-contain">
<h1 class="intro__heading">Web Push Notifications</h1>
<img src="images/header-embellishment.svg" class="heading__image" role="presentation" alt="" />
<div class="intro__section__overview l-subsection" id="intro" data-nav-label="Introduction">
<p class="highlight h3">Welcome to the future of the web — where push messages can help you achieve better engagement for your site or web app.</p>
<div class="l-flex--guide l-subsection wrap">
<div class="guide-col-main--alt">
<p>It goes without saying that push notifications expand your reach to your users in a timely, power-efficient and dependable way. Users are re-engaged with customized and relevant content that will have them coming back for more.</p>
<p>Inspired by the parallels between basic astrological ideas and push notification architecture, come join us on this journey to better understand how to set up your site – both the front-end and the back-end – to send push messages to a user without needing the browser or app to be opened. You can fork this tutorial itself on <a href="https://github.com/MicrosoftEdge/pushnotifications-demo-aspnetcore">GitHub</a> and even set it up locally so that you can play with it.</p>
<p>To see it in action, click the button below, accept the permission prompt (if you haven’t already), and then immediately close this tab (or browser). You should get a notification within 5 seconds after clicking the button below. Don’t worry, when you click the notification, it’ll take you right back here, so you can continue learning about push messages.</p>
<button class="btn--action" id="initiate-push" disabled="disabled">Initiating...</button>
<p>Just so you know, after clicking the button above, you’ll be getting push message updates daily. If you prefer to stop getting these push messages, click below to unsubcribe.</p>
<button class="btn--action" id="unsubscribe-push" disabled="disabled">Initiating...</button>
</div>
<div class="guide-col-side">
<img src="images/hero-image.svg" role="presentation" alt="" />
</div>
</div>
<img src="images/hero-background-stars.svg" class="heading__image-background" role="presentation" alt="" />
</div>
</section>
</div>
<div class="l-section--banner--dark">
<div class="intro__section__cards">
<section class="intro__section" id="different-pieces" data-nav-label="The different pieces">
<h2>The different pieces</h2>
<div class="intro__section__highlight l-subsection l-contain">
<div class="guide-col-main">
<p>
At a high-level, there is a server-side and client-side component that makes push messages possible. The client side consists of the browser and the app’s web page while the server side involves the app’s server and the notification service’s server. Each of these different pieces play a role in subscribing for the push message, sending push message updates, showing a notification and ultimately opening a page when that notification is clicked.
</p>
</div>
</div>
<div class="l-flex--halves l-flex-center l-subsection l-contain">
<div class="card card--right">
<h3>Server Side</h3>
<div class="card--illustration">
<img src="images/leo-server-side.svg" role="presentation" alt="" />
</div>
<div class="card--bottom">
<p class="card--caption">If the signs of the zodiac were sending messages to those on a terrestrial plane, they would be the server side (and up in the cloud no less) of push notifications.</p>
</div>
</div>
<div class="card card--left">
<h3>Client Side</h3>
<div class="card--illustration">
<img src="images/client-side.svg" role="presentation" alt="" />
</div>
<div class="card--bottom">
<p class="card--caption">The client side of push notifications, the browser or user agent and the app page, is represented by the terrestrial plane (earth) where the updates from the zodiac are routed through and displayed.</p>
</div>
</div>
</div>
<div class="l-subsection l-contain subsection-dark">
<div class="guide-col-main">
<p>Before we explore building out a push notification in depth, let's break down the interaction between front-end and back-end.</p>
</div>
<div class="l-flex--halves">
<div class="chart">
<div class="chart--illustration">
<img src="images/astrology-demo_chart-1.svg" role="presentation" alt="" />
</div>
<p>The app first attempts to subscribe the user to push messages. The browser will ask the user for permission to show notifications.</p>
</div>
<div class="chart">
<div class="chart--illustration">
<img src="images/astrology-demo_chart-2.svg" role="presentation" alt="" />
</div>
<p>Once the user has given consent, the browser will communicate with the notification service’s server to establish a push message channel that can be used to send push messages.</p>
</div>
<div class="chart">
<div class="chart--illustration">
<img src="images/astrology-demo_chart-3.svg" role="presentation" alt="" />
</div>
<p>The app’s server can store the necessary information related to the push subscription so that it can send push messages even after the page is gone. It does this by sending a message to the notification service’s server to push a message to the relevant client’s device. </p>
</div>
<div class="chart">
<div class="chart--illustration">
<img src="images/astrology-demo_chart-4.svg" role="presentation" alt="" />
</div>
<p>When such a message arrives at the client’s device, the browser allows the app to prescribe a notification to show.</p>
</div>
<div class="chart">
<div class="chart--illustration">
<img src="images/astrology-demo_chart-5.svg" role="presentation" alt="" />
</div>
<p>Once the notification is clicked, the browser allows the app to prescribe a page to open, focus, or navigate. </p>
</div>
<div class="non-chart">
<div class="callout">
<p class="highlight">Now that you have an overview of how Push Notifications work, let's dive into building the back-end and front-end pieces to put it all together.</p>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
<section class="l-section guide-section side-image" id="setup-server" data-nav-label="Set up your server">
<h2><span class="title-step">Step 1</span> Set up your server</h2>
<div class="l-contain">
<div class="l-flex--guide l-subsection">
<div class="guide-col-main">
<p>To start, you’ll first need to make sure your web server is setup to send pushes. We’ll be using an ASP.NET Core server and take advantage of the open-source <a href="https://www.nuget.org/packages/WebPush/">WebPush</a> library so that we don’t have to worry about the encryption details involved with sending a push.</p>
<p>We’ll first need to call <code>Install-Package WebPush</code> (web push library) from a terminal or from the Package Manager so that we can use it in our app.</p>
<p>We’ll need to specify the VAPID keys that will allow identifications between our app’s server and the notification server (e.g. <a href="https://firebase.google.com/docs/cloud-messaging/">Firebase Cloud Messaging</a> (FCM), <a href="https://blog.mozilla.org/services/">Mozilla Cloud Services</a> (MCS), and <a href="https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/windows-push-notification-services--wns--overview">Windows Push Notification Service</a> (WNS) depending on which browser is being used). You only need to set up the VAPID keys once which can be generated easily:</p>
<pre><code>using WebPush;
var vapidKeys = VapidHelper.GenerateVapidKeys();
</code></pre>
<p>Once they’re generated, you can use them for as long as you need to by setting the vapidKeys variable to what you just got when calling <code>GenerateVapidKeys()</code>:</p>
<pre><code>var vapidSubject = "mailto:myaccount@outlook.com";
var vapidPublicKey = "BL6As_YCGHPf3ZeDbklyVxgvJVb4Tr5qjZFS-J7XzkT5zQNghd9iUBUsqSlVO5znwTsZZrEOx8JFRDJc1JmkymA";
var vapidPrivateKey = "GnMVDgbtZrqs7tgKEkJaV5aZF8cVjoq7Ncz_TEVI_lo";
_vapidDetails = new VapidDetails(vapidSubject, vapidPublicKey, vapidPrivateKey);
</code></pre>
<p>We’ll need to set up a few endpoints on our server so that we can provide the public key to our site. The public key will be used when subscribing for push messages so that the notifications server (e.g. WNS or FCM) can know that we are who we say we are when we send subsequent push messages.</p>
<pre><code>[HttpGet, Route("vapidpublickey")]
public ActionResult<string> GetVapidPublicKey()
{
return Ok(_pushService.GetVapidPublicKey());
}</code></pre>
<p>We’ll also need an endpoint that will allow us to store the subscription details for a user so that we’ll be able to push messages to them from our app’s server.</p>
<pre><code>[HttpPost("subscribe")]
public async Task<ActionResult<PushSubscription>> Subscribe([FromBody] PushSubscriptionViewModel model)
{
// save subscription to a database
return Ok();
}</code></pre>
</div>
</div>
<div class="l-flex--guide--callout l-subsection">
<div class="guide-col-side callout-text callout-light">
<p class="highlight">In our example, we are going to be sending astrology trivia push updates to each user that is subscribed daily at 7am Pacific Time. This will be done in a scheduled task that runs on the server. Therefore, it’s important that we store the subscription information on the server so that we can send this update whenever we need to.</p>
</div>
<div class="guide-col-main--alt callout-img callout-light">
<img src="images/cron-job.svg" class="callout-img--vertical" role="presentation" alt="" />
</div>
</div>
<div class="l-flex--guide l-subsection">
<div class="guide-col-main">
<p>You can send pushes whenever you like, such as when something changes in your database, or after something changes on your site or app. For instance, if your site allows users to message each other and someone messages a user, you will want to send a push to that user so that they know they just got a message. Be mindful that the goal is to produce a notification that will have a good chance of being clicked by the user, otherwise it might defeat the purpose. For instance, it might make more sense to not send any push messages to a user that is already on your page since it might annoy them to get notifications when they’re already there. You’ll need to coordinate with the server to ensure that users are only getting notifications when they should be getting them.</p>
<p>Using the web push library, we can send a push message from the server.</p>
<pre><code>_webPushClient.SendNotification(
subscription,
JsonConvert.SerializeObject(notification),
_vapidDetails);
</code></pre>
<p>Now that we have the server all set up, let’s move on to making the actual page that will subscribe for the push!</p>
</div>
</div>
</div>
</section>
<section class="l-section l-section--banner--dark" id="setup-page" data-nav-label="Set up your web page">
<h2><span class="title-step">Step 2</span> Set up your web page</h2>
<div class="l-contain side-image-left">
<div class="l-flex--guide l-subsection">
<div class="guide-col-main">
<p>When the page loads, the first thing you’ll want to do is get the public key from the application server so that you can set up the push subscription.</p>
<pre><code>if (navigator.serviceWorker) {
fetch('./api/push/vapidpublickey')
.then(function(res) {
res.json().then(function(data) {
registerPush(data);
});
});
}</code></pre>
<p>Now, with the public key in hand, we’ll need to install the service worker and also create a push subscription.</p>
<pre><code>function registerPush(appPubkey) {
navigator.serviceWorker.register('service-worker.js');
navigator.serviceWorker.ready.then(function(registration) {
return registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription;
}
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(appPubkey)
});
})
.then(function(subscription) {
return fetch('./api/push/subscribe', {
method: 'post',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify({ subscription: subscription })
});
});
});
}
function urlBase64ToUint8Array(base64String) {
var padding = '='.repeat((4 - base64String.length % 4) % 4);
var base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
var rawData = window.atob(base64);
var outputArray = new Uint8Array(rawData.length);
for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}</code></pre>
<p>We’ll need to make sure that we have an active service worker before we attempt to subscribe for push. We can do this by using <code>navigator.serviceWorker.ready</code> which will resolve when a service worker is active.</p>
</div>
</div>
<div class="l-flex--guide--callout l-subsection">
<div class="guide-col-side callout-text-dark callout-dark">
<p class="highlight">At this point, before a new push subscription is created, the browser will check whether the user granted permission to receive notifications. If not, the user will be prompted by the browser for permission.</p>
</div>
<div class="guide-col-main--alt callout-img callout-dark">
<img src="images/notifications-permission.svg" class="callout-img--vertical" alt="" role="presentation" />
</div>
</div>
<div class="l-flex--guide l-subsection">
<div class="guide-col-main">
<p>To create a push subscription, you’ll need to set the <code>userVisibleOnly</code> option to “true” – meaning a notification must be shown as a result of a push – and provide a valid <code>applicationServerKey</code>. If there is already a push subscription, there is no need to subscribe again. That said, we’ll want to first check if there’s an existing push subscription and use that first.</p>
<p>At any point when a push is received by the client, a corresponding service worker is run to handle the push event. As part of this push handling, a notification must be shown so that the user understands that something is happening in the background. In the service worker (sw.js), we will handle the <code>push</code> event and show a notification:</p>
<pre><code>self.addEventListener('push', function(event) {
event.waitUntil(
registration.showNotification('WEATHER ADVISORY', {
body: event.data ? event.data.text() : 'no payload',
icon: 'icon.png'
})
);
});</code></pre>
<p>After a notification is shown, there is still the matter of dealing with when it’s been clicked. As such, we need to have another event listener in the service worker that would handle this case. In the same service worker, we will handle the <code>notificationclick</code> event to close the notification and then open a new window for our site:</p>
<pre><code>self.addEventListener('notificationclick', function(event) {
event.notification.close();
event.waitUntil(clients.openWindow('weather/advisory'));
});</code></pre>
<p>You’re also able to sort through the already open windows and focus one of those, or perhaps even navigate an existing window.</p>
</div>
</div>
</div>
</section>
<section class="l-section" id="does-it-work" data-nav-label="Make sure it all works">
<h2><span class="title-step">Step 3</span> Make sure it all works</h2>
<div class="l-contain">
<div class="l-flex--guide l-subsection">
<div class="guide-col-main">
<p>It’s now time to give it a try! The button below will send a message to the application server to send along a push request to the push service’s server so that your machine can get a push notification.</p>
<button class="btn--action" id="initiate-push-2" disabled="disabled">Initiating...</button>
<p>And again, after clicking the button above, you’ll be getting push message updates daily. If you prefer to stop getting these push messages, click below to unsubcribe.</p>
<button class="btn--action" id="unsubscribe-push-2" disabled="disabled">Initiating...</button>
<p>As a reminder, you can check out a fully working version of this demo on the <a href="https://github.com/MicrosoftEdge/pushnotifications-demo-aspnetcore">GitHub repo</a>.</p>
</div>
</div>
</div>
</section>
<section class="l-section l-section--banner moon-background" id="try-it" data-nav-label="Try it out!">
<h2>Try it out!</h2>
<div class="l-contain">
<div class="l-subsection l-flex--guide">
<div class="guide-col-main">
<p>Great, so now we have a fully working push site. It’s time for you to get your hands dirty and see what you can come up with. Make sure you upgrade to the latest version of Windows which comes with Edge 17 that supports service workers and push. We’d love to hear from you if you have any questions or find any bugs. Please let us know! </p>
</div>
</div>
</div>
</section>
<section class="l-section" id="appendix" data-nav-label="Appendix">
<h2>Appendix</h2>
<div class="l-contain">
<div class="l-flex--guide l-subsection">
<div class="guide-col-main">
<p>The W3C <a href="https://w3c.github.io/push-api/">Push API</a> and <a href="https://notifications.spec.whatwg.org/">Notification API</a> go hand-in-hand to enable push notifications in modern browsers. The Push API is used to set up a push subscription and is invoked when a message is pushed to the corresponding service worker. The service worker then is responsible for showing a notification to the user using the Notification API and reacting to user interaction with the notification. </p>
<p>A standardized method of message delivery is also important for the W3C Push API to work consistently across all major browsers where application servers will need to use multiple push services. For instance, Google Chrome and Mozilla Firefox use <a href="https://firebase.google.com/docs/cloud-messaging/">Firebase Cloud Messaging</a> (FCM) <a href="https://blog.mozilla.org/services/">and Mozilla Cloud Services</a> (MCS), respectively while Microsoft Edge relies on the <a href="https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/windows-push-notification-services--wns--overview">Windows Push Notification Service</a> (WNS) to deliver push messages. To reach reasonable interoperability with other browsers’ messaging services, WNS has now deployed support for the <a href="https://datatracker.ietf.org/doc/rfc8030/">Web Push</a> protocols being finalized within IETF, as well as the <a href="https://datatracker.ietf.org/doc/rfc8291/">Message Encryption</a> spec and the <a href="https://datatracker.ietf.org/doc/rfc8292/">Voluntary Application Server Identification</a> (VAPID) spec for web push. Web developers can now use the Web Push APIs and service workers to provide an interoperable push service on the web.</p>
</div>
</div>
</div>
</section>
</main>
<footer class="l-section l-section--banner c-outro" role="contentinfo" aria-label="Demo credits">
<div class="l-contain">
<div class="c-outro__byline">
<p><a href="https://developer.microsoft.com/en-us/microsoft-edge/testdrive/" class="c-nav-bar__more">A demo</a> by Microsoft Edge</p>
<p>Contributors: <a href="https://github.com/aliams">Ali Alabbas</a>, <a href="https://github.com/ststimac">Stephanie Drescher</a>, <a href="https://github.com/molant">Antón Molleda</a> and <a href="https://github.com/mark-szabo">Mark Szabo</a></p>
</div>
<a href="https://github.com/MicrosoftEdge/pushnotifications-demo-aspnetcore" class="c-outro__github" aria-label="Explore code on Github">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path fill-rule="evenodd" clip-rule="evenodd" fill="#FFF" d="M10 .2C4.5.2 0 4.7 0 10.2c0 4.4 2.9 8.2 6.8 9.5.5.1.7-.2.7-.5v-1.7c-2.8.6-3.4-1.3-3.4-1.3-.4-1.1-1.1-1.5-1.1-1.5-.9-.6.1-.6.1-.6 1 .1 1.5 1 1.5 1 .9 1.6 2.4 1.2 2.9.9.1-.6.3-1.1.6-1.3-2.1-.3-4.5-1.1-4.5-5 0-1.1.4-2 1-2.7-.1-.2-.4-1.2.1-2.6 0 0 .8-.3 2.7 1 .9-.2 1.8-.3 2.6-.3s1.7.1 2.5.3c1.9-1.3 2.7-1 2.7-1 .5 1.4.2 2.4.1 2.6.6.7 1 1.6 1 2.7 0 3.8-2.3 4.7-4.6 4.9.4.3.7.9.7 1.9v2.7c0 .3.2.6.7.5 4-1.3 6.8-5.1 6.8-9.5.1-5.5-4.4-10-9.9-10z" /></svg><span>Explore code</span>
</a>
</div>
</footer>
<script src="js/demo-template.js"></script>
<script src="js/util.js"></script>
<script src="js/script.js"></script>
</body>
</html>