├── .dockerignore
├── .env.example
├── .gitattributes
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
├── android
│ ├── android-launchericon-144-144.png
│ ├── android-launchericon-192-192.png
│ ├── android-launchericon-48-48.png
│ ├── android-launchericon-512-512.png
│ ├── android-launchericon-72-72.png
│ └── android-launchericon-96-96.png
├── ios-pwa
│ └── pwa_ios.jpg
├── ios
│ ├── 100.png
│ ├── 1024.png
│ ├── 114.png
│ ├── 120.png
│ ├── 128.png
│ ├── 144.png
│ ├── 152.png
│ ├── 16.png
│ ├── 167.png
│ ├── 180.png
│ ├── 192.png
│ ├── 20.png
│ ├── 256.png
│ ├── 29.png
│ ├── 32.png
│ ├── 40.png
│ ├── 50.png
│ ├── 512.png
│ ├── 57.png
│ ├── 58.png
│ ├── 60.png
│ ├── 64.png
│ ├── 72.png
│ ├── 76.png
│ ├── 80.png
│ └── 87.png
├── logo.jpg
├── logo.svg
├── manifest.json
├── notification-sw.js
├── user.png
└── windows11
│ ├── LargeTile.scale-100.png
│ ├── LargeTile.scale-125.png
│ ├── LargeTile.scale-150.png
│ ├── LargeTile.scale-200.png
│ ├── LargeTile.scale-400.png
│ ├── SmallTile.scale-100.png
│ ├── SmallTile.scale-125.png
│ ├── SmallTile.scale-150.png
│ ├── SmallTile.scale-200.png
│ ├── SmallTile.scale-400.png
│ ├── SplashScreen.scale-100.png
│ ├── SplashScreen.scale-125.png
│ ├── SplashScreen.scale-150.png
│ ├── SplashScreen.scale-200.png
│ ├── SplashScreen.scale-400.png
│ ├── Square150x150Logo.scale-100.png
│ ├── Square150x150Logo.scale-125.png
│ ├── Square150x150Logo.scale-150.png
│ ├── Square150x150Logo.scale-200.png
│ ├── Square150x150Logo.scale-400.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-16.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-20.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-24.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-256.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-30.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-32.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-36.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-40.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-44.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-48.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-60.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-64.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-72.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-80.png
│ ├── Square44x44Logo.altform-lightunplated_targetsize-96.png
│ ├── Square44x44Logo.altform-unplated_targetsize-16.png
│ ├── Square44x44Logo.altform-unplated_targetsize-20.png
│ ├── Square44x44Logo.altform-unplated_targetsize-24.png
│ ├── Square44x44Logo.altform-unplated_targetsize-256.png
│ ├── Square44x44Logo.altform-unplated_targetsize-30.png
│ ├── Square44x44Logo.altform-unplated_targetsize-32.png
│ ├── Square44x44Logo.altform-unplated_targetsize-36.png
│ ├── Square44x44Logo.altform-unplated_targetsize-40.png
│ ├── Square44x44Logo.altform-unplated_targetsize-44.png
│ ├── Square44x44Logo.altform-unplated_targetsize-48.png
│ ├── Square44x44Logo.altform-unplated_targetsize-60.png
│ ├── Square44x44Logo.altform-unplated_targetsize-64.png
│ ├── Square44x44Logo.altform-unplated_targetsize-72.png
│ ├── Square44x44Logo.altform-unplated_targetsize-80.png
│ ├── Square44x44Logo.altform-unplated_targetsize-96.png
│ ├── Square44x44Logo.scale-100.png
│ ├── Square44x44Logo.scale-125.png
│ ├── Square44x44Logo.scale-150.png
│ ├── Square44x44Logo.scale-200.png
│ ├── Square44x44Logo.scale-400.png
│ ├── Square44x44Logo.targetsize-16.png
│ ├── Square44x44Logo.targetsize-20.png
│ ├── Square44x44Logo.targetsize-24.png
│ ├── Square44x44Logo.targetsize-256.png
│ ├── Square44x44Logo.targetsize-30.png
│ ├── Square44x44Logo.targetsize-32.png
│ ├── Square44x44Logo.targetsize-36.png
│ ├── Square44x44Logo.targetsize-40.png
│ ├── Square44x44Logo.targetsize-44.png
│ ├── Square44x44Logo.targetsize-48.png
│ ├── Square44x44Logo.targetsize-60.png
│ ├── Square44x44Logo.targetsize-64.png
│ ├── Square44x44Logo.targetsize-72.png
│ ├── Square44x44Logo.targetsize-80.png
│ ├── Square44x44Logo.targetsize-96.png
│ ├── StoreLogo.scale-100.png
│ ├── StoreLogo.scale-125.png
│ ├── StoreLogo.scale-150.png
│ ├── StoreLogo.scale-200.png
│ ├── StoreLogo.scale-400.png
│ ├── Wide310x150Logo.scale-100.png
│ ├── Wide310x150Logo.scale-125.png
│ ├── Wide310x150Logo.scale-150.png
│ ├── Wide310x150Logo.scale-200.png
│ └── Wide310x150Logo.scale-400.png
├── src
├── app
│ ├── api
│ │ └── web-push
│ │ │ └── send
│ │ │ └── route.ts
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── NotificationSubscriptionForm.tsx
│ ├── NotificationSubscriptionStatus.tsx
│ └── UnsupportedNotificationMessage.tsx
└── notifications
│ ├── NotificationPush.ts
│ ├── NotificationSender.ts
│ └── useNotification.tsx
├── tailwind.config.ts
└── tsconfig.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | # .dockerignore file
2 | .DS_Store
3 | .next
4 | node_modules
5 | .gitignore
6 | README.md
7 | .dockerignore
8 | LICENSE
9 | .docker
10 | .gitlab
11 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # use command `web-push generate-vapid-keys --json` to generate a new key
2 | NEXT_PUBLIC_VAPID_PUBLIC_KEY=
3 | VAPID_PRIVATE_KEY=
4 | NOTIFICATION_URL=http://localhost:3000/
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
39 | public/sw-pwa.js
40 | public/sw-pwa.js.map
41 | public/workbox-*.js
42 | public/workbox-*.js.map
43 | /.idea
44 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20-alpine AS builder
2 | WORKDIR /app
3 | ENV NEXT_TELEMETRY_DISABLED=1
4 | COPY package*.json ./
5 | RUN yarn --network-timeout 100000
6 | COPY . .
7 | RUN yarn standalone
8 |
9 | FROM alpine:3.20
10 | RUN apk add --no-cache nodejs && \
11 | addgroup --system --gid 1001 node && \
12 | adduser --system --uid 1001 node && \
13 | mkdir -p /nextjs-app && \
14 | chown -R node:node /nextjs-app
15 | WORKDIR /nextjs-app
16 | ENV NEXT_TELEMETRY_DISABLED=1
17 | ENV NODE_ENV=production
18 | COPY --from=builder --chown=node:node /app/.next/standalone .
19 | USER node
20 | EXPOSE 3000
21 | ENV PORT=3000
22 | CMD [ "node", "server.js" ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 David Randoll
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A sample project for push notifications with Next.js. The app used web push notifications to send messages to users. The
2 | notification should work on all devices and browsers.
3 |
4 | **Require IOS 16+ for Apple devices.**
5 |
6 | ## Demo
7 |
8 | A live demo of the project can be found [here](https://push-notification.davidrandoll.com/)
9 |
10 | ## Running Locally
11 |
12 | First, run the development server:
13 |
14 | ```bash
15 | npm install
16 | ```
17 |
18 | Run the below command to generate the vapid keys. Once you have the keys, rename the `.env.example` file to `.env` and
19 | insert the keys.
20 |
21 | ```bash
22 | web-push generate-vapid-keys --json
23 | ```
24 |
25 | Start the development server:
26 |
27 | ```bash
28 | npm run dev
29 | ```
30 |
31 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
32 |
33 | ## How it works
34 |
35 | The app uses the [web-push](https://www.npmjs.com/package/web-push) package to send push notifications. The app has a
36 | service worker that listens for push events and displays the notification.
37 |
38 | Using just this package is enough to send push notifications from most devices and browsers.
39 |
40 | However, on Apple devices, there are a few extra things we have to do.
41 |
42 | - The app must be served over HTTPS with a valid SSL certificate.
43 | - The app must be a PWA (Progressive Web App).
44 |
45 | You can read more about it [here](https://developer.apple.com/documentation/usernotifications/sending-web-push-notifications-in-web-apps-and-browsers).
46 |
47 | ## Configuration
48 |
49 | Install the below packages.
50 |
51 | ```bash
52 | npm install web-push next-pwa
53 | ```
54 |
55 | Skip this step if you are using typescript.
56 |
57 | ```bash
58 | npm install @types/web-push --save-dev
59 | ```
60 |
61 | ### Setting up the notification
62 |
63 | Copy the [notification-sw.js](https://github.com/david-randoll/push-notification-nextjs/blob/main/public/notification-sw.js) file into the `public` folder. This is a service worker that listens for push events and displays the notification.
64 |
65 | Copy the files under the [notifications](https://github.com/david-randoll/push-notification-nextjs/tree/main/src/notifications) folder and paste them into your src folder. In my case I pasted them in the
66 | `src/notifications` folder. The `useNotification` hook will be used in the app to subscribe a user and store the
67 | subscription in state. This subscription can be stored in a database and used to send notifications per user.
68 |
69 | Take a look at the `page.tsx` file to see how the `useNotification` hook is used. The `page.tsx` calls an endpoint that is found under `src/app/api/web-push/send/route.ts`. This will send the notification to the user.
70 |
71 | With this should be able to send notifications now. For Apple devices, you will need to configure the app as a PWA in the next step.
72 |
73 | ### Configuring as a PWA
74 |
75 | The [next-pwa](https://www.npmjs.com/package/next-pwa) package will generate a `sw-pwa.js` and a `workbox-*.js` file in
76 | the public folder.
77 |
78 | I am going to use [pwabuilder](https://www.pwabuilder.com/imageGenerator) to generate the icons for the app. This will generate the different sizes of the icon that are needed for different devices. After going to the site, download the zip file and place the contents into the public folder. You should get 3 folders: android, ios, and windows. Also, an `icons.json` file which we will use for our manifest file.
79 |
80 | Move the `icons.json` file to the `public` folder and rename it to `manifest.json`.
81 |
82 | ```json
83 | {
84 | "name": "Push Notification Sample",
85 | "short_name": "Push Notification Sample",
86 | "description": "A sample project for push notifications with Next.js",
87 | "theme_color": "#FFFFFF",
88 | "background_color": "#FFFFFF",
89 | "start_url": "/",
90 | "display": "standalone",
91 | "orientation": "portrait",
92 | "icons": // the icons will be here
93 | }
94 | ```
95 |
96 | add the `manifest.json` file to the `layout.tsx` file.
97 |
98 | ```tsx
99 | export default function RootLayout({
100 | children,
101 | }: Readonly<{
102 | children: React.ReactNode;
103 | }>) {
104 | return (
105 |
106 |
107 |
108 |
109 |
110 | {children}
111 |
112 |
113 | );
114 | }
115 | ```
116 |
117 | modify the `next.config.js` file to include the `next-pwa` configuration.
118 |
119 | ```javascript
120 | /** @type {import('next').NextConfig} */
121 |
122 | const withPWA = require("next-pwa")({
123 | dest: "public",
124 | sw: "sw-pwa.js",
125 | });
126 |
127 | module.exports = withPWA({
128 | output: "standalone",
129 | });
130 | ```
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 |
3 | const withPWA = require("next-pwa")({
4 | dest: "public",
5 | sw: "sw-pwa.js",
6 | });
7 |
8 | module.exports = withPWA({
9 | output: "standalone",
10 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "push-notification-nextjs",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "standalone": "next build && cp -r public/ .next/standalone && cp -r .next/static .next/standalone/.next",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "next": "14.2.14",
14 | "next-pwa": "^5.6.0",
15 | "react": "^18",
16 | "react-dom": "^18",
17 | "web-push": "^3.6.7"
18 | },
19 | "devDependencies": {
20 | "@types/node": "^20",
21 | "@types/react": "^18",
22 | "@types/react-dom": "^18",
23 | "@types/web-push": "^3.6.3",
24 | "postcss": "^8",
25 | "tailwindcss": "^3.4.1",
26 | "typescript": "^5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/android/android-launchericon-144-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/android/android-launchericon-144-144.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-192-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/android/android-launchericon-192-192.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-48-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/android/android-launchericon-48-48.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-512-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/android/android-launchericon-512-512.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-72-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/android/android-launchericon-72-72.png
--------------------------------------------------------------------------------
/public/android/android-launchericon-96-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/android/android-launchericon-96-96.png
--------------------------------------------------------------------------------
/public/ios-pwa/pwa_ios.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios-pwa/pwa_ios.jpg
--------------------------------------------------------------------------------
/public/ios/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/100.png
--------------------------------------------------------------------------------
/public/ios/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/1024.png
--------------------------------------------------------------------------------
/public/ios/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/114.png
--------------------------------------------------------------------------------
/public/ios/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/120.png
--------------------------------------------------------------------------------
/public/ios/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/128.png
--------------------------------------------------------------------------------
/public/ios/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/144.png
--------------------------------------------------------------------------------
/public/ios/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/152.png
--------------------------------------------------------------------------------
/public/ios/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/16.png
--------------------------------------------------------------------------------
/public/ios/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/167.png
--------------------------------------------------------------------------------
/public/ios/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/180.png
--------------------------------------------------------------------------------
/public/ios/192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/192.png
--------------------------------------------------------------------------------
/public/ios/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/20.png
--------------------------------------------------------------------------------
/public/ios/256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/256.png
--------------------------------------------------------------------------------
/public/ios/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/29.png
--------------------------------------------------------------------------------
/public/ios/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/32.png
--------------------------------------------------------------------------------
/public/ios/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/40.png
--------------------------------------------------------------------------------
/public/ios/50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/50.png
--------------------------------------------------------------------------------
/public/ios/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/512.png
--------------------------------------------------------------------------------
/public/ios/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/57.png
--------------------------------------------------------------------------------
/public/ios/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/58.png
--------------------------------------------------------------------------------
/public/ios/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/60.png
--------------------------------------------------------------------------------
/public/ios/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/64.png
--------------------------------------------------------------------------------
/public/ios/72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/72.png
--------------------------------------------------------------------------------
/public/ios/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/76.png
--------------------------------------------------------------------------------
/public/ios/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/80.png
--------------------------------------------------------------------------------
/public/ios/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/ios/87.png
--------------------------------------------------------------------------------
/public/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/logo.jpg
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.10, written by Peter Selinger 2001-2011
9 |
10 |
12 |
15 |
63 |
71 |
109 |
111 |
224 |
226 |
228 |
230 |
232 |
234 |
236 |
238 |
241 |
243 |
246 |
249 |
252 |
254 |
257 |
259 |
261 |
264 |
269 |
270 |
272 |
275 |
283 |
293 |
295 |
297 |
299 |
302 |
307 |
309 |
320 |
324 |
330 |
332 |
334 |
336 |
340 |
342 |
344 |
361 |
364 |
366 |
367 |
369 |
371 |
373 |
376 |
381 |
382 |
389 |
393 |
396 |
398 |
403 |
404 |
412 |
417 |
419 |
421 |
423 |
424 |
426 |
428 |
429 |
430 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Push Notification Sample",
3 | "short_name": "Push Notification Sample",
4 | "description": "A sample project for push notifications with Next.js",
5 | "theme_color": "#FFFFFF",
6 | "background_color": "#FFFFFF",
7 | "start_url": "/",
8 | "display": "standalone",
9 | "orientation": "portrait",
10 | "icons": [
11 | {
12 | "src": "windows11/SmallTile.scale-100.png",
13 | "sizes": "71x71"
14 | },
15 | {
16 | "src": "windows11/SmallTile.scale-125.png",
17 | "sizes": "89x89"
18 | },
19 | {
20 | "src": "windows11/SmallTile.scale-150.png",
21 | "sizes": "107x107"
22 | },
23 | {
24 | "src": "windows11/SmallTile.scale-200.png",
25 | "sizes": "142x142"
26 | },
27 | {
28 | "src": "windows11/SmallTile.scale-400.png",
29 | "sizes": "284x284"
30 | },
31 | {
32 | "src": "windows11/Square150x150Logo.scale-100.png",
33 | "sizes": "150x150"
34 | },
35 | {
36 | "src": "windows11/Square150x150Logo.scale-125.png",
37 | "sizes": "188x188"
38 | },
39 | {
40 | "src": "windows11/Square150x150Logo.scale-150.png",
41 | "sizes": "225x225"
42 | },
43 | {
44 | "src": "windows11/Square150x150Logo.scale-200.png",
45 | "sizes": "300x300"
46 | },
47 | {
48 | "src": "windows11/Square150x150Logo.scale-400.png",
49 | "sizes": "600x600"
50 | },
51 | {
52 | "src": "windows11/Wide310x150Logo.scale-100.png",
53 | "sizes": "310x150"
54 | },
55 | {
56 | "src": "windows11/Wide310x150Logo.scale-125.png",
57 | "sizes": "388x188"
58 | },
59 | {
60 | "src": "windows11/Wide310x150Logo.scale-150.png",
61 | "sizes": "465x225"
62 | },
63 | {
64 | "src": "windows11/Wide310x150Logo.scale-200.png",
65 | "sizes": "620x300"
66 | },
67 | {
68 | "src": "windows11/Wide310x150Logo.scale-400.png",
69 | "sizes": "1240x600"
70 | },
71 | {
72 | "src": "windows11/LargeTile.scale-100.png",
73 | "sizes": "310x310"
74 | },
75 | {
76 | "src": "windows11/LargeTile.scale-125.png",
77 | "sizes": "388x388"
78 | },
79 | {
80 | "src": "windows11/LargeTile.scale-150.png",
81 | "sizes": "465x465"
82 | },
83 | {
84 | "src": "windows11/LargeTile.scale-200.png",
85 | "sizes": "620x620"
86 | },
87 | {
88 | "src": "windows11/LargeTile.scale-400.png",
89 | "sizes": "1240x1240"
90 | },
91 | {
92 | "src": "windows11/Square44x44Logo.scale-100.png",
93 | "sizes": "44x44"
94 | },
95 | {
96 | "src": "windows11/Square44x44Logo.scale-125.png",
97 | "sizes": "55x55"
98 | },
99 | {
100 | "src": "windows11/Square44x44Logo.scale-150.png",
101 | "sizes": "66x66"
102 | },
103 | {
104 | "src": "windows11/Square44x44Logo.scale-200.png",
105 | "sizes": "88x88"
106 | },
107 | {
108 | "src": "windows11/Square44x44Logo.scale-400.png",
109 | "sizes": "176x176"
110 | },
111 | {
112 | "src": "windows11/StoreLogo.scale-100.png",
113 | "sizes": "50x50"
114 | },
115 | {
116 | "src": "windows11/StoreLogo.scale-125.png",
117 | "sizes": "63x63"
118 | },
119 | {
120 | "src": "windows11/StoreLogo.scale-150.png",
121 | "sizes": "75x75"
122 | },
123 | {
124 | "src": "windows11/StoreLogo.scale-200.png",
125 | "sizes": "100x100"
126 | },
127 | {
128 | "src": "windows11/StoreLogo.scale-400.png",
129 | "sizes": "200x200"
130 | },
131 | {
132 | "src": "windows11/SplashScreen.scale-100.png",
133 | "sizes": "620x300"
134 | },
135 | {
136 | "src": "windows11/SplashScreen.scale-125.png",
137 | "sizes": "775x375"
138 | },
139 | {
140 | "src": "windows11/SplashScreen.scale-150.png",
141 | "sizes": "930x450"
142 | },
143 | {
144 | "src": "windows11/SplashScreen.scale-200.png",
145 | "sizes": "1240x600"
146 | },
147 | {
148 | "src": "windows11/SplashScreen.scale-400.png",
149 | "sizes": "2480x1200"
150 | },
151 | {
152 | "src": "windows11/Square44x44Logo.targetsize-16.png",
153 | "sizes": "16x16"
154 | },
155 | {
156 | "src": "windows11/Square44x44Logo.targetsize-20.png",
157 | "sizes": "20x20"
158 | },
159 | {
160 | "src": "windows11/Square44x44Logo.targetsize-24.png",
161 | "sizes": "24x24"
162 | },
163 | {
164 | "src": "windows11/Square44x44Logo.targetsize-30.png",
165 | "sizes": "30x30"
166 | },
167 | {
168 | "src": "windows11/Square44x44Logo.targetsize-32.png",
169 | "sizes": "32x32"
170 | },
171 | {
172 | "src": "windows11/Square44x44Logo.targetsize-36.png",
173 | "sizes": "36x36"
174 | },
175 | {
176 | "src": "windows11/Square44x44Logo.targetsize-40.png",
177 | "sizes": "40x40"
178 | },
179 | {
180 | "src": "windows11/Square44x44Logo.targetsize-44.png",
181 | "sizes": "44x44"
182 | },
183 | {
184 | "src": "windows11/Square44x44Logo.targetsize-48.png",
185 | "sizes": "48x48"
186 | },
187 | {
188 | "src": "windows11/Square44x44Logo.targetsize-60.png",
189 | "sizes": "60x60"
190 | },
191 | {
192 | "src": "windows11/Square44x44Logo.targetsize-64.png",
193 | "sizes": "64x64"
194 | },
195 | {
196 | "src": "windows11/Square44x44Logo.targetsize-72.png",
197 | "sizes": "72x72"
198 | },
199 | {
200 | "src": "windows11/Square44x44Logo.targetsize-80.png",
201 | "sizes": "80x80"
202 | },
203 | {
204 | "src": "windows11/Square44x44Logo.targetsize-96.png",
205 | "sizes": "96x96"
206 | },
207 | {
208 | "src": "windows11/Square44x44Logo.targetsize-256.png",
209 | "sizes": "256x256"
210 | },
211 | {
212 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-16.png",
213 | "sizes": "16x16"
214 | },
215 | {
216 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-20.png",
217 | "sizes": "20x20"
218 | },
219 | {
220 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-24.png",
221 | "sizes": "24x24"
222 | },
223 | {
224 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-30.png",
225 | "sizes": "30x30"
226 | },
227 | {
228 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-32.png",
229 | "sizes": "32x32"
230 | },
231 | {
232 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-36.png",
233 | "sizes": "36x36"
234 | },
235 | {
236 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-40.png",
237 | "sizes": "40x40"
238 | },
239 | {
240 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-44.png",
241 | "sizes": "44x44"
242 | },
243 | {
244 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-48.png",
245 | "sizes": "48x48"
246 | },
247 | {
248 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-60.png",
249 | "sizes": "60x60"
250 | },
251 | {
252 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-64.png",
253 | "sizes": "64x64"
254 | },
255 | {
256 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-72.png",
257 | "sizes": "72x72"
258 | },
259 | {
260 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-80.png",
261 | "sizes": "80x80"
262 | },
263 | {
264 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-96.png",
265 | "sizes": "96x96"
266 | },
267 | {
268 | "src": "windows11/Square44x44Logo.altform-unplated_targetsize-256.png",
269 | "sizes": "256x256"
270 | },
271 | {
272 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png",
273 | "sizes": "16x16"
274 | },
275 | {
276 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png",
277 | "sizes": "20x20"
278 | },
279 | {
280 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png",
281 | "sizes": "24x24"
282 | },
283 | {
284 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png",
285 | "sizes": "30x30"
286 | },
287 | {
288 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png",
289 | "sizes": "32x32"
290 | },
291 | {
292 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png",
293 | "sizes": "36x36"
294 | },
295 | {
296 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png",
297 | "sizes": "40x40"
298 | },
299 | {
300 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png",
301 | "sizes": "44x44"
302 | },
303 | {
304 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png",
305 | "sizes": "48x48"
306 | },
307 | {
308 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png",
309 | "sizes": "60x60"
310 | },
311 | {
312 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png",
313 | "sizes": "64x64"
314 | },
315 | {
316 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png",
317 | "sizes": "72x72"
318 | },
319 | {
320 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png",
321 | "sizes": "80x80"
322 | },
323 | {
324 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png",
325 | "sizes": "96x96"
326 | },
327 | {
328 | "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png",
329 | "sizes": "256x256"
330 | },
331 | {
332 | "src": "android/android-launchericon-512-512.png",
333 | "sizes": "512x512"
334 | },
335 | {
336 | "src": "android/android-launchericon-192-192.png",
337 | "sizes": "192x192"
338 | },
339 | {
340 | "src": "android/android-launchericon-144-144.png",
341 | "sizes": "144x144"
342 | },
343 | {
344 | "src": "android/android-launchericon-96-96.png",
345 | "sizes": "96x96"
346 | },
347 | {
348 | "src": "android/android-launchericon-72-72.png",
349 | "sizes": "72x72"
350 | },
351 | {
352 | "src": "android/android-launchericon-48-48.png",
353 | "sizes": "48x48"
354 | },
355 | {
356 | "src": "ios/16.png",
357 | "sizes": "16x16"
358 | },
359 | {
360 | "src": "ios/20.png",
361 | "sizes": "20x20"
362 | },
363 | {
364 | "src": "ios/29.png",
365 | "sizes": "29x29"
366 | },
367 | {
368 | "src": "ios/32.png",
369 | "sizes": "32x32"
370 | },
371 | {
372 | "src": "ios/40.png",
373 | "sizes": "40x40"
374 | },
375 | {
376 | "src": "ios/50.png",
377 | "sizes": "50x50"
378 | },
379 | {
380 | "src": "ios/57.png",
381 | "sizes": "57x57"
382 | },
383 | {
384 | "src": "ios/58.png",
385 | "sizes": "58x58"
386 | },
387 | {
388 | "src": "ios/60.png",
389 | "sizes": "60x60"
390 | },
391 | {
392 | "src": "ios/64.png",
393 | "sizes": "64x64"
394 | },
395 | {
396 | "src": "ios/72.png",
397 | "sizes": "72x72"
398 | },
399 | {
400 | "src": "ios/76.png",
401 | "sizes": "76x76"
402 | },
403 | {
404 | "src": "ios/80.png",
405 | "sizes": "80x80"
406 | },
407 | {
408 | "src": "ios/87.png",
409 | "sizes": "87x87"
410 | },
411 | {
412 | "src": "ios/100.png",
413 | "sizes": "100x100"
414 | },
415 | {
416 | "src": "ios/114.png",
417 | "sizes": "114x114"
418 | },
419 | {
420 | "src": "ios/120.png",
421 | "sizes": "120x120"
422 | },
423 | {
424 | "src": "ios/128.png",
425 | "sizes": "128x128"
426 | },
427 | {
428 | "src": "ios/144.png",
429 | "sizes": "144x144"
430 | },
431 | {
432 | "src": "ios/152.png",
433 | "sizes": "152x152"
434 | },
435 | {
436 | "src": "ios/167.png",
437 | "sizes": "167x167"
438 | },
439 | {
440 | "src": "ios/180.png",
441 | "sizes": "180x180"
442 | },
443 | {
444 | "src": "ios/192.png",
445 | "sizes": "192x192"
446 | },
447 | {
448 | "src": "ios/256.png",
449 | "sizes": "256x256"
450 | },
451 | {
452 | "src": "ios/512.png",
453 | "sizes": "512x512"
454 | },
455 | {
456 | "src": "ios/1024.png",
457 | "sizes": "1024x1024"
458 | }
459 | ]
460 | }
--------------------------------------------------------------------------------
/public/notification-sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener("install", () => {
2 | console.info("service worker installed.");
3 | });
4 |
5 | const sendDeliveryReportAction = () => {
6 | console.log("Web push delivered.");
7 | };
8 |
9 | self.addEventListener("push", function (event) {
10 | if (!event.data) {
11 | return;
12 | }
13 |
14 | const payload = event.data.json();
15 | const { body, icon, image, badge, url, title } = payload;
16 | const notificationTitle = title ?? "Unknown title";
17 | const notificationOptions = {
18 | body,
19 | icon,
20 | image,
21 | data: {
22 | url,
23 | },
24 | badge,
25 | };
26 |
27 | event.waitUntil(
28 | self.registration.showNotification(notificationTitle, notificationOptions).then(() => {
29 | sendDeliveryReportAction();
30 | })
31 | );
32 | });
33 |
34 | self.addEventListener("notificationclick", function (event) {
35 | console.log("Notification clicked.");
36 | event.notification.close();
37 |
38 | event.waitUntil(
39 | clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
40 | const url = event.notification.data.url;
41 |
42 | if (!url) return;
43 |
44 | for (const client of clientList) {
45 | if (client.url === url && "focus" in client) {
46 | return client.focus();
47 | }
48 | }
49 |
50 | if (clients.openWindow) {
51 | console.log("Opening window.");
52 | return clients.openWindow(url);
53 | }
54 | })
55 | );
56 | });
57 |
--------------------------------------------------------------------------------
/public/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/user.png
--------------------------------------------------------------------------------
/public/windows11/LargeTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/LargeTile.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/LargeTile.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/LargeTile.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/LargeTile.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/LargeTile.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/LargeTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/LargeTile.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/LargeTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/LargeTile.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/SmallTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/SmallTile.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/SmallTile.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/SmallTile.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/SmallTile.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/SmallTile.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/SmallTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/SmallTile.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/SmallTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/SmallTile.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/SplashScreen.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/SplashScreen.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/SplashScreen.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/SplashScreen.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/SplashScreen.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/SplashScreen.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/SplashScreen.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/SplashScreen.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/Square150x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square150x150Logo.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/Square150x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square150x150Logo.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/Square150x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square150x150Logo.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/Square150x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square150x150Logo.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-16.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-20.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-24.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-256.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-30.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-32.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-36.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-40.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-44.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-48.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-60.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-64.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-72.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-80.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.altform-unplated_targetsize-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.altform-unplated_targetsize-96.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-16.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-20.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-24.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-256.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-30.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-32.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-36.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-40.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-44.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-48.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-60.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-64.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-72.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-80.png
--------------------------------------------------------------------------------
/public/windows11/Square44x44Logo.targetsize-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Square44x44Logo.targetsize-96.png
--------------------------------------------------------------------------------
/public/windows11/StoreLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/StoreLogo.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/StoreLogo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/StoreLogo.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/StoreLogo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/StoreLogo.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/StoreLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/StoreLogo.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/StoreLogo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/StoreLogo.scale-400.png
--------------------------------------------------------------------------------
/public/windows11/Wide310x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Wide310x150Logo.scale-100.png
--------------------------------------------------------------------------------
/public/windows11/Wide310x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Wide310x150Logo.scale-125.png
--------------------------------------------------------------------------------
/public/windows11/Wide310x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Wide310x150Logo.scale-150.png
--------------------------------------------------------------------------------
/public/windows11/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/public/windows11/Wide310x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/david-randoll/push-notification-nextjs/f934e55e5523ad66380189c1e56599bea185579d/public/windows11/Wide310x150Logo.scale-400.png
--------------------------------------------------------------------------------
/src/app/api/web-push/send/route.ts:
--------------------------------------------------------------------------------
1 | import { sendNotification } from "@/notifications/NotificationSender";
2 | import { NextRequest } from "next/server";
3 |
4 | export async function POST(req: NextRequest) {
5 | const { subscription, title, message } = await req.json();
6 | await sendNotification(subscription, title, message);
7 | return new Response(JSON.stringify({ message: "Push sent." }), {});
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter, IBM_Plex_Serif } from "next/font/google";
3 | import "./globals.css";
4 | import { NotificationProvider } from "@/notifications/useNotification";
5 |
6 | const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
7 | const ibmPlexSerif = IBM_Plex_Serif({
8 | subsets: ["latin"],
9 | weight: ["400", "700"],
10 | variable: "--font-ibm-plex-serif",
11 | });
12 |
13 | export const metadata: Metadata = {
14 | title: "Push Notification Sample",
15 | description:
16 | "A sample project for push notifications with Next.js. The app used web push notifications to send messages to users.",
17 | icons: {
18 | icon: "/logo.svg",
19 | },
20 | };
21 |
22 | export default function RootLayout({
23 | children,
24 | }: Readonly<{
25 | children: React.ReactNode;
26 | }>) {
27 | return (
28 |
29 |
30 |
31 |
32 |
33 | {children}
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import {useNotification} from "@/notifications/useNotification";
3 | import React from "react";
4 | import {NotificationSubscriptionForm} from "@/components/NotificationSubscriptionForm";
5 | import {UnsupportedNotificationMessage} from "@/components/UnsupportedNotificationMessage";
6 | import NotificationSubscriptionStatus from "@/components/NotificationSubscriptionStatus";
7 |
8 | const Home = () => {
9 | const {isSupported, isSubscribed} = useNotification();
10 |
11 | return (
12 |
13 | {!isSupported ? (
14 |
15 | ) : (
16 |
17 | )}
18 |
19 | {isSubscribed && (
20 |
21 | )}
22 |
23 | );
24 | };
25 |
26 | export default Home;
27 |
--------------------------------------------------------------------------------
/src/components/NotificationSubscriptionForm.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import {useNotification} from "@/notifications/useNotification";
3 |
4 | export const NotificationSubscriptionForm = () => {
5 | const {subscription} = useNotification();
6 |
7 | const [message, setMessage] = useState("");
8 | const [title, setTitle] = useState("");
9 |
10 | const sendNotification = async () => {
11 | await fetch("/api/web-push/send", {
12 | method: "POST",
13 | body: JSON.stringify({title, message, subscription}),
14 | headers: {
15 | "Content-Type": "application/json",
16 | },
17 | });
18 | setMessage("");
19 | setTitle("");
20 | };
21 |
22 | return (
23 |
24 |
Send a Notification
25 |
26 | {/* Title Input */}
27 | setTitle(e.target.value)}
32 | className="w-full mb-4 p-2 border border-gray-300 rounded-lg"
33 | />
34 |
35 | {/* Message Input */}
36 |
48 | );
49 | };
--------------------------------------------------------------------------------
/src/components/NotificationSubscriptionStatus.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {useNotification} from "@/notifications/useNotification";
3 |
4 | const NotificationSubscriptionStatus = () => {
5 | const {isSubscribed, handleSubscribe, isGranted, isDenied, errorMessage} = useNotification();
6 |
7 | return (
8 |
9 |
Push Notification Subscription
10 |
11 | {isDenied && (
12 |
13 | You have denied permission for push notifications. To enable, please update your browser settings.
14 |
15 | )}
16 |
17 | {errorMessage && (
18 |
19 | Error: {errorMessage}
20 |
21 | )}
22 |
23 |
24 | {!isSubscribed && (
25 |
30 | Subscribe to Push Notifications
31 |
32 | )}
33 |
34 | {isGranted && (
35 |
36 |
You are subscribed!
37 |
38 | )}
39 |
40 |
41 | );
42 | };
43 |
44 | export default NotificationSubscriptionStatus;
45 |
--------------------------------------------------------------------------------
/src/components/UnsupportedNotificationMessage.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import React from "react";
3 |
4 | export const UnsupportedNotificationMessage = () => {
5 | return (
6 |
7 |
Push notifications are not supported in this browser.
8 | Consider adding to the home screen (PWA) if on iOS.
9 |
11 |
12 | );
13 | };
--------------------------------------------------------------------------------
/src/notifications/NotificationPush.ts:
--------------------------------------------------------------------------------
1 | const SERVICE_WORKER_FILE_PATH = "./notification-sw.js";
2 |
3 | export function isNotificationSupported(): boolean {
4 | let unsupported = false;
5 | if (
6 | !("serviceWorker" in navigator) ||
7 | !("PushManager" in window) ||
8 | !("showNotification" in ServiceWorkerRegistration.prototype)
9 | ) {
10 | unsupported = true;
11 | }
12 | return !unsupported;
13 | }
14 |
15 | export function isPermissionGranted(): boolean {
16 | return Notification.permission === "granted";
17 | }
18 |
19 | export function isPermissionDenied(): boolean {
20 | return Notification.permission === "denied";
21 | }
22 |
23 | export async function registerAndSubscribe(onSubscribe: (subs: PushSubscription | null) => void,
24 | onError: (e: Error) => void): Promise {
25 | try {
26 | await navigator.serviceWorker.register(SERVICE_WORKER_FILE_PATH);
27 | //subscribe to notification
28 | navigator.serviceWorker.ready
29 | .then((registration: ServiceWorkerRegistration) => {
30 | return registration.pushManager.subscribe({
31 | userVisibleOnly: true,
32 | applicationServerKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY,
33 | });
34 | })
35 | .then((subscription: PushSubscription) => {
36 | console.info("Created subscription Object: ", subscription.toJSON());
37 | onSubscribe(subscription);
38 | })
39 | .catch((e) => {
40 | onError(e);
41 | });
42 | } catch (e: any) {
43 | onError(e);
44 | }
45 | }
--------------------------------------------------------------------------------
/src/notifications/NotificationSender.ts:
--------------------------------------------------------------------------------
1 | import webpush, { PushSubscription } from "web-push";
2 |
3 | webpush.setVapidDetails(
4 | "mailto:blog@davidrandoll.com",
5 | process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY ?? "",
6 | process.env.VAPID_PRIVATE_KEY ?? ""
7 | );
8 |
9 | export const sendNotification = async (subscription: PushSubscription, title: string, message: string) => {
10 | const pushPayload: any = {
11 | title: title,
12 | body: message,
13 | //image: "/logo.png", if you want to add an image
14 | icon: "/user.png",
15 | url: process.env.NOTIFICATION_URL ?? "/",
16 | badge: "/logo.svg",
17 | };
18 |
19 | webpush
20 | .sendNotification(subscription, JSON.stringify(pushPayload))
21 | .then(() => {
22 | console.log("Notification sent");
23 | })
24 | .catch((error) => {
25 | console.error("Error sending notification", error);
26 | });
27 | };
28 |
--------------------------------------------------------------------------------
/src/notifications/useNotification.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import {
3 | isNotificationSupported,
4 | isPermissionDenied,
5 | isPermissionGranted,
6 | registerAndSubscribe
7 | } from "./NotificationPush";
8 | import React, {createContext, ReactNode, useContext, useEffect, useMemo, useState} from "react";
9 |
10 | interface NotificationContextType {
11 | isSupported: boolean;
12 | isSubscribed: boolean;
13 | isGranted: boolean;
14 | isDenied: boolean;
15 | subscription: PushSubscription | null;
16 | errorMessage: string | null;
17 | handleSubscribe: () => void;
18 | }
19 |
20 | const NotificationContext = createContext(undefined);
21 |
22 | export const NotificationProvider: React.FC<{ children: ReactNode }> = ({children}) => {
23 | const [isSupported, setIsSupported] = useState(false);
24 | const [isGranted, setIsGranted] = useState(false);
25 | const [isDenied, setIsDenied] = useState(false);
26 | const [isSubscribed, setIsSubscribed] = useState(false);
27 | const [subscription, setSubscription] = useState(null);
28 | const [errorMessage, setErrorMessage] = useState(null);
29 |
30 | useEffect(() => {
31 | if (isNotificationSupported()) {
32 | setIsSupported(true);
33 | const granted = isPermissionGranted();
34 | setIsGranted(granted);
35 | setIsDenied(isPermissionDenied());
36 | if (granted) {
37 | handleSubscribe();
38 | }
39 | }
40 | }, []);
41 |
42 | const handleSubscribe = () => {
43 | const onSubscribe = (subscription: PushSubscription | null) => {
44 | if (subscription) {
45 | // for a production app, you would probably have a user account and save the subscription to the user
46 | // make http request to save the subscription
47 | setIsSubscribed(true);
48 | setSubscription(subscription);
49 | }
50 | setIsGranted(isPermissionGranted());
51 | setIsDenied(isPermissionDenied());
52 | };
53 | const onError = (e: Error) => {
54 | console.error("Failed to subscribe cause of: ", e);
55 | setIsGranted(isPermissionGranted());
56 | setIsDenied(isPermissionDenied());
57 | setIsSubscribed(false);
58 | setErrorMessage(e?.message);
59 | }
60 | registerAndSubscribe(onSubscribe, onError);
61 | };
62 |
63 | const contextValue = useMemo(
64 | () => ({
65 | isSupported,
66 | isSubscribed,
67 | isGranted,
68 | isDenied,
69 | subscription,
70 | errorMessage,
71 | handleSubscribe,
72 | }),
73 | [isSupported, isSubscribed, isGranted, isDenied, subscription, errorMessage]
74 | );
75 |
76 | return {children} ;
77 | };
78 |
79 | export const useNotification = (): NotificationContextType => {
80 | const context = useContext(NotificationContext);
81 | if (context === undefined) {
82 | throw new Error("useNotification must be used within a NotificationProvider");
83 | }
84 | return context;
85 | };
86 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | colors: {
12 | background: "var(--background)",
13 | foreground: "var(--foreground)",
14 | },
15 | },
16 | },
17 | plugins: [],
18 | };
19 | export default config;
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./src/*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------