bump version

This commit is contained in:
undefined committed 2024-08-10 23:18:10 +08:00
1 parent 21316fafaf
commit 1789e524fc
8 files changed
+75 -52

No files matched your search

+12
View File
@@ -111,6 +111,18 @@ Hydro 用户群:1085853538
<details> <details>
<summary><h2>更新日志(点击展开)</h2></summary> <summary><h2>更新日志(点击展开)</h2></summary>
### 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 ### Hydro 4.13.4 / UI 4.52.3
- framework: 修复 404 返回页 - framework: 修复 404 返回页
- ui: 优化用户权限页布局 - ui: 优化用户权限页布局
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@hydrooj/elastic-search", "name": "@hydrooj/elastic-search",
"version": "1.1.5", "version": "1.1.6",
"main": "index.ts", "main": "index.ts",
"repository": "https://github.com/hydro-dev/Hydro", "repository": "https://github.com/hydro-dev/Hydro",
"author": "undefined <i@undefined.moe>", "author": "undefined <i@undefined.moe>",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hydrooj", "name": "hydrooj",
"version": "4.13.4", "version": "4.14.0",
"bin": "bin/hydrooj.js", "bin": "bin/hydrooj.js",
"main": "src/plugin-api", "main": "src/plugin-api",
"module": "src/plugin-api", "module": "src/plugin-api",
+7 -3
View File
@@ -31,9 +31,13 @@ window.UiContext = JSON.parse(window.UiContext);
window.UserContext = JSON.parse(window.UserContext); window.UserContext = JSON.parse(window.UserContext);
try { __webpack_public_path__ = UiContext.cdn_prefix; } catch (e) { } try { __webpack_public_path__ = UiContext.cdn_prefix; } catch (e) { }
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
const encodedConfig = encodeURIComponent(JSON.stringify(UiContext.SWConfig)); navigator.serviceWorker.register('/service-worker.js').then((registration) => {
navigator.serviceWorker.register(`/service-worker.js?config=${encodedConfig}`).then((registration) => {
console.log('SW registered: ', registration); console.log('SW registered: ', registration);
fetch('/service-worker-config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(UiContext.SWConfig),
});
}).catch((registrationError) => { }).catch((registrationError) => {
console.log('SW registration failed: ', 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); if (!e.isUserFacingError) window._sentryEvents.push(e);
}; };
const script = document.createElement('script'); 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); document.body.appendChild(script);
} }
+1 -1
View File
@@ -158,8 +158,8 @@ export function apply(ctx: Context) {
`https://${that.request.host}`, `https://${that.request.host}`,
SystemModel.get('server.url'), SystemModel.get('server.url'),
SystemModel.get('server.cdn'), 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') || [], domains: SystemModel.get('ui-default.domains') || [],
}; };
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@hydrooj/ui-default", "name": "@hydrooj/ui-default",
"version": "4.52.3", "version": "4.53.0",
"author": "undefined <i@undefined.moe>", "author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "index.ts", "main": "index.ts",
+51 -44
View File
@@ -114,19 +114,20 @@ interface ServiceWorkerConfig {
hosts: string[]; hosts: string[];
/** service domains */ /** service domains */
domains: string[]; domains: string[];
/** assets domains */
assets: string[];
preload?: string; preload?: string;
} }
let config: ServiceWorkerConfig = null; let config: ServiceWorkerConfig = null;
function initConfig() { function initConfig(cfg) {
config = JSON.parse(new URLSearchParams(location.search).get('config')); config = cfg;
config.hosts ||= []; config.hosts ||= [];
config.domains = Array.from(new Set([location.host, ...(config.domains || [])])); config.domains = Array.from(new Set([location.host, ...(config.domains || [])]));
console.log('Config:', config); console.log('Config:', config);
} }
self.addEventListener('install', (event) => event.waitUntil((async () => { self.addEventListener('install', (event) => event.waitUntil((async () => {
initConfig();
if (process.env.NODE_ENV === 'production' && config?.preload) { if (process.env.NODE_ENV === 'production' && config?.preload) {
const [cache, manifest] = await Promise.all([ const [cache, manifest] = await Promise.all([
caches.open(PRECACHE), caches.open(PRECACHE),
@@ -140,7 +141,6 @@ self.addEventListener('install', (event) => event.waitUntil((async () => {
})())); })()));
self.addEventListener('activate', (event) => { self.addEventListener('activate', (event) => {
initConfig();
event.waitUntil(self.clients.claim()); event.waitUntil(self.clients.claim());
const valid = [PRECACHE]; const valid = [PRECACHE];
caches.keys().then((names) => names caches.keys().then((names) => names
@@ -181,23 +181,47 @@ async function get(request: Request) {
function transformUrl(url: string) { function transformUrl(url: string) {
const urlObject = new URL(url); const urlObject = new URL(url);
const path = urlObject.pathname; 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(); return urlObject.toString();
} }
async function cachedRespond(request: Request) { async function cached(request: Request, cacheKey: string, fetchFunc: () => Promise<Response>) {
const url = transformUrl(request.url); const url = transformUrl(request.url);
const cachedResponse = await caches.match(url); const urlObject = new URL(url);
if (cachedResponse) return cachedResponse; const isAsset = config.assets.some((i) => request.url.startsWith(i));
console.log(`Caching ${url}`); 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([ const [cache, response] = await Promise.all([
caches.open(PRECACHE), caches.open(cacheKey),
get(request), fetchFunc(),
]); ]);
if (response.status === 206) return response; // partial response cannot be cached
if (response.ok) { if (response.ok) {
console.log(`Cached ${url}`);
cache.put(url, response.clone()); cache.put(url, response.clone());
return response; 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); return fetch(request);
} }
@@ -206,6 +230,11 @@ self.addEventListener('fetch', (event: FetchEvent) => {
event.respondWith(new Response('pong')); event.respondWith(new Response('pong'));
return; 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)) { if (map.get(event.request.url)) {
streamsaver(event); streamsaver(event);
return; return;
@@ -213,29 +242,18 @@ self.addEventListener('fetch', (event: FetchEvent) => {
if (!['get', 'post', 'head'].includes(event.request.method.toLowerCase())) return; if (!['get', 'post', 'head'].includes(event.request.method.toLowerCase())) return;
if (!config) return; // Don't do anything when not initialized if (!config) return; // Don't do anything when not initialized
const url = new URL(event.request.url); const url = new URL(event.request.url);
const rewritable = config.domains.length > 1 const isAsset = config.assets.some((i) => event.request.url.startsWith(i));
&& config.domains.includes(url.hostname) && url.origin === location.origin; const rewritable = !isAsset && config.domains.length > 1
&& config.domains.includes(url.hostname)
&& url.origin === location.origin;
// Only handle whitelisted origins; // 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 () => cached(event.request, isAsset ? 'assets' : PRECACHE, () => (rewritable
event.respondWith((async () => { ? get(event.request)
if (rewritable) { : fetch(url, {
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, method: event.request.method,
headers: event.request.headers, headers: event.request.headers,
redirect: event.request.redirect, redirect: event.request.redirect,
@@ -243,16 +261,5 @@ self.addEventListener('fetch', (event: FetchEvent) => {
referrer: event.request.referrer, referrer: event.request.referrer,
referrerPolicy: event.request.referrerPolicy, referrerPolicy: event.request.referrerPolicy,
signal: event.request.signal, 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));
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@hydrooj/vjudge", "name": "@hydrooj/vjudge",
"version": "1.9.9", "version": "1.9.10",
"description": "Submit problems to remote oj", "description": "Submit problems to remote oj",
"main": "./src/index.ts", "main": "./src/index.ts",
"repository": "https://github.com/hydro-dev/Hydro.git", "repository": "https://github.com/hydro-dev/Hydro.git",