diff --git a/.lintstagedrc.js b/.lintstagedrc.js index 63ae2286..c6db7659 100644 --- a/.lintstagedrc.js +++ b/.lintstagedrc.js @@ -5,7 +5,9 @@ const tsconfigFilename = "tsconfig.staged.json"; const typecheckOnlyStaged = (stagedFilenames) => { const tsconfig = JSON.parse(fs.readFileSync("tsconfig.json")); - tsconfig.include = stagedFilenames; + tsconfig.include = stagedFilenames.filter((filename) => + filename.includes("/src/") + ); fs.writeFileSync(tsconfigFilename, JSON.stringify(tsconfig)); return `yarn typecheck --project ${tsconfigFilename}`; }; diff --git a/package.json b/package.json index 1c07cc59..66dbd973 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "license": "MIT", "scripts": { "dev": "vite", - "typecheck": "vue-tsc --noEmit", - "build": "yarn typecheck && vite build", + "typecheck": "yarn build-serviceworker --noEmit && vue-tsc --noEmit", + "build": "yarn typecheck && vite build && yarn build-serviceworker", "build:staging": "vite build --mode staging", "preview": "yarn build && vite preview --port 8080", "format": "prettier ./src --check", @@ -15,6 +15,7 @@ "test": "jest", "apigen": "rm -rf src/api && npx openapi2aspida", "prepare": "husky install && rimraf ./node_modules/@types/react", + "build-serviceworker": "tsc -p serviceworkers/tsconfig.json", "storybook": "start-storybook -p 6006", "build-storybook": "yarn typecheck && build-storybook" }, diff --git a/serviceworkers/fallback.ts b/serviceworkers/fallback.ts new file mode 100644 index 00000000..2a82fe11 --- /dev/null +++ b/serviceworkers/fallback.ts @@ -0,0 +1,71 @@ +import * as dotenv from "dotenv"; + +dotenv.config(); + +const CACHE_NAME = "TwinteFallback"; +const VERSIONS = ["v1"]; + +let base_url: string; + +if (process.env.NODE_ENV === "development") { + base_url = process.env.BASE_URL || "http://localhost:4000"; +} else { + base_url = process.env.BASE_URL; +} + +declare const self: ServiceWorkerGlobalScope; + +self.addEventListener("install", () => { + VERSIONS.slice(0, Math.min(0, VERSIONS.length - 2)).forEach( + async (version) => { + await caches.delete(getCacheName(CACHE_NAME, version)); + } + ); +}); + +self.addEventListener("fetch", (event) => { + const request = event.request; + + if (request.method !== "GET") return; + if ( + !( + request.url.startsWith(base_url) || + request.url.startsWith("https://fonts.googleapis.com/") || + request.url.startsWith("https://fonts.gstatic.com/") + ) + ) + return; + + event.respondWith(getResponseWithCaching(request)); +}); + +const getCacheName = (name: string, version: string) => `${name}_${version}`; + +const getResponseWithCaching = async (request: Request): Promise => { + const cache = await caches.open( + getCacheName(CACHE_NAME, VERSIONS[VERSIONS.length - 1]) + ); + + let response: Response | undefined; + + const hasCredentials = request.url.startsWith(base_url); + + try { + response = await fetch( + request, + hasCredentials + ? { + credentials: "include", + } + : {} + ); + } catch (error) { + console.error(error); + response = await cache.match(request.url); + if (!response) throw new Error("There is no cache"); + } + + if (response.ok) cache.put(request, response.clone()); + + return response; +}; diff --git a/serviceworkers/tsconfig.json b/serviceworkers/tsconfig.json new file mode 100644 index 00000000..0f3468d1 --- /dev/null +++ b/serviceworkers/tsconfig.json @@ -0,0 +1,18 @@ +{ + "include": ["./**/*.ts"], + "compilerOptions": { + "sourceMap": true, + "module": "esnext", + "moduleResolution": "node", + "preserveValueImports": false, + "esModuleInterop": true, + "importsNotUsedAsValues": "remove", + "baseUrl": ".", + "paths": { + "~/*": ["./*"] + }, + "lib": ["ES2021", "WebWorker"], + "types": ["node"], + "outDir": "../dist" + } +} diff --git a/src/ui/App.vue b/src/ui/App.vue index 1206d93d..93e55622 100644 --- a/src/ui/App.vue +++ b/src/ui/App.vue @@ -17,7 +17,7 @@