diff --git a/CHANGELOG.md b/CHANGELOG.md index cf2e910d41..7c33deaa2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - ### Client +- Fix: 特定の条件下でMisskeyが起動せず空白のページが表示されることがある問題を軽減 - Fix: 初回読み込み時などに、言語設定で不整合が発生することがある問題を修正 ### Server diff --git a/packages/sw/src/const.ts b/packages/sw/src/const.ts new file mode 100644 index 0000000000..9dd9bad30f --- /dev/null +++ b/packages/sw/src/const.ts @@ -0,0 +1,6 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export const FETCH_TIMEOUT_MS = 10000; diff --git a/packages/sw/src/scripts/lang.ts b/packages/sw/src/scripts/lang.ts index 40b6aa4e7b..63ac4ce399 100644 --- a/packages/sw/src/scripts/lang.ts +++ b/packages/sw/src/scripts/lang.ts @@ -8,6 +8,7 @@ */ import { get, set } from 'idb-keyval'; import { I18n } from '@@/js/i18n.js'; +import { FETCH_TIMEOUT_MS } from '@/const.js'; import type { Locale } from 'i18n'; class SwLang { @@ -37,11 +38,21 @@ class SwLang { // _DEV_がtrueの場合は常に最新化 if (!localeRes || _DEV_) { - localeRes = await fetch(localeUrl); - const clone = localeRes.clone(); - if (!clone.clone().ok) throw new Error('locale fetching error'); + const controller = new AbortController(); + const timeout = globalThis.setTimeout(() => { + controller.abort('locale-fetch-timeout'); + }, FETCH_TIMEOUT_MS); - caches.open(this.cacheName).then(cache => cache.put(localeUrl, clone)); + try { + localeRes = await fetch(localeUrl, { signal: controller.signal }); + + const clone = localeRes.clone(); + if (!clone.clone().ok) throw new Error('locale fetching error'); + + caches.open(this.cacheName).then(cache => cache.put(localeUrl, clone)); + } finally { + globalThis.clearTimeout(timeout); + } } return new I18n(await localeRes.json()); diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index 5cece73401..21017f8241 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -5,6 +5,7 @@ import { get } from 'idb-keyval'; import * as Misskey from 'misskey-js'; +import { FETCH_TIMEOUT_MS } from '@/const.js'; import type { PushNotificationDataMap } from '@/types.js'; import type { I18n } from '@@/js/i18n.js'; import type { Locale } from 'i18n'; @@ -12,6 +13,52 @@ import { createEmptyNotification, createNotification } from '@/scripts/create-no import { swLang } from '@/scripts/lang.js'; import * as swos from '@/scripts/operations.js'; +async function respondToNavigation(request: Request): Promise { + const controller = new AbortController(); + const timeout = globalThis.setTimeout(() => { + controller.abort('navigation-timeout'); + }, FETCH_TIMEOUT_MS); + + try { + const response = await fetch(request, { signal: controller.signal }); + + if (response?.status && response.status < 500) return response; + if (response?.type === 'opaqueredirect') return response; + } catch (error) { + if (_DEV_) { + console.warn('navigation fetch failed; showing offline page', error); + } + } finally { + globalThis.clearTimeout(timeout); + } + + // Only show offline page when network request actually fails + const html = await offlineContentHTML(); + return new Response(html, { + status: 200, + headers: { + 'content-type': 'text/html', + }, + }); +} + +async function offlineContentHTML() { + let i18n: Partial>; + try { + i18n = await (swLang.i18n ?? await swLang.fetchLocale()) as Partial>; + } catch { + i18n = {}; + } + + const messages = { + title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server', + header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server', + reload: i18n.ts?.reload ?? 'Reload', + }; + + return `${messages.title}
${messages.header}
v${_VERSION_}
`; +} + globalThis.addEventListener('install', () => { // ev.waitUntil(globalThis.skipWaiting()); }); @@ -28,17 +75,6 @@ globalThis.addEventListener('activate', ev => { ); }); -async function offlineContentHTML() { - const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial>; - const messages = { - title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server', - header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server', - reload: i18n.ts?.reload ?? 'Reload', - }; - - return `${messages.title}
${messages.header}
v${_VERSION_}
`; -} - globalThis.addEventListener('fetch', ev => { let isHTMLRequest = false; if (ev.request.headers.get('sec-fetch-dest') === 'document') { @@ -50,18 +86,7 @@ globalThis.addEventListener('fetch', ev => { } if (!isHTMLRequest) return; - ev.respondWith( - fetch(ev.request) - .catch(async () => { - const html = await offlineContentHTML(); - return new Response(html, { - status: 200, - headers: { - 'content-type': 'text/html', - }, - }); - }), - ); + ev.respondWith(respondToNavigation(ev.request)); }); globalThis.addEventListener('push', ev => {