From e240470d04f2a2e22e94f063a6a44bec156dc8e5 Mon Sep 17 00:00:00 2001 From: Lectom Date: Wed, 27 May 2026 12:29:56 +0900 Subject: [PATCH] =?UTF-8?q?[WIP]=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=EC=B0=BD=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B0=95=ED=99=94=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ssologin-android-context.png | Bin 74408 -> 0 bytes ssologin-desktop-context.png | Bin 7367 -> 0 bytes ssologin-mobile.png | Bin 4254 -> 0 bytes .../lib/core/i18n/toml_asset_loader.dart | 1 + userfront/lib/core/services/log_policy.dart | 17 ++-- userfront/scripts/dev-server.sh | 88 +++++++++++++++++- userfront/scripts/optimize-web-build.mjs | 39 +++++++- userfront/test/log_policy_test.dart | 9 +- userfront/test/toml_asset_loader_test.dart | 4 + userfront/web/index.html | 61 +++++++++++- 10 files changed, 199 insertions(+), 20 deletions(-) delete mode 100644 ssologin-android-context.png delete mode 100644 ssologin-desktop-context.png delete mode 100644 ssologin-mobile.png diff --git a/ssologin-android-context.png b/ssologin-android-context.png deleted file mode 100644 index 7fef3d0b831686b640de0991a3e7d47228d77e0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74408 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 _normalizeFlatTranslations(Map flatMap) => bool _isUserfrontTranslationKey(String key) { return key.startsWith('domain.') || + key.startsWith('err.userfront.') || key.startsWith('msg.userfront.') || key.startsWith('ui.userfront.') || key.startsWith('ui.common.'); diff --git a/userfront/lib/core/services/log_policy.dart b/userfront/lib/core/services/log_policy.dart index df8e3c24..0aa1b834 100644 --- a/userfront/lib/core/services/log_policy.dart +++ b/userfront/lib/core/services/log_policy.dart @@ -52,14 +52,11 @@ class LogPolicy { required String? appEnv, required String? productionDebugFlag, }) { - final flag = parseOptionalBoolFlag(productionDebugFlag); - if (flag.specified) { - return flag.enabled; - } if (!isProductionEnv(appEnv)) { return true; } - return false; + final flag = parseOptionalBoolFlag(productionDebugFlag); + return flag.specified && flag.enabled; } static bool shouldRelayClientLog({ @@ -67,10 +64,12 @@ class LogPolicy { required String? appEnv, required String? productionDebugFlag, }) { - if (debugEnabled( - appEnv: appEnv, - productionDebugFlag: productionDebugFlag, - )) { + final flag = parseOptionalBoolFlag(productionDebugFlag); + final debugRelayEnabled = isProductionEnv(appEnv) + ? flag.specified && flag.enabled + : !(flag.specified && !flag.enabled); + + if (debugRelayEnabled) { return true; } final normalized = level.trim().toUpperCase(); diff --git a/userfront/scripts/dev-server.sh b/userfront/scripts/dev-server.sh index 2e1691e1..a17fe2d4 100644 --- a/userfront/scripts/dev-server.sh +++ b/userfront/scripts/dev-server.sh @@ -5,16 +5,98 @@ cd /workspace /bin/sh ./scripts/sync_userfront_locales.sh cd /workspace/userfront +USERFRONT_INTERNAL_PORT="${USERFRONT_INTERNAL_PORT:-5000}" +USERFRONT_FLUTTER_RUN_FLAGS="${USERFRONT_FLUTTER_RUN_FLAGS:---debug}" +USERFRONT_BOOT_WARMUP_ATTEMPTS="${USERFRONT_BOOT_WARMUP_ATTEMPTS:-120}" +USERFRONT_BOOT_WARMUP_INTERVAL_SECONDS="${USERFRONT_BOOT_WARMUP_INTERVAL_SECONDS:-0.5}" +USERFRONT_BOOT_WARMUP_LOCALES="${USERFRONT_BOOT_WARMUP_LOCALES:-ko en}" +USERFRONT_BOOT_WARMUP_VIEWPORTS="${USERFRONT_BOOT_WARMUP_VIEWPORTS:-mobile:390 desktop:1440}" + +warm_get() { + path="$1" + locale="$2" + viewport="$3" + width="${viewport#*:}" + if [ "$width" = "$viewport" ]; then + width="" + fi + + wget -qO- \ + --header="Accept-Language: $locale" \ + --header="Viewport-Width: $width" \ + "http://127.0.0.1:${USERFRONT_INTERNAL_PORT}${path}" >/dev/null 2>&1 +} + +warm_userfront_once() { + flutter_pid="$1" + attempt=1 + started_at="$(date +%s)" + + while [ "$attempt" -le "$USERFRONT_BOOT_WARMUP_ATTEMPTS" ]; do + if wget -qO- "http://127.0.0.1:${USERFRONT_INTERNAL_PORT}/flutter_bootstrap.js" >/dev/null 2>&1; then + break + fi + if ! kill -0 "$flutter_pid" 2>/dev/null; then + echo "[userfront-boot] warmup skipped because flutter exited before readiness" >&2 + return 0 + fi + attempt=$((attempt + 1)) + sleep "$USERFRONT_BOOT_WARMUP_INTERVAL_SECONDS" + done + + if [ "$attempt" -gt "$USERFRONT_BOOT_WARMUP_ATTEMPTS" ]; then + echo "[userfront-boot] warmup skipped after ${USERFRONT_BOOT_WARMUP_ATTEMPTS} readiness attempts" >&2 + return 0 + fi + + echo "[userfront-boot] one-shot warmup starting locales=\"${USERFRONT_BOOT_WARMUP_LOCALES}\" viewports=\"${USERFRONT_BOOT_WARMUP_VIEWPORTS}\"" >&2 + + for locale in $USERFRONT_BOOT_WARMUP_LOCALES; do + for viewport in $USERFRONT_BOOT_WARMUP_VIEWPORTS; do + warm_get "/${locale}/signin" "$locale" "$viewport" || true + done + done + + for asset in \ + / \ + /flutter_bootstrap.js \ + /main.dart.mjs \ + /main.dart.wasm \ + /canvaskit/skwasm.js \ + /canvaskit/skwasm.wasm \ + /canvaskit/skwasm_heavy.js \ + /canvaskit/skwasm_heavy.wasm \ + /assets/AssetManifest.bin.json \ + /assets/FontManifest.json + do + wget -qO- "http://127.0.0.1:${USERFRONT_INTERNAL_PORT}${asset}" >/dev/null 2>&1 || true + done + + finished_at="$(date +%s)" + elapsed_seconds=$((finished_at - started_at)) + echo "[userfront-boot] one-shot warmup completed in ${elapsed_seconds}s" >&2 +} + set -- flutter run \ -d web-server \ --web-hostname 0.0.0.0 \ - --web-port "${USERFRONT_INTERNAL_PORT:-5000}" \ + --web-port "${USERFRONT_INTERNAL_PORT}" \ --wasm \ --dart-define=BACKEND_URL="${BACKEND_URL:-}" \ --dart-define=CLIENT_LOG_DEBUG="${CLIENT_LOG_DEBUG:-false}" \ --dart-define=APP_ENV="${APP_ENV:-dev}" \ --dart-define=USERFRONT_URL="${USERFRONT_URL:-}" \ - ${USERFRONT_FLUTTER_RUN_FLAGS:-} \ + ${USERFRONT_FLUTTER_RUN_FLAGS} \ --no-web-resources-cdn -exec "$@" +"$@" & +flutter_pid="$!" + +terminate() { + kill "$flutter_pid" 2>/dev/null || true + wait "$flutter_pid" 2>/dev/null || true +} + +trap terminate INT TERM +warm_userfront_once "$flutter_pid" +wait "$flutter_pid" diff --git a/userfront/scripts/optimize-web-build.mjs b/userfront/scripts/optimize-web-build.mjs index 5de34a56..75a03f95 100644 --- a/userfront/scripts/optimize-web-build.mjs +++ b/userfront/scripts/optimize-web-build.mjs @@ -78,15 +78,18 @@ const canvasKitConfig = 'config:{canvasKitBaseUrl:"canvaskit/"}'; bootstrap = bootstrap.replace( /_flutter\.loader\.load\(\{\s*serviceWorkerSettings:\s*(\{[^{}]*\})\s*,\s*config:\s*\{[\s\S]*?serviceWorkerUrl[\s\S]*?\}\s*,\s*config:\s*\{[^}]*\}\s*\}\);/g, - `_flutter.loader.load({${canvasKitConfig}});`, + (_match, settings) => + `_flutter.loader.load({serviceWorkerSettings:${ensureServiceWorkerUrl(settings)},${canvasKitConfig}});`, ); bootstrap = bootstrap.replace( /_flutter\.loader\.load\(\{\s*serviceWorkerSettings:\s*(\{[^{}]*\})\s*,\s*config:\s*\{[^}]*\}\s*\}\);/g, - `_flutter.loader.load({${canvasKitConfig}});`, + (_match, settings) => + `_flutter.loader.load({serviceWorkerSettings:${ensureServiceWorkerUrl(settings)},${canvasKitConfig}});`, ); bootstrap = bootstrap.replace( /_flutter\.loader\.load\(\{\s*serviceWorkerSettings:\s*(\{[^{}]*\})\s*\}\);/g, - `_flutter.loader.load({${canvasKitConfig}});`, + (_match, settings) => + `_flutter.loader.load({serviceWorkerSettings:${ensureServiceWorkerUrl(settings)},${canvasKitConfig}});`, ); bootstrap = bootstrap.replace( /_flutter\.loader\.load\(\);/g, @@ -302,4 +305,34 @@ async function cacheFirst(request) { `; } +function ensureServiceWorkerUrl(settings) { + const serviceWorkerUrl = `"/flutter_service_worker.js?v=" + ${serviceWorkerVersionExpression(settings)}`; + if (/serviceWorkerUrl\s*:/.test(settings)) { + return settings.replace( + /serviceWorkerUrl\s*:\s*[^,\n}]+,?/, + `serviceWorkerUrl: ${serviceWorkerUrl},`, + ); + } + + const closingBraceIndex = settings.lastIndexOf('}'); + if (closingBraceIndex < 0) { + return settings; + } + const beforeClosing = settings.slice(0, closingBraceIndex).trimEnd(); + const afterClosing = settings.slice(closingBraceIndex); + const separator = + beforeClosing.endsWith('{') || beforeClosing.endsWith(',') ? '' : ','; + return `${beforeClosing}${separator} + serviceWorkerUrl: ${serviceWorkerUrl}, + ${afterClosing}`; +} + +function serviceWorkerVersionExpression(settings) { + const match = settings.match(/serviceWorkerVersion\s*:\s*([^,\n}]+)/); + return ( + match?.[1]?.replace(/\/\*[\s\S]*?\*\//g, '').trim() ?? + 'serviceWorkerVersion' + ); +} + console.log(`[userfront] optimized ${basename(buildDir)} with hashed entrypoints and brotli assets`); diff --git a/userfront/test/log_policy_test.dart b/userfront/test/log_policy_test.dart index f53e69a8..fb8a49ec 100644 --- a/userfront/test/log_policy_test.dart +++ b/userfront/test/log_policy_test.dart @@ -14,7 +14,7 @@ void main() { ); }); - test('explicit debug flag applies in development-like environment', () { + test('explicit true enables debug in development-like environment', () { expect( LogPolicy.debugEnabled(appEnv: 'dev', productionDebugFlag: 'true'), isTrue, @@ -23,13 +23,16 @@ void main() { LogPolicy.debugEnabled(appEnv: 'development', productionDebugFlag: '1'), isTrue, ); + }); + + test('explicit false does not suppress local debug in development', () { expect( LogPolicy.debugEnabled(appEnv: 'dev', productionDebugFlag: 'false'), - isFalse, + isTrue, ); expect( LogPolicy.debugEnabled(appEnv: 'development', productionDebugFlag: '0'), - isFalse, + isTrue, ); }); diff --git a/userfront/test/toml_asset_loader_test.dart b/userfront/test/toml_asset_loader_test.dart index 313fe4e6..f66294bf 100644 --- a/userfront/test/toml_asset_loader_test.dart +++ b/userfront/test/toml_asset_loader_test.dart @@ -43,6 +43,10 @@ void main() { expect(translations['ui.admin.nav.api_keys'], isNull); expect(translations['ui.dev.console_title'], isNull); + expect( + translations['err.userfront.auth_proxy.login_failed'], + 'Login failed.', + ); expect(translations['ui.userfront.login.action.submit'], 'Sign in'); expect(translations['ui.common.theme_light'], 'Light'); }, diff --git a/userfront/web/index.html b/userfront/web/index.html index 2950294f..a8f5692d 100644 --- a/userfront/web/index.html +++ b/userfront/web/index.html @@ -122,7 +122,7 @@ - +