diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index f71f1d7e34..1e8bed97d4 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -185,8 +185,10 @@ export type Config = { driveUrl: string; userAgent: string; frontendEntry: { file: string | null }; + frontendBootLoader: { file: string }; frontendManifestExists: boolean; frontendEmbedEntry: { file: string | null }; + frontendEmbedBootLoader: { file: string }; frontendEmbedManifestExists: boolean; mediaProxy: string; externalMediaProxyEnabled: boolean; @@ -235,10 +237,10 @@ export function loadConfig(): Config { const frontendEmbedManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_embed_vite_/manifest.json'); const frontendManifest = frontendManifestExists ? JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_vite_/manifest.json`, 'utf-8')) - : { 'src/_boot_.ts': { file: null } }; + : { 'src/_boot_.ts': { file: null }, 'src/_bootloader.ts': { file: 'src/_bootloader.ts' } }; const frontendEmbedManifest = frontendEmbedManifestExists ? JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8')) - : { 'src/boot.ts': { file: null } }; + : { 'src/boot.ts': { file: null }, 'src/_bootloader.ts': { file: 'src/_bootloader.ts' } }; const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; @@ -312,8 +314,10 @@ export function loadConfig(): Config { : null, userAgent: `Misskey/${version} (${config.url})`, frontendEntry: frontendManifest['src/_boot_.ts'], + frontendBootLoader: frontendManifest['src/_bootloader.ts'], frontendManifestExists: frontendManifestExists, frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'], + frontendEmbedBootLoader: frontendEmbedManifest['src/_bootloader.ts'], frontendEmbedManifestExists: frontendEmbedManifestExists, perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000, perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500, diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug index 29de86b8b6..e4e70acb22 100644 --- a/packages/backend/src/server/web/views/base-embed.pug +++ b/packages/backend/src/server/web/views/base-embed.pug @@ -2,6 +2,7 @@ block vars block loadClientEntry - const entry = config.frontendEmbedEntry; + - const bootLoader = config.frontendEmbedBootLoader; doctype html @@ -47,8 +48,7 @@ html(class='embed') script(type='application/json' id='misskey_embedCtx' data-generated-at=now) != embedCtx - script - include ../boot.embed.js + script(type='module', src=`/embed_vite/${bootLoader.file}`) body noscript: p diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index a76c75fe5c..3ab420ea4b 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -2,6 +2,7 @@ block vars block loadClientEntry - const entry = config.frontendEntry; + - const bootLoader = config.frontendBootLoader; - const baseUrl = config.url; doctype html @@ -76,8 +77,7 @@ html script(type='application/json' id='misskey_clientCtx' data-generated-at=now) != clientCtx - script - include ../boot.js + script(type='module', src=`/vite/${bootLoader.file}`) body noscript: p diff --git a/packages/frontend-embed/@types/global.d.ts b/packages/frontend-embed/@types/global.d.ts index 8a067a78ec..d8bb566760 100644 --- a/packages/frontend-embed/@types/global.d.ts +++ b/packages/frontend-embed/@types/global.d.ts @@ -6,6 +6,7 @@ type FIXME = any; declare const _LANGS_: string[][]; +declare const _LANG_IDS_: string[]; declare const _VERSION_: string; declare const _ENV_: string; declare const _DEV_: boolean; diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/frontend-embed/src/_bootloader.ts similarity index 65% rename from packages/backend/src/server/web/boot.embed.js rename to packages/frontend-embed/src/_bootloader.ts index 022ff064ad..e7b3c2299b 100644 --- a/packages/backend/src/server/web/boot.embed.js +++ b/packages/frontend-embed/src/_bootloader.ts @@ -5,18 +5,24 @@ 'use strict'; +import { addStyle, bootloaderLocales, detectLanguage } from '@@/js/bootloader'; + +interface Window { + CLIENT_ENTRY: string | undefined; +} + // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので (async () => { - window.onerror = (e) => { - console.error(e); + window.onerror = (error) => { + console.error(error); renderError('SOMETHING_HAPPENED'); }; - window.onunhandledrejection = (e) => { - console.error(e); + window.onunhandledrejection = (error) => { + console.error(error); renderError('SOMETHING_HAPPENED_IN_PROMISE'); }; - let forceError = localStorage.getItem('forceError'); + const forceError = localStorage.getItem('forceError'); if (forceError != null) { renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.'); return; @@ -31,33 +37,13 @@ document.documentElement.classList.add('noborder'); } - //#region Detect language & fetch translations - const supportedLangs = LANGS; - /** @type { string } */ - let lang = localStorage.getItem('lang'); - if (lang == null || !supportedLangs.includes(lang)) { - if (supportedLangs.includes(navigator.language)) { - lang = navigator.language; - } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - - // Fallback - if (lang == null) lang = 'en-US'; - } - } - - // for https://github.com/misskey-dev/misskey/issues/10202 - if (lang == null || lang.toString == null || lang.toString() === 'null') { - console.error('invalid lang value detected!!!', typeof lang, lang); - lang = 'en-US'; - } - //#endregion + const lang = detectLanguage(); //#region Script async function importAppScript() { - await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/_boot_.ts') - .catch(async e => { - console.error(e); + await import(`/embed_vite/${(window.CLIENT_ENTRY ?? 'src/boot.ts').replace('scripts', lang)}`) + .catch(async error => { + console.error(error); renderError('APP_IMPORT'); }); } @@ -72,44 +58,19 @@ } //#endregion - async function addStyle(styleText) { - let css = document.createElement('style'); - css.appendChild(document.createTextNode(styleText)); - document.head.appendChild(css); - } - - async function renderError(code) { + async function renderError(code: string, _details?) { // Cannot set property 'innerHTML' of null を回避 if (document.readyState === 'loading') { await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); } - let messages = null; - const bootloaderLocales = localStorage.getItem('bootloaderLocales'); - if (bootloaderLocales) { - messages = JSON.parse(bootloaderLocales); - } - if (!messages) { - // older version of misskey does not store bootloaderLocales, stores locale as a whole - const legacyLocale = localStorage.getItem('locale'); - if (legacyLocale) { - const parsed = JSON.parse(legacyLocale); - messages = { - ...(parsed._bootErrors ?? {}), - reload: parsed.reload, - }; - } - } - if (!messages) messages = {}; - - const title = messages?.title || 'Failed to initialize Misskey'; - const reload = messages?.reload || 'Reload'; + const messages = bootloaderLocales(); document.body.innerHTML = ` -
+ `; addStyle(` #misskey_app, diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts index 3ddee9b8a9..eae02c4909 100644 --- a/packages/frontend-embed/vite.config.ts +++ b/packages/frontend-embed/vite.config.ts @@ -121,6 +121,7 @@ export function getConfig(): UserConfig { define: { _VERSION_: JSON.stringify(meta.version), _LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])), + _LANG_IDS_: JSON.stringify(Object.keys(locales)), _ENV_: JSON.stringify(process.env.NODE_ENV), _DEV_: process.env.NODE_ENV !== 'production', _PERF_PREFIX_: JSON.stringify('Misskey:'), @@ -139,6 +140,7 @@ export function getConfig(): UserConfig { input: { i18n: './src/i18n.ts', entry: './src/boot.ts', + bootloader: './src/_bootloader.ts', }, external: externalPackages.map(p => p.match), preserveEntrySignatures: 'allow-extension', diff --git a/packages/frontend-shared/@types/global.d.ts b/packages/frontend-shared/@types/global.d.ts index 52081d07b3..83011e7557 100644 --- a/packages/frontend-shared/@types/global.d.ts +++ b/packages/frontend-shared/@types/global.d.ts @@ -7,6 +7,7 @@ type FIXME = any; declare const _LANGS_: string[][]; +declare const _LANG_IDS_: string[]; declare const _VERSION_: string; declare const _ENV_: string; declare const _DEV_: boolean; diff --git a/packages/frontend-shared/js/bootloader.ts b/packages/frontend-shared/js/bootloader.ts new file mode 100644 index 0000000000..792023f03f --- /dev/null +++ b/packages/frontend-shared/js/bootloader.ts @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// common portion of bootloader for frontend and frontend-embed + +import type { Locale } from '../../../locales/index.js'; + +export function detectLanguage(): string { + const supportedLangs = _LANG_IDS_; + let lang: string | null | undefined = localStorage.getItem('lang'); + if (lang == null || !supportedLangs.includes(lang)) { + if (supportedLangs.includes(navigator.language)) { + lang = navigator.language; + } else { + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + + // Fallback + lang ??= 'en-US'; + } + } + + // for https://github.com/misskey-dev/misskey/issues/10202 + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (lang == null || lang.toString == null || lang.toString() === 'null') { + console.error('invalid lang value detected!!!', typeof lang, lang); + lang = 'en-US'; + } + + return lang; +} + +type BootLoaderLocales = Locale['_bootErrors'] & Pick