diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 3ee64afc78..7c5b4094ca 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -963,8 +963,8 @@ dependencies: specifier: ^1.5.1 version: 1.5.1 body-parser: - specifier: ~1.19.1 - version: 1.19.2 + specifier: ^1.20.2 + version: 1.20.2 browserslist: specifier: 4.21.5 version: 4.21.5 @@ -1065,10 +1065,10 @@ dependencies: specifier: ^2.34.0 version: 2.35.1(eslint@8.56.0)(svelte@4.2.11)(ts-node@10.9.2) express: - specifier: ^4.17.1 - version: 4.18.2 + specifier: ^4.18.3 + version: 4.18.3 express-fileupload: - specifier: ^1.2.1 + specifier: ^1.4.3 version: 1.4.3 faker: specifier: ~5.5.3 @@ -4797,7 +4797,7 @@ packages: ejs: 3.1.9 esbuild: 0.18.20 esbuild-plugin-alias: 0.2.1 - express: 4.18.2 + express: 4.18.3 find-cache-dir: 3.3.2 fs-extra: 11.2.0 process: 0.11.10 @@ -4834,7 +4834,7 @@ packages: constants-browserify: 1.0.0 css-loader: 6.10.0(webpack@5.90.3) es-module-lexer: 1.4.1 - express: 4.18.2 + express: 4.18.3 fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.3)(webpack@5.90.3) fs-extra: 11.2.0 html-webpack-plugin: 5.6.0(webpack@5.90.3) @@ -4908,7 +4908,7 @@ packages: detect-indent: 6.1.0 envinfo: 7.11.1 execa: 5.1.1 - express: 4.18.2 + express: 4.18.3 find-up: 5.0.0 fs-extra: 11.2.0 get-npm-tarball-url: 2.1.0 @@ -5086,7 +5086,7 @@ packages: cli-table3: 0.6.3 compression: 1.7.4 detect-port: 1.5.1 - express: 4.18.2 + express: 4.18.3 fs-extra: 11.2.0 globby: 11.1.0 ip: 2.0.1 @@ -7581,24 +7581,8 @@ packages: readable-stream: 3.6.2 dev: false - /body-parser@1.19.2: - resolution: {integrity: sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==} - engines: {node: '>= 0.8'} - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 1.1.2 - http-errors: 1.8.1 - iconv-lite: 0.4.24 - on-finished: 2.3.0 - qs: 6.9.7 - raw-body: 2.4.3 - type-is: 1.6.18 - dev: false - - /body-parser@1.20.1: - resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + /body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dependencies: bytes: 3.1.2 @@ -7610,7 +7594,7 @@ packages: iconv-lite: 0.4.24 on-finished: 2.4.1 qs: 6.11.0 - raw-body: 2.5.1 + raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 dev: false @@ -9756,13 +9740,13 @@ packages: busboy: 1.6.0 dev: false - /express@4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + /express@4.18.3: + resolution: {integrity: sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==} engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.1 + body-parser: 1.20.2 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.5.0 @@ -13965,11 +13949,6 @@ packages: side-channel: 1.0.5 dev: false - /qs@6.9.7: - resolution: {integrity: sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==} - engines: {node: '>=0.6'} - dev: false - /query-string@7.1.3: resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} engines: {node: '>=6'} @@ -14017,26 +13996,6 @@ packages: engines: {node: '>= 0.6'} dev: false - /raw-body@2.4.3: - resolution: {integrity: sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==} - engines: {node: '>= 0.8'} - dependencies: - bytes: 3.1.2 - http-errors: 1.8.1 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - dev: false - - /raw-body@2.5.1: - resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} - engines: {node: '>= 0.8'} - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - dev: false - /raw-body@2.5.2: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} @@ -16320,7 +16279,7 @@ packages: compression: 1.7.4 connect-history-api-fallback: 2.0.0 default-gateway: 6.0.3 - express: 4.18.2 + express: 4.18.3 graceful-fs: 4.2.11 html-entities: 2.4.0 http-proxy-middleware: 2.0.6(@types/express@4.17.21) @@ -17749,7 +17708,7 @@ packages: dev: false file:projects/collaborator.tgz(@tiptap/pm@2.2.3)(bufferutil@4.0.8)(prosemirror-model@1.19.4)(svelte@4.2.11): - resolution: {integrity: sha512-HvLgY+NwEKic10k5ZvdykepwdECRL2FQHFp3XfjK3Li7pvHMPzKUcTYfnBsJFSHD+jjEZ31C2wP9oRD5s5lSag==, tarball: file:projects/collaborator.tgz} + resolution: {integrity: sha512-C2X7Yyjr7DoJenvaHw4y2eOCKejrUUI4IUXBNMWpSLvZx0L6O+6DwY2KP+N7UrKaifKX3vICt35Tyb0UA3K/dQ==, tarball: file:projects/collaborator.tgz} id: file:projects/collaborator.tgz name: '@rush-temp/collaborator' version: 0.0.0 @@ -17767,7 +17726,7 @@ packages: '@types/ws': 8.5.10 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) - body-parser: 1.19.2 + body-parser: 1.20.2 compression: 1.7.4 cors: 2.8.5 cross-env: 7.0.3 @@ -17777,7 +17736,7 @@ packages: eslint-plugin-import: 2.29.1(eslint@8.56.0) eslint-plugin-n: 15.7.0(eslint@8.56.0) eslint-plugin-promise: 6.1.1(eslint@8.56.0) - express: 4.18.2 + express: 4.18.3 jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2) mongodb: 6.3.0 prettier: 3.2.5 @@ -18317,7 +18276,7 @@ packages: dev: false file:projects/front.tgz(esbuild@0.20.1)(svelte@4.2.11): - resolution: {integrity: sha512-Q1CZmbbm59dl6s4oEnEHBJPIxWzMEfaNIzWRPFJnV9wc4wckFya5G525eP1BwzwtWeQic7GY9nQrUQ96qhXtuQ==, tarball: file:projects/front.tgz} + resolution: {integrity: sha512-Oh/0YDbHbRu5+GmJeWOf9gVUaX0q31L9pdRbKq1zMt34GrzJGfSpRZO0bOxhu1Bsda3RSErk8q1rlY9EO2TCIA==, tarball: file:projects/front.tgz} id: file:projects/front.tgz name: '@rush-temp/front' version: 0.0.0 @@ -18334,7 +18293,7 @@ packages: '@types/uuid': 8.3.4 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) - body-parser: 1.19.2 + body-parser: 1.20.2 compression: 1.7.4 cors: 2.8.5 cross-env: 7.0.3 @@ -18343,7 +18302,7 @@ packages: eslint-plugin-import: 2.29.1(eslint@8.56.0) eslint-plugin-n: 15.7.0(eslint@8.56.0) eslint-plugin-promise: 6.1.1(eslint@8.56.0) - express: 4.18.2 + express: 4.18.3 express-fileupload: 1.4.3 jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2) morgan: 1.10.0 @@ -20937,7 +20896,7 @@ packages: dev: false file:projects/pod-front.tgz(svelte@4.2.11): - resolution: {integrity: sha512-IbqAyQ8rYXPdITQ9dFovtQ6q89wIiUTzkhafA+K5cX65SUfeSM6iMx94gce9/LbLIWIs/v87/3ftO9Lf88gswA==, tarball: file:projects/pod-front.tgz} + resolution: {integrity: sha512-GL3Lo44ZK+ONrZYYP192Ixh6X9/Vq3wkWvgosdYesIrmQEvvZaxPmtLZUwJRpZ5HiYAequ5u015rUIz+gjhVcQ==, tarball: file:projects/pod-front.tgz} id: file:projects/pod-front.tgz name: '@rush-temp/pod-front' version: 0.0.0 @@ -20953,7 +20912,7 @@ packages: '@types/uuid': 8.3.4 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) - body-parser: 1.19.2 + body-parser: 1.20.2 compression: 1.7.4 cors: 2.8.5 cross-env: 7.0.3 @@ -20963,7 +20922,7 @@ packages: eslint-plugin-import: 2.29.1(eslint@8.56.0) eslint-plugin-n: 15.7.0(eslint@8.56.0) eslint-plugin-promise: 6.1.1(eslint@8.56.0) - express: 4.18.2 + express: 4.18.3 express-fileupload: 1.4.3 jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2) prettier: 3.2.5 @@ -20986,7 +20945,7 @@ packages: dev: false file:projects/pod-server.tgz(svelte@4.2.11): - resolution: {integrity: sha512-rRrgSjbgcgoFqfaUlCyx/b0kG4FG+ULExDOMrCX9xF8i3rWRE4H96NhEYCLRfW7Tc/lRgb9z3CEJKRGwRyVkjQ==, tarball: file:projects/pod-server.tgz} + resolution: {integrity: sha512-RfcF42iv64hXOnQ1OMrGVXV0wUXqKOtAx0nEXbsGETtY059o/Rt+rIVimT+R8GQVaYfqyY5AR9kC91eBbWls+w==, tarball: file:projects/pod-server.tgz} id: file:projects/pod-server.tgz name: '@rush-temp/pod-server' version: 0.0.0 @@ -22503,7 +22462,7 @@ packages: dev: false file:projects/server-request-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(svelte@4.2.11)(ts-node@10.9.2): - resolution: {integrity: sha512-Hh38VO8SURroi8TtBT0UfnxIqUwZjPhHf9nv3GfIpcbwkKgMr0qAWo9P6k9eEeKSRjriPcNhIM9VNOFwMsG1SQ==, tarball: file:projects/server-request-resources.tgz} + resolution: {integrity: sha512-bVFnGUZCaRd2aKmIc8CtCuY7Jgoyt2D/ocKsObNwd+iKsIhVCaZrcYoqSn8jhqKUdWMyc2784n9mmHeNqKSTQw==, tarball: file:projects/server-request-resources.tgz} id: file:projects/server-request-resources.tgz name: '@rush-temp/server-request-resources' version: 0.0.0 @@ -23124,7 +23083,7 @@ packages: dev: false file:projects/server-ws.tgz(esbuild@0.20.1)(svelte@4.2.11)(ts-node@10.9.2): - resolution: {integrity: sha512-IdOX7544NnlwLGuaLfDhiWdJkESqXP24M4fIa6Twlg0nK9d0sj6//TTD3JDrvrkkHwjFHDQjYywQjwGLYwONoQ==, tarball: file:projects/server-ws.tgz} + resolution: {integrity: sha512-owCA424nSv/RxJNLRWaOswSHP2ztwXKL/YhULEOFPo3sqPBustc/49QDoGjMFk/1aV3XEH4y4cPsl4xNsxqssg==, tarball: file:projects/server-ws.tgz} id: file:projects/server-ws.tgz name: '@rush-temp/server-ws' version: 0.0.0 @@ -23145,7 +23104,7 @@ packages: eslint-plugin-import: 2.29.1(eslint@8.56.0) eslint-plugin-n: 15.7.0(eslint@8.56.0) eslint-plugin-promise: 6.1.1(eslint@8.56.0) - express: 4.18.2 + express: 4.18.3 jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2) prettier: 3.2.5 prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11) diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index 1dc0bba39c..a388480807 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -539,7 +539,12 @@ export function isCollectionAttr (hierarchy: Hierarchy, key: KeyedAttribute): bo * @public */ export function decodeTokenPayload (token: string): any { - return JSON.parse(atob(token.split('.')[1])) + try { + return JSON.parse(atob(token.split('.')[1])) + } catch (err: any) { + console.error(err) + return {} + } } export function isAdminUser (): boolean { diff --git a/plugins/login-resources/src/components/SelectWorkspace.svelte b/plugins/login-resources/src/components/SelectWorkspace.svelte index 12594f2920..123497e0e1 100644 --- a/plugins/login-resources/src/components/SelectWorkspace.svelte +++ b/plugins/login-resources/src/components/SelectWorkspace.svelte @@ -16,8 +16,15 @@
@@ -95,16 +105,32 @@
+ {#if isAdmin} +
+ +
+ {/if} {#await _getWorkspaces() then}
- {#each workspaces as workspace} + {#each workspaces.filter((it) => search === '' || (it.workspaceName?.includes(search) ?? false) || it.workspace.includes(search)) as workspace} + {@const wsName = workspace.workspaceName ?? workspace.workspace} +
select(workspace.workspace)} > - {workspace.workspaceName ?? workspace.workspace} +
+ + {wsName} + + {#if isAdmin && wsName !== workspace.workspace} + + {workspace.workspace} + + {/if} +
{/each} {#if workspaces.length === 0 && account?.confirmed === true} diff --git a/plugins/login-resources/src/utils.ts b/plugins/login-resources/src/utils.ts index cf59f786df..8df585c2a5 100644 --- a/plugins/login-resources/src/utils.ts +++ b/plugins/login-resources/src/utils.ts @@ -203,7 +203,8 @@ export async function getWorkspaces (): Promise { if (endpoint !== undefined) { return [ { - workspace: DEV_WORKSPACE + workspace: DEV_WORKSPACE, + workspaceId: DEV_WORKSPACE } ] } diff --git a/plugins/login/src/index.ts b/plugins/login/src/index.ts index 5cab00148e..45863b0497 100644 --- a/plugins/login/src/index.ts +++ b/plugins/login/src/index.ts @@ -26,8 +26,9 @@ export const loginId = 'login' as Plugin * @public */ export interface Workspace { - workspace: string // + workspace: string // workspace Url workspaceName?: string // A company name + workspaceId: string // A unique identifier for the workspace } /** diff --git a/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte b/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte index cfc3fb3998..9bdf4e00f2 100644 --- a/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte +++ b/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte @@ -14,23 +14,27 @@ --> {#if $workspacesStore.length}
+ {#if isAdmin} +
+ +
+ {/if}
- {#each $workspacesStore as ws, i} + {#each $workspacesStore.filter((it) => search === '' || (it.workspaceName?.includes(search) ?? false) || it.workspace.includes(search)) as ws, i} + {@const wsName = ws.workspaceName ?? ws.workspace} + {@const _activeSession = activeSessions[ws.workspaceId]} 0} class:hover={btns[i] === activeElement} on:mousemove={() => { focusTarget(btns[i]) @@ -116,7 +162,24 @@ - {ws.workspaceName ?? ws.workspace} +
+ + {wsName} + + {#if isAdmin && wsName !== ws.workspace} + + ({ws.workspace}) + + {/if} + {#if isAdmin && (_activeSession?.length ?? 0) > 0} + +
+ +
+ {_activeSession?.length ?? 0} +
+ {/if} +
@@ -134,3 +197,9 @@ {:else}
{/if} + + diff --git a/plugins/workbench-resources/src/components/ServerManager.svelte b/plugins/workbench-resources/src/components/ServerManager.svelte index 16e7c14d5a..f3de8b4f07 100644 --- a/plugins/workbench-resources/src/components/ServerManager.svelte +++ b/plugins/workbench-resources/src/components/ServerManager.svelte @@ -19,6 +19,7 @@ import { ObjectPresenter } from '@hcengineering/view-resources' import { onDestroy } from 'svelte' import MetricsInfo from './statistics/MetricsInfo.svelte' + import { workspacesStore } from '../utils' const _endpoint: string = fetchMetadataLocalStorage(login.metadata.LoginEndpoint) ?? '' const token: string = getMetadata(presentation.metadata.Token) ?? '' @@ -166,6 +167,7 @@ {:else if selectedTab === 'users'}
{#each Object.entries(activeSessions) as act} + {@const wsInstance = $workspacesStore.find((it) => it.workspaceId === act[0])} {@const totalFind = act[1].reduce((it, itm) => itm.current.find + it, 0)} {@const totalTx = act[1].reduce((it, itm) => itm.current.tx + it, 0)} {@const employeeGroups = Array.from(new Set(act[1].map((it) => it.userId)))} @@ -173,7 +175,7 @@
- Workspace: {act[0]}: {act[1].length} current 5 mins => {totalFind}/{totalTx} + Workspace: {wsInstance?.workspaceName ?? act[0]}: {act[1].length} current 5 mins => {totalFind}/{totalTx}
diff --git a/pods/front/package.json b/pods/front/package.json index 321a04de46..b44ecff56b 100644 --- a/pods/front/package.json +++ b/pods/front/package.json @@ -60,15 +60,15 @@ "@hcengineering/front": "^0.6.0", "@hcengineering/core": "^0.6.28", "@hcengineering/platform": "^0.6.9", - "express": "^4.17.1", - "express-fileupload": "^1.2.1", + "express": "^4.18.3", + "express-fileupload": "^1.4.3", "uuid": "^8.3.2", "cors": "^2.8.5", "@hcengineering/elastic": "^0.6.0", "@hcengineering/server-core": "^0.6.1", "@hcengineering/server-token": "^0.6.7", "@hcengineering/attachment": "^0.6.9", - "body-parser": "~1.19.1", + "body-parser": "^1.20.2", "compression": "~1.7.4", "sharp": "~0.32.0" } diff --git a/server/account/src/index.ts b/server/account/src/index.ts index dc90d1308e..c9774da4d8 100644 --- a/server/account/src/index.ts +++ b/server/account/src/index.ts @@ -896,7 +896,7 @@ export async function getInviteLink ( /** * @public */ -export type ClientWorkspaceInfo = Omit +export type ClientWorkspaceInfo = Omit & { workspaceId: string } /** * @public @@ -905,7 +905,7 @@ export type WorkspaceInfo = Omit function mapToClientWorkspace (ws: Workspace): ClientWorkspaceInfo { const { _id, accounts, ...data } = ws - return { ...data, workspace: ws.workspaceUrl ?? ws.workspace } + return { ...data, workspace: ws.workspaceUrl ?? ws.workspace, workspaceId: ws.workspace } } function trimWorkspaceInfo (ws: Workspace): WorkspaceInfo { diff --git a/server/collaborator/package.json b/server/collaborator/package.json index 1c341b3479..e50d5ff964 100644 --- a/server/collaborator/package.json +++ b/server/collaborator/package.json @@ -69,8 +69,8 @@ "mongodb": "^6.3.0", "yjs": "^13.5.52", "y-prosemirror": "^1.2.1", - "express": "^4.17.1", - "body-parser": "~1.19.1", + "express": "^4.18.3", + "body-parser": "^1.20.2", "cors": "^2.8.5", "compression": "~1.7.4", "ws": "^8.10.0" diff --git a/server/front/package.json b/server/front/package.json index 6ce8e9cb90..eba0fb0bd1 100644 --- a/server/front/package.json +++ b/server/front/package.json @@ -46,15 +46,15 @@ "dependencies": { "@hcengineering/core": "^0.6.28", "@hcengineering/platform": "^0.6.9", - "express": "^4.17.1", - "express-fileupload": "^1.2.1", + "express": "^4.18.3", + "express-fileupload": "^1.4.3", "uuid": "^8.3.2", "cors": "^2.8.5", "@hcengineering/elastic": "^0.6.0", "@hcengineering/server-core": "^0.6.1", "@hcengineering/server-token": "^0.6.7", "@hcengineering/attachment": "^0.6.9", - "body-parser": "~1.19.1", + "body-parser": "^1.20.2", "compression": "~1.7.4", "sharp": "~0.32.0", "@hcengineering/minio": "^0.6.0", diff --git a/server/front/src/index.ts b/server/front/src/index.ts index e6caeb3225..881a1a65ca 100644 --- a/server/front/src/index.ts +++ b/server/front/src/index.ts @@ -23,10 +23,15 @@ import cors from 'cors' import express, { Response } from 'express' import fileUpload, { UploadedFile } from 'express-fileupload' import https from 'https' +import morgan from 'morgan' import { join, resolve } from 'path' +import { cwd } from 'process' import sharp from 'sharp' import { v4 as uuid } from 'uuid' -import morgan from 'morgan' +import { preConditions } from './utils' + +const cacheControlValue = 'public, max-age=365d' +const cacheControlMaxAge = '365d' async function minioUpload (minio: MinioService, workspace: WorkspaceId, file: UploadedFile): Promise { const id = uuid() @@ -103,15 +108,41 @@ async function getFileRange ( } } -async function getFile (client: MinioService, workspace: WorkspaceId, uuid: string, res: Response): Promise { +async function getFile ( + client: MinioService, + workspace: WorkspaceId, + uuid: string, + req: Request, + res: Response +): Promise { const stat = await client.stat(workspace, uuid) + const etag = stat.etag + + if ( + preConditions.IfNoneMatch(req.headers, { etag }) === 'notModified' || + preConditions.IfMatch(req.headers, { etag }) === 'notModified' || + preConditions.IfModifiedSince(req.headers, { lastModified: stat.lastModified }) === 'notModified' + ) { + // Matched, return not modified + res.statusCode = 304 + res.end() + return + } + if (preConditions.IfUnmodifiedSince(req.headers, { lastModified: stat.lastModified }) === 'failed') { + // Send 412 (Precondition Failed) + res.statusCode = 412 + res.end() + return + } + try { const dataStream = await client.get(workspace, uuid) res.writeHead(200, { 'Content-Type': stat.metaData['content-type'], Etag: stat.etag, - 'Last-Modified': stat.lastModified.toISOString() + 'Last-Modified': stat.lastModified.toISOString(), + 'Cache-Control': cacheControlValue }) dataStream.on('data', function (chunk) { @@ -182,9 +213,7 @@ export function start ( // eslint-disable-next-line @typescript-eslint/no-misused-promises app.get('/config.json', async (req, res) => { - res.status(200) - res.set('Cache-Control', 'no-cache') - res.json({ + const data = { ACCOUNTS_URL: config.accountsUrl, UPLOAD_URL: config.uploadUrl, MODEL_VERSION: config.modelVersion, @@ -199,19 +228,19 @@ export function start ( DEFAULT_LANGUAGE: config.defaultLanguage, LAST_NAME_FIRST: config.lastNameFirst, ...(extraConfig ?? {}) - }) + } + res.set('Cache-Control', cacheControlValue) + res.status(200) + res.json(data) }) - const dist = resolve(process.env.PUBLIC_DIR ?? __dirname, 'dist') + const dist = resolve(process.env.PUBLIC_DIR ?? cwd(), 'dist') console.log('serving static files from', dist) app.use( express.static(dist, { - maxAge: '7d', - setHeaders (res, path) { - if (path.includes('index.html')) { - res.setHeader('Cache-Control', 'public, max-age=0') - } - } + maxAge: '365d', + etag: true, + lastModified: true }) ) @@ -275,7 +304,7 @@ export function start ( if (range !== undefined) { await getFileRange(range, config.minio, payload.workspace, uuid, res) } else { - await getFile(config.minio, payload.workspace, uuid, res) + await getFile(config.minio, payload.workspace, uuid, req, res) } } catch (error: any) { if (error?.code === 'NoSuchKey' || error?.code === 'NotFound') { @@ -527,8 +556,11 @@ export function start ( }) app.get('*', function (request, response) { - response.setHeader('Cache-Control', 'max-age=0') - response.sendFile(join(dist, 'index.html')) + response.sendFile(join(dist, 'index.html'), { + maxAge: cacheControlMaxAge, + etag: true, + lastModified: true + }) }) const server = app.listen(port) diff --git a/server/front/src/utils.ts b/server/front/src/utils.ts new file mode 100644 index 0000000000..ff2821e4de --- /dev/null +++ b/server/front/src/utils.ts @@ -0,0 +1,83 @@ +export const ETagSupport = { + isWeak (etag: string): boolean { + return etag.startsWith('W/"') + }, + + isStrong (etag: string): boolean { + return etag.startsWith('"') + }, + + opaqueTag (etag: string): string { + if (this.isWeak(etag)) { + return etag.substring(2) + } + + return etag + }, + weakMatch (a: string, b: string): boolean { + return this.opaqueTag(a) === this.opaqueTag(b) + }, + + strongMatch (a: string, b: string): boolean { + return this.isStrong(a) && this.isStrong(b) && a === b + } +} + +function toList (value: string): string[] { + return value.split(',').map((s) => s.trim()) +} + +export const preConditions = { + IfMatch: (headers: Request['headers'], state: { etag: string }): 'fetch' | 'notModified' => { + const header = (headers as any)['if-match'] + if (header == null) { + return 'fetch' + } + if (header === '*') { + return 'fetch' + } + return toList(header).some((etag) => ETagSupport.strongMatch(etag, state.etag)) ? 'notModified' : 'fetch' + }, + IfNoneMatch: (headers: Request['headers'], state: { etag: string }): 'fetch' | 'notModified' => { + const header = (headers as any)['if-none-match'] + if (header == null) { + return 'fetch' + } + + if (header === '*') { + return 'fetch' + } else { + return toList(header).some((etag) => ETagSupport.weakMatch(etag, state.etag)) ? 'notModified' : 'fetch' + } + }, + IfModifiedSince: (headers: Request['headers'], state: { lastModified: Date }): 'fetch' | 'notModified' => { + if ((headers as any)['if-none-match'] != null) { + return 'fetch' + } + const header = (headers as any)['if-modified-since'] + if (header == null) { + return 'fetch' + } + + const date = Date.parse(header) + if (isNaN(date)) { + return 'fetch' + } + return state.lastModified.getTime() <= date ? 'notModified' : 'fetch' + }, + IfUnmodifiedSince: (headers: Request['headers'], state: { lastModified: Date }): 'fetch' | 'failed' => { + if ((headers as any)['if-match'] != null) { + return 'fetch' + } + const header = (headers as any)['if-unmodified-since'] + if (header == null) { + return 'fetch' + } + + const date = Date.parse(header) + if (isNaN(date)) { + return 'fetch' + } + return state.lastModified.getTime() > date ? 'failed' : 'fetch' + } +} diff --git a/server/ws/package.json b/server/ws/package.json index ff616a3bc2..8c91854605 100644 --- a/server/ws/package.json +++ b/server/ws/package.json @@ -46,7 +46,7 @@ "@hcengineering/server-token": "^0.6.7", "@hcengineering/rpc": "^0.6.1", "bufferutil": "^4.0.7", - "express": "^4.17.1", + "express": "^4.18.3", "compression": "~1.7.4", "cors": "^2.8.5" }