From 8bd7f30c19f9d3c7a1b5ffd585affb76e199b697 Mon Sep 17 00:00:00 2001 From: liquidliang Date: Tue, 20 Jan 2026 13:11:17 +0800 Subject: [PATCH] chore: bump version to 0.5.25 --- .npmrc | 3 + CHANGELOG.md | 23 ++++++ apps/web/index.html | 5 +- apps/web/public/version.json | 9 +-- apps/web/src/sw/cdn-fallback.ts | 6 +- apps/web/src/sw/index.ts | 79 ++++++++++++++++--- package.json | 2 +- .../src/services/workspace-storage-service.ts | 68 +++++++++++++++- 8 files changed, 173 insertions(+), 22 deletions(-) diff --git a/.npmrc b/.npmrc index 521a9f7c..66c9e646 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,4 @@ legacy-peer-deps=true + +# 发布使用官方 npm registry +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/CHANGELOG.md b/CHANGELOG.md index f20a938a..298281c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## 0.5.23 (2026-01-20) + +### 🚀 Features + +- 优化素材库 UI 交互、缩放缓存、Grid 布局及空间效率 ([c64e4a7](https://github.com/ljquan/aitu/commit/c64e4a7)) + +### 🔧 Chores + +- bump version to 0.5.22 ([5571d3f](https://github.com/ljquan/aitu/commit/5571d3f)) +- bump version to 0.5.21 ([55ad29c](https://github.com/ljquan/aitu/commit/55ad29c)) +- bump version to 0.5.20 ([8bfc191](https://github.com/ljquan/aitu/commit/8bfc191)) +- bump version to 0.5.19 ([8707a5b](https://github.com/ljquan/aitu/commit/8707a5b)) +- bump version to 0.5.18 ([88c0dfd](https://github.com/ljquan/aitu/commit/88c0dfd)) +- bump version to 0.5.17 ([91799c9](https://github.com/ljquan/aitu/commit/91799c9)) +- bump version to 0.5.17 and update build time in version.json ([51b5f3f](https://github.com/ljquan/aitu/commit/51b5f3f)) +- bump version to 0.5.17 ([c41c9bd](https://github.com/ljquan/aitu/commit/c41c9bd)) +- docs(claude): 更新调试日志处理和命令行工具使用说明,添加后台清理任务日志记录示例 ([30cf458](https://github.com/ljquan/aitu/commit/30cf458)) +- docs(claude): 添加 React 事件委托、筛选逻辑、Grid 布局及 UI 高度统一规则 ([dcabd75](https://github.com/ljquan/aitu/commit/dcabd75)) + +### ❤️ Thank You + +- liquidliang + ## 0.5.17 (2026-01-20) ### 🚀 Features diff --git a/apps/web/index.html b/apps/web/index.html index 9bab258d..1ab774c3 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -61,7 +61,7 @@ - + @@ -88,6 +88,9 @@ + + +
diff --git a/apps/web/public/version.json b/apps/web/public/version.json index 0614085e..db908c78 100644 --- a/apps/web/public/version.json +++ b/apps/web/public/version.json @@ -1,9 +1,6 @@ { - "version": "0.5.22", - "buildTime": "2026-01-20T04:55:16.014Z", + "version": "0.5.25", + "buildTime": "2026-01-20T05:11:17.776Z", "gitCommit": "unknown", - "changelog": [ - "优化素材库 UI 交互、缩放缓存、Grid 布局及空间效率", - "封装函数不应被绕过、页面卸载清理定时器" - ] + "changelog": [] } \ No newline at end of file diff --git a/apps/web/src/sw/cdn-fallback.ts b/apps/web/src/sw/cdn-fallback.ts index 2e4c62a8..668b6fe2 100644 --- a/apps/web/src/sw/cdn-fallback.ts +++ b/apps/web/src/sw/cdn-fallback.ts @@ -52,8 +52,10 @@ const CDN_CONFIG = { degradeTimeout: 60 * 1000, // 连续失败次数阈值 failThreshold: 3, - // 请求超时(毫秒) - fetchTimeout: 10000, + // 请求超时(毫秒)- 短超时策略: + // CDN 缓存命中通常 < 200ms,设置 1.5s 超时 + // 超时后快速回退到服务器,避免 CDN 回源慢影响用户体验 + fetchTimeout: 1500, }; // CDN 源列表(按优先级排序) diff --git a/apps/web/src/sw/index.ts b/apps/web/src/sw/index.ts index 3475cc5c..27b76e62 100644 --- a/apps/web/src/sw/index.ts +++ b/apps/web/src/sw/index.ts @@ -1,10 +1,6 @@ /// /* eslint-disable no-restricted-globals */ -// fix: self redeclaration error and type casting -const sw = self as unknown as ServiceWorkerGlobalScope; -export { }; // Make this a module - // Import task queue module import { initTaskQueue, @@ -33,9 +29,17 @@ import { initMessageSender, setDebugMode as setMessageSenderDebugMode, setBroadcastCallback, - sendToClient, - broadcastToAllClients, } from './task-queue/utils/message-bus'; +import { + fetchFromCDNWithFallback, + getCDNStatusReport, + resetCDNStatus, + performHealthCheck, +} from './cdn-fallback'; + +// fix: self redeclaration error and type casting +const sw = self as unknown as ServiceWorkerGlobalScope; +export { }; // Make this a module // Initialize task queue (instance used internally by handleTaskQueueMessage) initTaskQueue(sw); @@ -1404,6 +1408,27 @@ sw.addEventListener('message', (event: ExtendableMessageEvent) => { event.source?.postMessage({ type: 'SW_DEBUG_LOGS_CLEARED', }); + } else if (event.data && event.data.type === 'SW_CDN_GET_STATUS') { + // 获取 CDN 状态 + event.source?.postMessage({ + type: 'SW_CDN_STATUS', + status: getCDNStatusReport(), + }); + } else if (event.data && event.data.type === 'SW_CDN_RESET_STATUS') { + // 重置 CDN 状态 + resetCDNStatus(); + event.source?.postMessage({ + type: 'SW_CDN_STATUS_RESET', + }); + } else if (event.data && event.data.type === 'SW_CDN_HEALTH_CHECK') { + // 执行 CDN 健康检查 + (async () => { + const results = await performHealthCheck(APP_VERSION); + event.source?.postMessage({ + type: 'SW_CDN_HEALTH_CHECK_RESULT', + results: Object.fromEntries(results), + }); + })(); } else if (event.data && event.data.type === 'SW_DEBUG_GET_CACHE_ENTRIES') { // 获取缓存条目列表 const { cacheName, limit = 50, offset = 0 } = event.data; @@ -3086,7 +3111,42 @@ async function handleStaticRequest(request: Request): Promise { return cachedResponse; } - // Cache miss - try network + // Cache miss - determine if this is a CDN-cacheable static resource + const resourcePath = url.pathname; + const isStaticResource = !isDevelopment && ( + resourcePath.match(/\.(js|css|png|jpg|jpeg|gif|webp|svg|woff|woff2|ttf|eot|json|ico)$/i) || + request.destination === 'script' || + request.destination === 'style' || + request.destination === 'image' || + request.destination === 'font' + ); + + // ============================================ + // CDN 优先策略:暂时禁用,等 npm 包发布后再启用 + // TODO: npm publish aitu-app 后取消注释下面的代码 + // ============================================ + // if (isStaticResource) { + // try { + // console.log(`[SW CDN] Trying CDN first for: ${resourcePath}`); + // const cdnResult = await fetchFromCDNWithFallback( + // resourcePath, + // APP_VERSION, + // location.origin + // ); + // + // if (cdnResult && cdnResult.response.ok) { + // console.log(`[SW CDN] Success from ${cdnResult.source}: ${resourcePath}`); + // // 缓存成功的响应 + // const responseToCache = cdnResult.response.clone(); + // cache.put(request, responseToCache); + // return cdnResult.response; + // } + // } catch (cdnError) { + // console.warn('[SW CDN] CDN sources failed, trying local server:', cdnError); + // } + // } + + // 回退到本地服务器(开发模式或 CDN 失败) try { const response = await fetchQuick(request); @@ -3134,7 +3194,7 @@ async function handleStaticRequest(request: Request): Promise { return response; } catch (networkError) { - console.error('Static resource unavailable:', request.url); + console.warn('[SW] Network failed, trying old caches:', request.url); // Try to find the resource in any cache (including old version caches) const allCacheNames = await caches.keys(); @@ -3144,7 +3204,7 @@ async function handleStaticRequest(request: Request): Promise { const oldCache = await caches.open(cacheName); const oldCachedResponse = await oldCache.match(request); if (oldCachedResponse) { - // console.log(`Service Worker: Found resource in ${cacheName} after network failure`); + console.log(`[SW] Found resource in ${cacheName} after network failure`); return oldCachedResponse; } } catch (e) { @@ -3153,6 +3213,7 @@ async function handleStaticRequest(request: Request): Promise { } } + // 所有来源都失败了 return new Response('Resource unavailable offline', { status: 503, statusText: 'Service Unavailable', diff --git a/package.json b/package.json index 339400b1..ac0cc216 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aitu/source", - "version": "0.5.22", + "version": "0.5.25", "license": "MIT", "packageManager": "pnpm@10.21.0", "description": "Aitu - An open-source whiteboard tool with AI-powered image/video creation, mind mapping, flowcharts, and freehand drawing", diff --git a/packages/drawnix/src/services/workspace-storage-service.ts b/packages/drawnix/src/services/workspace-storage-service.ts index f4545c7d..9266b3fd 100644 --- a/packages/drawnix/src/services/workspace-storage-service.ts +++ b/packages/drawnix/src/services/workspace-storage-service.ts @@ -132,6 +132,7 @@ function waitForIdle(timeout = 50): Promise { async function detectDatabaseVersion(dbName: string): Promise { return new Promise((resolve) => { if (typeof indexedDB === 'undefined') { + console.warn('[WorkspaceStorage] IndexedDB not available, using min version'); resolve(WORKSPACE_DB_CONFIG.MIN_DATABASE_VERSION); return; } @@ -143,11 +144,22 @@ async function detectDatabaseVersion(dbName: string): Promise { const db = request.result; const version = db.version; db.close(); - resolve(Math.max(version, WORKSPACE_DB_CONFIG.MIN_DATABASE_VERSION)); + const targetVersion = Math.max(version, WORKSPACE_DB_CONFIG.MIN_DATABASE_VERSION); + console.log(`[WorkspaceStorage] Detected DB version: ${version}, using: ${targetVersion}`); + resolve(targetVersion); }; - request.onerror = () => { - resolve(WORKSPACE_DB_CONFIG.MIN_DATABASE_VERSION); + request.onerror = (event) => { + // Try to get version from error event or use a safe high version + console.error('[WorkspaceStorage] Error detecting DB version:', event); + // Use a higher version to avoid downgrade - version 10 should be safe + resolve(10); + }; + + request.onblocked = () => { + // Database is blocked by another connection, use a safe high version + console.warn('[WorkspaceStorage] DB blocked, using safe version 10'); + resolve(10); }; }); } @@ -226,10 +238,60 @@ class WorkspaceStorageService { this.initialized = true; } catch (error) { console.error('[WorkspaceStorage] Failed to initialize:', error); + + // Check if it's a version downgrade error + const errorMsg = String(error); + if (errorMsg.includes("can't be downgraded") || errorMsg.includes('version')) { + console.warn('[WorkspaceStorage] Version conflict detected, attempting recovery...'); + + // Try to delete the database and reinitialize + try { + await this.deleteDatabase(); + await this.createStores(); + await Promise.all([ + this.foldersStore!.ready(), + this.boardsStore!.ready(), + this.stateStore!.ready(), + ]); + this.initialized = true; + console.log('[WorkspaceStorage] Recovery successful, database recreated'); + return; + } catch (recoveryError) { + console.error('[WorkspaceStorage] Recovery failed:', recoveryError); + } + } + throw new Error('Workspace storage initialization failed'); } } + /** + * Delete the database (for recovery from version conflicts) + */ + private async deleteDatabase(): Promise { + return new Promise((resolve, reject) => { + if (typeof indexedDB === 'undefined') { + resolve(); + return; + } + + const request = indexedDB.deleteDatabase(WORKSPACE_DB_CONFIG.DATABASE_NAME); + request.onsuccess = () => { + console.log('[WorkspaceStorage] Database deleted successfully'); + resolve(); + }; + request.onerror = () => { + console.error('[WorkspaceStorage] Failed to delete database'); + reject(new Error('Failed to delete database')); + }; + request.onblocked = () => { + console.warn('[WorkspaceStorage] Database deletion blocked'); + // Still resolve after a timeout + setTimeout(resolve, 1000); + }; + }); + } + // ========== Private Store Getters (ensure initialized) ========== private getFoldersStore(): LocalForage {