mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-01-19 00:27:25 +01:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88eb728fe3 | ||
|
|
26c835cdd1 | ||
|
|
7d3d697a20 | ||
|
|
798ee3c23c | ||
|
|
7581058c93 | ||
|
|
4f0ddfc30d | ||
|
|
0b918464c1 | ||
|
|
57bd37ef2f | ||
|
|
9fa1288dbc | ||
|
|
55eed868fa | ||
|
|
abb1baeecd | ||
|
|
5784b07f14 | ||
|
|
8e1e0b3740 | ||
|
|
3f42e0e945 | ||
|
|
9146e439d2 | ||
|
|
7a14a0b81f | ||
|
|
9247475ab2 | ||
|
|
6b4c04c390 | ||
|
|
e8216ae9e7 | ||
|
|
365a0b2832 | ||
|
|
f78389b6ef | ||
|
|
0d231d8bd9 | ||
|
|
d838790b8f | ||
|
|
9ce3545901 | ||
|
|
8db569e8a5 |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.19.x'
|
||||
go-version: '1.20.x'
|
||||
-
|
||||
name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.19.x'
|
||||
go-version: '1.20.x'
|
||||
-
|
||||
name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.19.x'
|
||||
go-version: '1.20.x'
|
||||
-
|
||||
name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
@@ -119,8 +119,6 @@ archives:
|
||||
- server/ntfy.service
|
||||
- client/client.yml
|
||||
- client/ntfy-client.service
|
||||
replacements:
|
||||
amd64: x86_64
|
||||
-
|
||||
id: ntfy_windows
|
||||
builds:
|
||||
@@ -131,8 +129,6 @@ archives:
|
||||
- LICENSE
|
||||
- README.md
|
||||
- client/client.yml
|
||||
replacements:
|
||||
amd64: x86_64
|
||||
-
|
||||
id: ntfy_darwin
|
||||
builds:
|
||||
@@ -142,8 +138,6 @@ archives:
|
||||
- LICENSE
|
||||
- README.md
|
||||
- client/client.yml
|
||||
replacements:
|
||||
darwin: macOS
|
||||
universal_binaries:
|
||||
-
|
||||
id: ntfy_darwin_all
|
||||
|
||||
@@ -29,37 +29,37 @@ deb/rpm packages.
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_x86_64.tar.gz
|
||||
tar zxvf ntfy_2.6.0_linux_x86_64.tar.gz
|
||||
sudo cp -a ntfy_2.6.0_linux_x86_64/ntfy /usr/local/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.0_linux_x86_64/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_amd64.tar.gz
|
||||
tar zxvf ntfy_2.6.2_linux_amd64.tar.gz
|
||||
sudo cp -a ntfy_2.6.2_linux_amd64/ntfy /usr/local/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_amd64/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv6.tar.gz
|
||||
tar zxvf ntfy_2.6.0_linux_armv6.tar.gz
|
||||
sudo cp -a ntfy_2.6.0_linux_armv6/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.0_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv6.tar.gz
|
||||
tar zxvf ntfy_2.6.2_linux_armv6.tar.gz
|
||||
sudo cp -a ntfy_2.6.2_linux_armv6/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_armv6/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv7.tar.gz
|
||||
tar zxvf ntfy_2.6.0_linux_armv7.tar.gz
|
||||
sudo cp -a ntfy_2.6.0_linux_armv7/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.0_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv7.tar.gz
|
||||
tar zxvf ntfy_2.6.2_linux_armv7.tar.gz
|
||||
sudo cp -a ntfy_2.6.2_linux_armv7/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_armv7/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_arm64.tar.gz
|
||||
tar zxvf ntfy_2.6.0_linux_arm64.tar.gz
|
||||
sudo cp -a ntfy_2.6.0_linux_arm64/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.0_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_arm64.tar.gz
|
||||
tar zxvf ntfy_2.6.2_linux_arm64.tar.gz
|
||||
sudo cp -a ntfy_2.6.2_linux_arm64/ntfy /usr/bin/ntfy
|
||||
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_arm64/{client,server}/*.yml /etc/ntfy
|
||||
sudo ntfy serve
|
||||
```
|
||||
|
||||
@@ -109,7 +109,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_amd64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_amd64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -117,7 +117,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv6.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv6.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -125,7 +125,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv7.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv7.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -133,7 +133,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_arm64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_arm64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -143,34 +143,36 @@ Manually installing the .deb file:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_amd64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_amd64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv6"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv6.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv6.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv7.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv7.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_arm64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_arm64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
## Arch Linux
|
||||
ntfy can be installed using an [AUR package](https://aur.archlinux.org/packages/ntfysh-bin/). You can use an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) like `paru`, `yay` or others to download, build and install ntfy and keep it up to date.
|
||||
ntfy can be installed using an [AUR package](https://aur.archlinux.org/packages/ntfysh-bin/).
|
||||
You can use an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) like `paru`, `yay` or others to download,
|
||||
build and install ntfy and keep it up to date.
|
||||
```
|
||||
paru -S ntfysh-bin
|
||||
```
|
||||
@@ -192,18 +194,18 @@ NixOS also supports [declarative setup of the ntfy server](https://search.nixos.
|
||||
|
||||
## macOS
|
||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
|
||||
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_macOS_all.tar.gz),
|
||||
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_darwin_all.tar.gz),
|
||||
extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`).
|
||||
|
||||
If run as `root`, ntfy will look for its config at `/etc/ntfy/client.yml`. For all other users, it'll look for it at
|
||||
`~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
|
||||
|
||||
```bash
|
||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_macOS_all.tar.gz > ntfy_2.6.0_macOS_all.tar.gz
|
||||
tar zxvf ntfy_2.6.0_macOS_all.tar.gz
|
||||
sudo cp -a ntfy_2.6.0_macOS_all/ntfy /usr/local/bin/ntfy
|
||||
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_darwin_all.tar.gz > ntfy_2.6.2_darwin_all.tar.gz
|
||||
tar zxvf ntfy_2.6.2_darwin_all.tar.gz
|
||||
sudo cp -a ntfy_2.6.2_darwin_all/ntfy /usr/local/bin/ntfy
|
||||
mkdir ~/Library/Application\ Support/ntfy
|
||||
cp ntfy_2.6.0_macOS_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||
cp ntfy_2.6.2_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
|
||||
ntfy --help
|
||||
```
|
||||
|
||||
@@ -221,7 +223,7 @@ brew install ntfy
|
||||
|
||||
## Windows
|
||||
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well.
|
||||
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_windows_x86_64.zip),
|
||||
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_windows_amd64.zip),
|
||||
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`.
|
||||
|
||||
The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file).
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
|
||||
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
|
||||
|
||||
### ntfy server v2.6.0
|
||||
Released June 28, 2023
|
||||
### ntfy server v2.6.2
|
||||
Released June 30, 2023
|
||||
|
||||
With this release, the ntfy web app now contains a **[progressive web app](https://docs.ntfy.sh/subscribe/pwa/) (PWA)
|
||||
With this release, the ntfy web app now contains a **[progressive web app](subscribe/pwa.md) (PWA)
|
||||
with Web Push support**, which means you'll be able to **install the ntfy web app on your desktop or phone** similar
|
||||
to a native app (__even on iOS!__ 🥳). Installing the PWA gives ntfy web its own launcher, a standalone window,
|
||||
push notifications, and an app badge with the unread notification count.
|
||||
push notifications, and an app badge with the unread notification count. Note that for self-hosted servers,
|
||||
[Web Push](config.md#web-push) must be configured.
|
||||
|
||||
On top of that, this release also brings **dark mode** 🧛🌙 to the web app.
|
||||
|
||||
@@ -30,6 +31,8 @@ if you use promo code `MYTOPIC`). ntfy will always remain open source.
|
||||
* Do not forward poll requests for UnifiedPush messages (no ticket, thanks to NoName for reporting)
|
||||
* Fix `ntfy pub %` segfaulting ([#760](https://github.com/binwiederhier/ntfy/issues/760), thanks to [@clesmian](https://github.com/clesmian) for reporting)
|
||||
* Newly created access tokens are now lowercase only to fully support `<topic>+<token>@<domain>` email syntax ([#773](https://github.com/binwiederhier/ntfy/issues/773), thanks to gingervitiz for reporting)
|
||||
* The .1 release fixes a few visual issues with dark mode, and other web app updates ([#791](https://github.com/binwiederhier/ntfy/pull/791), [#793](https://github.com/binwiederhier/ntfy/pull/793), [#792](https://github.com/binwiederhier/ntfy/pull/792), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||
* The .2 release fixes issues with the service worker in Firefox and adds automatic service worker updates ([#795](https://github.com/binwiederhier/ntfy/pull/795), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||
|
||||
**Maintenance:**
|
||||
|
||||
@@ -38,6 +41,14 @@ if you use promo code `MYTOPIC`). ntfy will always remain open source.
|
||||
* Web: Add eslint with eslint-config-airbnb ([#748](https://github.com/binwiederhier/ntfy/pull/748), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||
* Web: Switch to Vite ([#749](https://github.com/binwiederhier/ntfy/pull/749), thanks to [@nimbleghost](https://github.com/nimbleghost))
|
||||
|
||||
**Changes in tarball/zip naming:**
|
||||
Due to a [change in GoReleaser](https://goreleaser.com/deprecations/#archivesreplacements), some of the binary release
|
||||
archives now have slightly different names. My apologies if this causes issues in the downstream projects that use ntfy:
|
||||
|
||||
- `ntfy_v${VERSION}_windows_x86_64.zip` -> `ntfy_v${VERSION}_windows_amd64.zip`
|
||||
- `ntfy_v${VERSION}_linux_x86_64.tar.gz` -> `ntfy_v${VERSION}_linux_amd64.tar.gz`
|
||||
- `ntfy_v${VERSION}_macOS_all.tar.gz` -> `ntfy_v${VERSION}_darwin_all.tar.gz`
|
||||
|
||||
## ntfy server v2.5.0
|
||||
Released May 18, 2023
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ Web app installation is **supported on** (see [compatibility table](https://cani
|
||||
- **Firefox:** Android, as well as on Windows/Linux [via an extension](https://addons.mozilla.org/en-US/firefox/addon/pwas-for-firefox/)
|
||||
- **Edge:** Windows
|
||||
|
||||
Note that for self-hosted servers, [Web Push](../config.md#web-push) must be configured for the PWA to work.
|
||||
|
||||
## Installation
|
||||
|
||||
### Chrome on Desktop
|
||||
|
||||
10
go.mod
10
go.mod
@@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
cloud.google.com/go/firestore v1.11.0 // indirect
|
||||
cloud.google.com/go/storage v1.30.1 // indirect
|
||||
cloud.google.com/go/storage v1.31.0 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/emersion/go-smtp v0.16.0
|
||||
@@ -29,7 +29,7 @@ require (
|
||||
firebase.google.com/go/v4 v4.11.0
|
||||
github.com/SherClockHolmes/webpush-go v1.2.0
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/stripe/stripe-go/v74 v74.23.0
|
||||
github.com/stripe/stripe-go/v74 v74.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -69,9 +69,9 @@ require (
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/appengine/v2 v2.0.3 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230626202813-9b080da550b3 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230626202813-9b080da550b3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230626202813-9b080da550b3 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 // indirect
|
||||
google.golang.org/grpc v1.56.1 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
20
go.sum
20
go.sum
@@ -12,8 +12,8 @@ cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y=
|
||||
cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU=
|
||||
cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI=
|
||||
cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc=
|
||||
cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM=
|
||||
cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E=
|
||||
cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI=
|
||||
cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0=
|
||||
firebase.google.com/go/v4 v4.11.0 h1:szjBoiF33A2FavRLIDZjW1mw+OsW/XAtHoYNIqWOjRk=
|
||||
firebase.google.com/go/v4 v4.11.0/go.mod h1:60c36dWLK4+j05Vw5XMllek3b3PCynU3BfI46OSwsUE=
|
||||
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||
@@ -143,8 +143,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stripe/stripe-go/v74 v74.23.0 h1:9spORjBMhg8SieRrlrqQdlrw+JllpL6gZnD3QGsCN6Q=
|
||||
github.com/stripe/stripe-go/v74 v74.23.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
|
||||
github.com/stripe/stripe-go/v74 v74.24.0 h1:h+hXEI5avC5moAh2YLtphMFTBnp11TfXTcP4suuWDLk=
|
||||
github.com/stripe/stripe-go/v74 v74.24.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
|
||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
@@ -242,12 +242,12 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20230626202813-9b080da550b3 h1:Yofj1/U0xc/Zi5KEpoIxm51I2f85X+eGyY4YzAujRdw=
|
||||
google.golang.org/genproto v0.0.0-20230626202813-9b080da550b3/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230626202813-9b080da550b3 h1:wl7z+A0jkB3Rl8Hz74SqGDlnnn5VlL2CV+9UTdZOo00=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230626202813-9b080da550b3/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230626202813-9b080da550b3 h1:QJuqz7YzNTyKDspkp2lrzqtq4lf2AhUSpXTsGP5SbLw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230626202813-9b080da550b3/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
|
||||
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
|
||||
96
web/package-lock.json
generated
96
web/package-lock.json
generated
@@ -2257,14 +2257,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
|
||||
"integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz",
|
||||
"integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
"debug": "^4.3.2",
|
||||
"espree": "^9.5.2",
|
||||
"espree": "^9.6.0",
|
||||
"globals": "^13.19.0",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
@@ -2307,9 +2307,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "8.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz",
|
||||
"integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
|
||||
"version": "8.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz",
|
||||
"integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@@ -2381,14 +2381,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
|
||||
"integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
}
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.4.tgz",
|
||||
"integrity": "sha512-KE/SxsDqNs3rrWwFHcRh15ZLVFrI0YoZtgAdIyIq9k5hUNmiWRXXThPomIxHuL20sLdgzbDFyvkUMna14bvtrw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
@@ -2698,9 +2694,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.0.tgz",
|
||||
"integrity": "sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw==",
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.1.tgz",
|
||||
"integrity": "sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
@@ -2730,9 +2726,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz",
|
||||
"integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==",
|
||||
"version": "20.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz",
|
||||
"integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
@@ -3441,9 +3437,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.442",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.442.tgz",
|
||||
"integrity": "sha512-RkrZF//Ya+0aJq2NM3OdisNh5ZodZq1rdXOS96G8DdDgpDKqKE81yTbbQ3F/4CKm1JBPsGu1Lp/akkna2xO06Q==",
|
||||
"version": "1.4.447",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.447.tgz",
|
||||
"integrity": "sha512-sxX0LXh+uL41hSJsujAN86PjhrV/6c79XmpY0TvjZStV6VxIgarf8SRkUoUTuYmFcZQTemsoqo8qXOGw5npWfw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
@@ -3614,15 +3610,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.43.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
|
||||
"integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
|
||||
"version": "8.44.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz",
|
||||
"integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.4.0",
|
||||
"@eslint/eslintrc": "^2.0.3",
|
||||
"@eslint/js": "8.43.0",
|
||||
"@eslint/eslintrc": "^2.1.0",
|
||||
"@eslint/js": "8.44.0",
|
||||
"@humanwhocodes/config-array": "^0.11.10",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
@@ -3634,7 +3630,7 @@
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"eslint-scope": "^7.2.0",
|
||||
"eslint-visitor-keys": "^3.4.1",
|
||||
"espree": "^9.5.2",
|
||||
"espree": "^9.6.0",
|
||||
"esquery": "^1.4.2",
|
||||
"esutils": "^2.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@@ -3654,7 +3650,7 @@
|
||||
"lodash.merge": "^4.6.2",
|
||||
"minimatch": "^3.1.2",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.9.1",
|
||||
"optionator": "^0.9.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"strip-json-comments": "^3.1.0",
|
||||
"text-table": "^0.2.0"
|
||||
@@ -4043,12 +4039,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "9.5.2",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
|
||||
"integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz",
|
||||
"integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.8.0",
|
||||
"acorn": "^8.9.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"eslint-visitor-keys": "^3.4.1"
|
||||
},
|
||||
@@ -4114,9 +4110,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.12",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
|
||||
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
|
||||
"integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
@@ -5828,11 +5824,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.0.tgz",
|
||||
"integrity": "sha512-OD+vkrcGbvlwkspUFDgMzsu1RXwdjNh83YgG/28lBnDzgslhCgxIqoExLlxsfTpIygp7fc+Hd3esloNwzkm2xA==",
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.1.tgz",
|
||||
"integrity": "sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.7.0"
|
||||
"@remix-run/router": "1.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -5842,12 +5838,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.0.tgz",
|
||||
"integrity": "sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==",
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.1.tgz",
|
||||
"integrity": "sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.7.0",
|
||||
"react-router": "6.14.0"
|
||||
"@remix-run/router": "1.7.1",
|
||||
"react-router": "6.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -6018,9 +6014,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.25.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.3.tgz",
|
||||
"integrity": "sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw==",
|
||||
"version": "3.26.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.0.tgz",
|
||||
"integrity": "sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
|
||||
@@ -55,14 +55,14 @@
|
||||
"nav_upgrade_banner_label": "Upgrade to ntfy Pro",
|
||||
"nav_upgrade_banner_description": "Reserve topics, more messages & emails, and larger attachments",
|
||||
"alert_notification_permission_required_title": "Notifications are disabled",
|
||||
"alert_notification_permission_required_description": "Grant your browser permission to display desktop notifications.",
|
||||
"alert_notification_permission_required_description": "Grant your browser permission to display desktop notifications",
|
||||
"alert_notification_permission_required_button": "Grant now",
|
||||
"alert_notification_permission_denied_title": "Notifications are blocked",
|
||||
"alert_notification_permission_denied_description": "Please re-enable them in your browser and refresh the page to receive notifications",
|
||||
"alert_notification_permission_denied_description": "Please re-enable them in your browser",
|
||||
"alert_notification_ios_install_required_title": "iOS install required",
|
||||
"alert_notification_ios_install_required_description": "Click on the Share icon and Add to Home Screen to enable notifications on iOS",
|
||||
"alert_not_supported_title": "Notifications not supported",
|
||||
"alert_not_supported_description": "Notifications are not supported in your browser.",
|
||||
"alert_not_supported_description": "Notifications are not supported in your browser",
|
||||
"alert_not_supported_context_description": "Notifications are only supported over HTTPS. This is a limitation of the <mdnLink>Notifications API</mdnLink>.",
|
||||
"notifications_list": "Notifications list",
|
||||
"notifications_list_item": "Notification",
|
||||
|
||||
@@ -352,5 +352,12 @@
|
||||
"account_upgrade_dialog_tier_price_billed_monthly": "{{price}} 每年。按月计费。",
|
||||
"account_upgrade_dialog_tier_price_billed_yearly": "{{价格}} 按年计费。节省 {{save}}。",
|
||||
"account_upgrade_dialog_billing_contact_email": "有关账单问题,请直接<Link>联系我们 </Link>。",
|
||||
"account_upgrade_dialog_billing_contact_website": "有关账单问题,请参考我们的<Link>网站 </Link>。"
|
||||
"account_upgrade_dialog_billing_contact_website": "有关账单问题,请参考我们的<Link>网站 </Link>。",
|
||||
"publish_dialog_call_item": "拨打电话 {{number}}",
|
||||
"publish_dialog_call_label": "拨号",
|
||||
"publish_dialog_chip_call_label": "拨号",
|
||||
"publish_dialog_chip_call_no_verified_numbers_tooltip": "未验证的手机号",
|
||||
"account_basics_phone_numbers_title": "电话号码",
|
||||
"account_basics_phone_numbers_description": "电话通知",
|
||||
"account_basics_phone_numbers_dialog_description": "要使用来电通知功能,您需要添加并验证至少一个电话号码。可以通过短信或电话进行验证。"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from "workbox-precaching";
|
||||
import { NavigationRoute, registerRoute } from "workbox-routing";
|
||||
import { NetworkFirst } from "workbox-strategies";
|
||||
import { clientsClaim } from "workbox-core";
|
||||
|
||||
import { dbAsync } from "../src/app/db";
|
||||
|
||||
@@ -224,6 +225,8 @@ precacheAndRoute(
|
||||
self.__WB_MANIFEST
|
||||
);
|
||||
|
||||
// Claim all open windows
|
||||
clientsClaim();
|
||||
// Delete any cached old dist files from previous service worker versions
|
||||
cleanupOutdatedCaches();
|
||||
|
||||
|
||||
@@ -44,9 +44,6 @@ class Notifier {
|
||||
}
|
||||
|
||||
async webPushSubscription(hasWebPushTopics) {
|
||||
if (!this.pushPossible()) {
|
||||
throw new Error("Unsupported or denied");
|
||||
}
|
||||
const pushManager = await this.pushManager();
|
||||
const existingSubscription = await pushManager.getSubscription();
|
||||
if (existingSubscription) {
|
||||
|
||||
@@ -27,7 +27,7 @@ class SubscriptionManager {
|
||||
* It is important to note that "mutedUntil" must be part of the where() query, otherwise the Dexie live query
|
||||
* will not react to it, and the Web Push topics will not be updated when the user mutes a topic.
|
||||
*/
|
||||
async webPushTopics(pushPossible = notifier.pushPossible()) {
|
||||
async webPushTopics(pushPossible) {
|
||||
if (!pushPossible) {
|
||||
return [];
|
||||
}
|
||||
@@ -120,13 +120,14 @@ class SubscriptionManager {
|
||||
);
|
||||
}
|
||||
|
||||
async updateWebPushSubscriptions(presetTopics) {
|
||||
const topics = presetTopics ?? (await this.webPushTopics());
|
||||
async updateWebPushSubscriptions(topics) {
|
||||
const hasWebPushTopics = topics.length > 0;
|
||||
const browserSubscription = await notifier.webPushSubscription(hasWebPushTopics);
|
||||
|
||||
if (!browserSubscription) {
|
||||
console.log("[SubscriptionManager] No browser subscription currently exists, so web push was never enabled. Skipping.");
|
||||
console.log(
|
||||
"[SubscriptionManager] No browser subscription currently exists, so web push was never enabled or the notification permission was removed. Skipping."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -787,7 +787,7 @@ const Tokens = () => {
|
||||
}}
|
||||
/>
|
||||
</Paragraph>
|
||||
<div style={{ width: "100%", overflowX: "scroll" }}>{tokens?.length > 0 && <TokensTable tokens={tokens} />}</div>
|
||||
<div style={{ width: "100%", overflowX: "auto" }}>{tokens?.length > 0 && <TokensTable tokens={tokens} />}</div>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button onClick={handleCreateClick}>{t("account_tokens_table_create_token_button")}</Button>
|
||||
|
||||
@@ -19,11 +19,14 @@ import Navigation from "./Navigation";
|
||||
import accountApi from "../app/AccountApi";
|
||||
import PopupMenu from "./PopupMenu";
|
||||
import { SubscriptionPopup } from "./SubscriptionPopup";
|
||||
import { useIsLaunchedPWA } from "./hooks";
|
||||
|
||||
const ActionBar = (props) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const isLaunchedPWA = useIsLaunchedPWA();
|
||||
|
||||
let title = "ntfy";
|
||||
if (props.selected) {
|
||||
title = topicDisplayName(props.selected);
|
||||
@@ -32,6 +35,22 @@ const ActionBar = (props) => {
|
||||
} else if (location.pathname === routes.account) {
|
||||
title = t("action_bar_account");
|
||||
}
|
||||
|
||||
const getActionBarBackground = () => {
|
||||
if (isLaunchedPWA) {
|
||||
return "#317f6f";
|
||||
}
|
||||
|
||||
switch (theme.palette.mode) {
|
||||
case "dark":
|
||||
return "linear-gradient(150deg, #203631 0%, #2a6e60 100%)";
|
||||
|
||||
case "light":
|
||||
default:
|
||||
return "linear-gradient(150deg, #338574 0%, #56bda8 100%)";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
position="fixed"
|
||||
@@ -44,7 +63,7 @@ const ActionBar = (props) => {
|
||||
<Toolbar
|
||||
sx={{
|
||||
pr: "24px",
|
||||
background: theme.palette.actionBarBackground,
|
||||
background: getActionBarBackground(),
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import * as React from "react";
|
||||
import { createContext, Suspense, useContext, useEffect, useState, useMemo } from "react";
|
||||
import { Box, Toolbar, CssBaseline, Backdrop, CircularProgress, useMediaQuery } from "@mui/material";
|
||||
import { ThemeProvider, createTheme } from "@mui/material/styles";
|
||||
import { Box, Toolbar, CssBaseline, Backdrop, CircularProgress, useMediaQuery, ThemeProvider, createTheme } from "@mui/material";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom";
|
||||
import { AllSubscriptions, SingleSubscription } from "./Notifications";
|
||||
import themeOptions, { darkPalette, lightPalette } from "./theme";
|
||||
import { darkTheme, lightTheme } from "./theme";
|
||||
import Navigation from "./Navigation";
|
||||
import ActionBar from "./ActionBar";
|
||||
import notifier from "../app/Notifier";
|
||||
import Preferences from "./Preferences";
|
||||
import subscriptionManager from "../app/SubscriptionManager";
|
||||
import userManager from "../app/UserManager";
|
||||
@@ -46,13 +44,7 @@ const App = () => {
|
||||
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
|
||||
const themePreference = useLiveQuery(() => prefs.theme());
|
||||
const theme = React.useMemo(
|
||||
() =>
|
||||
createTheme({
|
||||
...themeOptions,
|
||||
palette: {
|
||||
...(darkModeEnabled(prefersDarkMode, themePreference) ? darkPalette : lightPalette),
|
||||
},
|
||||
}),
|
||||
() => createTheme(darkModeEnabled(prefersDarkMode, themePreference) ? darkTheme : lightTheme),
|
||||
[prefersDarkMode, themePreference]
|
||||
);
|
||||
|
||||
@@ -91,7 +83,6 @@ const Layout = () => {
|
||||
const params = useParams();
|
||||
const { account, setAccount } = useContext(AccountContext);
|
||||
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
|
||||
const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted());
|
||||
const [sendDialogOpenMode, setSendDialogOpenMode] = useState("");
|
||||
const users = useLiveQuery(() => userManager.all());
|
||||
const subscriptions = useLiveQuery(() => subscriptionManager.all());
|
||||
@@ -115,10 +106,8 @@ const Layout = () => {
|
||||
<Navigation
|
||||
subscriptions={subscriptionsWithoutInternal}
|
||||
selectedSubscription={selected}
|
||||
notificationsGranted={notificationsGranted}
|
||||
mobileDrawerOpen={mobileDrawerOpen}
|
||||
onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
|
||||
onNotificationGranted={setNotificationsGranted}
|
||||
onPublishMessageClick={() => setSendDialogOpenMode(PublishDialog.OPEN_MODE_DEFAULT)}
|
||||
/>
|
||||
<Main>
|
||||
@@ -143,7 +132,7 @@ const Main = (props) => (
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
flexDirection: "column",
|
||||
padding: 3,
|
||||
padding: { xs: 0, md: 3 },
|
||||
width: { sm: `calc(100% - ${Navigation.width}px)` },
|
||||
height: "100dvh",
|
||||
overflow: "auto",
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
Box,
|
||||
IconButton,
|
||||
Button,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import * as React from "react";
|
||||
import { useContext, useState } from "react";
|
||||
@@ -43,6 +44,7 @@ import UpgradeDialog from "./UpgradeDialog";
|
||||
import { AccountContext } from "./App";
|
||||
import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons";
|
||||
import { SubscriptionPopup } from "./SubscriptionPopup";
|
||||
import { useNotificationPermissionListener } from "./hooks";
|
||||
|
||||
const navWidth = 280;
|
||||
|
||||
@@ -59,7 +61,7 @@ const Navigation = (props) => {
|
||||
ModalProps={{ keepMounted: true }} // Better open performance on mobile.
|
||||
sx={{
|
||||
display: { xs: "block", sm: "none" },
|
||||
"& .MuiDrawer-paper": { boxSizing: "border-box", width: navWidth },
|
||||
"& .MuiDrawer-paper": { boxSizing: "border-box", width: navWidth, backgroundImage: "none" },
|
||||
}}
|
||||
>
|
||||
{navigationList}
|
||||
@@ -82,6 +84,7 @@ const Navigation = (props) => {
|
||||
Navigation.width = navWidth;
|
||||
|
||||
const NavList = (props) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -109,17 +112,12 @@ const NavList = (props) => {
|
||||
const isPaid = account?.billing?.subscription;
|
||||
const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid;
|
||||
const showSubscriptionsList = props.subscriptions?.length > 0;
|
||||
const [showNotificationPermissionRequired, setShowNotificationPermissionRequired] = useState(notifier.notRequested());
|
||||
const [showNotificationPermissionDenied, setShowNotificationPermissionDenied] = useState(notifier.denied());
|
||||
const showNotificationPermissionRequired = useNotificationPermissionListener(() => notifier.notRequested());
|
||||
const showNotificationPermissionDenied = useNotificationPermissionListener(() => notifier.denied());
|
||||
const showNotificationIOSInstallRequired = notifier.iosSupportedButInstallRequired();
|
||||
const showNotificationBrowserNotSupportedBox = !showNotificationIOSInstallRequired && !notifier.browserSupported();
|
||||
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
|
||||
|
||||
const refreshPermissions = () => {
|
||||
setShowNotificationPermissionRequired(notifier.notRequested());
|
||||
setShowNotificationPermissionDenied(notifier.denied());
|
||||
};
|
||||
|
||||
const alertVisible =
|
||||
showNotificationPermissionRequired ||
|
||||
showNotificationPermissionDenied ||
|
||||
@@ -130,8 +128,8 @@ const NavList = (props) => {
|
||||
return (
|
||||
<>
|
||||
<Toolbar sx={{ display: { xs: "none", sm: "block" } }} />
|
||||
<List component="nav" sx={{ paddingTop: alertVisible ? "0" : "" }}>
|
||||
{showNotificationPermissionRequired && <NotificationPermissionRequired refreshPermissions={refreshPermissions} />}
|
||||
<List component="nav" sx={{ paddingTop: { xs: 0, sm: alertVisible ? 0 : "" } }}>
|
||||
{showNotificationPermissionRequired && <NotificationPermissionRequired />}
|
||||
{showNotificationPermissionDenied && <NotificationPermissionDeniedAlert />}
|
||||
{showNotificationBrowserNotSupportedBox && <NotificationBrowserNotSupportedAlert />}
|
||||
{showNotificationContextNotSupportedBox && <NotificationContextNotSupportedAlert />}
|
||||
@@ -190,7 +188,11 @@ const NavList = (props) => {
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("nav_button_subscribe")} />
|
||||
</ListItemButton>
|
||||
{showUpgradeBanner && <UpgradeBanner />}
|
||||
{showUpgradeBanner && (
|
||||
// The text background gradient didn't seem to do well with switching between light/dark mode,
|
||||
// So adding a `key` forces React to replace the entire component when the theme changes
|
||||
<UpgradeBanner key={`upgrade-banner-${theme.palette.mode}`} mode={theme.palette.mode} />
|
||||
)}
|
||||
</List>
|
||||
<SubscribeDialog
|
||||
key={`subscribeDialog${subscribeDialogKey}`} // Resets dialog when canceled/closed
|
||||
@@ -203,7 +205,7 @@ const NavList = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const UpgradeBanner = () => {
|
||||
const UpgradeBanner = ({ mode }) => {
|
||||
const { t } = useTranslation();
|
||||
const [dialogKey, setDialogKey] = useState(0);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
@@ -220,13 +222,16 @@ const UpgradeBanner = () => {
|
||||
width: `${Navigation.width - 1}px`,
|
||||
bottom: 0,
|
||||
mt: "auto",
|
||||
background: "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)",
|
||||
background:
|
||||
mode === "light"
|
||||
? "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)"
|
||||
: "linear-gradient(150deg, #203631 0%, #2a6e60 100%)",
|
||||
}}
|
||||
>
|
||||
<Divider />
|
||||
<ListItemButton onClick={handleClick} sx={{ pt: 2, pb: 2 }}>
|
||||
<ListItemIcon>
|
||||
<CelebrationIcon sx={{ color: "#55b86e" }} fontSize="large" />
|
||||
<CelebrationIcon sx={{ color: mode === "light" ? "#55b86e" : "#00ff95" }} fontSize="large" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{ ml: 1 }}
|
||||
@@ -236,7 +241,10 @@ const UpgradeBanner = () => {
|
||||
style: {
|
||||
fontWeight: 500,
|
||||
fontSize: "1.1rem",
|
||||
background: "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)",
|
||||
background:
|
||||
mode === "light"
|
||||
? "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)"
|
||||
: "-webkit-linear-gradient(45deg,rgb(255, 255, 255), #00ff95 80%)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
},
|
||||
@@ -354,11 +362,10 @@ const SubscriptionItem = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationPermissionRequired = ({ refreshPermissions }) => {
|
||||
const NotificationPermissionRequired = () => {
|
||||
const { t } = useTranslation();
|
||||
const requestPermission = async () => {
|
||||
await notifier.maybeRequestPermission();
|
||||
refreshPermissions();
|
||||
};
|
||||
return (
|
||||
<Alert severity="warning" sx={{ paddingTop: 2 }}>
|
||||
|
||||
@@ -184,7 +184,7 @@ const NotificationItem = (props) => {
|
||||
const hasUserActions = notification.actions && notification.actions.length > 0;
|
||||
const showActions = hasAttachmentActions || hasClickAction || hasUserActions;
|
||||
return (
|
||||
<Card sx={{ minWidth: 275, padding: 1 }} role="listitem" aria-label={t("notifications_list_item")}>
|
||||
<Card sx={{ padding: 1 }} role="listitem" aria-label={t("notifications_list_item")}>
|
||||
<CardContent>
|
||||
<Tooltip title={t("notifications_delete")} enterDelay={500}>
|
||||
<IconButton onClick={handleDelete} sx={{ float: "right", marginRight: -1, marginTop: -1 }} aria-label={t("notifications_delete")}>
|
||||
|
||||
@@ -11,14 +11,14 @@ const PrefRow = styled("div")`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
> :first-child {
|
||||
> div:first-of-type {
|
||||
flex: 1 0 40%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: ${(props) => (props.alignTop ? "normal" : "center")};
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
> div:last-of-type {
|
||||
flex: 1 0 calc(60% - 50px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -29,12 +29,12 @@ const PrefRow = styled("div")`
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
> :first-child,
|
||||
> :last-child {
|
||||
> :div:first-of-type,
|
||||
> :div:last-of-type {
|
||||
flex: unset;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
> div:last-of-type {
|
||||
.MuiFormControl-root {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./Rese
|
||||
import { UnauthorizedError } from "../app/errors";
|
||||
import { subscribeTopic } from "./SubscribeDialog";
|
||||
import notifier from "../app/Notifier";
|
||||
import { useIsLaunchedPWA } from "./hooks";
|
||||
import { useIsLaunchedPWA, useNotificationPermissionListener } from "./hooks";
|
||||
|
||||
const maybeUpdateAccountSettings = async (payload) => {
|
||||
if (!session.exists()) {
|
||||
@@ -79,6 +79,7 @@ const Preferences = () => (
|
||||
const Notifications = () => {
|
||||
const { t } = useTranslation();
|
||||
const isLaunchedPWA = useIsLaunchedPWA();
|
||||
const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible());
|
||||
|
||||
return (
|
||||
<Card sx={{ p: 3 }} aria-label={t("prefs_notifications_title")}>
|
||||
@@ -89,7 +90,7 @@ const Notifications = () => {
|
||||
<Sound />
|
||||
<MinPriority />
|
||||
<DeleteAfter />
|
||||
{!isLaunchedPWA && notifier.pushPossible() && <WebPushEnabled />}
|
||||
{!isLaunchedPWA && pushPossible && <WebPushEnabled />}
|
||||
</PrefGroup>
|
||||
</Card>
|
||||
);
|
||||
@@ -240,7 +241,7 @@ const DeleteAfter = () => {
|
||||
const Theme = () => {
|
||||
const { t } = useTranslation();
|
||||
const labelId = "prefTheme";
|
||||
const enabled = useLiveQuery(async () => prefs.theme());
|
||||
const theme = useLiveQuery(async () => prefs.theme());
|
||||
const handleChange = async (ev) => {
|
||||
await prefs.setTheme(ev.target.value);
|
||||
};
|
||||
@@ -248,7 +249,7 @@ const Theme = () => {
|
||||
return (
|
||||
<Pref labelId={labelId} title={t("prefs_appearance_theme_title")}>
|
||||
<FormControl fullWidth variant="standard" sx={{ m: 1 }}>
|
||||
<Select value={enabled ?? false} onChange={handleChange} aria-labelledby={labelId}>
|
||||
<Select value={theme ?? THEME.SYSTEM} onChange={handleChange} aria-labelledby={labelId}>
|
||||
<MenuItem value={THEME.SYSTEM}>{t("prefs_appearance_theme_system")}</MenuItem>
|
||||
<MenuItem value={THEME.DARK}>{t("prefs_appearance_theme_dark")}</MenuItem>
|
||||
<MenuItem value={THEME.LIGHT}>{t("prefs_appearance_theme_light")}</MenuItem>
|
||||
|
||||
@@ -136,8 +136,31 @@ export const useAutoSubscribe = (subscriptions, selected) => {
|
||||
};
|
||||
|
||||
const webPushBroadcastChannel = new BroadcastChannel("web-push-broadcast");
|
||||
const matchMedia = window.matchMedia("(display-mode: standalone)");
|
||||
const isIOSStandalone = window.navigator.standalone === true;
|
||||
|
||||
/**
|
||||
* Hook to return a value that's refreshed when the notification permission changes
|
||||
*/
|
||||
export const useNotificationPermissionListener = (query) => {
|
||||
const [result, setResult] = useState(query());
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => {
|
||||
setResult(query());
|
||||
};
|
||||
|
||||
if ("permissions" in navigator) {
|
||||
navigator.permissions.query({ name: "notifications" }).then((permission) => {
|
||||
permission.addEventListener("change", handler);
|
||||
|
||||
return () => {
|
||||
permission.removeEventListener("change", handler);
|
||||
};
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the Web Push subscriptions when the list of topics changes,
|
||||
@@ -145,11 +168,12 @@ const isIOSStandalone = window.navigator.standalone === true;
|
||||
* the service worker, since the service worker cannot play sounds.
|
||||
*/
|
||||
const useWebPushListener = (topics) => {
|
||||
const [lastTopics, setLastTopics] = useState();
|
||||
const [prevUpdate, setPrevUpdate] = useState();
|
||||
const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible());
|
||||
|
||||
useEffect(() => {
|
||||
const topicsChanged = JSON.stringify(topics) !== JSON.stringify(lastTopics);
|
||||
if (!notifier.pushPossible() || !topicsChanged) {
|
||||
const nextUpdate = JSON.stringify({ topics, pushPossible });
|
||||
if (topics === undefined || nextUpdate === prevUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,12 +181,12 @@ const useWebPushListener = (topics) => {
|
||||
try {
|
||||
console.log("[useWebPushListener] Refreshing web push subscriptions", topics);
|
||||
await subscriptionManager.updateWebPushSubscriptions(topics);
|
||||
setLastTopics(topics);
|
||||
setPrevUpdate(nextUpdate);
|
||||
} catch (e) {
|
||||
console.error("[useWebPushListener] Error refreshing web push subscriptions", e);
|
||||
}
|
||||
})();
|
||||
}, [topics, lastTopics]);
|
||||
}, [topics, pushPossible, prevUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
const onMessage = () => {
|
||||
@@ -183,25 +207,7 @@ const useWebPushListener = (topics) => {
|
||||
* automatically.
|
||||
*/
|
||||
export const useWebPushTopics = () => {
|
||||
const [pushPossible, setPushPossible] = useState(notifier.pushPossible());
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => {
|
||||
const newPushPossible = notifier.pushPossible();
|
||||
console.log(`[useWebPushTopics] Notification Permission changed`, { pushPossible: newPushPossible });
|
||||
setPushPossible(newPushPossible);
|
||||
};
|
||||
|
||||
if ("permissions" in navigator) {
|
||||
navigator.permissions.query({ name: "notifications" }).then((permission) => {
|
||||
permission.addEventListener("change", handler);
|
||||
|
||||
return () => {
|
||||
permission.removeEventListener("change", handler);
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible());
|
||||
|
||||
const topics = useLiveQuery(
|
||||
async () => subscriptionManager.webPushTopics(pushPossible),
|
||||
@@ -214,6 +220,9 @@ export const useWebPushTopics = () => {
|
||||
return topics;
|
||||
};
|
||||
|
||||
const matchMedia = window.matchMedia("(display-mode: standalone)");
|
||||
const isIOSStandalone = window.navigator.standalone === true;
|
||||
|
||||
/*
|
||||
* Watches the "display-mode" to detect if the app is running as a standalone app (PWA).
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/** @type {import("@mui/material").ThemeOptions} */
|
||||
const themeOptions = {
|
||||
const baseThemeOptions = {
|
||||
components: {
|
||||
MuiListItemIcon: {
|
||||
styleOverrides: {
|
||||
@@ -22,37 +22,53 @@ const themeOptions = {
|
||||
|
||||
// https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/colors.xml
|
||||
|
||||
/** @type {import("@mui/material").ThemeOptions['palette']} */
|
||||
export const lightPalette = {
|
||||
mode: "light",
|
||||
primary: {
|
||||
main: "#338574",
|
||||
/** @type {import("@mui/material").ThemeOptions} */
|
||||
export const lightTheme = {
|
||||
...baseThemeOptions,
|
||||
components: {
|
||||
...baseThemeOptions.components,
|
||||
},
|
||||
secondary: {
|
||||
main: "#6cead0",
|
||||
palette: {
|
||||
mode: "light",
|
||||
primary: {
|
||||
main: "#338574",
|
||||
},
|
||||
secondary: {
|
||||
main: "#6cead0",
|
||||
},
|
||||
error: {
|
||||
main: "#c30000",
|
||||
},
|
||||
},
|
||||
error: {
|
||||
main: "#c30000",
|
||||
},
|
||||
actionBarBackground: "linear-gradient(150deg, #338574 0%, #56bda8 100%)",
|
||||
};
|
||||
|
||||
/** @type {import("@mui/material").ThemeOptions['palette']} */
|
||||
export const darkPalette = {
|
||||
mode: "dark",
|
||||
background: {
|
||||
paper: "#1b2124",
|
||||
/** @type {import("@mui/material").ThemeOptions} */
|
||||
export const darkTheme = {
|
||||
...baseThemeOptions,
|
||||
components: {
|
||||
...baseThemeOptions.components,
|
||||
MuiSnackbarContent: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: "#000",
|
||||
backgroundColor: "#aeaeae",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
primary: {
|
||||
main: "#65b5a3",
|
||||
palette: {
|
||||
mode: "dark",
|
||||
background: {
|
||||
paper: "#1b2124",
|
||||
},
|
||||
primary: {
|
||||
main: "#65b5a3",
|
||||
},
|
||||
secondary: {
|
||||
main: "#6cead0",
|
||||
},
|
||||
error: {
|
||||
main: "#fe4d2e",
|
||||
},
|
||||
},
|
||||
secondary: {
|
||||
main: "#6cead0",
|
||||
},
|
||||
error: {
|
||||
main: "#fe4d2e",
|
||||
},
|
||||
actionBarBackground: "linear-gradient(150deg, #203631 0%, #2a6e60 100%)",
|
||||
};
|
||||
|
||||
export default themeOptions;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./components/App";
|
||||
import registerSW from "./registerSW";
|
||||
|
||||
registerSW();
|
||||
|
||||
const root = createRoot(document.querySelector("#root"));
|
||||
root.render(<App />);
|
||||
|
||||
31
web/src/registerSW.js
Normal file
31
web/src/registerSW.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { registerSW as viteRegisterSW } from "virtual:pwa-register";
|
||||
|
||||
// fetch new sw every hour, i.e. update app every hour while running
|
||||
const intervalMS = 60 * 60 * 1000;
|
||||
|
||||
// https://vite-pwa-org.netlify.app/guide/periodic-sw-updates.html
|
||||
const registerSW = () =>
|
||||
viteRegisterSW({
|
||||
onRegisteredSW(swUrl, registration) {
|
||||
if (!registration) {
|
||||
return;
|
||||
}
|
||||
|
||||
setInterval(async () => {
|
||||
if (registration.installing || navigator?.onLine === false) return;
|
||||
|
||||
const resp = await fetch(swUrl, {
|
||||
cache: "no-store",
|
||||
headers: {
|
||||
cache: "no-store",
|
||||
"cache-control": "no-cache",
|
||||
},
|
||||
});
|
||||
|
||||
if (resp?.status === 200) await registration.update();
|
||||
}, intervalMS);
|
||||
},
|
||||
});
|
||||
|
||||
export default registerSW;
|
||||
@@ -16,7 +16,8 @@ export default defineConfig(({ mode }) => ({
|
||||
react(),
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
injectRegister: "inline",
|
||||
// see registerSW.js imported by index.jsx
|
||||
injectRegister: null,
|
||||
strategies: "injectManifest",
|
||||
devOptions: {
|
||||
enabled: true,
|
||||
@@ -25,7 +26,7 @@ export default defineConfig(({ mode }) => ({
|
||||
navigateFallback: "index.html",
|
||||
},
|
||||
injectManifest: {
|
||||
globPatterns: ["**/*.{js,css,html,mp3,ico,png,svg,json}"],
|
||||
globPatterns: ["**/*.{js,css,html,ico,png,svg,json}"],
|
||||
globIgnores: ["config.js"],
|
||||
manifestTransforms: [
|
||||
(entries) => ({
|
||||
|
||||
Reference in New Issue
Block a user