diff --git a/server/Dockerfile b/server/Dockerfile index 969666f18d..0f9b3fa806 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -53,6 +53,7 @@ COPY --from=prod /usr/src/app/node_modules ./node_modules COPY --from=prod /usr/src/app/dist ./dist COPY --from=prod /usr/src/app/bin ./bin COPY --from=web /usr/src/app/build /build/www +COPY --from=web /usr/src/app/node_modules /build/node_modules COPY server/resources resources COPY server/package.json server/package-lock.json ./ COPY server/start*.sh ./ diff --git a/server/src/workers/api.ts b/server/src/workers/api.ts index ddf6e50aa2..76bf4a8b1a 100644 --- a/server/src/workers/api.ts +++ b/server/src/workers/api.ts @@ -2,8 +2,7 @@ import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { json } from 'body-parser'; import cookieParser from 'cookie-parser'; -import { existsSync } from 'node:fs'; -import sirv from 'sirv'; +import type { Handler, NextFunction, Request, Response } from 'express'; import { ApiModule } from 'src/app.module'; import { excludePaths, serverVersion } from 'src/constants'; import { ImmichEnvironment } from 'src/enum'; @@ -42,24 +41,21 @@ async function bootstrap() { useSwagger(app, { write: isDev }); app.setGlobalPrefix('api', { exclude: excludePaths }); - if (existsSync(resourcePaths.web.root)) { - // copied from https://github.com/sveltejs/kit/blob/679b5989fe62e3964b9a73b712d7b41831aa1f07/packages/adapter-node/src/handler.js#L46 - // provides serving of precompressed assets and caching of immutable assets - app.use( - sirv(resourcePaths.web.root, { - etag: true, - gzip: true, - brotli: true, - extensions: [], - setHeaders: (res, pathname) => { - if (pathname.startsWith(`/_app/immutable`) && res.statusCode === 200) { - res.setHeader('cache-control', 'public,max-age=31536000,immutable'); - } - }, - }), - ); + + const svelteHandler = `${resourcePaths.web.root}/handler.js`; + try { + const { handler } = (await import(svelteHandler)) as { handler: Handler }; + app.use((req: Request, res: Response, next: NextFunction) => { + if (req.url.startsWith('/api') || excludePaths.some((path) => req.url.startsWith(path))) { + return next(); + } + return handler(req, res, next); + }); + } catch { + logger.log('Not serving SvelteKit.'); } - app.use(app.get(ApiService).ssr(excludePaths)); + + // app.use(app.get(ApiService).ssr(excludePaths)); const server = await (host ? app.listen(port, host) : app.listen(port)); server.requestTimeout = 24 * 60 * 60 * 1000; diff --git a/web/bin/immich-web b/web/bin/immich-web index 6b2880d6d2..2ef2f2b2ac 100755 --- a/web/bin/immich-web +++ b/web/bin/immich-web @@ -5,8 +5,8 @@ TYPESCRIPT_SDK=/usr/src/open-api/typescript-sdk npm --prefix "$TYPESCRIPT_SDK" install npm --prefix "$TYPESCRIPT_SDK" run build -UPSTREAM="${IMMICH_SERVER_URL:-http://immich-server:2283/}" -until wget --spider --quiet "${UPSTREAM}/api/server/config"; do +UPSTREAM="${IMMICH_SERVER_URL:-http://immich-server:2283}" +until wget --spider --quiet "${UPSTREAM}/api/server/ping"; do echo 'waiting for api server...' sleep 1 done diff --git a/web/package-lock.json b/web/package-lock.json index d6fcb816f8..5211983c83 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -42,9 +42,9 @@ "@eslint/js": "^9.18.0", "@faker-js/faker": "^9.3.0", "@socket.io/component-emitter": "^3.1.0", - "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/adapter-node": "^5.2.12", "@sveltejs/enhanced-img": "^0.4.4", - "@sveltejs/kit": "^2.15.2", + "@sveltejs/kit": "^2.20.7", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@testing-library/jest-dom": "^6.4.2", "@testing-library/svelte": "^5.2.6", @@ -70,7 +70,7 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", "rollup-plugin-visualizer": "^5.14.0", - "svelte": "^5.25.3", + "svelte": "^5.28.1", "svelte-check": "^4.1.5", "tailwindcss": "^3.4.17", "tslib": "^2.6.2", @@ -1756,6 +1756,89 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz", + "integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/pluginutils": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", @@ -2074,14 +2157,20 @@ "acorn": "^8.9.0" } }, - "node_modules/@sveltejs/adapter-static": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", - "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", + "node_modules/@sveltejs/adapter-node": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.12.tgz", + "integrity": "sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==", "dev": true, "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup": "^4.9.5" + }, "peerDependencies": { - "@sveltejs/kit": "^2.0.0" + "@sveltejs/kit": "^2.4.0" } }, "node_modules/@sveltejs/enhanced-img": { @@ -2433,6 +2522,13 @@ "@types/node": "*" } }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/supercluster": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", @@ -3531,6 +3627,13 @@ "node": ">= 6" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5443,6 +5546,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -8178,9 +8288,9 @@ } }, "node_modules/svelte": { - "version": "5.27.3", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.27.3.tgz", - "integrity": "sha512-MK16NUEFwAunCkdJpIIJ6hvKElx0zFlKMqQd7NAIugMfrL0YeOH8VEn5pg9g2Q6RLj2JrGJL6c0zaAwmXx/nHQ==", + "version": "5.28.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.28.2.tgz", + "integrity": "sha512-FbWBxgWOpQfhKvoGJv/TFwzqb4EhJbwCD17dB0tEpQiw1XyUEKZJtgm4nA4xq3LLsMo7hu5UY/BOFmroAxKTMg==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", diff --git a/web/package.json b/web/package.json index b4b9a80da1..c8e1c9ed1c 100644 --- a/web/package.json +++ b/web/package.json @@ -58,9 +58,9 @@ "@eslint/js": "^9.18.0", "@faker-js/faker": "^9.3.0", "@socket.io/component-emitter": "^3.1.0", - "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/adapter-node": "^5.2.12", "@sveltejs/enhanced-img": "^0.4.4", - "@sveltejs/kit": "^2.15.2", + "@sveltejs/kit": "^2.20.7", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@testing-library/jest-dom": "^6.4.2", "@testing-library/svelte": "^5.2.6", @@ -86,7 +86,7 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", "rollup-plugin-visualizer": "^5.14.0", - "svelte": "^5.25.3", + "svelte": "^5.28.1", "svelte-check": "^4.1.5", "tailwindcss": "^3.4.17", "tslib": "^2.6.2", diff --git a/web/src/lib/utils/tunables.ts b/web/src/lib/utils/tunables.ts index 3e2ed4e5c3..2ac1cf9310 100644 --- a/web/src/lib/utils/tunables.ts +++ b/web/src/lib/utils/tunables.ts @@ -10,18 +10,30 @@ function getNumber(string: string | null, fallback: number) { } return Number.parseInt(string); } + +const getItem = (key: string) => { + if (!globalThis.localStorage) { + const error = new Error('test'); + Error.stackTraceLimit = Infinity; + console.log('local storage is not available', error.stack); + return null; + } + + return globalThis.localStorage.getItem(key); +}; + export const TUNABLES = { LAYOUT: { - WASM: getBoolean(localStorage.getItem('LAYOUT.WASM'), false), + WASM: getBoolean(getItem('LAYOUT.WASM'), false), }, TIMELINE: { - INTERSECTION_EXPAND_TOP: getNumber(localStorage.getItem('TIMELINE_INTERSECTION_EXPAND_TOP'), 500), - INTERSECTION_EXPAND_BOTTOM: getNumber(localStorage.getItem('TIMELINE_INTERSECTION_EXPAND_BOTTOM'), 500), + INTERSECTION_EXPAND_TOP: getNumber(getItem('TIMELINE_INTERSECTION_EXPAND_TOP'), 500), + INTERSECTION_EXPAND_BOTTOM: getNumber(getItem('TIMELINE_INTERSECTION_EXPAND_BOTTOM'), 500), }, ASSET_GRID: { - NAVIGATE_ON_ASSET_IN_VIEW: getBoolean(localStorage.getItem('ASSET_GRID.NAVIGATE_ON_ASSET_IN_VIEW'), false), + NAVIGATE_ON_ASSET_IN_VIEW: getBoolean(getItem('ASSET_GRID.NAVIGATE_ON_ASSET_IN_VIEW'), false), }, IMAGE_THUMBNAIL: { - THUMBHASH_FADE_DURATION: getNumber(localStorage.getItem('THUMBHASH_FADE_DURATION'), 150), + THUMBHASH_FADE_DURATION: getNumber(getItem('THUMBHASH_FADE_DURATION'), 150), }, }; diff --git a/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 7e6057696a..1e153a3b88 100644 --- a/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -15,6 +15,7 @@ import { navigate } from '$lib/utils/navigation'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { tick } from 'svelte'; + import { browser } from '$app/environment'; interface Props { data: PageData; @@ -57,41 +58,45 @@