Browse Source

fix noisy startup

remove extra close button on cache dialog
stagger relay fetches
simplify relay page URLs
imwald
Silberengel 3 days ago
parent
commit
9b8489f84b
  1. 1
      .gitignore
  2. 1
      .npm-cache/_cacache/content-v2/sha512/20/79/bd6cdead1741a56336784107c6a849cb8898ab6c1d9794ae30d548265d6fb6d8a4288bcf6939c4591c7c498beca25521116b8cf8e3be52fb4b3319836053
  3. BIN
      .npm-cache/_cacache/content-v2/sha512/2e/05/5332942d228a428bbf5225e0e23f44df5a2e4234473f2ff691753877c88be66574e785e5a92a3499867bcc97c8976c2d6d160f607471c330ba15ec29c6fb
  4. 1
      .npm-cache/_cacache/content-v2/sha512/2f/87/632af0ab08306cd761d6ca70d98fe257589ab3ec142176f73c6499b64dd4c7c8d24071110ffb889ff91c96332c52d6cbf4d38a5cb64b3e5c28e3b74b2d3c
  5. 1
      .npm-cache/_cacache/content-v2/sha512/4e/f4/23941d402feb258f35702097f9a2d0e3b22554ce34feb0d3b430df4e245db1058be9e5e1555fc6005a34941d863bb719dc7867b9bf4b5ff60e8137a960e2
  6. BIN
      .npm-cache/_cacache/content-v2/sha512/58/cc/fbd53d494badb89d68f69f67e7600b8fa2f86fec452193af51ea2f5db15cb3c6de5e3376e3c2f824f4bf803c4d5346c706cac0153003640748e1d0b419ad
  7. 1
      .npm-cache/_cacache/content-v2/sha512/60/53/ba87b84b221bbfed8ac45c98583ede92a66ede5d531614c47029723d33f233d0ee3b00c6d385a12577919e3f5e5a829ffa76e992783d898b7d5e5f86e991
  8. BIN
      .npm-cache/_cacache/content-v2/sha512/7e/ee/b9e9a2749f691c5f0b27c27bfd836e2d914e6e4a63a54e377a5b2c9a2a6d99fa1d6a998e9b023adc688c45fc1b2516a6d9f1406d6c98bb2a040e643d8f4f
  9. 1
      .npm-cache/_cacache/content-v2/sha512/aa/e9/6808875f89ad4c591f3f26890f32d53d46b256fe3dedf936b93426f0788f2522769d10d258488d60d489e3b52f852e8319b41b629eecb2cdf61d6fc274cb
  10. 1
      .npm-cache/_cacache/content-v2/sha512/af/02/37dbc7a6e88a5efa19aff3a7df266e7aa008cc4b7c540814d68da79656da15bc565dddc225184b682c44205f14df07f6ba0451493edf8ba9d717073ea013
  11. 1
      .npm-cache/_cacache/content-v2/sha512/bb/03/d33e192ae2fb385e80697a4a870ac009afe746d6d0ad43538b8154d3b2e227d14d623a0ba8d29d9f129e7c65a887510067dd57b85f8b0e76e61bd16dc9d7
  12. 1
      .npm-cache/_cacache/content-v2/sha512/f1/eb/a50b7323de23f6836e93f66ab0a3678b378c5fdfdbab032ac60bf07ebf5451abd79230982fd855614585f69f5d1e1082cd9eb6af914f21549216945e0806
  13. 2
      .npm-cache/_cacache/index-v5/18/3e/574d99ff72d35aa0aaf552d1e8e0462773489c28a8f0e1b44e1e8ce3b97f
  14. 2
      .npm-cache/_cacache/index-v5/19/1a/2173b7c605d7994258a0cc26df1238ca9e24197683b0649bd984b111ec3d
  15. 2
      .npm-cache/_cacache/index-v5/33/18/345a9c6711c99ab824a907825119b3bf58033a4f167564edfa7dbcd5a478
  16. 2
      .npm-cache/_cacache/index-v5/51/21/37746eca26874ae29422c488869485253e12dd5d29ad6ec0da8436041fce
  17. 2
      .npm-cache/_cacache/index-v5/7c/52/c36d8e608ac6d0f2a203d3badadb8fc1498c43610c172083bb7130217faf
  18. 2
      .npm-cache/_cacache/index-v5/83/ef/aff47278d7ef72e864717a2bb5e09eca947c1b761d2a3f590af1b5f1b379
  19. 2
      .npm-cache/_cacache/index-v5/8b/a1/8972c51bcf3f5ab54e397b8994d83532748efc1e6006adf83d48deb0b358
  20. 2
      .npm-cache/_cacache/index-v5/a1/c2/66605a237edb08e021089accf3eb606c023aed536baba4691cdc89cceb82
  21. 3
      .npm-cache/_cacache/index-v5/ac/ba/f22fa31b850d07f8d92263849db0d7019715081c71c9f9d45d4e901eec73
  22. 2
      .npm-cache/_cacache/index-v5/dd/80/b58b64a8ae4220a779ee84d832903235364ab1cfd570d45b81c0c17ecfae
  23. 0
      .npm-cache/_update-notifier-last-checked
  24. 2
      Dockerfile
  25. 1
      eslint.config.js
  26. 6
      src/components/CacheRelaysSetting/index.tsx
  27. 5
      src/components/FavoriteRelaysSetting/RelaySet.tsx
  28. 9
      src/components/NpubQrCode/index.tsx
  29. 13
      src/components/RelayIcon/index.tsx
  30. 8
      src/components/VersionUpdateBanner/index.tsx
  31. 74
      src/lib/error-suppression.ts
  32. 6
      src/pages/primary/NoteListPage/FeedButton.tsx
  33. 14
      src/providers/NostrProvider/index.tsx
  34. 13
      src/services/client.service.ts
  35. 61
      src/services/indexed-db.service.ts
  36. 3
      vite.config.ts

1
.gitignore vendored

@ -8,6 +8,7 @@ pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
node_modules node_modules
.npm-cache
dist dist
dist-ssr dist-ssr
dev-dist dev-dist

