personal web client for Bluesky
typescript solidjs bluesky atcute

feat: service worker caching

mary.my.id 989b6577 2c7a5ec9

verified
+3
.npmrc
··· 1 + auto-install-peers=false 2 + public-hoist-pattern[]=workbox-window 3 + 1 4 @jsr:registry=https://npm.jsr.io
+2 -2
package.json
··· 38 38 "terser": "^5.31.6", 39 39 "typescript": "5.6.0-beta", 40 40 "vite": "^5.4.2", 41 - "vite-plugin-pwa": "0.17.4", 41 + "vite-plugin-pwa": "0.20.5", 42 42 "vite-plugin-solid": "^2.10.2", 43 43 "wrangler": "^3.72.3" 44 44 }, ··· 46 46 "patchedDependencies": { 47 47 "@tanstack/query-core@5.17.19": "patches/@tanstack__query-core@5.17.19.patch", 48 48 "solid-js": "patches/solid-js.patch", 49 - "vite-plugin-pwa@0.17.4": "patches/vite-plugin-pwa@0.17.4.patch", 49 + "vite-plugin-pwa": "patches/vite-plugin-pwa.patch", 50 50 "vite": "patches/vite.patch", 51 51 "workbox-precaching@7.1.0": "patches/workbox-precaching@7.1.0.patch" 52 52 }
+8 -9
patches/vite-plugin-pwa@0.17.4.patch patches/vite-plugin-pwa.patch
··· 1 1 diff --git a/dist/client/build/register.js b/dist/client/build/register.js 2 - index 0dc588160b1a58778ebc02757d9757e45cb212e3..aef9ecfc0eee73fd8406bfa3025e38ba2468064c 100644 2 + index 95340c19195a56fb0ff3f9a24b00e4ed8ce08858..dc9fb67b8e1dc3ace58cf0322afbf4f2a73dacf7 100644 3 3 --- a/dist/client/build/register.js 4 4 +++ b/dist/client/build/register.js 5 - @@ -7,6 +7,7 @@ function registerSW(options = {}) { 5 + @@ -6,6 +6,7 @@ var autoDestroy = selfDestroying === "true"; 6 + function registerSW(options = {}) { 6 7 const { 7 8 immediate = false, 8 - onNeedRefresh, 9 9 + onBeginUpdate, 10 + onNeedRefresh, 10 11 onOfflineReady, 11 12 onRegistered, 12 - onRegisteredSW, 13 - @@ -71,6 +72,12 @@ function registerSW(options = {}) { 13 + @@ -77,6 +78,12 @@ function registerSW(options = {}) { 14 14 } 15 15 } 16 16 wb.register({ immediate }).then((r) => { ··· 24 24 onRegisteredSW("__SW__", r); 25 25 else 26 26 diff --git a/types/index.d.ts b/types/index.d.ts 27 - index c2553517a12c98f4f7d1b0ef10a2dd203842d45e..694f29ec1ca485c3d620d3cd47517abdef7e17c1 100644 27 + index c2553517a12c98f4f7d1b0ef10a2dd203842d45e..ea9006e2d44617f80ed7dd51ce6dd83d16819ad0 100644 28 28 --- a/types/index.d.ts 29 29 +++ b/types/index.d.ts 30 - @@ -1,6 +1,7 @@ 30 + @@ -1,5 +1,6 @@ 31 31 export interface RegisterSWOptions { 32 32 immediate?: boolean 33 - onNeedRefresh?: () => void 34 33 + onBeginUpdate?: () => void 34 + onNeedRefresh?: () => void 35 35 onOfflineReady?: () => void 36 36 /** 37 - * Called only if `onRegisteredSW` is not provided.
+42 -12
pnpm-lock.yaml
··· 1 1 lockfileVersion: '9.0' 2 2 3 3 settings: 4 - autoInstallPeers: true 4 + autoInstallPeers: false 5 5 excludeLinksFromLockfile: false 6 6 7 7 patchedDependencies: ··· 14 14 vite: 15 15 hash: enol6dkeaosc6vsynualw3gkvi 16 16 path: patches/vite.patch 17 - vite-plugin-pwa@0.17.4: 18 - hash: ve5hypcrajivuvoyst6zln6qyq 19 - path: patches/vite-plugin-pwa@0.17.4.patch 17 + vite-plugin-pwa: 18 + hash: olrb2mj3h6xudx6stbxydcwynq 19 + path: patches/vite-plugin-pwa.patch 20 20 workbox-precaching@7.1.0: 21 21 hash: uwqzx25dqx6gokakqgp7nxcupi 22 22 path: patches/workbox-precaching@7.1.0.patch ··· 111 111 specifier: ^5.4.2 112 112 version: 5.4.2(patch_hash=enol6dkeaosc6vsynualw3gkvi)(@types/node@22.5.1)(terser@5.31.6) 113 113 vite-plugin-pwa: 114 - specifier: 0.17.4 115 - version: 0.17.4(patch_hash=ve5hypcrajivuvoyst6zln6qyq)(vite@5.4.2(patch_hash=enol6dkeaosc6vsynualw3gkvi)(@types/node@22.5.1)(terser@5.31.6))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0) 114 + specifier: 0.20.5 115 + version: 0.20.5(patch_hash=olrb2mj3h6xudx6stbxydcwynq)(@types/babel__core@7.20.5)(vite@5.4.2(patch_hash=enol6dkeaosc6vsynualw3gkvi)(@types/node@22.5.1)(terser@5.31.6)) 116 116 vite-plugin-solid: 117 117 specifier: ^2.10.2 118 118 version: 2.10.2(solid-js@1.8.22(patch_hash=5rodyfcb76rtbo26dwlsojy7jy))(vite@5.4.2(patch_hash=enol6dkeaosc6vsynualw3gkvi)(@types/node@22.5.1)(terser@5.31.6)) ··· 1707 1707 fastq@1.17.1: 1708 1708 resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} 1709 1709 1710 + fdir@6.3.0: 1711 + resolution: {integrity: sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==} 1712 + peerDependencies: 1713 + picomatch: ^3 || ^4 1714 + peerDependenciesMeta: 1715 + picomatch: 1716 + optional: true 1717 + 1710 1718 filelist@1.0.4: 1711 1719 resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} 1712 1720 ··· 2167 2175 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 2168 2176 engines: {node: '>=8.6'} 2169 2177 2178 + picomatch@4.0.2: 2179 + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 2180 + engines: {node: '>=12'} 2181 + 2170 2182 pify@2.3.0: 2171 2183 resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} 2172 2184 engines: {node: '>=0.10.0'} ··· 2557 2569 thenify@3.3.1: 2558 2570 resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 2559 2571 2572 + tinyglobby@0.2.6: 2573 + resolution: {integrity: sha512-NbBoFBpqfcgd1tCiO8Lkfdk+xrA7mlLR9zgvZcZWQQwU63XAfUePyd6wZBaU93Hqw347lHnwFzttAkemHzzz4g==} 2574 + engines: {node: '>=12.0.0'} 2575 + 2560 2576 to-fast-properties@2.0.0: 2561 2577 resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} 2562 2578 engines: {node: '>=4'} ··· 2655 2671 validate-html-nesting@1.2.2: 2656 2672 resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==} 2657 2673 2658 - vite-plugin-pwa@0.17.4: 2659 - resolution: {integrity: sha512-j9iiyinFOYyof4Zk3Q+DtmYyDVBDAi6PuMGNGq6uGI0pw7E+LNm9e+nQ2ep9obMP/kjdWwzilqUrlfVRj9OobA==} 2674 + vite-plugin-pwa@0.20.5: 2675 + resolution: {integrity: sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==} 2660 2676 engines: {node: '>=16.0.0'} 2661 2677 peerDependencies: 2678 + '@vite-pwa/assets-generator': ^0.2.6 2662 2679 vite: ^3.1.0 || ^4.0.0 || ^5.0.0 2663 - workbox-build: ^7.0.0 2664 - workbox-window: ^7.0.0 2680 + peerDependenciesMeta: 2681 + '@vite-pwa/assets-generator': 2682 + optional: true 2665 2683 2666 2684 vite-plugin-solid@2.10.2: 2667 2685 resolution: {integrity: sha512-AOEtwMe2baBSXMXdo+BUwECC8IFHcKS6WQV/1NEd+Q7vHPap5fmIhLcAzr+DUJ04/KHx/1UBU0l1/GWP+rMAPQ==} ··· 4543 4561 dependencies: 4544 4562 reusify: 1.0.4 4545 4563 4564 + fdir@6.3.0(picomatch@4.0.2): 4565 + optionalDependencies: 4566 + picomatch: 4.0.2 4567 + 4546 4568 filelist@1.0.4: 4547 4569 dependencies: 4548 4570 minimatch: 5.1.6 ··· 4956 4978 4957 4979 picomatch@2.3.1: {} 4958 4980 4981 + picomatch@4.0.2: {} 4982 + 4959 4983 pify@2.3.0: {} 4960 4984 4961 4985 pirates@4.0.6: {} ··· 5359 5383 dependencies: 5360 5384 any-promise: 1.3.0 5361 5385 5386 + tinyglobby@0.2.6: 5387 + dependencies: 5388 + fdir: 6.3.0(picomatch@4.0.2) 5389 + picomatch: 4.0.2 5390 + 5362 5391 to-fast-properties@2.0.0: {} 5363 5392 5364 5393 to-regex-range@5.0.1: ··· 5462 5491 5463 5492 validate-html-nesting@1.2.2: {} 5464 5493 5465 - vite-plugin-pwa@0.17.4(patch_hash=ve5hypcrajivuvoyst6zln6qyq)(vite@5.4.2(patch_hash=enol6dkeaosc6vsynualw3gkvi)(@types/node@22.5.1)(terser@5.31.6))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0): 5494 + vite-plugin-pwa@0.20.5(patch_hash=olrb2mj3h6xudx6stbxydcwynq)(@types/babel__core@7.20.5)(vite@5.4.2(patch_hash=enol6dkeaosc6vsynualw3gkvi)(@types/node@22.5.1)(terser@5.31.6)): 5466 5495 dependencies: 5467 5496 debug: 4.3.6 5468 - fast-glob: 3.3.2 5469 5497 pretty-bytes: 6.1.1 5498 + tinyglobby: 0.2.6 5470 5499 vite: 5.4.2(patch_hash=enol6dkeaosc6vsynualw3gkvi)(@types/node@22.5.1)(terser@5.31.6) 5471 5500 workbox-build: 7.1.1(@types/babel__core@7.20.5) 5472 5501 workbox-window: 7.1.0 5473 5502 transitivePeerDependencies: 5503 + - '@types/babel__core' 5474 5504 - supports-color 5475 5505 5476 5506 vite-plugin-solid@2.10.2(solid-js@1.8.22(patch_hash=5rodyfcb76rtbo26dwlsojy7jy))(vite@5.4.2(patch_hash=enol6dkeaosc6vsynualw3gkvi)(@types/node@22.5.1)(terser@5.31.6)):
public/favicon.png

This is a binary file and will not be displayed.

+15
src/components/icons-central/download-outline.tsx
··· 1 + import { createIcon } from './_icon'; 2 + 3 + // arrow-inbox 4 + const DownloadOutlinedIcon = createIcon(() => ( 5 + <svg width="1em" height="1em" fill="none" viewBox="0 0 24 24"> 6 + <path 7 + stroke="currentColor" 8 + stroke-linecap="square" 9 + stroke-width="2" 10 + d="M20 15v5H4v-5m8-11v9.5M8.5 11l3.5 3.5 3.5-3.5" 11 + /> 12 + </svg> 13 + )); 14 + 15 + export default DownloadOutlinedIcon;
+28
src/components/main/main-sidebar-authenticated.tsx
··· 5 5 import { openModal, useModalContext } from '~/globals/modals'; 6 6 7 7 import { formatCompact } from '~/lib/intl/number'; 8 + import { SWStatus, swStatus, updateSW } from '~/lib/service-worker'; 8 9 import { useSession } from '~/lib/states/session'; 9 10 10 11 import Avatar, { getUserAvatarType } from '../avatar'; ··· 12 13 import AddOutlinedIcon from '../icons-central/add-outline'; 13 14 import BookmarkOutlinedIcon from '../icons-central/bookmark-outline'; 14 15 import BulletListOutlinedIcon from '../icons-central/bullet-list-outline'; 16 + import DownloadOutlinedIcon from '../icons-central/download-outline'; 15 17 import GearOutlinedIcon from '../icons-central/gear-outline'; 16 18 import HeartOutlinedIcon from '../icons-central/heart-outline'; 17 19 import LeaveOutlinedIcon from '../icons-central/leave-outline'; ··· 46 48 close(); 47 49 }} 48 50 /> 51 + 52 + <div class="grow">{'' + swStatus()}</div> 53 + 54 + <div class="sticky bottom-0 flex flex-col border-t border-outline bg-background empty:hidden"> 55 + <Switch> 56 + <Match when={swStatus() === SWStatus.UPDATING || swStatus() === SWStatus.INSTALLING}> 57 + <div class="flex gap-4 px-4 py-3 text-contrast opacity-50"> 58 + <DownloadOutlinedIcon class="mt-0.5 text-xl" /> 59 + <span class="text-base font-bold"> 60 + {swStatus() === SWStatus.UPDATING ? `Updating app` : `Installing app`} 61 + </span> 62 + </div> 63 + </Match> 64 + 65 + <Match when={swStatus() === SWStatus.NEED_REFRESH}> 66 + <Sidebar.Item 67 + icon={DownloadOutlinedIcon} 68 + label="Update app" 69 + onClick={() => { 70 + close(); 71 + updateSW(); 72 + }} 73 + /> 74 + </Match> 75 + </Switch> 76 + </div> 49 77 </Sidebar.Container> 50 78 </> 51 79 );
+50
src/lib/service-worker.ts
··· 1 + import { createSignal } from 'solid-js'; 2 + import { registerSW } from 'virtual:pwa-register'; 3 + 4 + const shouldInstall = async (): Promise<boolean> => { 5 + if (matchMedia('(display-mode: standalone)').matches) { 6 + return true; 7 + } 8 + 9 + // Just in case. 10 + const registration = await navigator.serviceWorker.getRegistration(); 11 + return !!registration; 12 + }; 13 + 14 + export const enum SWStatus { 15 + NOT_INSTALLED = 0, 16 + INSTALLING = 1, 17 + UPDATING = 2, 18 + NEED_REFRESH = 3, 19 + INSTALLED = 4, 20 + } 21 + 22 + const [swStatus, setSwStatus] = createSignal<SWStatus>(SWStatus.NOT_INSTALLED); 23 + 24 + let updateSW = () => {}; 25 + 26 + shouldInstall().then(async (canInstall) => { 27 + if (!canInstall) { 28 + return; 29 + } 30 + 31 + let alreadyInstalled = !!(await navigator.serviceWorker.getRegistration()); 32 + 33 + updateSW = registerSW({ 34 + onRegisteredSW() { 35 + setSwStatus(SWStatus.INSTALLED); 36 + }, 37 + onBeginUpdate() { 38 + setSwStatus(alreadyInstalled ? SWStatus.UPDATING : SWStatus.INSTALLING); 39 + }, 40 + onNeedRefresh() { 41 + setSwStatus(SWStatus.NEED_REFRESH); 42 + }, 43 + onOfflineReady() { 44 + setSwStatus(SWStatus.INSTALLED); 45 + alreadyInstalled = true; 46 + }, 47 + }); 48 + }); 49 + 50 + export { swStatus, updateSW };
+2
src/vite-env.d.ts
··· 1 1 /// <reference types="vite/client" /> 2 + /// <reference types="vite-plugin-pwa/vanillajs" /> 3 + 2 4 /// <reference types="@atcute/bluesky/lexicons" /> 3 5 /// <reference types="@atcute/bluemoji/lexicons" /> 4 6
+45 -24
vite.config.js
··· 1 1 import * as path from 'node:path'; 2 2 3 3 import { defineConfig } from 'vite'; 4 + import { VitePWA } from 'vite-plugin-pwa'; 4 5 import solid from 'vite-plugin-solid'; 5 6 6 7 import metadata from './public/oauth/client-metadata.json'; ··· 41 42 plugins: [['babel-plugin-transform-typescript-const-enums']], 42 43 }, 43 44 }), 45 + VitePWA({ 46 + registerType: 'prompt', 47 + injectRegister: null, 48 + workbox: { 49 + globPatterns: ['**/*.{js,css,html,svg,jpg,png}'], 50 + cleanupOutdatedCaches: true, 51 + }, 52 + manifest: { 53 + id: '/', 54 + start_url: '/', 55 + scope: '/', 56 + name: 'Aglais', 57 + short_name: 'Aglais', 58 + description: 'Alternative web client for Bluesky', 59 + display: 'standalone', 60 + background_color: '#000000', 61 + icons: [ 62 + { 63 + src: 'favicon.png', 64 + type: 'image/png', 65 + sizes: '150x150', 66 + }, 67 + ], 68 + }, 69 + }), 70 + 44 71 // Transform the icon components to remove the `() => _tmpl$()` wrapper 45 72 { 46 73 transform(code, id) { ··· 57 84 }, 58 85 }, 59 86 60 - oauthMetadataPlugin(), 61 - ], 62 - }); 63 - 64 - /** 65 - * @returns {import('vite').Plugin} 66 - */ 67 - function oauthMetadataPlugin() { 68 - return { 69 - config(_conf, { command }) { 70 - if (command === 'build') { 71 - process.env.VITE_OAUTH_CLIENT_ID = metadata.client_id; 72 - process.env.VITE_OAUTH_REDIRECT_URL = metadata.redirect_uris[0]; 73 - } else { 74 - const redirectUri = `http://${SERVER_HOST}:${SERVER_PORT}/oauth/callback`; 75 - const clientId = `http://localhost?redirect_uri=${encodeURIComponent(redirectUri)}`; 87 + // Injects OAuth-related variables 88 + { 89 + config(_conf, { command }) { 90 + if (command === 'build') { 91 + process.env.VITE_OAUTH_CLIENT_ID = metadata.client_id; 92 + process.env.VITE_OAUTH_REDIRECT_URL = metadata.redirect_uris[0]; 93 + } else { 94 + const redirectUri = `http://${SERVER_HOST}:${SERVER_PORT}/oauth/callback`; 95 + const clientId = `http://localhost?redirect_uri=${encodeURIComponent(redirectUri)}`; 76 96 77 - process.env.VITE_DEV_SERVER_PORT = '' + SERVER_PORT; 78 - process.env.VITE_OAUTH_CLIENT_ID = clientId; 79 - process.env.VITE_OAUTH_REDIRECT_URL = redirectUri; 80 - } 97 + process.env.VITE_DEV_SERVER_PORT = '' + SERVER_PORT; 98 + process.env.VITE_OAUTH_CLIENT_ID = clientId; 99 + process.env.VITE_OAUTH_REDIRECT_URL = redirectUri; 100 + } 81 101 82 - process.env.VITE_CLIENT_URI = metadata.client_uri; 83 - process.env.VITE_OAUTH_SCOPE = metadata.scope; 102 + process.env.VITE_CLIENT_URI = metadata.client_uri; 103 + process.env.VITE_OAUTH_SCOPE = metadata.scope; 104 + }, 84 105 }, 85 - }; 86 - } 106 + ], 107 + });