diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..6507eadc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/web" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + groups: + all: + patterns: + - "*" diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ca44e4b6..5453fb3a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -8,13 +8,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '1.25.x' + go-version: '1.26.x' - name: Install node - uses: actions/setup-node@v3 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24' cache: 'npm' diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 6991dea6..da1e59ea 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -9,10 +9,10 @@ jobs: steps: - name: Checkout ntfy code - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Checkout docs pages code - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: binwiederhier/ntfy-docs.github.io path: build/ntfy-docs.github.io diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3c959bb6..db8f15f5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -25,19 +25,19 @@ jobs: NTFY_TEST_S3_URL: ${{ secrets.NTFY_TEST_S3_URL }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '1.25.x' + go-version: '1.26.x' - name: Install node - uses: actions/setup-node@v3 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24' cache: 'npm' cache-dependency-path: './web/package-lock.json' - name: Docker login - uses: docker/login-action@v2 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: username: ${{ github.repository_owner }} password: ${{ secrets.DOCKER_HUB_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 4d6bbbdb..ead55ba6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,13 +25,13 @@ jobs: NTFY_TEST_S3_URL: ${{ secrets.NTFY_TEST_S3_URL }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: '1.25.x' + go-version: '1.26.x' - name: Install node - uses: actions/setup-node@v3 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24' cache: 'npm' diff --git a/Dockerfile-build b/Dockerfile-build index 23503fd7..0a7f0623 100644 --- a/Dockerfile-build +++ b/Dockerfile-build @@ -45,6 +45,9 @@ ADD ./db ./db ADD ./message ./message ADD ./model ./model ADD ./webpush ./webpush +ADD ./attachment ./attachment +ADD ./mail ./mail +ADD ./s3 ./s3 RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build make VERSION=$VERSION COMMIT=$COMMIT cli-linux-server FROM alpine diff --git a/Makefile b/Makefile index a46f4dba..7bfd1cfc 100644 --- a/Makefile +++ b/Makefile @@ -151,7 +151,7 @@ web-deps: # If this fails for .svg files, optimize them with svgo web-deps-update: - cd web && $(NPM) update + cd web && $(NPM) update --before="$(shell date -d '7 days ago' +%Y-%m-%d)" cd web && $(NPM) install web-fmt: diff --git a/docs/develop.md b/docs/develop.md index dde20b56..01ffbb50 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -255,6 +255,7 @@ Reference: \ diff --git a/docs/integrations.md b/docs/integrations.md index a78d46f8..2c21115e 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -89,6 +89,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had - [ntfy-desktop](https://codeberg.org/zvava/ntfy-desktop) - Cross-platform desktop application for ntfy - [ntfy-desktop](https://github.com/Aetherinox/ntfy-desktop) - Desktop client for Windows, Linux, and MacOS with push notifications - [ntfy svelte front-end](https://github.com/novatorem/Ntfy) - Front-end built with svelte +- [ntfy Desktop (Windows)](https://github.com/simoneferrari/ntfy-desktop) - Native Windows desktop client with multi-server support, toast notifications and message history, built with WPF and .NET (C#) - [wio-ntfy-ticker](https://github.com/nachotp/wio-ntfy-ticker) - Ticker display for a ntfy.sh topic - [ntfysh-windows](https://github.com/mshafer1/ntfysh-windows) - A ntfy client for Windows Desktop - [ntfyr](https://github.com/haxwithaxe/ntfyr) - A simple commandline tool to send notifications to ntfy @@ -191,6 +192,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had ## Blog + forum posts +- [Push alerts for WHM using ntfy](https://rubixstudios.com.au/insights/push-alerts-for-whm-using-ntfy) - rubixstudios.com.au - 5/2026 - [Device notifications via HTTP with ntfy](https://alistairshepherd.uk/writing/ntfy/) - alistairshepherd.uk - 6/2025 - [Notifications about (almost) anything with ntfy.sh](https://hamatti.org/posts/notifications-about-almost-anything-with-ntfy-sh/) - hamatti.org - 6/2025 - [I set up a self-hosted notification service for everything, and I'll never look back](https://www.xda-developers.com/set-up-self-hosted-notification-service/) ⭐ - xda-developers.com - 5/2025 diff --git a/docs/releases.md b/docs/releases.md index ce6c3bf0..df18add5 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -8,10 +8,29 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release |------------------|---------|--------------| | ntfy server | v2.23.0 | May 17, 2026 | | ntfy Android app | v1.24.0 | Mar 5, 2026 | -| ntfy iOS app | v1.6.0 | May 12, 2026 | +| ntfy iOS app | v1.7.0 | May 30, 2026 | Please check out the release notes for [upcoming releases](#not-released-yet) below. +## ntfy iOS app v1.7.0 +Released May 30, 2026 + +This release brings **image and attachment support** to the iOS app, finally closing one of the longest-standing iOS +feature gaps. Images sent via the `Attach` header (or as a PUT body) are now previewed inline in the notification banner +and inside the app, and other attachments can be downloaded, previewed via Quick Look, and shared from the notification +row. There's also a new "Download attachments" setting to control auto-download by size. + +**Features:** + +* Show image previews in notifications and inline in the notification list, with tap-to-zoom Quick Look preview and share sheet ([ntfy-ios#40](https://github.com/binwiederhier/ntfy-ios/pull/40), [#276](https://github.com/binwiederhier/ntfy/issues/276), [#1226](https://github.com/binwiederhier/ntfy/issues/1226), thanks to [@am7590](https://github.com/am7590) for the contribution) +* Download non-image attachments on demand with progress indication, persist them locally, and reuse files already fetched by the notification service extension ([ntfy-ios#40](https://github.com/binwiederhier/ntfy-ios/pull/40), thanks to [@am7590](https://github.com/am7590) for the contribution) +* Add "Download attachments" setting with size thresholds (Never, Under 100 KB / 500 KB / 1 MB / 5 MB / 10 MB / 50 MB, Always) to control automatic attachment downloads ([ntfy-ios#40](https://github.com/binwiederhier/ntfy-ios/pull/40), thanks to [@am7590](https://github.com/am7590) for the contribution) + +**Bug fixes + maintenance:** + +* Improve background download reliability so attachments continue downloading when the app is suspended ([ntfy-ios#40](https://github.com/binwiederhier/ntfy-ios/pull/40), thanks to [@am7590](https://github.com/am7590) for the contribution) +* Reorganize notification and subscription views into their own folders and split out `NotificationRowView` for readability ([ntfy-ios#40](https://github.com/binwiederhier/ntfy-ios/pull/40), thanks to [@am7590](https://github.com/am7590) for the contribution) + ## ntfy server v2.23.0 Released May 17, 2026 @@ -1905,25 +1924,14 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release ## Not released yet -## ntfy iOS app v1.7.0 (UNRELEASED) - -This release brings **image and attachment support** to the iOS app, finally closing one of the longest-standing iOS -feature gaps. Images sent via the `Attach` header (or as a PUT body) are now previewed inline in the notification banner -and inside the app, and other attachments can be downloaded, previewed via Quick Look, and shared from the notification -row. There's also a new "Download attachments" setting to control auto-download by size. - -**Features:** - -* Show image previews in notifications and inline in the notification list, with tap-to-zoom Quick Look preview and share sheet ([ntfy-ios#40](https://github.com/binwiederhier/ntfy-ios/pull/40), [#276](https://github.com/binwiederhier/ntfy/issues/276), [#1226](https://github.com/binwiederhier/ntfy/issues/1226), thanks to [@am7590](https://github.com/am7590) for the contribution) -* Download non-image attachments on demand with progress indication, persist them locally, and reuse files already fetched by the notification service extension ([ntfy-ios#40](https://github.com/binwiederhier/ntfy-ios/pull/40), thanks to [@am7590](https://github.com/am7590) for the contribution) -* Add "Download attachments" setting with size thresholds (Never, Under 100 KB / 500 KB / 1 MB / 5 MB / 10 MB / 50 MB, Always) to control automatic attachment downloads ([ntfy-ios#40](https://github.com/binwiederhier/ntfy-ios/pull/40), thanks to [@am7590](https://github.com/am7590) for the contribution) +### ntfy server v2.24.0 (UNRELEASED) **Bug fixes + maintenance:** -* Improve background download reliability so attachments continue downloading when the app is suspended ([ntfy-ios#40](https://github.com/binwiederhier/ntfy-ios/pull/40), thanks to [@am7590](https://github.com/am7590) for the contribution) -* Reorganize notification and subscription views into their own folders and split out `NotificationRowView` for readability ([ntfy-ios#40](https://github.com/binwiederhier/ntfy-ios/pull/40), thanks to [@am7590](https://github.com/am7590) for the contribution) +* Extend account token automatically from the PWA service worker, so installed PWAs don't get logged out ([#1669](https://github.com/binwiederhier/ntfy/pull/1669), [#1203](https://github.com/binwiederhier/ntfy/issues/1203), [#1533](https://github.com/binwiederhier/ntfy/issues/1533), thanks to [@nihalgonsalves](https://github.com/nihalgonsalves) for the contribution) +* Fix `rel` attribute on auto-linked notification URLs so `noreferrer`/`noopener` are actually applied ([#1720](https://github.com/binwiederhier/ntfy/pull/1720), thanks to [@dmitrylyzo](https://github.com/dmitrylyzo) for the contribution) -## ntfy Android v1.25.x (UNRELEASED) +### ntfy Android v1.25.x (UNRELEASED) This release makes the "connection lost" alert configurable and turns it off by default. Folks did not like it and many reached out or even gave ntfy bad reviews. I heard you! You can re-enable the alert in the advanced settings. diff --git a/go.mod b/go.mod index c3732a06..75d7c8d1 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module heckel.io/ntfy/v2 -go 1.25.0 +go 1.25.8 require ( cloud.google.com/go/firestore v1.22.0 // indirect - cloud.google.com/go/storage v1.62.1 // indirect + cloud.google.com/go/storage v1.62.2 // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/emersion/go-smtp v0.24.0 @@ -14,12 +14,12 @@ require ( github.com/olebedev/when v1.1.0 github.com/stretchr/testify v1.11.1 github.com/urfave/cli/v2 v2.27.7 - golang.org/x/crypto v0.51.0 + golang.org/x/crypto v0.52.0 golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 golang.org/x/term v0.43.0 golang.org/x/time v0.15.0 - google.golang.org/api v0.279.0 + google.golang.org/api v0.282.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -34,7 +34,7 @@ require ( github.com/microcosm-cc/bluemonday v1.0.27 github.com/prometheus/client_golang v1.23.2 github.com/stripe/stripe-go/v74 v74.30.0 - golang.org/x/sys v0.44.0 + golang.org/x/sys v0.45.0 golang.org/x/text v0.37.0 ) @@ -69,7 +69,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.16 // indirect github.com/googleapis/gax-go/v2 v2.22.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -79,27 +79,26 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/common v0.68.0 // indirect github.com/prometheus/procfs v0.20.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/sdk v1.43.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect - go.yaml.in/yaml/v2 v2.4.4 // indirect - golang.org/x/net v0.54.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + golang.org/x/net v0.55.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20260511170946-3700d4141b60 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect + google.golang.org/genproto v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/grpc v1.81.1 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 7010ba7b..ae92229d 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ cloud.google.com/go/longrunning v1.0.0 h1:lwzWEYD8+NkYV7dhexOz6kmlvajZA70+bW/xMh cloud.google.com/go/longrunning v1.0.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM= cloud.google.com/go/monitoring v1.29.0 h1:AHhDsFaSax1/4k+qlIDX/SDGe6hggnfXJ9dkgD9qBPY= cloud.google.com/go/monitoring v1.29.0/go.mod h1:72NOVjJXHY/HBfoLT0+qlCZBT059+9VXLeAnL2PeeVM= -cloud.google.com/go/storage v1.62.1 h1:Os0G3XbUbjZumkpDUf2Y0rLoXJTCF1kU2kWUujKYXD8= -cloud.google.com/go/storage v1.62.1/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA= +cloud.google.com/go/storage v1.62.2 h1:WgR4U9n7bIzXkkVnwPKKE8bkaKUNsHG+0MAAlh9DGU4= +cloud.google.com/go/storage v1.62.2/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA= cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E= cloud.google.com/go/trace v1.16.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0= firebase.google.com/go/v4 v4.20.0 h1:ighpjeAC45rY/95cUQ+ojIKlKcTnz2YC0ldam56z2YU= @@ -96,8 +96,8 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas= -github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/enterprise-certificate-proxy v0.3.16 h1:F/VPrx0YPBdksZJQdCAp0WUsqnNmZpUZszzfYt0M5Dw= +github.com/googleapis/enterprise-certificate-proxy v0.3.16/go.mod h1:9Yb0eAkH/Xqhvv3zbeKf/+wMJqCeocWc6KIhDvEAuYE= github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4= github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= @@ -139,8 +139,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= -github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/common v0.68.0 h1:8rQJvQmYltsR2L7h8Zw0Iyj8WYNNmpwikoQTZXwfVeA= +github.com/prometheus/common v0.68.0/go.mod h1:4soH+U8yJSROk7OJ//hmTiWKsxapv6zRGgTt3keN8gQ= github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -165,24 +165,26 @@ github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBi github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU= -go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw= +go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 h1:2yEATaop1/a1I4psnSLgWVPLWwCzkqWakgJy7xTDVy0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0/go.mod h1:D7J12YRapIekYyPWgGPlA/23pRmpSEZC5xJC/TTLI9U= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= @@ -193,8 +195,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -209,8 +211,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -234,8 +236,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -272,16 +274,16 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.279.0 h1:hsx2M2OaRcaKtVYK6vXEUnQvdjnend7ZYES+lYaot74= -google.golang.org/api v0.279.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ= +google.golang.org/api v0.282.0 h1:WmJiSVqUnKqJCpJOx7YADbXaC+9DDsnGSfllFSj7R2I= +google.golang.org/api v0.282.0/go.mod h1:6Wssta4c5n9qHq5CBhmlai5h/PUa1djdDAIhYEHyvcM= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI= -google.golang.org/genproto v0.0.0-20260511170946-3700d4141b60 h1:rhBdfmsOlOZIvz3Y5/BdUzPg2CkO8L7QQPKj96B8554= -google.golang.org/genproto v0.0.0-20260511170946-3700d4141b60/go.mod h1:8xo2Pj1b20ZOCpzlU3B9qieMwVIAXx1QVZWLMlPL6sM= -google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 h1:3WsB1FAbiRIf2tOxscWKs3pQBD9he1NsrnbhMuWfekc= -google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60/go.mod h1:7yoXV7RIh5gblj/xVYoogxAWvA9wUeVbpsK/M694l00= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto v0.0.0-20260526163538-3dc84a4a5aaa h1:mfj8IS4EA4VAR9a6QDVxTQkLY64iBybb5QI1B4pXrpE= +google.golang.org/genproto v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:fuT7yonGw1Iq2oa+YC0fyqPPQJkgo/54gPNC6VitOkI= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= diff --git a/web/package-lock.json b/web/package-lock.json index ebb55f34..a2fc6aaa 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -3293,9 +3293,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", "license": "MIT", "peer": true, "dependencies": { @@ -3747,9 +3747,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.30", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.30.tgz", - "integrity": "sha512-xjOFN16Ha1+Rz4nFYKqHU/LSB+gx/Vi3yQLX7r7sAW+Wa+8hhF2h4pvqTrTMc8+WcDBEunnUurr46Jvv0jk3Vg==", + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4308,9 +4308,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.357", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.357.tgz", - "integrity": "sha512-NHlTIQDK8fmVwHwuIzmXYEJ1Ewq3D9wDNc0cWXxDGysP6Pb21giwGNkxiTifyKy/4SoPuN5l6GLP1W9Sv7zB2g==", + "version": "1.5.361", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", + "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", "dev": true, "license": "ISC" }, @@ -4447,9 +4447,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", "dependencies": { @@ -6830,11 +6830,14 @@ } }, "node_modules/node-releases": { - "version": "2.0.44", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", - "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/object-assign": { "version": "4.1.1", @@ -7145,9 +7148,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.3.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", - "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", + "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -7193,9 +7196,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -7213,7 +7216,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -8326,9 +8329,9 @@ } }, "node_modules/terser": { - "version": "5.47.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.47.1.tgz", - "integrity": "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", + "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", "dev": true, "license": "BSD-2-Clause", "dependencies": { diff --git a/web/public/sw.js b/web/public/sw.js index fa2ac9e0..0ace286e 100644 --- a/web/public/sw.js +++ b/web/public/sw.js @@ -4,6 +4,7 @@ import { NavigationRoute, registerRoute } from "workbox-routing"; import { NetworkFirst } from "workbox-strategies"; import { clientsClaim } from "workbox-core"; import { dbAsync } from "../src/app/db"; +import session from "../src/app/Session"; import { ACTION_HTTP, ACTION_VIEW } from "../src/app/actions"; import { badge, icon, messageWithSequenceId, notificationTag, toNotificationParams } from "../src/app/notificationUtils"; import initI18n from "../src/app/i18n"; @@ -11,8 +12,9 @@ import { EVENT_MESSAGE, EVENT_MESSAGE_CLEAR, EVENT_MESSAGE_DELETE, - WEBPUSH_EVENT_MESSAGE, - WEBPUSH_EVENT_SUBSCRIPTION_EXPIRING, + SW_PERIODIC_SYNC_EXTEND_TOKEN_TAG, + SW_WEBPUSH_EVENT_MESSAGE, + SW_WEBPUSH_EVENT_SUBSCRIPTION_EXPIRING, } from "../src/app/events"; /** @@ -35,6 +37,7 @@ const broadcastChannel = new BroadcastChannel("web-push-broadcast"); */ const handlePushMessage = async (data) => { const { subscription_id: subscriptionId, message } = data; + const db = await dbAsync(); console.log("[ServiceWorker] Message received", data); @@ -43,9 +46,24 @@ const handlePushMessage = async (data) => { const subscription = await db.subscriptions.get(subscriptionId); if (!subscription) { console.log("[ServiceWorker] Subscription not found", subscriptionId); + handlePushUnknown(data); return; } + // NOTE: As soon as possible, to avoid this Safari error: + // > Push event handling completed without showing any notification via + // > ServiceWorkerRegistration.showNotification(). This may trigger removal of + // > the push subscription. + await self.registration.showNotification( + ...toNotificationParams({ + message, + defaultTitle: message.topic, + topicRoute: new URL(message.topic, self.location.origin).toString(), + baseUrl: subscription.baseUrl, + topic: subscription.topic, + }) + ); + // Delete existing notification with same sequence ID (if any) const sequenceId = message.sequence_id || message.id; if (sequenceId) { @@ -71,17 +89,71 @@ const handlePushMessage = async (data) => { // Broadcast the message to potentially play a sound broadcastChannel.postMessage(message); - await self.registration.showNotification( - ...toNotificationParams({ - message, - defaultTitle: message.topic, - topicRoute: new URL(message.topic, self.location.origin).toString(), - baseUrl: subscription.baseUrl, - topic: subscription.topic, - }) - ); + await maybeExtendToken(); }; +const refreshTokenThreshold = 1000 * 60 * 60; // 1 hour +const maybeExtendToken = async () => { + if (import.meta.env.DEV) { + console.warn("[ServiceWorker] Skipping token extension in development since no config.base_url exists"); + return; + } + + const token = await session.tokenAsync(); + if (!token) { + console.debug("[ServiceWorker] No session token, skipping token extension"); + return; + } + + const lastExtendedAt = await session.lastExtendedAtAsync(); + const now = Date.now(); + + if (lastExtendedAt && now - lastExtendedAt < refreshTokenThreshold) { + console.debug(`[ServiceWorker] Token extended ${Math.floor((now - lastExtendedAt) / 1000 / 60)} minutes ago, skipping`); + return; + } + + console.log("[ServiceWorker] Extending user access token"); + + // duplicated from utils.js#accountTokenUrl since we can't import that here + // as long as there's mp3 and other incompatible imports there + const tokenUrl = `${config.base_url}/v1/account/token`; + + try { + const response = await fetch(tokenUrl, { + method: "PATCH", + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (response.ok) { + await session.setLastExtendedAtAsync(); + console.log(`[ServiceWorker] Token extended successfully`); + } else { + console.error(`[ServiceWorker] Failed to extend token: ${response.status} ${response.statusText}`); + } + } catch (e) { + console.error("[ServiceWorker] Failed to extend token", e); + } +}; + +/** + * Registers a periodic-sync listener for `extend_token` (see hooks.js). + * This extends the token regardless of whether the browser is open. + * + * CAVEATS: + * - Chromium-only + * - Only when the PWA is _installed_ (not just running in a browser tab) + * - Only when notifications are granted + */ +self.addEventListener("periodicsync", (event) => { + if (event.tag === SW_PERIODIC_SYNC_EXTEND_TOKEN_TAG) { + console.log(`[ServiceWorker] Received periodicsync event "${SW_PERIODIC_SYNC_EXTEND_TOKEN_TAG}"`); + event.waitUntil(maybeExtendToken()); + } +}); + /** * Handle a message_delete event: delete the notification from the database. */ @@ -192,7 +264,7 @@ const handlePush = async (data) => { // - Web app: hooks.js:handleNotification() // - Web app: sw.js:handleMessage(), sw.js:handleMessageClear(), ... - if (data.event === WEBPUSH_EVENT_MESSAGE) { + if (data.event === SW_WEBPUSH_EVENT_MESSAGE) { const { message } = data; if (message.event === EVENT_MESSAGE) { return await handlePushMessage(data); @@ -201,7 +273,7 @@ const handlePush = async (data) => { } else if (message.event === EVENT_MESSAGE_CLEAR) { return await handlePushMessageClear(data); } - } else if (data.event === WEBPUSH_EVENT_SUBSCRIPTION_EXPIRING) { + } else if (data.event === SW_WEBPUSH_EVENT_SUBSCRIPTION_EXPIRING) { return await handlePushSubscriptionExpiring(data); } diff --git a/web/src/app/AccountApi.js b/web/src/app/AccountApi.js index d340c2c0..4fadb8c5 100644 --- a/web/src/app/AccountApi.js +++ b/web/src/app/AccountApi.js @@ -155,6 +155,7 @@ class AccountApi { method: "PATCH", headers: withBearerAuth({}, session.token()), }); + await session.setLastExtendedAtAsync(); } async deleteToken(token) { diff --git a/web/src/app/Session.js b/web/src/app/Session.js index 7464150c..976e9f3c 100644 --- a/web/src/app/Session.js +++ b/web/src/app/Session.js @@ -36,6 +36,7 @@ class Session { await this.db.kv.bulkPut([ { key: "user", value: username }, { key: "token", value: token }, + { key: "lastExtendedAt", value: Date.now() }, ]); localStorage.setItem("user", username); localStorage.setItem("token", token); @@ -52,6 +53,18 @@ class Session { return (await this.db.kv.get({ key: "user" }))?.value; } + async tokenAsync() { + return (await this.db.kv.get({ key: "token" }))?.value; + } + + async lastExtendedAtAsync() { + return (await this.db.kv.get({ key: "lastExtendedAt" }))?.value; + } + + async setLastExtendedAtAsync() { + await this.db.kv.put({ key: "lastExtendedAt", value: Date.now() }); + } + exists() { return this.username() && this.token(); } diff --git a/web/src/app/events.js b/web/src/app/events.js index d5c5ab88..65755106 100644 --- a/web/src/app/events.js +++ b/web/src/app/events.js @@ -8,8 +8,10 @@ export const EVENT_MESSAGE_DELETE = "message_delete"; export const EVENT_MESSAGE_CLEAR = "message_clear"; export const EVENT_POLL_REQUEST = "poll_request"; -export const WEBPUSH_EVENT_MESSAGE = "message"; -export const WEBPUSH_EVENT_SUBSCRIPTION_EXPIRING = "subscription_expiring"; +export const SW_WEBPUSH_EVENT_MESSAGE = "message"; +export const SW_WEBPUSH_EVENT_SUBSCRIPTION_EXPIRING = "subscription_expiring"; + +export const SW_PERIODIC_SYNC_EXTEND_TOKEN_TAG = "extend_token"; // Check if an event is a notification event (message, delete, or read) export const isNotificationEvent = (event) => event === EVENT_MESSAGE || event === EVENT_MESSAGE_DELETE || event === EVENT_MESSAGE_CLEAR; diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 2a62aa38..db38801b 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -209,7 +209,7 @@ export const formatPrice = (n) => { }; export const openUrl = (url) => { - window.open(url, "_blank", "noopener,noreferrer"); + window.open(url, "_blank", "noreferrer"); }; export const sounds = { diff --git a/web/src/components/Notifications.jsx b/web/src/components/Notifications.jsx index ca07847d..3b134abc 100644 --- a/web/src/components/Notifications.jsx +++ b/web/src/components/Notifications.jsx @@ -164,7 +164,7 @@ const autolink = (s) => { const parts = s.split(/(\bhttps?:\/\/[-A-Z0-9+\u0026\u2019@#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026@#/%=~()_|]\b)/gi); for (let i = 1; i < parts.length; i += 2) { parts[i] = ( - + {shortUrl(parts[i])} ); diff --git a/web/src/components/hooks.js b/web/src/components/hooks.js index 1b4a78b7..db832809 100644 --- a/web/src/components/hooks.js +++ b/web/src/components/hooks.js @@ -13,7 +13,7 @@ import versionChecker from "../app/VersionChecker"; import { UnauthorizedError } from "../app/errors"; import notifier from "../app/Notifier"; import prefs from "../app/Prefs"; -import { EVENT_MESSAGE_DELETE, EVENT_MESSAGE_CLEAR } from "../app/events"; +import { EVENT_MESSAGE_DELETE, EVENT_MESSAGE_CLEAR, SW_PERIODIC_SYNC_EXTEND_TOKEN_TAG } from "../app/events"; /** * Wire connectionManager and subscriptionManager so that subscriptions are updated when the connection @@ -283,6 +283,52 @@ export const useStandaloneWebPushAutoSubscribe = () => { }, [isLaunchedPWA]); }; +/** + * Registers a periodicsync listener for `extend_token` (see sw.js). + * This extends the token regardless of whether the browser is open. + * + * CAVEATS: + * - Chromium-only + * - Only when the PWA is _installed_ (not just running in a browser tab) + * - Only when notifications are granted + * + * This is an experimental feature: + * https://developer.mozilla.org/en-US/docs/Web/API/Web_Periodic_Background_Synchronization_API + */ +const usePeriodicTokenExtend = () => { + const isLaunchedPWA = useIsLaunchedPWA(); + const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible()); + + useEffect(() => { + (async () => { + if (!isLaunchedPWA) { + console.debug("[usePeriodicTokenExtend] Skipping: Not running as PWA"); + return; + } + + if (!pushPossible) { + console.debug("[usePeriodicTokenExtend] Skipping: Web push not possible or granted"); + return; + } + + try { + const registration = await navigator.serviceWorker.ready; + if (!registration.periodicSync) { + console.debug("[usePeriodicTokenExtend] Skipping: Periodic Sync not supported"); + return; + } + + console.log(`[usePeriodicTokenExtend] Turning on periodicsync "${SW_PERIODIC_SYNC_EXTEND_TOKEN_TAG}"`); + await registration.periodicSync.register(SW_PERIODIC_SYNC_EXTEND_TOKEN_TAG, { + minInterval: 12 * 60 * 60 * 1000, // 12 hours + }); + } catch (error) { + console.log("[usePeriodicTokenExtend] Periodic Sync could not be registered", error); + } + })(); + }, [isLaunchedPWA, pushPossible]); +}; + /** * Start the poller and the pruner. This is done in a side effect as opposed to just in Pruner.js * and Poller.js, because side effect imports are not a thing in JS, and "Optimize imports" cleans @@ -305,6 +351,7 @@ const stopWorkers = () => { export const useBackgroundProcesses = () => { useStandaloneWebPushAutoSubscribe(); + usePeriodicTokenExtend(); useEffect(() => { console.log("[useBackgroundProcesses] mounting");