mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-07 00:17:12 +08:00
feat: support runtime frontend configuration (#1135)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
VITE_API_BASE=https://temp-email-api.xxx.xxx
|
||||
VITE_DEFAULT_LANG=zh
|
||||
VITE_CF_WEB_ANALY_TOKEN=
|
||||
VITE_IS_TELEGRAM=false
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<link rel="icon" href="/logo.png" sizes="any">
|
||||
<link rel="apple-touch-icon" href="/logo.png">
|
||||
<script id="app-config" type="application/json">{}</script>
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -12,12 +12,13 @@ import Footer from './views/Footer.vue';
|
||||
import { api } from './api'
|
||||
import { getNaiveLocaleConfig } from './i18n/naive-locale'
|
||||
import { DEFAULT_LOCALE, isSupportedLocale } from './i18n/utils'
|
||||
import { APP_CONFIG } from './config'
|
||||
|
||||
const {
|
||||
isDark, loading, useSideMargin, telegramApp, isTelegram
|
||||
} = useGlobalState()
|
||||
const adClient = import.meta.env.VITE_GOOGLE_AD_CLIENT;
|
||||
const adSlot = import.meta.env.VITE_GOOGLE_AD_SLOT;
|
||||
const adClient = APP_CONFIG.GOOGLE_AD_CLIENT;
|
||||
const adSlot = APP_CONFIG.GOOGLE_AD_SLOT;
|
||||
const { locale } = useI18n({ useScope: 'global' });
|
||||
const theme = computed(() => isDark.value ? darkTheme : null)
|
||||
const localeConfig = computed(() => getNaiveLocaleConfig(isSupportedLocale(locale.value) ? locale.value : DEFAULT_LOCALE))
|
||||
@@ -47,7 +48,7 @@ onMounted(async () => {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
const token = import.meta.env.VITE_CF_WEB_ANALY_TOKEN;
|
||||
const token = APP_CONFIG.CF_WEB_ANALY_TOKEN;
|
||||
|
||||
const exist = document.querySelector('script[src="https://static.cloudflareinsights.com/beacon.min.js"]') !== null
|
||||
if (token && !exist) {
|
||||
@@ -66,7 +67,7 @@ onMounted(async () => {
|
||||
|
||||
|
||||
// check if telegram is enabled
|
||||
const enableTelegram = import.meta.env.VITE_IS_TELEGRAM;
|
||||
const enableTelegram = APP_CONFIG.IS_TELEGRAM;
|
||||
if (
|
||||
(typeof enableTelegram === 'boolean' && enableTelegram === true)
|
||||
||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const setRuntimeConfig = (config) => {
|
||||
const element = document.createElement('script')
|
||||
element.id = 'app-config'
|
||||
element.type = 'application/json'
|
||||
element.textContent = JSON.stringify(config)
|
||||
document.head.appendChild(element)
|
||||
}
|
||||
|
||||
describe('APP_CONFIG', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
vi.stubEnv('VITE_API_BASE', 'https://build.example.com')
|
||||
vi.stubEnv('VITE_DEFAULT_LANG', 'zh')
|
||||
vi.stubEnv('VITE_IS_TELEGRAM', 'false')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
document.querySelector('#app-config')?.remove()
|
||||
vi.unstubAllEnvs()
|
||||
})
|
||||
|
||||
it('uses build settings when runtime settings are absent', async () => {
|
||||
const { APP_CONFIG } = await import('../config')
|
||||
|
||||
expect(APP_CONFIG.API_BASE).toBe('https://build.example.com')
|
||||
expect(APP_CONFIG.DEFAULT_LANG).toBe('zh')
|
||||
})
|
||||
|
||||
it('overrides only settings provided by index.html', async () => {
|
||||
setRuntimeConfig({ API_BASE: 'https://runtime.example.com', DEFAULT_LANG: 'en' })
|
||||
|
||||
const { APP_CONFIG } = await import('../config')
|
||||
|
||||
expect(APP_CONFIG.API_BASE).toBe('https://runtime.example.com')
|
||||
expect(APP_CONFIG.DEFAULT_LANG).toBe('en')
|
||||
})
|
||||
|
||||
it('allows an explicit empty runtime value', async () => {
|
||||
setRuntimeConfig({ API_BASE: '' })
|
||||
|
||||
const { APP_CONFIG } = await import('../config')
|
||||
|
||||
expect(APP_CONFIG.API_BASE).toBe('')
|
||||
expect(APP_CONFIG.DEFAULT_LANG).toBe('zh')
|
||||
})
|
||||
|
||||
it('falls back to build settings for invalid runtime value types', async () => {
|
||||
setRuntimeConfig({ API_BASE: {}, DEFAULT_LANG: 1, IS_TELEGRAM: [] })
|
||||
|
||||
const { APP_CONFIG } = await import('../config')
|
||||
|
||||
expect(APP_CONFIG.API_BASE).toBe('https://build.example.com')
|
||||
expect(APP_CONFIG.DEFAULT_LANG).toBe('zh')
|
||||
expect(APP_CONFIG.IS_TELEGRAM).toBe('false')
|
||||
})
|
||||
|
||||
it('reads runtime settings only once', async () => {
|
||||
setRuntimeConfig({ DEFAULT_LANG: 'en' })
|
||||
const firstImport = await import('../config')
|
||||
|
||||
document.querySelector('#app-config').textContent = JSON.stringify({ DEFAULT_LANG: 'de' })
|
||||
const secondImport = await import('../config')
|
||||
|
||||
expect(secondImport.APP_CONFIG).toBe(firstImport.APP_CONFIG)
|
||||
expect(secondImport.APP_CONFIG.DEFAULT_LANG).toBe('en')
|
||||
})
|
||||
})
|
||||
@@ -6,8 +6,9 @@ import i18n from '../i18n'
|
||||
import { getFingerprint } from '../utils/fingerprint'
|
||||
import { safeBearerHeader, safeHeaderValue } from '../utils/headers'
|
||||
import { sanitizeHtml } from '../utils/sanitize-html'
|
||||
import { APP_CONFIG } from '../config'
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_BASE || "";
|
||||
const API_BASE = APP_CONFIG.API_BASE || "";
|
||||
const {
|
||||
loading, auth, jwt, settings, openSettings,
|
||||
userOpenSettings, userSettings, announcement,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useScopedI18n } from '@/i18n/app'
|
||||
import { APP_CONFIG } from '@/config'
|
||||
|
||||
import { useGlobalState } from '../store'
|
||||
|
||||
@@ -34,7 +35,7 @@ const modalShow = computed({
|
||||
set: (value) => emit('update:show', value),
|
||||
})
|
||||
|
||||
const configuredApiBaseUrl = import.meta.env.VITE_API_BASE || ''
|
||||
const configuredApiBaseUrl = APP_CONFIG.API_BASE || ''
|
||||
const frontendBaseUrl = computed(() => window.location.origin)
|
||||
const apiBaseUrl = computed(() => (configuredApiBaseUrl || frontendBaseUrl.value).replace(/\/$/, ''))
|
||||
const docLocale = computed(() => locale.value === 'zh' ? 'zh' : 'en')
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
type RuntimeConfig = Record<string, unknown>
|
||||
|
||||
const getRuntimeConfig = (): RuntimeConfig => {
|
||||
if (typeof document === 'undefined') return {}
|
||||
|
||||
const content = document.querySelector('#app-config')?.textContent
|
||||
if (!content?.trim()) return {}
|
||||
|
||||
try {
|
||||
const config = JSON.parse(content)
|
||||
return config && typeof config === 'object' && !Array.isArray(config) ? config : {}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse app config', error)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
const runtimeConfig = getRuntimeConfig()
|
||||
|
||||
const getStringConfigValue = (key: string, buildValue: string): string => {
|
||||
const runtimeValue = runtimeConfig[key]
|
||||
return typeof runtimeValue === 'string' ? runtimeValue : buildValue
|
||||
}
|
||||
|
||||
const getTelegramConfigValue = (buildValue: string): string | boolean => {
|
||||
const runtimeValue = runtimeConfig.IS_TELEGRAM
|
||||
return typeof runtimeValue === 'string' || typeof runtimeValue === 'boolean'
|
||||
? runtimeValue
|
||||
: buildValue
|
||||
}
|
||||
|
||||
export const APP_CONFIG = {
|
||||
API_BASE: getStringConfigValue('API_BASE', import.meta.env.VITE_API_BASE || ''),
|
||||
DEFAULT_LANG: getStringConfigValue('DEFAULT_LANG', import.meta.env.VITE_DEFAULT_LANG || ''),
|
||||
CF_WEB_ANALY_TOKEN: getStringConfigValue('CF_WEB_ANALY_TOKEN', import.meta.env.VITE_CF_WEB_ANALY_TOKEN || ''),
|
||||
IS_TELEGRAM: getTelegramConfigValue(import.meta.env.VITE_IS_TELEGRAM || ''),
|
||||
GOOGLE_AD_CLIENT: getStringConfigValue('GOOGLE_AD_CLIENT', import.meta.env.VITE_GOOGLE_AD_CLIENT || ''),
|
||||
GOOGLE_AD_SLOT: getStringConfigValue('GOOGLE_AD_SLOT', import.meta.env.VITE_GOOGLE_AD_SLOT || ''),
|
||||
} as const
|
||||
@@ -1,11 +1,11 @@
|
||||
import { LOCALE_REGISTRY, SUPPORTED_LOCALES } from './locale-registry'
|
||||
import { APP_CONFIG } from '../config'
|
||||
|
||||
export { SUPPORTED_LOCALES } from './locale-registry'
|
||||
export type { SupportedLocale } from './locale-registry'
|
||||
|
||||
import type { SupportedLocale } from './locale-registry'
|
||||
|
||||
export const DEFAULT_LOCALE: SupportedLocale = 'zh'
|
||||
export const FALLBACK_LOCALE: SupportedLocale = 'zh'
|
||||
export const PREFERRED_LOCALE_STORAGE_KEY = 'preferredLocale'
|
||||
export const EMPTY_LOCALE_MESSAGES = Object.fromEntries(
|
||||
@@ -29,6 +29,9 @@ export const resolveSupportedLocale = (locale: string | null | undefined): Suppo
|
||||
return null
|
||||
}
|
||||
|
||||
export const DEFAULT_LOCALE: SupportedLocale = resolveSupportedLocale(APP_CONFIG.DEFAULT_LANG)
|
||||
|| FALLBACK_LOCALE
|
||||
|
||||
export const matchSupportedLocale = (locale: string | null | undefined): SupportedLocale | null => {
|
||||
if (!locale) return null
|
||||
const normalizedLocale = locale.trim().toLowerCase()
|
||||
@@ -135,4 +138,4 @@ const getLocaleAliasPath = (path: string, locale: SupportedLocale): string => {
|
||||
}
|
||||
|
||||
return getPathWithLocale(basePath, locale)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user