Skip to content

Commit b1c83d2

Browse files
committed
Refactor code and suppoort iOS web push
1 parent 35663f9 commit b1c83d2

File tree

12 files changed

+5160
-3746
lines changed

12 files changed

+5160
-3746
lines changed

GenWebPushKey.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// https://developers.google.com/web/updates/2015/05/notifying-you-of-changes-to-notifications
2+
// https://felixgerschau.com/web-push-notifications-tutorial/
3+
// https://developers.google.com/web/fundamentals/codelabs/push-notifications
4+
const webPush = require('web-push');
5+
6+
//VAPID keys should be generated only once with:
7+
const vapidKeys = webPush.generateVAPIDKeys();
8+
console.log(vapidKeys);

PushMessageFromServer.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
const webPush = require('web-push');
55

66
// VAPID keys should be generated only once with:
7-
// const vapidKeys = webPush.generateVAPIDKeys();
8-
// console.log(vapidKeys);
7+
// node GenWebPushKey.js
98

109
const vapidKeys = {
1110
publicKey: 'BHG9NdYdfiCIx7xUS8u2CMhtDD-GWHb6QYuSZ908NZYZHhJEjGjcX0yTjHrWx7gDICmCEUORrLmw3uwOGBqzm2s',
@@ -21,11 +20,11 @@ webPush.setVapidDetails(
2120
// This is an sample subscription object that we get after subscript on browser
2221
const pushSubscription =
2322
{
24-
"endpoint": "",
23+
"endpoint": "https://fcm.googleapis.com/fcm/send/eh4su7PQuF4:APA91bGJ2lTdArT_n0zLEkth_p7NTqyQ41KtXO5p0SsbcQ2iGgLHz1Y8FyDFS-MnWZjD5e6fair2TozX1bvccy_pHo4k-B3Ov_SLI-7lRvTE1_dppXQvP6IawxnSTBQk6AK5o4ks_mHv",
2524
"expirationTime": null,
2625
"keys": {
27-
"p256dh": "",
28-
"auth": ""
26+
"p256dh": "BEIlI4DO6N7gKHmT_FMHVHt8D2X4FWJp3vx9L8MOONfOsYNuYuxWAUWsscEWH4q5c-t6l5s769gGJr74n40H74w",
27+
"auth": "HSyGvAqddC7T0crQ3B6bHw"
2928
}
3029
}
3130

@@ -34,4 +33,7 @@ const payload = {
3433
url: 'https://www.dotnetthailand.com/web-frameworks/orchard-core-cms/why-orchard-core-cms'
3534
};
3635

37-
webPush.sendNotification(pushSubscription, JSON.stringify(payload));
36+
webPush.sendNotification(
37+
pushSubscription,
38+
JSON.stringify(payload)
39+
);

README.md

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,25 @@
55
- CD to the root folder of the project and run `yarn` to install all Node packages.
66
- Then run `yarn start` to start the project and automatically launch a browser.
77
- Click `Subscribe Web Push` and copy a subscription JSON object in a text box.
8-
- Use subscription object on a server side to send push notification.
8+
- Use copied subscription object on a server side to send push notification.
99
- This project requires Node.js version >= 14.
1010

11-
12-
# Send push notification from a server
13-
- Set a push subscription object that we get after a user's subscribed web push in `PushMessageFromServer.js`
11+
# How to send a push message from a server
12+
- Set a `pushSubscription` object in `PushMessageFromServer.js`, we can get it after subscribing.
1413
- Then execute the following command:
15-
```sh
16-
$ node PushMessageFromServer.js
17-
```
14+
```sh
15+
$ node PushMessageFromServer.js
16+
```
1817

1918
# Trouble shooting
20-
## Notification does not always show as banner on Windows.
19+
20+
## Notification does not always show as a banner on Windows.
2121
- Try to turn off focus assist.
22+
2223
## No push notification message at all
23-
- You may need to manually update service worker. Open Chrome developer tool with ctrl+shift+i keys and then go to application tab.
24-
Then click Service Workers node > Skip waiting service worker.
24+
- You may need to manually update service worker. Open a Chrome developer tool with `ctrl+shift+i` keys
25+
and select an application tab.
26+
- Then click Service Workers node > Skip waiting service worker.
2527

2628
# Key concept
2729
- Generate publish and private key public and private keys with the following code
@@ -30,28 +32,39 @@
3032
const vapidKeys = webPush.generateVAPIDKeys();
3133
console.log(vapidKeys);
3234
```
33-
- Use a public key in subscription script (App.tsx)
34-
- User subscribes web push with subscription script.
35-
- Use both public, private keys and subscription object on server script (PushMessageFromServer.js) to send push to a user.
35+
- Use a public key in subscription script (PushSubscription.tsx)
36+
- A user subscribes web push with subscription script (PushSubscription.tsx) and bet subscription object.
37+
- Use public, private keys and subscription object on server script (PushMessageFromServer.js) to send push message to a subscribed user.
3638
- Subscription object is what we store in a database. It is an identity of a subscribed user.
37-
- Use service worker script (service-worker.js) to handle push notification and customize how to show it to a user
39+
- Use a service worker script (service-worker.js) to handle push notification and customize how to show it to a user.
3840
- You can test push with a localhost and do need to deploy to public URL
3941

40-
# Get subscription object on a published website
41-
- For the main project in .NET Thailand repository, it can be accessed as http://www.dotnetthailand.com/web-push-api-example/.
42+
# Get subscription object on a published website for testing only
43+
- It can be accessed as http://www.dotnetthailand.com/web-push-api-example/.
4244

43-
# Useful articles for .NET Web Push
45+
# For using Web Push in .NET project. Here are some useful articles:
4446
- https://www.pluralsight.com/guides/html5-desktop-notifications-with-react
4547
- https://blog.elmah.io/how-to-send-push-notifications-to-a-browser-in-asp-net-core/
4648
- https://www.bartvanuden.com/2018/01/23/push-notifications-to-your-pwa-with-asp-net-core-2-0-and-aurelia/
4749
- https://www.tpeczek.com/2017/12/push-notifications-and-aspnet-core-part.html
4850
- https://stackoverflow.com/a/47617427/1872200
51+
- https://knowledgebase.webengage.com/docs/web-push-image-text-specifications
52+
- https://web-push-book.gauntface.com/demos/notification-examples/
53+
54+
# Requirements for Web Push on iOS
55+
- iOS 16.4+ (Released March 2023)
56+
- Safari or any iOS browser using WebKit (all iOS browsers use WebKit, including Chrome, Firefox, Edge)
57+
- Your site must be:
58+
- Served over HTTPS
59+
- Installed as a PWA (Progressive Web App) via "Add to Home Screen"
60+
- Must use the standard Push API + Notification API
4961

50-
# Upgrade React to major release version
51-
```sh
62+
# How Upgrade React to the major release version
63+
```sh
5264
yarn upgrade react --latest
5365
yarn upgrade react-dom --latest
5466
yarn upgrade react-dom --latest
5567
yarn upgrade @types/react --latest
5668
yarn upgrade @types/react-dom --latest
57-
```
69+
```
70+

public/icon-192.png

28 KB
Loading

public/index.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
<head>
55
<meta charset="utf-8" />
66
<meta name="viewport" content="width=device-width, initial-scale=1" />
7-
<title>React App</title>
7+
<title>Web push example</title>
88
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
9+
10+
<!-- For iOS web push -->
11+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
12+
<meta name="apple-mobile-web-app-capable" content="yes">
913
</head>
1014

1115
<body>

public/manifest.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "Web push example",
3+
"short_name": "Web push",
4+
"start_url": "/",
5+
"display": "standalone",
6+
"icons": [
7+
{
8+
"src": "/icon-192.png",
9+
"sizes": "192x192",
10+
"type": "image/png"
11+
}
12+
]
13+
}

public/service-worker.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// https://web-push-book.gauntface.com/display-a-notification/
33
self.addEventListener('push', (event) => {
44
// https://developer.mozilla.org/en-US/docs/Web/API/PushEvent/data
5+
// { title: '', body: '', url: ''}
56
const payload = event.data.json();
67
console.log(`payload: ${JSON.stringify(payload, null, 2)}`);
78

@@ -50,7 +51,6 @@ self.addEventListener('push', (event) => {
5051
// }
5152
// });
5253

53-
5454
self.addEventListener('notificationclick', (event) => {
5555
const { action, notification } = event;
5656
notification.close();
@@ -59,7 +59,6 @@ self.addEventListener('notificationclick', (event) => {
5959
event.waitUntil(sendMessage(event, url));
6060
});
6161

62-
6362
// https://developer.mozilla.org/en-US/docs/Web/API/Clients
6463
async function sendMessage(event, content) {
6564
// If we didn't find an existing chat window,
@@ -107,7 +106,6 @@ async function sendMessage(event, content) {
107106
//await chatClient.focus();
108107
}
109108

110-
111109
// Send a message to the client.
112110
chatClient.postMessage({
113111
type: 'clipboard',
@@ -121,7 +119,6 @@ async function openLink(data) {
121119
await clients.openWindow(data.url);
122120
}
123121

124-
125122
// https://stackoverflow.com/a/63917373/1872200
126123
// async function is a function that synchronously returns a promise
127124
async function showNotificationAsync(payload, options) {

src/PushMessageFromClient.tsx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React from 'react';
2+
3+
const publicKey = 'BHG9NdYdfiCIx7xUS8u2CMhtDD-GWHb6QYuSZ908NZYZHhJEjGjcX0yTjHrWx7gDICmCEUORrLmw3uwOGBqzm2s';
4+
5+
// https://pushpad.xyz/blog/the-notification-prompt-can-only-be-triggered-by-a-user-gesture-on-some-browsers
6+
// https://pushpad.xyz/blog/web-push-notifications-without-a-service-worker-in-the-root
7+
export default function PushMessageFromClient() {
8+
9+
// https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission_static
10+
const notify = async () => {
11+
try {
12+
await requestWebPushPermission();
13+
14+
// // We do not need service worker for notifation
15+
// var notification = new Notification('Notification title', {
16+
// icon: 'https://cdn.sstatic.net/stackexchange/img/logos/so/so-icon.png',
17+
// body: "Hey there! You've been notified!",
18+
// });
19+
20+
let registration = await navigator.serviceWorker.getRegistration();
21+
if (registration) {
22+
console.log('found existing registration', registration);
23+
} else {
24+
console.log('no registration, register new service worker');
25+
registration = await navigator.serviceWorker.register('./service-worker.js');
26+
}
27+
28+
const serviceWorker = await navigator.serviceWorker.ready;
29+
let subscription = await serviceWorker.pushManager.getSubscription();
30+
if (subscription) {
31+
console.log('found existing subscription', subscription);
32+
} else {
33+
console.log('no subscription, subscribe new web push');
34+
subscription = await serviceWorker.pushManager.subscribe({
35+
userVisibleOnly: true,
36+
applicationServerKey: publicKey,
37+
});
38+
}
39+
40+
const payload = {
41+
title: 'Title text',
42+
body: 'Body message',
43+
url: 'https://www.codesanook.com'
44+
};
45+
46+
const options = {
47+
body: payload.body,
48+
vibrate: [300, 100, 400], // Vibrate 300ms, pause 100ms, then vibrate 400ms
49+
icon: './favicon.ico',
50+
silent: false,
51+
// Chrome does not support a sound attribute https://github.com/GoogleChromeLabs/airhorn/issues/18
52+
};
53+
54+
await registration.showNotification(payload.title, options);
55+
56+
} catch (err) {
57+
console.error(err);
58+
}
59+
}
60+
61+
const requestWebPushPermission = async () => {
62+
if (!('Notification' in window)) {
63+
// Check if the browser supports notifications
64+
alert('This browser does not support web push notification');
65+
return Promise.reject();
66+
}
67+
68+
if (Notification.permission === 'granted') {
69+
return Promise.resolve();
70+
}
71+
72+
// Check whether notification permissions have already been granted;
73+
// if so, create a notification
74+
if (Notification.permission === 'denied') {
75+
// We need to ask a user to reset permission
76+
alert('Denied. Please reset permission to send web push notification');
77+
return Promise.reject();
78+
}
79+
80+
// We need to ask the user for permission
81+
const permission = await Notification.requestPermission();
82+
if (permission === 'granted') {
83+
alert('Granted');
84+
return Promise.resolve();
85+
}
86+
87+
alert('Default to denied. Please reset permission to send web push notification');
88+
return Promise.reject();
89+
}
90+
91+
return (
92+
<div>
93+
Push message from client
94+
<div>
95+
<button onClick={notify}>
96+
Send push message from client
97+
</button>
98+
</div>
99+
</div>
100+
)
101+
}

src/PushSubscription.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
11
import { useEffect, useState } from 'react';
22

3+
const publicKey = 'BHG9NdYdfiCIx7xUS8u2CMhtDD-GWHb6QYuSZ908NZYZHhJEjGjcX0yTjHrWx7gDICmCEUORrLmw3uwOGBqzm2s';
4+
35
export default function PushSubscription() {
46
const [subscription, setSubscription] = useState('');
7+
58
useEffect(() => {
69
window.addEventListener('load', async () => {
7-
const sw = await navigator.serviceWorker.register('./service-worker.js');
10+
const serviceWorker = await navigator.serviceWorker.register('./service-worker.js');
811
console.log('A service worker has been registered.')
9-
console.log(sw);
12+
console.log(serviceWorker);
1013
});
1114
}, []);
1215

1316
const handleSubscribeWebPush = async () => {
17+
18+
// Make sure the service worker is ready, before subscribing a web push.
1419
const serviceWorker = await navigator.serviceWorker.ready;
15-
const publicKey = 'BHG9NdYdfiCIx7xUS8u2CMhtDD-GWHb6QYuSZ908NZYZHhJEjGjcX0yTjHrWx7gDICmCEUORrLmw3uwOGBqzm2s';
16-
const push = await serviceWorker.pushManager.subscribe({
17-
userVisibleOnly: true,
18-
applicationServerKey: publicKey,
19-
});
20-
setSubscription(JSON.stringify(push, null, 2));
20+
let subscription = await serviceWorker.pushManager.getSubscription();
21+
if (subscription) {
22+
console.log('found existing subscription', subscription);
23+
} else {
24+
console.log('no subscription, subscribe new web push');
25+
subscription = await serviceWorker.pushManager.subscribe({
26+
userVisibleOnly: true,
27+
applicationServerKey: publicKey,
28+
});
29+
}
30+
31+
setSubscription(JSON.stringify(subscription, null, 2));
2132
}
2233

2334
const style = {
@@ -27,7 +38,7 @@ export default function PushSubscription() {
2738
return (
2839
<div>
2940
<button onClick={handleSubscribeWebPush}>
30-
Subscript Web Push
41+
Subscribe Web Push
3142
</button>
3243
<div>
3344
<textarea rows={10} style={style} value={subscription} readOnly={true} />

src/index.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
import PushSubscription from './PushSubscription';
21
import { createRoot } from 'react-dom/client';
32

3+
import PushSubscription from './PushSubscription';
4+
import PushMessageFromClient from './PushMessageFromClient';
5+
46
// https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-client-rendering-apis
57
const container = document.getElementById('root') as HTMLElement
68
const root = createRoot(container);
7-
root.render(<PushSubscription />);
9+
10+
root.render(
11+
<div>
12+
<PushSubscription />
13+
<PushMessageFromClient />
14+
</div>
15+
);

0 commit comments

Comments
 (0)