From 09cde5ee863fb53f4d977b54be63820cb1b6fffd Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 16 May 2023 18:54:49 +0100 Subject: [PATCH] optimize bundle --- _headers | 2 + packages/app/package.json | 1 - packages/app/public/index.html | 10 +- packages/app/src/Cache/UserCache.ts | 32 +++++ packages/app/src/Cache/index.ts | 5 + packages/app/src/Element/CashuNuts.tsx | 21 ++- packages/app/src/Element/Nip05.tsx | 59 +------- packages/app/src/Element/Note.css | 2 + packages/app/src/Element/NoteCreator.css | 7 +- packages/app/src/Element/NoteToSelf.css | 13 +- packages/app/src/Element/NoteToSelf.tsx | 4 +- packages/app/src/Element/ProfileImage.css | 2 + packages/app/src/Nip05/Verifier.ts | 32 +++++ packages/app/src/Pages/WalletPage.tsx | 6 +- packages/app/src/Pages/settings/Profile.tsx | 3 +- .../app/src/Pages/settings/wallet/Cashu.tsx | 3 +- .../app/src/Pages/settings/wallet/LNC.tsx | 6 +- packages/app/src/Wallet/Cashu.ts | 2 +- packages/app/src/Wallet/index.ts | 17 ++- packages/app/src/index.css | 4 + packages/app/src/index.tsx | 15 +- .../{service-worker.js => service-worker.ts} | 4 +- packages/app/src/serviceWorkerRegistration.js | 134 ------------------ packages/app/src/serviceWorkerRegistration.ts | 37 +++++ packages/app/tsconfig.json | 1 + packages/app/webpack.config.js | 25 ++-- yarn.lock | 72 +--------- 27 files changed, 205 insertions(+), 314 deletions(-) create mode 100644 _headers create mode 100644 packages/app/src/Nip05/Verifier.ts rename packages/app/src/{service-worker.js => service-worker.ts} (91%) delete mode 100644 packages/app/src/serviceWorkerRegistration.js create mode 100644 packages/app/src/serviceWorkerRegistration.ts diff --git a/_headers b/_headers new file mode 100644 index 000000000..2f56eb676 --- /dev/null +++ b/_headers @@ -0,0 +1,2 @@ +/* + Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src youtube.com www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://nostrnests.com https://embed.wavlake.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src *; img-src * data:; font-src https://fonts.gstatic.com; media-src *; script-src 'self' 'wasm-unsafe-eval' https://static.cloudflareinsights.com https://platform.twitter.com https://embed.tidal.com; \ No newline at end of file diff --git a/packages/app/package.json b/packages/app/package.json index 4906c3617..a0251b618 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -30,7 +30,6 @@ "react-intersection-observer": "^9.4.1", "react-intl": "^6.2.8", "react-markdown": "^8.0.4", - "react-query": "^3.39.2", "react-redux": "^8.0.5", "react-router-dom": "^6.5.0", "react-textarea-autosize": "^8.4.0", diff --git a/packages/app/public/index.html b/packages/app/public/index.html index 8761a931d..5af92c666 100644 --- a/packages/app/public/index.html +++ b/packages/app/public/index.html @@ -4,21 +4,17 @@ - - + + - snort.social - Nostr interface + Snort - Nostr - -
diff --git a/packages/app/src/Cache/UserCache.ts b/packages/app/src/Cache/UserCache.ts index 79bf2b4be..ae083339a 100644 --- a/packages/app/src/Cache/UserCache.ts +++ b/packages/app/src/Cache/UserCache.ts @@ -2,13 +2,16 @@ import FeedCache from "Cache/FeedCache"; import { db } from "Db"; import { MetadataCache } from "Cache"; import { LNURL } from "LNURL"; +import { fetchNip05Pubkey } from "Nip05/Verifier"; class UserProfileCache extends FeedCache { #zapperQueue: Array<{ pubkey: string; lnurl: string }> = []; + #nip5Queue: Array<{ pubkey: string; nip05: string }> = []; constructor() { super("UserCache", db.users); this.#processZapperQueue(); + this.#processNip5Queue(); } key(of: MetadataCache): string { @@ -80,6 +83,12 @@ class UserProfileCache extends FeedCache { }); } } + if (m.nip05) { + this.#nip5Queue.push({ + pubkey: m.pubkey, + nip05: m.nip05, + }); + } } return updateType; } @@ -119,6 +128,29 @@ class UserProfileCache extends FeedCache { setTimeout(() => this.#processZapperQueue(), 1_000); } + + async #processNip5Queue() { + while (this.#nip5Queue.length > 0) { + const i = this.#nip5Queue.shift(); + if (i) { + try { + const [name, domain] = i.nip05.split("@"); + const nip5pk = await fetchNip05Pubkey(name, domain); + const p = this.getFromCache(i.pubkey); + if (p) { + this.#setItem({ + ...p, + isNostrAddressValid: i.pubkey === nip5pk, + }); + } + } catch { + console.warn("Failed to load nip-05", i.nip05); + } + } + } + + setTimeout(() => this.#processNip5Queue(), 1_000); + } } export const UserCache = new UserProfileCache(); diff --git a/packages/app/src/Cache/index.ts b/packages/app/src/Cache/index.ts index e194af9bf..b8ac6bc4e 100644 --- a/packages/app/src/Cache/index.ts +++ b/packages/app/src/Cache/index.ts @@ -29,6 +29,11 @@ export interface MetadataCache extends UserMetadata { * Pubkey of zapper service */ zapService?: HexKey; + + /** + * If the nip05 is valid for this user + */ + isNostrAddressValid: boolean; } export function mapEventToProfile(ev: RawEvent) { diff --git a/packages/app/src/Element/CashuNuts.tsx b/packages/app/src/Element/CashuNuts.tsx index 0f2a6130c..bf1a2a9d0 100644 --- a/packages/app/src/Element/CashuNuts.tsx +++ b/packages/app/src/Element/CashuNuts.tsx @@ -1,10 +1,19 @@ -import { getDecodedToken } from "@cashu/cashu-ts"; -import { useMemo } from "react"; +import { useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; import useLogin from "Hooks/useLogin"; import { useUserProfile } from "Hooks/useUserProfile"; +interface Token { + token: Array<{ + mint: string; + proofs: Array<{ + amount: number; + }>; + }>; + memo?: string; +} + export default function CashuNuts({ token }: { token: string }) { const login = useLogin(); const profile = useUserProfile(login.publicKey); @@ -22,12 +31,16 @@ export default function CashuNuts({ token }: { token: string }) { window.open(url, "_blank"); } - const cashu = useMemo(() => { + const [cashu, setCashu] = useState(); + useEffect(() => { try { if (!token.startsWith("cashuA") || token.length < 10) { return; } - return getDecodedToken(token); + import("@cashu/cashu-ts").then(({ getDecodedToken }) => { + const tkn = getDecodedToken(token); + setCashu(tkn); + }); } catch { // ignored } diff --git a/packages/app/src/Element/Nip05.tsx b/packages/app/src/Element/Nip05.tsx index 0583837a3..4d1fb286a 100644 --- a/packages/app/src/Element/Nip05.tsx +++ b/packages/app/src/Element/Nip05.tsx @@ -1,59 +1,12 @@ import "./Nip05.css"; -import { useQuery } from "react-query"; import { HexKey } from "@snort/nostr"; -import DnsOverHttpResolver from "dns-over-http-resolver"; import Icon from "Icons/Icon"; -import { bech32ToHex } from "Util"; +import { useUserProfile } from "Hooks/useUserProfile"; -interface NostrJson { - names: Record; -} - -const resolver = new DnsOverHttpResolver(); -async function fetchNip05Pubkey(name: string, domain: string) { - if (!name || !domain) { - return undefined; - } - try { - const res = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(name)}`); - const data: NostrJson = await res.json(); - const match = Object.keys(data.names).find(n => { - return n.toLowerCase() === name.toLowerCase(); - }); - return match ? data.names[match] : undefined; - } catch { - // ignored - } - - // Check as DoH TXT entry - try { - const resDns = await resolver.resolveTxt(`${name}._nostr.${domain}`); - return bech32ToHex(resDns[0][0]); - } catch { - // ignored - } - return undefined; -} - -const VERIFICATION_CACHE_TIME = 24 * 60 * 60 * 1000; -const VERIFICATION_STALE_TIMEOUT = 10 * 60 * 1000; - -export function useIsVerified(pubkey: HexKey, nip05?: string, bypassCheck?: boolean) { - const [name, domain] = nip05 ? nip05.split("@") : []; - const { isError, isSuccess, data } = useQuery( - ["nip05", nip05], - () => (bypassCheck ? Promise.resolve(pubkey) : fetchNip05Pubkey(name, domain)), - { - retry: false, - retryOnMount: false, - cacheTime: VERIFICATION_CACHE_TIME, - staleTime: VERIFICATION_STALE_TIMEOUT, - } - ); - const isVerified = isSuccess && data === pubkey; - const cantVerify = isSuccess && data !== pubkey; - return { isVerified, couldNotVerify: isError || cantVerify }; +export function useIsVerified(pubkey: HexKey, bypassCheck?: boolean) { + const profile = useUserProfile(pubkey); + return { isVerified: bypassCheck || profile?.isNostrAddressValid }; } export interface Nip05Params { @@ -65,10 +18,10 @@ export interface Nip05Params { const Nip05 = ({ nip05, pubkey, verifyNip = true }: Nip05Params) => { const [name, domain] = nip05 ? nip05.split("@") : []; const isDefaultUser = name === "_"; - const { isVerified, couldNotVerify } = useIsVerified(pubkey, nip05, !verifyNip); + const { isVerified } = useIsVerified(pubkey, !verifyNip); return ( -
+
{!isDefaultUser && isVerified && {`${name}@`}} {isVerified && ( <> diff --git a/packages/app/src/Element/Note.css b/packages/app/src/Element/Note.css index 28806a277..b20a6ecec 100644 --- a/packages/app/src/Element/Note.css +++ b/packages/app/src/Element/Note.css @@ -180,6 +180,8 @@ .note .poll-body > div > .progress { background-color: var(--gray); height: stretch; + height: -webkit-fill-available; + height: -moz-available; position: absolute; z-index: 1; } diff --git a/packages/app/src/Element/NoteCreator.css b/packages/app/src/Element/NoteCreator.css index b19f6d484..45605d6d6 100644 --- a/packages/app/src/Element/NoteCreator.css +++ b/packages/app/src/Element/NoteCreator.css @@ -18,8 +18,9 @@ background-color: var(--note-bg); border-radius: 10px 10px 0 0; min-height: 100px; - max-width: stretch; - min-width: stretch; + width: stretch; + width: -webkit-fill-available; + width: -moz-available; max-height: 210px; } @@ -57,6 +58,8 @@ display: flex; justify-content: flex-end; width: stretch; + width: -webkit-fill-available; + width: -moz-available; } .note-creator .insert > button { diff --git a/packages/app/src/Element/NoteToSelf.css b/packages/app/src/Element/NoteToSelf.css index dab13721e..d18040c7b 100644 --- a/packages/app/src/Element/NoteToSelf.css +++ b/packages/app/src/Element/NoteToSelf.css @@ -3,20 +3,19 @@ align-items: center; } -.note-to-self { - margin-left: 5px; - margin-top: 3px; -} - .nts .avatar-wrapper { margin-right: 8px; } .nts .avatar { border-width: 1px; - width: 40px; - height: 40px; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; } + .nts .avatar.clickable { cursor: pointer; } diff --git a/packages/app/src/Element/NoteToSelf.tsx b/packages/app/src/Element/NoteToSelf.tsx index 4d21deec5..348372dd9 100644 --- a/packages/app/src/Element/NoteToSelf.tsx +++ b/packages/app/src/Element/NoteToSelf.tsx @@ -16,7 +16,7 @@ export interface NoteToSelfProps { function NoteLabel() { return (
- +
); } @@ -34,7 +34,7 @@ export default function NoteToSelf({ pubkey, clickable, className, link }: NoteT
- +
diff --git a/packages/app/src/Element/ProfileImage.css b/packages/app/src/Element/ProfileImage.css index 5a586fe6d..8f3462157 100644 --- a/packages/app/src/Element/ProfileImage.css +++ b/packages/app/src/Element/ProfileImage.css @@ -31,6 +31,8 @@ a.pfp { .pfp .profile-name { max-width: stretch; + max-width: -webkit-fill-available; + max-width: -moz-available; } .pfp a { diff --git a/packages/app/src/Nip05/Verifier.ts b/packages/app/src/Nip05/Verifier.ts new file mode 100644 index 000000000..43f96d676 --- /dev/null +++ b/packages/app/src/Nip05/Verifier.ts @@ -0,0 +1,32 @@ +import DnsOverHttpResolver from "dns-over-http-resolver"; +import { bech32ToHex } from "Util"; + +const resolver = new DnsOverHttpResolver(); +interface NostrJson { + names: Record; +} + +export async function fetchNip05Pubkey(name: string, domain: string) { + if (!name || !domain) { + return undefined; + } + try { + const res = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(name)}`); + const data: NostrJson = await res.json(); + const match = Object.keys(data.names).find(n => { + return n.toLowerCase() === name.toLowerCase(); + }); + return match ? data.names[match] : undefined; + } catch { + // ignored + } + + // Check as DoH TXT entry + try { + const resDns = await resolver.resolveTxt(`${name}._nostr.${domain}`); + return bech32ToHex(resDns[0][0]); + } catch { + // ignored + } + return undefined; +} diff --git a/packages/app/src/Pages/WalletPage.tsx b/packages/app/src/Pages/WalletPage.tsx index 17797e7e9..171675625 100644 --- a/packages/app/src/Pages/WalletPage.tsx +++ b/packages/app/src/Pages/WalletPage.tsx @@ -62,11 +62,11 @@ export default function WalletPage() { function stateIcon(s: WalletInvoiceState) { switch (s) { case WalletInvoiceState.Pending: - return ; + return ; case WalletInvoiceState.Paid: - return ; + return ; case WalletInvoiceState.Expired: - return ; + return ; } } diff --git a/packages/app/src/Pages/settings/Profile.tsx b/packages/app/src/Pages/settings/Profile.tsx index cdf7eaf07..87eaa9b8e 100644 --- a/packages/app/src/Pages/settings/Profile.tsx +++ b/packages/app/src/Pages/settings/Profile.tsx @@ -64,13 +64,14 @@ export default function ProfileSettings(props: ProfileSettingsProps) { website, nip05, lud16, - } as Record; + } as Record; delete userCopy["loaded"]; delete userCopy["created"]; delete userCopy["pubkey"]; delete userCopy["npub"]; delete userCopy["deleted"]; delete userCopy["zapService"]; + delete userCopy["isNostrAddressValid"]; console.debug(userCopy); if (publisher) { diff --git a/packages/app/src/Pages/settings/wallet/Cashu.tsx b/packages/app/src/Pages/settings/wallet/Cashu.tsx index 977b1fcea..02e4b7287 100644 --- a/packages/app/src/Pages/settings/wallet/Cashu.tsx +++ b/packages/app/src/Pages/settings/wallet/Cashu.tsx @@ -4,7 +4,6 @@ import { v4 as uuid } from "uuid"; import AsyncButton from "Element/AsyncButton"; import { unwrap } from "Util"; -import { CashuWallet } from "Wallet/Cashu"; import { WalletConfig, WalletKind, Wallets } from "Wallet"; import { useNavigate } from "react-router-dom"; @@ -19,6 +18,8 @@ const ConnectCashu = () => { if (!mintUrl) { throw new Error("Mint URL is required"); } + + const { CashuWallet } = await import("Wallet/Cashu"); const connection = new CashuWallet(config); await connection.login(); const info = await connection.getInfo(); diff --git a/packages/app/src/Pages/settings/wallet/LNC.tsx b/packages/app/src/Pages/settings/wallet/LNC.tsx index 0f3ebbee1..85763c845 100644 --- a/packages/app/src/Pages/settings/wallet/LNC.tsx +++ b/packages/app/src/Pages/settings/wallet/LNC.tsx @@ -4,8 +4,7 @@ import { useNavigate } from "react-router-dom"; import { v4 as uuid } from "uuid"; import AsyncButton from "Element/AsyncButton"; -import { LNCWallet } from "Wallet/LNCWallet"; -import { WalletInfo, WalletKind, Wallets } from "Wallet"; +import { LNWallet, WalletInfo, WalletKind, Wallets } from "Wallet"; import { unwrap } from "Util"; const ConnectLNC = () => { @@ -13,12 +12,13 @@ const ConnectLNC = () => { const navigate = useNavigate(); const [pairingPhrase, setPairingPhrase] = useState(); const [error, setError] = useState(); - const [connectedLNC, setConnectedLNC] = useState(); + const [connectedLNC, setConnectedLNC] = useState(); const [walletInfo, setWalletInfo] = useState(); const [walletPassword, setWalletPassword] = useState(); async function tryConnect(cfg: string) { try { + const { LNCWallet } = await import("Wallet/LNCWallet"); const lnc = await LNCWallet.Initialize(cfg); const info = await lnc.getInfo(); diff --git a/packages/app/src/Wallet/Cashu.ts b/packages/app/src/Wallet/Cashu.ts index f23b7e712..e00c9e499 100644 --- a/packages/app/src/Wallet/Cashu.ts +++ b/packages/app/src/Wallet/Cashu.ts @@ -1,4 +1,4 @@ -import { LNWallet, Sats, WalletError, WalletErrorCode, WalletInfo, WalletInvoice } from "Wallet"; +import { InvoiceRequest, LNWallet, Sats, WalletError, WalletErrorCode, WalletInfo, WalletInvoice } from "Wallet"; import { CashuMint, CashuWallet as TheCashuWallet, Proof } from "@cashu/cashu-ts"; diff --git a/packages/app/src/Wallet/index.ts b/packages/app/src/Wallet/index.ts index b63b8d2ee..ec59fd561 100644 --- a/packages/app/src/Wallet/index.ts +++ b/packages/app/src/Wallet/index.ts @@ -1,10 +1,8 @@ import { useSyncExternalStore } from "react"; import { decodeInvoice, unwrap } from "Util"; -import { LNCWallet } from "./LNCWallet"; import LNDHubWallet from "./LNDHub"; import { NostrConnectWallet } from "./NostrWalletConnect"; -import { CashuWallet } from "./Cashu"; import { setupWebLNWalletConfig, WebLNWallet } from "./WebLN"; export enum WalletKind { @@ -171,7 +169,13 @@ export class WalletStore { } else { const w = this.#activateWallet(activeConfig); if (w) { - this.#instance.set(activeConfig.id, w); + if ("then" in w) { + w.then(wx => { + this.#instance.set(activeConfig.id, wx); + this.snapshotState(); + }); + return undefined; + } return w; } else { throw new Error("Unable to activate wallet config"); @@ -238,11 +242,10 @@ export class WalletStore { } } - #activateWallet(cfg: WalletConfig): LNWallet | undefined { + #activateWallet(cfg: WalletConfig): LNWallet | Promise | undefined { switch (cfg.kind) { case WalletKind.LNC: { - const w = LNCWallet.Empty(); - return w; + return import("./LNCWallet").then(({ LNCWallet }) => LNCWallet.Empty()); } case WalletKind.WebLN: { return new WebLNWallet(); @@ -254,7 +257,7 @@ export class WalletStore { return new NostrConnectWallet(unwrap(cfg.data)); } case WalletKind.Cashu: { - return new CashuWallet(unwrap(cfg.data)); + return import("./Cashu").then(({ CashuWallet }) => new CashuWallet(unwrap(cfg.data))); } } } diff --git a/packages/app/src/index.css b/packages/app/src/index.css index 2f6c771ee..058e04c07 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -366,11 +366,15 @@ input:disabled { .w-max { width: 100%; width: stretch; + width: -webkit-fill-available; + width: -moz-available; } .w-max-w { max-width: 100%; max-width: stretch; + max-width: -webkit-fill-available; + max-width: -moz-available; } a { diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 926e45d50..e3b5e458a 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -3,7 +3,6 @@ import "@szhsin/react-menu/dist/index.css"; import "public/manifest.json"; import { StrictMode } from "react"; -import { QueryClient, QueryClientProvider } from "react-query"; import * as ReactDOM from "react-dom/client"; import { Provider } from "react-redux"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; @@ -33,10 +32,8 @@ import Thread from "Element/Thread"; import { SubscribeRoutes } from "Pages/subscribe"; import ZapPoolPage from "Pages/ZapPool"; -/** - * HTTP query provider - */ -const HTTP = new QueryClient(); +// @ts-ignore +window.__webpack_nonce__ = "ZmlhdGphZiBzYWlkIHNub3J0LnNvY2lhbCBpcyBwcmV0dHkgZ29vZCwgd2UgbWFkZSBpdCE="; serviceWorkerRegistration.register(); @@ -114,11 +111,9 @@ const root = ReactDOM.createRoot(unwrap(document.getElementById("root"))); root.render( - - - - - + + + ); diff --git a/packages/app/src/service-worker.js b/packages/app/src/service-worker.ts similarity index 91% rename from packages/app/src/service-worker.js rename to packages/app/src/service-worker.ts index 1d75620f5..1e03c4918 100644 --- a/packages/app/src/service-worker.js +++ b/packages/app/src/service-worker.ts @@ -1,4 +1,6 @@ -/* eslint-disable no-restricted-globals */ +/// +import {} from "."; +declare var self: ServiceWorkerGlobalScope; import { clientsClaim } from "workbox-core"; import { ExpirationPlugin } from "workbox-expiration"; diff --git a/packages/app/src/serviceWorkerRegistration.js b/packages/app/src/serviceWorkerRegistration.js deleted file mode 100644 index e1b8889c1..000000000 --- a/packages/app/src/serviceWorkerRegistration.js +++ /dev/null @@ -1,134 +0,0 @@ -// This optional code is used to register a service worker. -// register() is not called by default. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on subsequent visits to a page, after all the -// existing tabs open on the page have been closed, since previously cached -// resources are updated in the background. - -// To learn more about the benefits of this model and instructions on how to -// opt-in, read https://cra.link/PWA - -const isLocalhost = Boolean( - window.location.hostname === "localhost" || - // [::1] is the IPv6 localhost address. - window.location.hostname === "[::1]" || - // 127.0.0.0/8 are considered localhost for IPv4. - window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) -); - -export function register(config) { - if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } - - window.addEventListener("load", () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); - - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - "This web app is being served cache-first by a service " + - "worker. To learn more, visit https://cra.link/PWA" - ); - }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } -} - -function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === "installed") { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - "New content is available and will be used when all " + - "tabs for this page are closed. See https://cra.link/PWA." - ); - - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log("Content is cached for offline use."); - - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch(error => { - console.error("Error during service worker registration:", error); - }); -} - -function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl, { - headers: { "Service-Worker": "script" }, - }) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get("content-type"); - if (response.status === 404 || (contentType != null && contentType.indexOf("javascript") === -1)) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log("No internet connection found. App is running in offline mode."); - }); -} - -export function unregister() { - if ("serviceWorker" in navigator) { - navigator.serviceWorker.ready - .then(registration => { - registration.unregister(); - }) - .catch(error => { - console.error(error.message); - }); - } -} diff --git a/packages/app/src/serviceWorkerRegistration.ts b/packages/app/src/serviceWorkerRegistration.ts new file mode 100644 index 000000000..acdc92ac8 --- /dev/null +++ b/packages/app/src/serviceWorkerRegistration.ts @@ -0,0 +1,37 @@ +export function register() { + if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { + window.addEventListener("load", () => { + registerValidSW("/service-worker.js"); + }); + } +} + +async function registerValidSW(swUrl: string) { + try { + const registration = await navigator.serviceWorker.register(swUrl); + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === "installed") { + if (navigator.serviceWorker.controller) { + console.log("Service worker updated, pending reload"); + } else { + console.log("Content is cached for offline use."); + } + } + }; + }; + } catch (e) { + console.error("Error during service worker registration:", e); + } +} + +export async function unregister() { + if ("serviceWorker" in navigator) { + const registration = await navigator.serviceWorker.ready; + await registration.unregister(); + } +} diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index 1e1e06d34..5d6a7c940 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "baseUrl": "src", "target": "es2020", + "module": "es2020", "jsx": "react-jsx", "moduleResolution": "node", "forceConsistentCasingInFileNames": true, diff --git a/packages/app/webpack.config.js b/packages/app/webpack.config.js index e64ea8626..16592995b 100644 --- a/packages/app/webpack.config.js +++ b/packages/app/webpack.config.js @@ -11,14 +11,21 @@ const TsTransformer = require("@formatjs/ts-transformer"); const isProduction = process.env.NODE_ENV == "production"; const config = { - entry: "./src/index.tsx", + entry: { + main: "./src/index.tsx", + }, target: "browserslist", - devtool: "source-map", + devtool: isProduction ? "source-map" : "eval", output: { publicPath: "/", path: path.resolve(__dirname, "build"), - filename: "[name].[chunkhash].js", - clean: true, + filename: ({ runtime }) => { + if (runtime === "sw") { + return "[name].js"; + } + return isProduction ? "[name].[chunkhash].js" : "[name].js"; + }, + clean: isProduction, }, devServer: { open: true, @@ -32,7 +39,7 @@ const config = { }), new ESLintPlugin(), new MiniCssExtractPlugin({ - filename: "[name].[chunkhash].css", + filename: isProduction ? "[name].[chunkhash].css" : "[name].css", }), ], module: { @@ -70,6 +77,7 @@ const config = { }, optimization: { usedExports: true, + chunkIds: "deterministic", minimizer: ["...", new CssMinimizerPlugin()], }, resolve: { @@ -81,11 +89,12 @@ const config = { module.exports = () => { if (isProduction) { config.mode = "production"; - - config.plugins.push(new WorkboxWebpackPlugin.GenerateSW()); + config.entry.sw = { + import: "./src/service-worker.ts", + name: "sw.js", + }; } else { config.mode = "development"; - config.output.clean = false; } return config; }; diff --git a/yarn.lock b/yarn.lock index f240a3edd..03940f201 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1169,7 +1169,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.20.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.20.13", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.21.0" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz" integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== @@ -3280,11 +3280,6 @@ before-after-hook@^2.2.0: resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz" integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== -big-integer@^1.6.16: - version "1.6.51" - resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" - integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== - bin-links@^3.0.0: version "3.0.3" resolved "https://registry.npmjs.org/bin-links/-/bin-links-3.0.3.tgz" @@ -3376,20 +3371,6 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -broadcast-channel@^3.4.1: - version "3.7.0" - resolved "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz" - integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== - dependencies: - "@babel/runtime" "^7.7.2" - detect-node "^2.1.0" - js-sha3 "0.8.0" - microseconds "0.2.0" - nano-time "1.0.0" - oblivious-set "1.0.0" - rimraf "3.0.2" - unload "2.2.0" - browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" @@ -4208,7 +4189,7 @@ destroy@1.2.0: resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -detect-node@^2.0.4, detect-node@^2.1.0: +detect-node@^2.0.4: version "2.1.0" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== @@ -6003,11 +5984,6 @@ js-sdsl@^4.1.4: resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz" integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== -js-sha3@0.8.0: - version "0.8.0" - resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz" - integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -6403,14 +6379,6 @@ match-sorter@4.0.0: dependencies: remove-accents "0.4.2" -match-sorter@^6.0.2: - version "6.3.1" - resolved "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz" - integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw== - dependencies: - "@babel/runtime" "^7.12.5" - remove-accents "0.4.2" - mdast-util-definitions@^5.0.0: version "5.1.2" resolved "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz" @@ -6730,11 +6698,6 @@ micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.2" picomatch "^2.3.1" -microseconds@0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz" - integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== - mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" @@ -6982,13 +6945,6 @@ mute-stream@0.0.8: resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nano-time@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz" - integrity sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA== - dependencies: - big-integer "^1.6.16" - nanoid@3.3.3: version "3.3.3" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz" @@ -7218,11 +7174,6 @@ object.assign@^4.1.4: has-symbols "^1.0.3" object-keys "^1.1.1" -oblivious-set@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz" - integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== - obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" @@ -8008,15 +7959,6 @@ react-markdown@^8.0.4: unist-util-visit "^4.0.0" vfile "^5.0.0" -react-query@^3.39.2: - version "3.39.3" - resolved "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz" - integrity sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g== - dependencies: - "@babel/runtime" "^7.5.5" - broadcast-channel "^3.4.1" - match-sorter "^6.0.2" - react-redux@^8.0.5: version "8.0.5" resolved "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz" @@ -8352,7 +8294,7 @@ rfdc@^1.3.0: resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== -rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -9501,14 +9443,6 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unload@2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz" - integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== - dependencies: - "@babel/runtime" "^7.6.2" - detect-node "^2.0.4" - unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"