1
.npm-cache/_cacache/content-v2/sha512/20/79/bd6cdead1741a56336784107c6a849cb8898ab6c1d9794ae30d548265d6fb6d8a4288bcf6939c4591c7c498beca25521116b8cf8e3be52fb4b3319836053 vendored

File diff suppressed because one or more lines are too long

BIN
.npm-cache/_cacache/content-v2/sha512/2e/05/5332942d228a428bbf5225e0e23f44df5a2e4234473f2ff691753877c88be66574e785e5a92a3499867bcc97c8976c2d6d160f607471c330ba15ec29c6fb vendored

Binary file not shown.

1
.npm-cache/_cacache/content-v2/sha512/2f/87/632af0ab08306cd761d6ca70d98fe257589ab3ec142176f73c6499b64dd4c7c8d24071110ffb889ff91c96332c52d6cbf4d38a5cb64b3e5c28e3b74b2d3c vendored

@ -1 +0,0 @@
{"source":1112455,"name":"lodash","dependency":"lodash","title":"Lodash has Prototype Pollution Vulnerability in `_.unset` and `_.omit` functions","url":"https://github.com/advisories/GHSA-xxjr-mmjv-4gpg","severity":"moderate","versions":["0.1.0","0.2.0","0.2.1","0.2.2","0.3.0","0.3.1","0.3.2","0.4.0","0.4.1","0.4.2","0.5.0-rc.1","0.5.0","0.5.1","0.5.2","0.6.0","0.6.1","0.7.0","0.8.0","0.8.1","0.8.2","0.9.0","0.9.1","0.9.2","0.10.0","1.0.0-rc.1","1.0.0-rc.2","1.0.0-rc.3","1.0.0","1.0.1","1.0.2","1.1.0","1.1.1","1.2.0","1.2.1","1.3.0","1.3.1","2.0.0","2.1.0","2.2.0","2.2.1","2.3.0","2.4.0","2.4.1","2.4.2","3.0.0","3.0.1","3.1.0","3.2.0","3.3.0","3.3.1","3.4.0","3.5.0","3.6.0","3.7.0","3.8.0","3.9.0","3.9.1","3.9.2","3.9.3","3.10.0","3.10.1","4.0.0","4.0.1","4.1.0","4.2.0","4.2.1","4.3.0","4.4.0","4.5.0","4.5.1","4.6.0","4.6.1","4.7.0","4.8.0","4.8.1","4.8.2","4.9.0","4.10.0","4.11.0","4.11.1","4.11.2","4.12.0","4.13.0","4.13.1","4.14.0","4.14.1","4.14.2","4.15.0","4.16.0","4.16.1","4.16.2","4.16.3","4.16.4","4.16.5","4.16.6","4.17.0","4.17.1","4.17.2","4.17.3","4.17.4","4.17.5","4.17.9","4.17.10","4.17.11","4.17.12","4.17.13","4.17.14","4.17.15","4.17.16","4.17.17","4.17.18","4.17.19","4.17.20","4.17.21","4.17.23"],"vulnerableVersions":["4.0.0","4.0.1","4.1.0","4.2.0","4.2.1","4.3.0","4.4.0","4.5.0","4.5.1","4.6.0","4.6.1","4.7.0","4.8.0","4.8.1","4.8.2","4.9.0","4.10.0","4.11.0","4.11.1","4.11.2","4.12.0","4.13.0","4.13.1","4.14.0","4.14.1","4.14.2","4.15.0","4.16.0","4.16.1","4.16.2","4.16.3","4.16.4","4.16.5","4.16.6","4.17.0","4.17.1","4.17.2","4.17.3","4.17.4","4.17.5","4.17.9","4.17.10","4.17.11","4.17.12","4.17.13","4.17.14","4.17.15","4.17.16","4.17.17","4.17.18","4.17.19","4.17.20","4.17.21"],"cwe":["CWE-1321"],"cvss":{"score":6.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L"},"range":">=4.0.0 <=4.17.22","id":"OGNA5lR8rR8aeucWHqvJLBV5oaroI/lF2JfG9J2l63dx9uiGUyHuI4ymC6nv+dqWwxtqCIMcqBukiWYEiF5/RQ=="}

1
.npm-cache/_cacache/content-v2/sha512/4e/f4/23941d402feb258f35702097f9a2d0e3b22554ce34feb0d3b430df4e245db1058be9e5e1555fc6005a34941d863bb719dc7867b9bf4b5ff60e8137a960e2 vendored

@ -1 +0,0 @@
{"source":"OGNA5lR8rR8aeucWHqvJLBV5oaroI/lF2JfG9J2l63dx9uiGUyHuI4ymC6nv+dqWwxtqCIMcqBukiWYEiF5/RQ==","name":"workbox-build","dependency":"lodash","title":"Depends on vulnerable versions of lodash","url":null,"severity":"moderate","versions":["0.0.1","0.0.2","0.0.3","1.0.0","1.0.1","1.1.0","1.2.0","1.3.0","2.0.0","2.0.1","2.0.2-2.0.2-rc1.0","2.0.2-rc1","2.0.2","2.0.3","2.1.0","2.1.1","2.1.2","2.1.3","3.0.0-alpha.1","3.0.0-alpha.2","3.0.0-alpha.3","3.0.0-alpha.4","3.0.0-alpha.5","3.0.0-alpha.6","3.0.0-beta.0","3.0.0-beta.1","3.0.0-beta.2","3.0.0","3.0.1","3.1.0","3.2.0","3.3.0","3.3.1","3.4.1","3.5.0","3.6.1","3.6.2","3.6.3","4.0.0-alpha.0","4.0.0-beta.0","4.0.0-beta.1","4.0.0-beta.2","4.0.0-rc.0","4.0.0-rc.1","4.0.0-rc.2","4.0.0-rc.3","4.0.0-rc.4","4.0.0","4.1.0","4.1.1","4.2.0","4.3.0","4.3.1","5.0.0-alpha.0","5.0.0-alpha.1","5.0.0-alpha.2","5.0.0-beta.0","5.0.0-beta.1","5.0.0-rc.0","5.0.0-rc.1","5.0.0-rc.2","5.0.0","5.1.0","5.1.1","5.1.2","5.1.3","5.1.4","6.0.0-alpha.0","6.0.0-alpha.1","6.0.0-alpha.2","6.0.0-alpha.3","6.0.0-rc.0","6.0.0","6.0.2","6.1.0","6.1.1","6.1.2","6.1.5","6.2.0-alpha.0","6.2.0-alpha.1","6.2.0-alpha.2","6.2.0","6.2.1","6.2.2","6.2.3","6.2.4","6.3.0","6.4.0","6.4.1","6.4.2","6.5.0","6.5.1","6.5.2","6.5.3","6.5.4","6.6.0","6.6.1","7.0.0","7.1.0","7.1.1","7.3.0","7.4.0"],"vulnerableVersions":[],"cwe":["CWE-1321"],"cvss":{"score":6.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L"},"range":"<0.0.0-0","id":"1QbBhWMbuLJj1mAZkIDrToBu1oWaLdyYvi+aTUtkHjoUtQdipuOatnRo5VmNSDzxd/JjrSj+O+7GcHYQSj6p9w=="}

BIN
.npm-cache/_cacache/content-v2/sha512/58/cc/fbd53d494badb89d68f69f67e7600b8fa2f86fec452193af51ea2f5db15cb3c6de5e3376e3c2f824f4bf803c4d5346c706cac0153003640748e1d0b419ad vendored

Binary file not shown.

1
.npm-cache/_cacache/content-v2/sha512/60/53/ba87b84b221bbfed8ac45c98583ede92a66ede5d531614c47029723d33f233d0ee3b00c6d385a12577919e3f5e5a829ffa76e992783d898b7d5e5f86e991 vendored

File diff suppressed because one or more lines are too long

BIN
.npm-cache/_cacache/content-v2/sha512/7e/ee/b9e9a2749f691c5f0b27c27bfd836e2d914e6e4a63a54e377a5b2c9a2a6d99fa1d6a998e9b023adc688c45fc1b2516a6d9f1406d6c98bb2a040e643d8f4f vendored

Binary file not shown.

1
.npm-cache/_cacache/content-v2/sha512/aa/e9/6808875f89ad4c591f3f26890f32d53d46b256fe3dedf936b93426f0788f2522769d10d258488d60d489e3b52f852e8319b41b629eecb2cdf61d6fc274cb vendored

File diff suppressed because one or more lines are too long

1
.npm-cache/_cacache/content-v2/sha512/af/02/37dbc7a6e88a5efa19aff3a7df266e7aa008cc4b7c540814d68da79656da15bc565dddc225184b682c44205f14df07f6ba0451493edf8ba9d717073ea013 vendored

File diff suppressed because one or more lines are too long

1
.npm-cache/_cacache/content-v2/sha512/bb/03/d33e192ae2fb385e80697a4a870ac009afe746d6d0ad43538b8154d3b2e227d14d623a0ba8d29d9f129e7c65a887510067dd57b85f8b0e76e61bd16dc9d7 vendored

File diff suppressed because one or more lines are too long

1
.npm-cache/_cacache/content-v2/sha512/f1/eb/a50b7323de23f6836e93f66ab0a3678b378c5fdfdbab032ac60bf07ebf5451abd79230982fd855614585f69f5d1e1082cd9eb6af914f21549216945e0806 vendored

File diff suppressed because one or more lines are too long

2
.npm-cache/_cacache/index-v5/18/3e/574d99ff72d35aa0aaf552d1e8e0462773489c28a8f0e1b44e1e8ce3b97f vendored

@ -1,2 +0,0 @@
8b55ce2e4803bc4f9b5b9c6dbfc4346bbde2c4cd {"key":"security-advisory:lodash:OGNA5lR8rR8aeucWHqvJLBV5oaroI/lF2JfG9J2l63dx9uiGUyHuI4ymC6nv+dqWwxtqCIMcqBukiWYEiF5/RQ==","integrity":"sha512-L4djKvCrCDBs12HWynDZj+JXWJqz7BQhdvc8ZJm2TdTHyNJAcREP+4if+RyWMyxS1sv004pctks+XCjjt0stPA==","time":1770670272984,"size":1956}

2
.npm-cache/_cacache/index-v5/19/1a/2173b7c605d7994258a0cc26df1238ca9e24197683b0649bd984b111ec3d vendored

@ -1,2 +0,0 @@
f2f8749277c277e5d385bc8d1abdd9acd69b9d04 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/@isaacs%2fbrace-expansion","integrity":"sha512-quloCIdfia1MWR8/JokPMtU9RrJW/j3t+Ta5NCbweI8lInadENJYSI1g1InjtS+FLoMZtBtinuyyzfYdb8J0yw==","time":1770670251811,"size":7366,"metadata":{"time":1770670251808,"url":"https://registry.npmjs.org/@isaacs%2fbrace-expansion","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Mon, 09 Feb 2026 20:50:51 GMT","etag":"W/\"dd5122ae424fb801a8701bfa21430df7\"","last-modified":"Tue, 03 Feb 2026 17:40:12 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}

2
.npm-cache/_cacache/index-v5/33/18/345a9c6711c99ab824a907825119b3bf58033a4f167564edfa7dbcd5a478 vendored

@ -1,2 +0,0 @@
5f3b1d4b24ed5a625440133c0cdc1a13432babc9 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/@isaacs%2fbalanced-match","integrity":"sha512-uwPTPhkq4vs4XoBpekqHCsAJr+dG1tCtQ1OLgVTTsuIn0U1iOguo0p2fEp58ZaiHUQBn3Ve4X4sOduYb0W3J1w==","time":1770670272177,"size":7823,"metadata":{"time":1770670272169,"url":"https://registry.npmjs.org/@isaacs%2fbalanced-match","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Mon, 09 Feb 2026 20:51:12 GMT","etag":"W/\"1dadbf5760c67cb3a012e02073994877\"","last-modified":"Thu, 12 Jun 2025 20:05:57 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}

2
.npm-cache/_cacache/index-v5/51/21/37746eca26874ae29422c488869485253e12dd5d29ad6ec0da8436041fce vendored

@ -1,2 +0,0 @@
a0bcda3df3cd894dada40500fb3b2b1a950388ae {"key":"security-advisory:workbox-build:1QbBhWMbuLJj1mAZkIDrToBu1oWaLdyYvi+aTUtkHjoUtQdipuOatnRo5VmNSDzxd/JjrSj+O+7GcHYQSj6p9w==","integrity":"sha512-TvQjlB1AL+sljzVwIJf5otDjsiVUzjT+sNO0MN9OJF2xBYvp5eFVX8YAWjSUHYY7txnceGe5v0tf9g6BN6lg4g==","time":1770670273112,"size":1553}

2
.npm-cache/_cacache/index-v5/7c/52/c36d8e608ac6d0f2a203d3badadb8fc1498c43610c172083bb7130217faf vendored

@ -1,2 +0,0 @@
9970619c5e0b79de00c0ba7ea7f43c503ca53d44 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/workbox-build","integrity":"sha512-IHm9bN6tF0GlYzZ4QQfGqEnLiJirbB2XlK4w1UgmXW+22KQoi89pOcRZHHxJi+yiVSERa4z4475S+0szGYNgUw==","time":1770670273105,"size":192745,"metadata":{"time":1770670273056,"url":"https://registry.npmjs.org/workbox-build","reqHeaders":{"accept":"application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/vnd.npm.install-v1+json","date":"Mon, 09 Feb 2026 20:51:13 GMT","etag":"W/\"933ffd9bb6d73318dc1a36e4acbf6e41\"","last-modified":"Wed, 19 Nov 2025 17:46:40 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}

2
.npm-cache/_cacache/index-v5/83/ef/aff47278d7ef72e864717a2bb5e09eca947c1b761d2a3f590af1b5f1b379 vendored

@ -1,2 +0,0 @@
032d2aba7ad86f5119ac8318e1da5d474df636fc {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/minimatch","integrity":"sha512-rwI328em6Ipe+hmv86ffJm56oAjMS3xUCBTWjaeWVtoVvFZd3cIlGEtoLEQgXxTfB/a6BFFJPt+LqdcXBz6gEw==","time":1770670251712,"size":306302,"metadata":{"time":1770670251579,"url":"https://registry.npmjs.org/minimatch","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Mon, 09 Feb 2026 20:50:51 GMT","etag":"W/\"56fb93bca9c9cf83a448322f6cbe1343\"","last-modified":"Tue, 03 Feb 2026 17:50:23 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}

2
.npm-cache/_cacache/index-v5/8b/a1/8972c51bcf3f5ab54e397b8994d83532748efc1e6006adf83d48deb0b358 vendored

@ -1,2 +0,0 @@
021dd286d8966b5dc9a1c1f7ec6f916f20b95993 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz","integrity":"sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==","time":1770670292053,"size":314877,"metadata":{"time":1770670291894,"url":"https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Mon, 09 Feb 2026 20:51:31 GMT","etag":"\"465377c7efb78593918ee117d7a5923b\"","last-modified":"Wed, 21 Jan 2026 17:29:54 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}

2
.npm-cache/_cacache/index-v5/a1/c2/66605a237edb08e021089accf3eb606c023aed536baba4691cdc89cceb82 vendored

@ -1,2 +0,0 @@
affc7b951b88611ffd85292873013320e02ec78d {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz","integrity":"sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==","time":1770670272594,"size":97757,"metadata":{"time":1770670272523,"url":"https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, immutable, max-age=31557600","content-type":"application/octet-stream","date":"Mon, 09 Feb 2026 20:51:12 GMT","etag":"\"4e9fb8701092f48a6d6e56aef4d597b6\"","last-modified":"Tue, 03 Feb 2026 17:50:23 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}

3
.npm-cache/_cacache/index-v5/ac/ba/f22fa31b850d07f8d92263849db0d7019715081c71c9f9d45d4e901eec73 vendored

@ -1,3 +0,0 @@
72e7142e032e7138b366c1908bebee7b17ec2c6e {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/lodash","integrity":"sha512-YFO6h7hLIhu/7YrEXJhYPt6Spm7eXVMWFMRwKXI9M/Iz0O47AMbThaEld5GeP15agp/6dumSeD2Ji31eX4bpkQ==","time":1770670272978,"size":68481,"metadata":{"time":1770670272600,"url":"https://registry.npmjs.org/lodash","reqHeaders":{"accept":"application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/vnd.npm.install-v1+json","date":"Mon, 09 Feb 2026 20:51:12 GMT","etag":"W/\"c4baa319a07c36c7ee907a9823db7825\"","last-modified":"Fri, 23 Jan 2026 09:31:29 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}
f2e64a394a6452ff829fdaf1f53353e6661aa3fa {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/lodash","integrity":"sha512-8eulC3Mj3iP2g26T9mqwo2eLN4xf39urAyrGC/B+v1RRq9eSMJgv2FVhRYX2n10eEILNnravkU8hVJIWlF4IBg==","time":1770670291599,"size":244244,"metadata":{"time":1770670291587,"url":"https://registry.npmjs.org/lodash","reqHeaders":{"accept":"application/json"},"resHeaders":{"cache-control":"public, max-age=300","content-encoding":"gzip","content-type":"application/json","date":"Mon, 09 Feb 2026 20:51:31 GMT","etag":"W/\"be9f341b4c524ad8fe80626a8776d44a\"","last-modified":"Fri, 23 Jan 2026 09:31:29 GMT","vary":"accept-encoding, accept"},"options":{"compress":true}}}

2
.npm-cache/_cacache/index-v5/dd/80/b58b64a8ae4220a779ee84d832903235364ab1cfd570d45b81c0c17ecfae vendored

@ -1,2 +0,0 @@
42849814c93e9204098299b7dd7a0394b05d3b34 {"key":"make-fetch-happen:request-cache:https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz","integrity":"sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==","time":1770670272539,"size":7778,"metadata":{"time":1770670272516,"url":"https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz","reqHeaders":{},"resHeaders":{"cache-control":"public, must-revalidate, max-age=31557600","content-type":"application/octet-stream","date":"Mon, 09 Feb 2026 20:51:12 GMT","etag":"\"96c583188cf6c62158f3eaf5e0024e48\"","last-modified":"Tue, 03 Feb 2026 17:40:12 GMT","vary":"Accept-Encoding"},"options":{"compress":true}}}

0
.npm-cache/_update-notifier-last-checked vendored

2
Dockerfile

@ -1,5 +1,5 @@
# Step 1: Build the application # Step 1: Build the application
FROM node:alpine AS builder FROM node:20-alpine AS builder
ARG VITE_PROXY_SERVER ARG VITE_PROXY_SERVER
ENV VITE_PROXY_SERVER=${VITE_PROXY_SERVER} ENV VITE_PROXY_SERVER=${VITE_PROXY_SERVER}

1
eslint.config.js

@ -23,7 +23,6 @@ export default tseslint.config(
'@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-function-return-type': 'off',
'react/prop-types': 'off', 'react/prop-types': 'off',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'react-refresh/only-export-components': 'off',
'react-hooks/exhaustive-deps': 'off', 'react-hooks/exhaustive-deps': 'off',
'@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }] '@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }]
} }

6
src/components/CacheRelaysSetting/index.tsx

@ -312,8 +312,8 @@ export default function CacheRelaysSetting() {
let unregisteredCount = 0 let unregisteredCount = 0
let cacheClearedCount = 0 let cacheClearedCount = 0
// Check for service worker support // Check for service worker support and secure context (SW API throws in insecure contexts)
if ('serviceWorker' in navigator) { if (window.isSecureContext && 'serviceWorker' in navigator) {
// Get all service worker registrations // Get all service worker registrations
let registrations: readonly ServiceWorkerRegistration[] = [] let registrations: readonly ServiceWorkerRegistration[] = []
try { try {
@ -1380,7 +1380,7 @@ export default function CacheRelaysSetting() {
</Drawer> </Drawer>
) : ( ) : (
<Dialog open={showConsoleLogs} onOpenChange={setShowConsoleLogs}> <Dialog open={showConsoleLogs} onOpenChange={setShowConsoleLogs}>
<DialogContent className="max-w-4xl max-h-[80vh] flex flex-col"> <DialogContent className="max-w-4xl max-h-[80vh] flex flex-col" withoutClose>
<DialogHeader> <DialogHeader>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex-1"> <div className="flex-1">

5
src/components/FavoriteRelaysSetting/RelaySet.tsx

@ -1,5 +1,5 @@
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer' import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer'
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@ -164,6 +164,9 @@ function RelaySetOptions({ relaySet }: { relaySet: TRelaySet }) {
<Drawer> <Drawer>
<DrawerTrigger asChild>{trigger}</DrawerTrigger> <DrawerTrigger asChild>{trigger}</DrawerTrigger>
<DrawerContent> <DrawerContent>
<DrawerHeader className="sr-only">
<DrawerTitle>{t('Relay set options')}</DrawerTitle>
</DrawerHeader>
<div className="py-2"> <div className="py-2">
<DrawerMenuItem onClick={rename}> <DrawerMenuItem onClick={rename}>
<Edit /> <Edit />

9
src/components/NpubQrCode/index.tsx

@ -1,5 +1,5 @@
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer' import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer'
import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { QrCodeIcon } from 'lucide-react' import { QrCodeIcon } from 'lucide-react'
import { nip19 } from 'nostr-tools' import { nip19 } from 'nostr-tools'
@ -41,7 +41,12 @@ export default function NpubQrCode({ pubkey }: { pubkey: string }) {
return ( return (
<Drawer> <Drawer>
<DrawerTrigger>{trigger}</DrawerTrigger> <DrawerTrigger>{trigger}</DrawerTrigger>
<DrawerContent>{content}</DrawerContent> <DrawerContent>
<DrawerHeader className="sr-only">
<DrawerTitle>Profile QR Code</DrawerTitle>
</DrawerHeader>
{content}
</DrawerContent>
</Drawer> </Drawer>
) )
} }

13
src/components/RelayIcon/index.tsx

@ -15,17 +15,22 @@ export default function RelayIcon({
}) { }) {
const { relayInfo } = useFetchRelayInfo(url) const { relayInfo } = useFetchRelayInfo(url)
const iconUrl = useMemo(() => { const iconUrl = useMemo(() => {
if (relayInfo?.icon) { if (relayInfo?.icon && typeof relayInfo.icon === 'string' && relayInfo.icon.startsWith('http')) {
return relayInfo.icon return relayInfo.icon
} }
if (!url) return if (!url) return undefined
try {
const u = new URL(url) const u = new URL(url)
return `${u.protocol === 'wss:' ? 'https:' : 'http:'}//${u.host}/favicon.ico` const href = `${u.protocol === 'wss:' ? 'https:' : 'http:'}//${u.host}/favicon.ico`
return href
} catch {
return undefined
}
}, [url, relayInfo]) }, [url, relayInfo])
return ( return (
<Avatar className={cn('w-6 h-6', className)}> <Avatar className={cn('w-6 h-6', className)}>
<AvatarImage src={iconUrl} className="object-cover object-center" /> {iconUrl && <AvatarImage src={iconUrl} className="object-cover object-center" />}
<AvatarFallback> <AvatarFallback>
<Server size={iconSize} /> <Server size={iconSize} />
</AvatarFallback> </AvatarFallback>

8
src/components/VersionUpdateBanner/index.tsx

@ -11,7 +11,8 @@ export default function VersionUpdateBanner() {
const [isUpdating, setIsUpdating] = useState(false) const [isUpdating, setIsUpdating] = useState(false)
useEffect(() => { useEffect(() => {
if (typeof window === 'undefined' || !('serviceWorker' in navigator)) { // Skip in dev: no SW is registered (vite-plugin-pwa devOptions.enabled: false), and .ready can reject with "operation is insecure"
if (import.meta.env.DEV || typeof window === 'undefined' || !window.isSecureContext || !('serviceWorker' in navigator)) {
return return
} }
@ -65,11 +66,12 @@ export default function VersionUpdateBanner() {
} }
} }
} catch (error) { } catch (error) {
logger.error('Error checking for updates', { error }) // In non-secure contexts or when no SW is registered, ready can reject with "The operation is insecure"
logger.debug('Service worker update check skipped or failed', { error })
} }
} }
checkForUpdates() checkForUpdates().catch(() => {})
}, []) }, [])
const handleUpdate = () => { const handleUpdate = () => {

74
src/lib/error-suppression.ts

@ -59,11 +59,13 @@ export function suppressExpectedErrors() {
// Suppress Radix UI Dialog accessibility warnings // Suppress Radix UI Dialog accessibility warnings
// These are informational warnings about DialogTitle/Description // These are informational warnings about DialogTitle/Description
// All our dialogs have titles (some hidden with sr-only for accessibility) // All our dialogs have titles (some hidden with sr-only for accessibility)
if (message.includes('DialogContent') && ( const isRadixDialogWarning =
message.includes('requires a DialogTitle') || (message.includes('DialogContent') || message.includes('DialogTitle')) &&
message.includes('Missing `Description`') || (message.includes('requires') ||
message.includes('aria-describedby') message.includes('Missing') ||
)) { message.includes('aria-describedby') ||
message.includes('DialogTitle'))
if (isRadixDialogWarning) {
return return
} }
@ -119,6 +121,15 @@ export function suppressExpectedErrors() {
return return
} }
// Suppress invalid URI / media resource errors (e.g. empty img src resolving to origin)
if (message.includes('Ungültige URI') ||
message.includes('Invalid URI') ||
message.includes('Laden der Medienressource fehlgeschlagen') ||
message.includes('Failed to load media resource') ||
message.includes('OpaqueResponseBlocking')) {
return
}
// Suppress "unrecognised filter item" errors from relays // Suppress "unrecognised filter item" errors from relays
if (message.includes('unrecognised filter item') || message.includes('unrecognized filter item')) { if (message.includes('unrecognised filter item') || message.includes('unrecognized filter item')) {
return return
@ -134,6 +145,14 @@ export function suppressExpectedErrors() {
console.warn = (...args: any[]) => { console.warn = (...args: any[]) => {
const message = args.join(' ') const message = args.join(' ')
// Suppress invalid URI / failed media resource (e.g. empty img src)
if (message.includes('Ungültige URI') ||
message.includes('Invalid URI') ||
message.includes('Laden der Medienressource') ||
message.includes('Failed to load media resource')) {
return
}
// Suppress React DevTools suggestion (only show once) // Suppress React DevTools suggestion (only show once)
if (message.includes('Download the React DevTools')) { if (message.includes('Download the React DevTools')) {
if (suppressedErrors.has('react-devtools')) { if (suppressedErrors.has('react-devtools')) {
@ -180,11 +199,21 @@ export function suppressExpectedErrors() {
// Suppress Radix UI Dialog accessibility warnings // Suppress Radix UI Dialog accessibility warnings
// These are informational warnings about DialogTitle/Description // These are informational warnings about DialogTitle/Description
// All our dialogs have titles (some hidden with sr-only for accessibility) // All our dialogs have titles (some hidden with sr-only for accessibility)
if (message.includes('DialogContent') && ( const isRadixDialogWarn =
message.includes('requires a DialogTitle') || (message.includes('DialogContent') || message.includes('DialogTitle')) &&
message.includes('Missing `Description`') || (message.includes('requires') ||
message.includes('aria-describedby') message.includes('Missing') ||
)) { message.includes('aria-describedby') ||
message.includes('DialogTitle'))
if (isRadixDialogWarn) {
return
}
// Suppress Nostr relay NOTICE messages (too many subscriptions, too many REQs, etc.)
if (message.includes('NOTICE from') ||
message.includes('Too many subscriptions') ||
message.includes('Subscription rejected') ||
message.includes('too many concurrent REQs')) {
return return
} }
@ -198,13 +227,21 @@ export function suppressExpectedErrors() {
console.log = (...args: any[]) => { console.log = (...args: any[]) => {
const message = args.join(' ') const message = args.join(' ')
// Suppress React DevTools suggestion (only show once)
if (message.includes('Download the React DevTools')) {
return
}
// Suppress Workbox logs // Suppress Workbox logs
if (message.includes('workbox') || message.includes('[NoteStats]')) { if (message.includes('workbox') || message.includes('[NoteStats]')) {
return return
} }
// Suppress nostr-tools notices (ping, etc.) // Suppress nostr-tools / relay NOTICE messages (subscription limits, REQ limits, etc.)
if (message.includes('NOTICE from')) { if (message.includes('NOTICE from') ||
message.includes('Too many subscriptions') ||
message.includes('Subscription rejected') ||
message.includes('too many concurrent REQs')) {
return return
} }
@ -213,7 +250,20 @@ export function suppressExpectedErrors() {
} }
} }
// Suppress unhandled promise rejections that are expected (e.g. SW "operation is insecure" in dev)
function suppressExpectedRejections() {
if (typeof window === 'undefined') return
window.addEventListener('unhandledrejection', (event) => {
const msg = event.reason?.message ?? String(event.reason)
if (msg.includes('The operation is insecure') || (event.reason?.name === 'SecurityError' && msg.includes('insecure'))) {
event.preventDefault()
event.stopPropagation()
}
})
}
// Initialize error suppression // Initialize error suppression
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
suppressExpectedErrors() suppressExpectedErrors()
suppressExpectedRejections()
} }

6
src/pages/primary/NoteListPage/FeedButton.tsx

@ -1,5 +1,5 @@
import FeedSwitcher from '@/components/FeedSwitcher' import FeedSwitcher from '@/components/FeedSwitcher'
import { Drawer, DrawerContent } from '@/components/ui/drawer' import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@/components/ui/drawer'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { simplifyUrl } from '@/lib/url' import { simplifyUrl } from '@/lib/url'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@ -11,6 +11,7 @@ import { forwardRef, HTMLAttributes, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
export default function FeedButton({ className }: { className?: string }) { export default function FeedButton({ className }: { className?: string }) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize() const { isSmallScreen } = useScreenSize()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@ -20,6 +21,9 @@ export default function FeedButton({ className }: { className?: string }) {
<FeedSwitcherTrigger className={className} onClick={() => setOpen(true)} /> <FeedSwitcherTrigger className={className} onClick={() => setOpen(true)} />
<Drawer open={open} onOpenChange={setOpen}> <Drawer open={open} onOpenChange={setOpen}>
<DrawerContent className="max-h-[80vh]"> <DrawerContent className="max-h-[80vh]">
<DrawerHeader className="sr-only">
<DrawerTitle>{t('Choose feed')}</DrawerTitle>
</DrawerHeader>
<div <div
className="overflow-y-auto overscroll-contain py-2 px-4" className="overflow-y-auto overscroll-contain py-2 px-4"
style={{ touchAction: 'pan-y' }} style={{ touchAction: 'pan-y' }}

14
src/providers/NostrProvider/index.tsx

@ -329,7 +329,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
} }
if (storedRssFeedListEvent) { if (storedRssFeedListEvent) {
setRssFeedListEvent(storedRssFeedListEvent) setRssFeedListEvent(storedRssFeedListEvent)
logger.info('[NostrProvider] Loaded RSS feed list event from cache', { logger.debug('[NostrProvider] Loaded RSS feed list event from cache', {
eventId: storedRssFeedListEvent.id, eventId: storedRssFeedListEvent.id,
created_at: storedRssFeedListEvent.created_at created_at: storedRssFeedListEvent.created_at
}) })
@ -340,7 +340,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
(dayjs().unix() - storedRssFeedListEvent.created_at > 3600) // 1 hour (dayjs().unix() - storedRssFeedListEvent.created_at > 3600) // 1 hour
if (rssFeedListStale) { if (rssFeedListStale) {
logger.info('[NostrProvider] RSS feed list cache is missing or stale, fetching from relays', { logger.debug('[NostrProvider] RSS feed list cache is missing or stale, fetching from relays', {
hasCache: !!storedRssFeedListEvent, hasCache: !!storedRssFeedListEvent,
cacheAge: storedRssFeedListEvent ? dayjs().unix() - storedRssFeedListEvent.created_at : 'N/A' cacheAge: storedRssFeedListEvent ? dayjs().unix() - storedRssFeedListEvent.created_at : 'N/A'
}) })
@ -355,32 +355,32 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
if (latestEvent) { if (latestEvent) {
// Only update if the fetched event is newer than cached // Only update if the fetched event is newer than cached
if (!storedRssFeedListEvent || latestEvent.created_at > storedRssFeedListEvent.created_at) { if (!storedRssFeedListEvent || latestEvent.created_at > storedRssFeedListEvent.created_at) {
logger.info('[NostrProvider] Found newer RSS feed list event from relays', { logger.debug('[NostrProvider] Found newer RSS feed list event from relays', {
eventId: latestEvent.id, eventId: latestEvent.id,
created_at: latestEvent.created_at, created_at: latestEvent.created_at,
wasCached: !!storedRssFeedListEvent wasCached: !!storedRssFeedListEvent
}) })
indexedDb.putReplaceableEvent(latestEvent).then(() => { indexedDb.putReplaceableEvent(latestEvent).then(() => {
setRssFeedListEvent(latestEvent) setRssFeedListEvent(latestEvent)
logger.info('[NostrProvider] Updated RSS feed list event in cache and state') logger.debug('[NostrProvider] Updated RSS feed list event in cache and state')
}).catch(err => { }).catch(err => {
logger.error('[NostrProvider] Failed to cache RSS feed list event', { error: err }) logger.error('[NostrProvider] Failed to cache RSS feed list event', { error: err })
}) })
} else { } else {
logger.info('[NostrProvider] Cached RSS feed list event is up to date', { logger.debug('[NostrProvider] Cached RSS feed list event is up to date', {
cachedCreatedAt: storedRssFeedListEvent.created_at, cachedCreatedAt: storedRssFeedListEvent.created_at,
fetchedCreatedAt: latestEvent.created_at fetchedCreatedAt: latestEvent.created_at
}) })
} }
} else if (!storedRssFeedListEvent) { } else if (!storedRssFeedListEvent) {
logger.info('[NostrProvider] No RSS feed list event found on relays (user may not have created one yet)') logger.debug('[NostrProvider] No RSS feed list event found on relays (user may not have created one yet)')
} }
}).catch(err => { }).catch(err => {
logger.error('[NostrProvider] Failed to fetch RSS feed list from relays', { error: err }) logger.error('[NostrProvider] Failed to fetch RSS feed list from relays', { error: err })
// Don't clear cache on fetch error - use cached value // Don't clear cache on fetch error - use cached value
}) })
} else { } else {
logger.info('[NostrProvider] RSS feed list cache is fresh, using cached value') logger.debug('[NostrProvider] RSS feed list cache is fresh, using cached value')
} }
const [relayListEvents, cacheRelayListEvents] = await Promise.all([ const [relayListEvents, cacheRelayListEvents] = await Promise.all([

13
src/services/client.service.ts

@ -65,6 +65,10 @@ class ClientService extends EventTarget {
tokenize: 'forward' tokenize: 'forward'
}) })
/** Min delay between starting subscriptions to the same relay to avoid hammering one relay with many REQs */
private static readonly PER_RELAY_SUB_STAGGER_MS = 80
private lastSubStartByRelay = new Map<string, number>()
constructor() { constructor() {
super() super()
this.pool = new SimplePool() this.pool = new SimplePool()
@ -652,12 +656,21 @@ class ClientService extends EventTarget {
let closedCount = 0 let closedCount = 0
const closeReasons: string[] = [] const closeReasons: string[] = []
const subPromises: Promise<{ close: () => void }>[] = [] const subPromises: Promise<{ close: () => void }>[] = []
const staggerMs = ClientService.PER_RELAY_SUB_STAGGER_MS
relays.forEach((url) => { relays.forEach((url) => {
let hasAuthed = false let hasAuthed = false
subPromises.push(startSub()) subPromises.push(startSub())
async function startSub() { async function startSub() {
const relayKey = normalizeUrl(url) || url
const now = Date.now()
const last = that.lastSubStartByRelay.get(relayKey) ?? 0
const wait = staggerMs - (now - last)
if (wait > 0) {
await new Promise<void>((r) => setTimeout(r, wait))
}
that.lastSubStartByRelay.set(relayKey, Date.now())
startedCount++ startedCount++
const relay = await that.pool.ensureRelay(url, { connectionTimeout: 5000 }).catch(() => { const relay = await that.pool.ensureRelay(url, { connectionTimeout: 5000 }).catch(() => {
return undefined return undefined

61
src/services/indexed-db.service.ts

@ -1,7 +1,8 @@
import { ExtendedKind } from '@/constants' import { ExtendedKind } from '@/constants'
import { tagNameEquals } from '@/lib/tag' import { tagNameEquals } from '@/lib/tag'
import { TRelayInfo } from '@/types' import { TRelayInfo } from '@/types'
import { Event, kinds } from 'nostr-tools' import type { Event } from 'nostr-tools'
import { kinds } from 'nostr-tools'
import { isReplaceableEvent } from '@/lib/event' import { isReplaceableEvent } from '@/lib/event'
import logger from '@/lib/logger' import logger from '@/lib/logger'
@ -36,6 +37,9 @@ export const StoreNames = {
PUBLICATION_EVENTS: 'publicationEvents' PUBLICATION_EVENTS: 'publicationEvents'
} }
/** Schema version we expect. When adding stores or migrations, bump this. */
const DB_VERSION = 17
/** Convert IDB request.onerror Event to a proper Error for logging and UI */ /** Convert IDB request.onerror Event to a proper Error for logging and UI */
function idbEventToError(ev: Parameters<NonNullable<IDBRequest['onerror']>>[0]): Error { function idbEventToError(ev: Parameters<NonNullable<IDBRequest['onerror']>>[0]): Error {
const request = ev.target as IDBRequest const request = ev.target as IDBRequest
@ -59,18 +63,59 @@ class IndexedDbService {
init(): Promise<void> { init(): Promise<void> {
if (!this.initPromise) { if (!this.initPromise) {
this.initPromise = new Promise<void>((resolve) => { this.initPromise = this.openDb()
const request = window.indexedDB.open('jumble', 17) }
return this.initPromise
}
private openDb(): Promise<void> {
return new Promise<void>((resolve) => {
const request = window.indexedDB.open('jumble', DB_VERSION)
request.onerror = (event) => { request.onerror = (event) => {
// Resolve instead of reject so the app can run without IndexedDB (e.g. mobile private mode) const err = idbEventToError(event)
logger.warn('IndexedDB unavailable, running without local cache', idbEventToError(event)) const isHigherVersion =
err.message.includes('higher version') || err.message.includes('version requested')
if (isHigherVersion) {
// Stored DB is newer than our DB_VERSION (e.g. other tab or previous deploy). We cannot
// open with a lower version. Use the existing DB at its version so the app keeps working.
// When we later bump DB_VERSION and ship new code, users at lower stored version will
// open with our new version and run onupgradeneeded as usual.
const probe = window.indexedDB.open('jumble')
probe.onerror = () => {
logger.warn('IndexedDB unavailable, running without local cache', err)
this.db = null
resolve()
}
probe.onsuccess = () => {
const probeDb = probe.result
const storedVersion = probeDb.version
probeDb.close()
const openWithStored = window.indexedDB.open('jumble', storedVersion)
openWithStored.onerror = (e) => {
logger.warn('IndexedDB unavailable, running without local cache', idbEventToError(e))
this.db = null
resolve()
}
openWithStored.onsuccess = () => {
this.db = openWithStored.result
setTimeout(() => this.cleanUp(), 1000 * 60)
resolve()
}
openWithStored.onupgradeneeded = () => {
// Should not fire when opening with existing version
}
}
return
}
logger.warn('IndexedDB unavailable, running without local cache', err)
this.db = null this.db = null
resolve() resolve()
} }
request.onsuccess = () => { request.onsuccess = () => {
this.db = request.result this.db = request.result
setTimeout(() => this.cleanUp(), 1000 * 60)
resolve() resolve()
} }
@ -142,10 +187,8 @@ class IndexedDbService {
store.createIndex('pubDate', 'pubDate', { unique: false }) store.createIndex('pubDate', 'pubDate', { unique: false })
} }
} }
})
setTimeout(() => this.cleanUp(), 1000 * 60) // 1 minute
} }
return this.initPromise );
} }
async putNullReplaceableEvent(pubkey: string, kind: number, d?: string) { async putNullReplaceableEvent(pubkey: string, kind: number, d?: string) {
@ -1212,7 +1255,7 @@ class IndexedDbService {
// Check current version // Check current version
const checkRequest = window.indexedDB.open('jumble') const checkRequest = window.indexedDB.open('jumble')
let currentVersion = 14 let currentVersion = DB_VERSION
checkRequest.onsuccess = () => { checkRequest.onsuccess = () => {
const db = checkRequest.result const db = checkRequest.result
currentVersion = db.version currentVersion = db.version

3
vite.config.ts

@ -130,7 +130,8 @@ export default defineConfig({
] ]
}, },
devOptions: { devOptions: {
enabled: true, // Disable in dev to avoid registerSW.js 404 → index.html returned → SyntaxError (expected expression, got '<')
enabled: false,
type: 'module' type: 'module'
}, },
manifest: { manifest: {

Loading…
Cancel
Save