From 1789e524fc4bb19071397c360e87c5e72de22d9a Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 10 Aug 2024 23:18:10 +0800 Subject: [PATCH] bump version --- README.md | 12 +++ packages/elastic/package.json | 2 +- packages/hydrooj/package.json | 2 +- packages/ui-default/entry.js | 10 ++- packages/ui-default/index.ts | 2 +- packages/ui-default/package.json | 2 +- packages/ui-default/service-worker.ts | 109 ++++++++++++++------------ packages/vjudge/package.json | 2 +- 8 files changed, 82 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index c72e17f35..f25265a42 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,18 @@ Hydro 用户群:1085853538

更新日志(点击展开)

+### Hydro 4.14.0 / UI 4.53.0 +- core: 修复能够越权查看隐藏题目的问题 [security] +- ui: 优化提交记录详情富文本展示 +- ui: 支持头像缓存 +- ui: 禁止文本越界显示 +- vjudge: codeforces: 优化提交 ID 获取 +- ui: 默认禁用 sentry +- core: 支持隐藏训练左侧用户栏 +- core: cli: 支持 eval +- ui: 添加实验性 speculation rules 支持 +- core: 修复训练参加人数计数 + ### Hydro 4.13.4 / UI 4.52.3 - framework: 修复 404 返回页 - ui: 优化用户权限页布局 diff --git a/packages/elastic/package.json b/packages/elastic/package.json index cda59af1d..2e1626597 100644 --- a/packages/elastic/package.json +++ b/packages/elastic/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/elastic-search", - "version": "1.1.5", + "version": "1.1.6", "main": "index.ts", "repository": "https://github.com/hydro-dev/Hydro", "author": "undefined ", diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 0bcae283f..83d211982 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "4.13.4", + "version": "4.14.0", "bin": "bin/hydrooj.js", "main": "src/plugin-api", "module": "src/plugin-api", diff --git a/packages/ui-default/entry.js b/packages/ui-default/entry.js index 777485e0a..d2164eabe 100644 --- a/packages/ui-default/entry.js +++ b/packages/ui-default/entry.js @@ -31,9 +31,13 @@ window.UiContext = JSON.parse(window.UiContext); window.UserContext = JSON.parse(window.UserContext); try { __webpack_public_path__ = UiContext.cdn_prefix; } catch (e) { } if ('serviceWorker' in navigator) { - const encodedConfig = encodeURIComponent(JSON.stringify(UiContext.SWConfig)); - navigator.serviceWorker.register(`/service-worker.js?config=${encodedConfig}`).then((registration) => { + navigator.serviceWorker.register('/service-worker.js').then((registration) => { console.log('SW registered: ', registration); + fetch('/service-worker-config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(UiContext.SWConfig), + }); }).catch((registrationError) => { console.log('SW registration failed: ', registrationError); }); @@ -48,7 +52,7 @@ if (process.env.NODE_ENV === 'production' && UiContext.sentry_dsn) { if (!e.isUserFacingError) window._sentryEvents.push(e); }; const script = document.createElement('script'); - script.src = new URL('sentry.js', __webpack_public_path__).href; + script.src = new URL('/sentry.js', __webpack_public_path__).href; document.body.appendChild(script); } diff --git a/packages/ui-default/index.ts b/packages/ui-default/index.ts index 24b175909..2008395aa 100644 --- a/packages/ui-default/index.ts +++ b/packages/ui-default/index.ts @@ -158,8 +158,8 @@ export function apply(ctx: Context) { `https://${that.request.host}`, SystemModel.get('server.url'), SystemModel.get('server.cdn'), - ...((SystemModel.get('server.assets') || '').split(',')).filter((i) => i), ], + assets: ((SystemModel.get('ui-default.assets') || '').split(',')).filter((i) => i) || [], domains: SystemModel.get('ui-default.domains') || [], }; }); diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index a68196433..13c0a072d 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.52.3", + "version": "4.53.0", "author": "undefined ", "license": "AGPL-3.0", "main": "index.ts", diff --git a/packages/ui-default/service-worker.ts b/packages/ui-default/service-worker.ts index 6f1c86101..cc1f621f2 100644 --- a/packages/ui-default/service-worker.ts +++ b/packages/ui-default/service-worker.ts @@ -114,19 +114,20 @@ interface ServiceWorkerConfig { hosts: string[]; /** service domains */ domains: string[]; + /** assets domains */ + assets: string[]; preload?: string; } let config: ServiceWorkerConfig = null; -function initConfig() { - config = JSON.parse(new URLSearchParams(location.search).get('config')); +function initConfig(cfg) { + config = cfg; config.hosts ||= []; config.domains = Array.from(new Set([location.host, ...(config.domains || [])])); console.log('Config:', config); } self.addEventListener('install', (event) => event.waitUntil((async () => { - initConfig(); if (process.env.NODE_ENV === 'production' && config?.preload) { const [cache, manifest] = await Promise.all([ caches.open(PRECACHE), @@ -140,7 +141,6 @@ self.addEventListener('install', (event) => event.waitUntil((async () => { })())); self.addEventListener('activate', (event) => { - initConfig(); event.waitUntil(self.clients.claim()); const valid = [PRECACHE]; caches.keys().then((names) => names @@ -181,23 +181,47 @@ async function get(request: Request) { function transformUrl(url: string) { const urlObject = new URL(url); const path = urlObject.pathname; - if (path.startsWith('/fs/') || path.includes('X-Amz')) urlObject.search = ''; + if (path.startsWith('/fs/') || path.toLowerCase().includes('x-amz')) urlObject.search = ''; return urlObject.toString(); } -async function cachedRespond(request: Request) { +async function cached(request: Request, cacheKey: string, fetchFunc: () => Promise) { const url = transformUrl(request.url); - const cachedResponse = await caches.match(url); - if (cachedResponse) return cachedResponse; - console.log(`Caching ${url}`); + const urlObject = new URL(url); + const isAsset = config.assets.some((i) => request.url.startsWith(i)); + const rewritable = !isAsset && config.domains.length > 1 + && config.domains.includes(urlObject.hostname) + && urlObject.origin === location.origin; + let targets = [url]; + if (rewritable) { + targets = config.domains.map((i) => { + const t = new URL(request.url); + t.host = i; + return transformUrl(t.toString()); + }); + } + + if (!shouldCache(request)) return rewritable ? fetchFunc() : fetch(request); + + const results = await Promise.all(targets.map((i) => caches.match(i))); + const found = results.find((i) => i); + if (found) { + console.debug('Serve from cache %s <- %s', request.url, found.url); + return found; + } const [cache, response] = await Promise.all([ - caches.open(PRECACHE), - get(request), + caches.open(cacheKey), + fetchFunc(), ]); + if (response.status === 206) return response; // partial response cannot be cached if (response.ok) { + console.log(`Cached ${url}`); cache.put(url, response.clone()); return response; } + console.log(`Failed to cache ${url}`, response); + // If response fails, re-fetch the original request to prevent + // errors caused by different headers and do not cache them return fetch(request); } @@ -206,6 +230,11 @@ self.addEventListener('fetch', (event: FetchEvent) => { event.respondWith(new Response('pong')); return; } + if (event.request.url.endsWith('/service-worker-config')) { + event.request.json().then((cfg) => initConfig(cfg)); + event.respondWith(new Response('ok')); + return; + } if (map.get(event.request.url)) { streamsaver(event); return; @@ -213,46 +242,24 @@ self.addEventListener('fetch', (event: FetchEvent) => { if (!['get', 'post', 'head'].includes(event.request.method.toLowerCase())) return; if (!config) return; // Don't do anything when not initialized const url = new URL(event.request.url); - const rewritable = config.domains.length > 1 - && config.domains.includes(url.hostname) && url.origin === location.origin; + const isAsset = config.assets.some((i) => event.request.url.startsWith(i)); + const rewritable = !isAsset && config.domains.length > 1 + && config.domains.includes(url.hostname) + && url.origin === location.origin; // Only handle whitelisted origins; - if (!config.hosts.some((i) => event.request.url.startsWith(i))) return; + if (!isAsset && !config.hosts.some((i) => event.request.url.startsWith(i))) return; + // Do not cache range requests + if (event.request.headers.get('range')?.trim()?.length) return; - if (shouldCache(event.request)) { - event.respondWith((async () => { - if (rewritable) { - const targets = config.domains.map((i) => { - const t = new URL(event.request.url); - t.host = i; - return transformUrl(t.toString()); - }); - const results = await Promise.all(targets.map((i) => caches.match(i))); - return results.find((i) => i) || cachedRespond(event.request); - } - const transformedUrl = transformUrl(event.request.url); - const cachedResponse = await caches.match(transformedUrl); - if (cachedResponse) return cachedResponse; - console.log(`Caching ${transformedUrl}`); - const [cache, response] = await Promise.all([ - caches.open(PRECACHE), - fetch(url, { - method: event.request.method, - headers: event.request.headers, - redirect: event.request.redirect, - keepalive: event.request.keepalive, - referrer: event.request.referrer, - referrerPolicy: event.request.referrerPolicy, - signal: event.request.signal, - }), // Fetch from url to prevent opaque response - ]); - if (response.ok) { - cache.put(transformedUrl, response.clone()); - return response; - } - console.log(`Failed to cache ${transformedUrl}`, response); - // If response fails, re-fetch the original request to prevent - // errors caused by different headers and do not cache them - return fetch(event.request); - })()); - } else if (rewritable) event.respondWith(get(event.request)); + event.respondWith((async () => cached(event.request, isAsset ? 'assets' : PRECACHE, () => (rewritable + ? get(event.request) + : fetch(url, { + method: event.request.method, + headers: event.request.headers, + redirect: event.request.redirect, + keepalive: event.request.keepalive, + referrer: event.request.referrer, + referrerPolicy: event.request.referrerPolicy, + signal: event.request.signal, + }))))()); }); diff --git a/packages/vjudge/package.json b/packages/vjudge/package.json index 6ec148c0e..f3ca9c355 100644 --- a/packages/vjudge/package.json +++ b/packages/vjudge/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/vjudge", - "version": "1.9.9", + "version": "1.9.10", "description": "Submit problems to remote oj", "main": "./src/index.ts", "repository": "https://github.com/hydro-dev/Hydro.git",