diff --git a/README.md b/README.md index 50783bc28..3928fbb94 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,14 @@ CAPACITOR_DEV_SERVER_URL=https://:5273 pnpm open:ios Then Xcode will open and you can click the "Run" button to run the app on your iPhone. +If you need to connect server channel on pocket in wireless mode, you need to start tamagotchi as root: + +```shell +sudo pnpm dev:tamagotchi +``` + +Then enable secure websocket in tamagotchi `settings/system/general`. + ### Documentation Site ```shell diff --git a/apps/stage-tamagotchi/electron.vite.config.ts b/apps/stage-tamagotchi/electron.vite.config.ts index 0a2231991..7c35f915e 100644 --- a/apps/stage-tamagotchi/electron.vite.config.ts +++ b/apps/stage-tamagotchi/electron.vite.config.ts @@ -130,8 +130,14 @@ export default defineConfig({ VueRouter({ dts: resolve(import.meta.dirname, 'src/renderer/typed-router.d.ts'), routesFolder: [ + { + src: resolve(import.meta.dirname, '..', '..', 'packages', 'stage-pages', 'src', 'pages'), + exclude: base => [ + ...base, + '**/settings/system/general.vue', + ], + }, resolve(import.meta.dirname, 'src', 'renderer', 'pages'), - resolve(import.meta.dirname, '..', '..', 'packages', 'stage-pages', 'src', 'pages'), ], exclude: ['**/components/**'], }), diff --git a/apps/stage-tamagotchi/package.json b/apps/stage-tamagotchi/package.json index c5721ae92..a956b3c3f 100644 --- a/apps/stage-tamagotchi/package.json +++ b/apps/stage-tamagotchi/package.json @@ -97,6 +97,7 @@ "jszip": "^3.10.1", "localforage": "^1.10.0", "mediabunny": "^1.29.0", + "mkcert": "catalog:", "node-vibrant": "^4.0.3", "nprogress": "^0.2.0", "onnxruntime-web": "^1.23.2", diff --git a/apps/stage-tamagotchi/src/main/index.ts b/apps/stage-tamagotchi/src/main/index.ts index 38669e621..0fd2296dc 100644 --- a/apps/stage-tamagotchi/src/main/index.ts +++ b/apps/stage-tamagotchi/src/main/index.ts @@ -13,7 +13,7 @@ import icon from '../../resources/icon.png?asset' import { openDebugger, setupDebugger } from './app/debugger' import { emitAppBeforeQuit, emitAppReady, emitAppWindowAllClosed } from './libs/bootkit/lifecycle' import { setElectronMainDirname } from './libs/electron/location' -import { setupServerChannel } from './services/airi/channel-server' +import { setupServerChannelHandlers } from './services/airi/channel-server' import { setupAutoUpdater } from './services/electron/auto-updater' import { setupTray } from './tray' import { setupAboutWindowReusable } from './windows/about' @@ -72,7 +72,7 @@ initScreenCaptureForMain() app.whenReady().then(async () => { injeca.setLogger(createLoggLogger(useLogg('injeca').useGlobalConfig())) - const serverChannel = injeca.provide('modules:channel-server', () => setupServerChannel()) + const serverChannel = injeca.provide('modules:channel-server', () => setupServerChannelHandlers()) const autoUpdater = injeca.provide('services:auto-updater', () => setupAutoUpdater()) const widgetsManager = injeca.provide('windows:widgets', () => setupWidgetsWindowManager()) const noticeWindow = injeca.provide('windows:notice', () => setupNoticeWindowManager()) diff --git a/apps/stage-tamagotchi/src/main/services/airi/channel-server/index.ts b/apps/stage-tamagotchi/src/main/services/airi/channel-server/index.ts index bc1515e40..5e8f11f1d 100644 --- a/apps/stage-tamagotchi/src/main/services/airi/channel-server/index.ts +++ b/apps/stage-tamagotchi/src/main/services/airi/channel-server/index.ts @@ -1,26 +1,139 @@ -import { env } from 'node:process' +import { execSync } from 'node:child_process' +import { existsSync, readFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { env, platform } from 'node:process' import { useLogg } from '@guiiai/logg' +import { defineInvokeHandler } from '@moeru/eventa' +import { createContext } from '@moeru/eventa/adapters/electron/main' +import { app, ipcMain } from 'electron' +import { createCA, createCert } from 'mkcert' +import { electronRestartWebSocketServer, electronStartWebSocketServer } from '../../../../shared/eventa' import { onAppBeforeQuit } from '../../../libs/bootkit/lifecycle' -export async function setupServerChannel() { +let serverInstance: { close: (closeActiveConnections?: boolean) => Promise } | null = null + +async function installCACertificate(caCert: string) { + const userDataPath = app.getPath('userData') + const caCertPath = join(userDataPath, 'websocket-ca-cert.pem') + writeFileSync(caCertPath, caCert) + + try { + if (platform === 'darwin') { + execSync(`security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${caCertPath}"`, { stdio: 'ignore' }) + } + else if (platform === 'win32') { + execSync(`certutil -addstore -f "Root" "${caCertPath}"`, { stdio: 'ignore' }) + } + else if (platform === 'linux') { + const caDir = '/usr/local/share/ca-certificates' + const caFileName = 'airi-websocket-ca.crt' + try { + writeFileSync(join(caDir, caFileName), caCert) + execSync('update-ca-certificates', { stdio: 'ignore' }) + } + catch { + const userCaDir = join(env.HOME || '', '.local/share/ca-certificates') + try { + if (!existsSync(userCaDir)) { + execSync(`mkdir -p "${userCaDir}"`, { stdio: 'ignore' }) + } + writeFileSync(join(userCaDir, caFileName), caCert) + } + catch { + // Ignore errors + } + } + } + } + catch { + // Ignore installation errors + } +} + +async function generateCertificate() { + const userDataPath = app.getPath('userData') + const caCertPath = join(userDataPath, 'websocket-ca-cert.pem') + const caKeyPath = join(userDataPath, 'websocket-ca-key.pem') + + let ca: { key: string, cert: string } + + if (existsSync(caCertPath) && existsSync(caKeyPath)) { + ca = { + cert: readFileSync(caCertPath, 'utf-8'), + key: readFileSync(caKeyPath, 'utf-8'), + } + } + else { + ca = await createCA({ + organization: 'AIRI', + countryCode: 'US', + state: 'Development', + locality: 'Local', + validity: 365, + }) + writeFileSync(caCertPath, ca.cert) + writeFileSync(caKeyPath, ca.key) + + await installCACertificate(ca.cert) + } + + const cert = await createCert({ + ca: { key: ca.key, cert: ca.cert }, + domains: ['localhost', '127.0.0.1', env.SERVER_RUNTIME_HOSTNAME || 'localhost'], + validity: 365, + }) + + return { + cert: cert.cert, + key: cert.key, + } +} + +async function getOrCreateCertificate() { + const userDataPath = app.getPath('userData') + const certPath = join(userDataPath, 'websocket-cert.pem') + const keyPath = join(userDataPath, 'websocket-key.pem') + + if (existsSync(certPath) && existsSync(keyPath)) { + return { + cert: readFileSync(certPath, 'utf-8'), + key: readFileSync(keyPath, 'utf-8'), + } + } + + const { cert, key } = await generateCertificate() + writeFileSync(certPath, cert) + writeFileSync(keyPath, key) + + return { cert, key } +} + +export async function setupServerChannel(options?: { websocketSecureEnabled?: boolean }) { const log = useLogg('main/server-runtime').useGlobalConfig() - // Start the server-runtime server with WebSocket support + const secureEnabled = options?.websocketSecureEnabled ?? false + try { - // Dynamically import the server-runtime and listhen const serverRuntime = await import('@proj-airi/server-runtime') - const { serve } = await import('h3') const { plugin: ws } = await import('crossws/server') + const { serve } = await import('h3') - const app = serverRuntime.setupApp() + const h3App = serverRuntime.setupApp() - const serverInstance = serve(app, { - // TODO: add proper crossws typing upstream - plugins: [ws({ resolve: async req => (await app.fetch(req) as any).crossws })], - port: env.PORT ? Number(env.PORT) : 6121, - hostname: env.SERVER_RUNTIME_HOSTNAME || 'localhost', + const port = env.PORT ? Number(env.PORT) : 6121 + const hostname = env.SERVER_RUNTIME_HOSTNAME || 'localhost' + + // FIXME: should prompt user to grant permission to save certificate files on macOS + const tls = secureEnabled ? await getOrCreateCertificate() : undefined + + const instance = serve(h3App.app, { + // @ts-expect-error - the .crossws property wasn't extended in types + plugins: [ws({ resolve: async req => (await h3App.app.fetch(req)).crossws })], + port, + hostname, + tls, reusePort: true, silent: true, manual: true, @@ -30,7 +143,17 @@ export async function setupServerChannel() { }, }) - const servePromise = serverInstance.serve() + serverInstance = { + close: async (closeActiveConnections = false) => { + log.log('closing all peers') + h3App.closeAllPeers() + log.log('closing server instance') + await instance.close(closeActiveConnections) + log.log('server instance closed') + }, + } + + const servePromise = instance.serve() if (servePromise instanceof Promise) { servePromise.catch((error) => { const nodejsError = error as NodeJS.ErrnoException @@ -43,6 +166,9 @@ export async function setupServerChannel() { }) } + const protocol = secureEnabled ? 'wss' : 'ws' + log.log(`@proj-airi/server-runtime started on ${protocol}://${hostname}:${port}`) + onAppBeforeQuit(async () => { if (serverInstance && typeof serverInstance.close === 'function') { try { @@ -59,10 +185,38 @@ export async function setupServerChannel() { } } }) - - log.log('@proj-airi/server-runtime started on ws://localhost:6121') } catch (error) { log.withError(error).error('failed to start WebSocket server') } } + +export async function restartServerChannel(options?: { websocketSecureEnabled?: boolean }) { + const log = useLogg('main/server-runtime').useGlobalConfig() + log.log('restarting server channel', { options }) + + if (serverInstance && typeof serverInstance.close === 'function') { + try { + log.log('closing existing server instance') + await serverInstance.close(true) + log.log('existing server instance closed') + } + catch { + // Ignore errors when closing + } + } + serverInstance = null + await setupServerChannel(options) +} + +export function setupServerChannelHandlers() { + const { context } = createContext(ipcMain) + + defineInvokeHandler(context, electronStartWebSocketServer, async (req) => { + await setupServerChannel({ websocketSecureEnabled: req?.websocketSecureEnabled }) + }) + + defineInvokeHandler(context, electronRestartWebSocketServer, async (req) => { + await restartServerChannel({ websocketSecureEnabled: req?.websocketSecureEnabled }) + }) +} diff --git a/apps/stage-tamagotchi/src/renderer/App.vue b/apps/stage-tamagotchi/src/renderer/App.vue index e5218c627..8912909e9 100644 --- a/apps/stage-tamagotchi/src/renderer/App.vue +++ b/apps/stage-tamagotchi/src/renderer/App.vue @@ -21,8 +21,8 @@ import { toast, Toaster } from 'vue-sonner' import ResizeHandler from './components/ResizeHandler.vue' -import { electronOpenSettings, electronStartTrackMousePosition } from '../shared/eventa' -import { useElectronEventaContext } from './composables/electron-vueuse' +import { electronOpenSettings, electronStartTrackMousePosition, electronStartWebSocketServer } from '../shared/eventa' +import { useElectronEventaContext, useElectronEventaInvoke } from './composables/electron-vueuse' const { isDark: dark } = useTheme() const i18n = useI18n() @@ -49,7 +49,8 @@ watch(dark, () => updateThemeColor(), { immediate: true }) watch(route, () => updateThemeColor(), { immediate: true }) onMounted(() => updateThemeColor()) -// FIXME: store settings to file +const startWebSocketServer = useElectronEventaInvoke(electronStartWebSocketServer) + onMounted(async () => { analyticsStore.initialize() cardStore.initialize() @@ -58,6 +59,7 @@ onMounted(async () => { await chatSessionStore.initialize() await displayModelsStore.loadDisplayModelsFromIndexedDB() await settingsStore.initializeStageModel() + await startWebSocketServer({ websocketSecureEnabled: settingsStore.websocketSecureEnabled }) await serverChannelStore.initialize({ possibleEvents: ['ui:configure'] }).catch(err => console.error('Failed to initialize Mods Server Channel in App.vue:', err)) await contextBridgeStore.initialize() diff --git a/apps/stage-tamagotchi/src/renderer/pages/settings/system/general.vue b/apps/stage-tamagotchi/src/renderer/pages/settings/system/general.vue new file mode 100644 index 000000000..a660e8ddc --- /dev/null +++ b/apps/stage-tamagotchi/src/renderer/pages/settings/system/general.vue @@ -0,0 +1,46 @@ + + + + + +meta: + layout: settings + titleKey: settings.pages.system.general.title + subtitleKey: settings.title + stageTransition: + name: slide + diff --git a/apps/stage-tamagotchi/src/shared/eventa.ts b/apps/stage-tamagotchi/src/shared/eventa.ts index c170aef78..2a3da64e9 100644 --- a/apps/stage-tamagotchi/src/shared/eventa.ts +++ b/apps/stage-tamagotchi/src/shared/eventa.ts @@ -7,6 +7,8 @@ export const electronOpenSettings = defineInvokeEventa('eventa:invoke:electron:w export const electronOpenChat = defineInvokeEventa('eventa:invoke:electron:windows:chat:open') export const electronOpenSettingsDevtools = defineInvokeEventa('eventa:invoke:electron:windows:settings:devtools:open') export const electronOpenDevtoolsWindow = defineInvokeEventa('eventa:invoke:electron:windows:devtools:open') +export const electronStartWebSocketServer = defineInvokeEventa('eventa:invoke:electron:start-websocket-server') +export const electronRestartWebSocketServer = defineInvokeEventa('eventa:invoke:electron:restart-websocket-server') export const captionIsFollowingWindowChanged = defineEventa('eventa:event:electron:windows:caption-overlay:is-following-window-changed') export const captionGetIsFollowingWindow = defineInvokeEventa('eventa:invoke:electron:windows:caption-overlay:get-is-following-window') diff --git a/packages/i18n/src/locales/en/settings.yaml b/packages/i18n/src/locales/en/settings.yaml index 931bf587a..f69309b8d 100644 --- a/packages/i18n/src/locales/en/settings.yaml +++ b/packages/i18n/src/locales/en/settings.yaml @@ -930,6 +930,10 @@ vrm: skybox: skybox-intensity: SkyBox Intensity skybox-specular-mix: Specular Mix +websocket-secure-enabled: + title: Enable Secure WebSocket (WSS) + description: >- + Enable WSS (WebSocket Secure) to allow HTTPS connections for mobile (pocket) in development mode. This will automatically generate a self-signed certificate. wip: title: Work in Progress description: >- diff --git a/packages/i18n/src/locales/es/settings.yaml b/packages/i18n/src/locales/es/settings.yaml index 19f4f4cc2..b6cf376b6 100644 --- a/packages/i18n/src/locales/es/settings.yaml +++ b/packages/i18n/src/locales/es/settings.yaml @@ -903,6 +903,10 @@ vrm: skybox: skybox-intensity: SkyBox Intensity skybox-specular-mix: Specular Mix +websocket-secure-enabled: + title: Habilitar WebSocket Seguro (WSS) + description: >- + Habilite WSS (WebSocket Secure) para permitir conexiones HTTPS para móvil (pocket) en modo de desarrollo. Esto generará automáticamente un certificado autofirmado. wip: title: Work in Progress description: >- diff --git a/packages/i18n/src/locales/fr/settings.yaml b/packages/i18n/src/locales/fr/settings.yaml index c2ab7d40a..0d0ccd596 100644 --- a/packages/i18n/src/locales/fr/settings.yaml +++ b/packages/i18n/src/locales/fr/settings.yaml @@ -903,6 +903,10 @@ vrm: skybox: skybox-intensity: Intensité de la SkyBox skybox-specular-mix: Mélange spéculaire +websocket-secure-enabled: + title: Activer WebSocket Sécurisé (WSS) + description: >- + Activez WSS (WebSocket Secure) pour permettre les connexions HTTPS pour mobile (pocket) en mode développement. Cela générera automatiquement un certificat auto-signé. wip: title: Work in Progress description: >- diff --git a/packages/i18n/src/locales/ja/settings.yaml b/packages/i18n/src/locales/ja/settings.yaml index 1dd0b9380..cbf7d63f6 100644 --- a/packages/i18n/src/locales/ja/settings.yaml +++ b/packages/i18n/src/locales/ja/settings.yaml @@ -903,6 +903,10 @@ vrm: skybox: skybox-intensity: スカイボックスの強度 skybox-specular-mix: 鏡面反射の混合 +websocket-secure-enabled: + title: セキュア WebSocket (WSS) を有効にする + description: >- + 開発モードでモバイル(pocket)のHTTPS接続を許可するために、WSS(WebSocket Secure)を有効にします。これにより自己署名証明書が自動的に生成されます。 wip: title: 開発中 description: >- diff --git a/packages/i18n/src/locales/ko/settings.yaml b/packages/i18n/src/locales/ko/settings.yaml index 9e50b3342..2ac7f7d61 100644 --- a/packages/i18n/src/locales/ko/settings.yaml +++ b/packages/i18n/src/locales/ko/settings.yaml @@ -903,6 +903,10 @@ vrm: skybox: skybox-intensity: SkyBox Intensity skybox-specular-mix: Specular Mix +websocket-secure-enabled: + title: 보안 WebSocket (WSS) 활성화 + description: >- + 개발 모드에서 모바일(pocket)의 HTTPS 연결을 허용하기 위해 WSS(WebSocket Secure)를 활성화합니다. 자체 서명 인증서가 자동으로 생성됩니다. wip: title: Work in Progress description: >- diff --git a/packages/i18n/src/locales/ru/settings.yaml b/packages/i18n/src/locales/ru/settings.yaml index 562cb809f..8ac435861 100644 --- a/packages/i18n/src/locales/ru/settings.yaml +++ b/packages/i18n/src/locales/ru/settings.yaml @@ -903,6 +903,10 @@ vrm: skybox: skybox-intensity: SkyBox Intensity skybox-specular-mix: Specular Mix +websocket-secure-enabled: + title: Включить безопасный WebSocket (WSS) + description: >- + Включите WSS (WebSocket Secure), чтобы разрешить HTTPS-подключения для мобильных устройств (pocket) в режиме разработки. Это автоматически сгенерирует самоподписанный сертификат. wip: title: Work in Progress description: >- diff --git a/packages/i18n/src/locales/vi/settings.yaml b/packages/i18n/src/locales/vi/settings.yaml index 586f8d9a3..003878c39 100644 --- a/packages/i18n/src/locales/vi/settings.yaml +++ b/packages/i18n/src/locales/vi/settings.yaml @@ -903,6 +903,10 @@ vrm: skybox: skybox-intensity: Cường Độ SkyBox skybox-specular-mix: Độ Trộn Phản Chiếu +websocket-secure-enabled: + title: Bật WebSocket Bảo Mật (WSS) + description: >- + Bật WSS (WebSocket Secure) để cho phép kết nối HTTPS cho di động (pocket) trong chế độ phát triển. Điều này sẽ tự động tạo chứng chỉ tự ký. wip: title: Work in Progress description: >- diff --git a/packages/i18n/src/locales/zh-Hans/settings.yaml b/packages/i18n/src/locales/zh-Hans/settings.yaml index 57f06ec02..a9e152795 100644 --- a/packages/i18n/src/locales/zh-Hans/settings.yaml +++ b/packages/i18n/src/locales/zh-Hans/settings.yaml @@ -903,6 +903,10 @@ vrm: skybox: skybox-intensity: 天空盒光照强度 skybox-specular-mix: 漫反射/镜面反射混合系数 +websocket-secure-enabled: + title: 启用安全 WebSocket (WSS) + description: >- + 启用 WSS(WebSocket Secure)以允许开发模式下移动端(pocket)的 HTTPS 连接。这将自动生成自签名证书。 wip: title: 正在开发中 description: >- diff --git a/packages/i18n/src/locales/zh-Hant/settings.yaml b/packages/i18n/src/locales/zh-Hant/settings.yaml index 3ed1e6987..428709b84 100644 --- a/packages/i18n/src/locales/zh-Hant/settings.yaml +++ b/packages/i18n/src/locales/zh-Hant/settings.yaml @@ -903,6 +903,10 @@ vrm: skybox: skybox-intensity: 天空盒光照強度 skybox-specular-mix: 漫反射/鏡面反射混合係數 +websocket-secure-enabled: + title: 啟用安全 WebSocket (WSS) + description: >- + 啟用 WSS(WebSocket Secure)以允許開發模式下行動裝置(pocket)的 HTTPS 連線。這將自動產生自簽名憑證。 wip: title: Work in Progress description: >- diff --git a/packages/server-runtime/package.json b/packages/server-runtime/package.json index c3a5e15ca..6c2540888 100644 --- a/packages/server-runtime/package.json +++ b/packages/server-runtime/package.json @@ -40,6 +40,7 @@ "crossws": "^0.4.3", "h3": "^2.0.1-rc.11", "listhen": "^1.9.0", - "nanoid": "catalog:" + "nanoid": "catalog:", + "srvx": "^0.10.1" } } diff --git a/packages/server-runtime/src/index.ts b/packages/server-runtime/src/index.ts index 0c6beaa82..deb040749 100644 --- a/packages/server-runtime/src/index.ts +++ b/packages/server-runtime/src/index.ts @@ -86,7 +86,7 @@ export function setupApp(options?: { readTimeout?: number message?: MessageHeartbeat | string } -}): H3 { +}): { app: H3, closeAllPeers: () => void } { const instanceId = options?.instanceId || optionOrEnv(undefined, 'SERVER_INSTANCE_ID', nanoid()) const authToken = optionOrEnv(options?.auth?.token, 'AUTHENTICATION_TOKEN', '') @@ -121,7 +121,7 @@ export function setupApp(options?: { if (now - peerInfo.lastHeartbeatAt > heartbeatTtlMs) { logger.withFields({ peer: id, peerName: peerInfo.name }).debug('heartbeat expired, dropping peer') try { - (peerInfo.peer as Peer & { close?: () => void }).close?.() + peerInfo.peer.close?.() } catch (error) { logger.withFields({ peer: id, peerName: peerInfo.name }).withError(error as Error).debug('failed to close expired peer') @@ -382,7 +382,18 @@ export function setupApp(options?: { }, })) - return app + function closeAllPeers() { + console.log('closing all peers', peers.size) + for (const peer of peers.values()) { + console.log('closing peer', peer.peer.id) + peer.peer.close?.() + } + } + + return { + app, + closeAllPeers, + } } -export const app = setupApp() as H3 +export const { app, closeAllPeers: _ } = setupApp() diff --git a/packages/server-runtime/src/types/conn.ts b/packages/server-runtime/src/types/conn.ts index 98d7aafec..883229d7e 100644 --- a/packages/server-runtime/src/types/conn.ts +++ b/packages/server-runtime/src/types/conn.ts @@ -8,6 +8,7 @@ export interface Peer { send: (data: unknown, options?: { compress?: boolean }) => number | void | undefined + close?: () => void /** * WebSocket lifecycle state (mirrors WebSocket.readyState) */ diff --git a/packages/stage-pages/src/components/settings-general-fields.vue b/packages/stage-pages/src/components/settings-general-fields.vue new file mode 100644 index 000000000..f2a16eaaa --- /dev/null +++ b/packages/stage-pages/src/components/settings-general-fields.vue @@ -0,0 +1,57 @@ + + + diff --git a/packages/stage-ui/src/stores/settings/general.ts b/packages/stage-ui/src/stores/settings/general.ts index 51cfee83b..e3951f14a 100644 --- a/packages/stage-ui/src/stores/settings/general.ts +++ b/packages/stage-ui/src/stores/settings/general.ts @@ -31,6 +31,8 @@ export const useSettingsGeneral = defineStore('settings-general', () => { const disableTransitions = useLocalStorageManualReset('settings/disable-transitions', true) const usePageSpecificTransitions = useLocalStorageManualReset('settings/use-page-specific-transitions', true) + const websocketSecureEnabled = useLocalStorageManualReset('settings/websocket/secure-enabled', false) + function getLanguage() { let language = localStorage.getItem('settings/language') @@ -53,6 +55,7 @@ export const useSettingsGeneral = defineStore('settings-general', () => { language.reset() disableTransitions.reset() usePageSpecificTransitions.reset() + websocketSecureEnabled.reset() } onMounted(() => language.value = getLanguage()) @@ -61,6 +64,7 @@ export const useSettingsGeneral = defineStore('settings-general', () => { language, disableTransitions, usePageSpecificTransitions, + websocketSecureEnabled, getLanguage, resetState, } diff --git a/packages/stage-ui/src/stores/settings/index.ts b/packages/stage-ui/src/stores/settings/index.ts index 783d575b9..0aa263755 100644 --- a/packages/stage-ui/src/stores/settings/index.ts +++ b/packages/stage-ui/src/stores/settings/index.ts @@ -50,6 +50,7 @@ export const useSettings = defineStore('settings', () => { disableTransitions: generalRefs.disableTransitions, usePageSpecificTransitions: generalRefs.usePageSpecificTransitions, language: generalRefs.language, + websocketSecureEnabled: generalRefs.websocketSecureEnabled, // Stage model settings stageModelRenderer: stageModelRefs.stageModelRenderer, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f36850a6d..7e94d7665 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,6 +144,9 @@ catalogs: knip: specifier: ^5.82.1 version: 5.82.1 + mkcert: + specifier: ^3.2.0 + version: 3.2.0 nano-staged: specifier: ^0.9.0 version: 0.9.0 @@ -1025,6 +1028,9 @@ importers: mediabunny: specifier: ^1.29.0 version: 1.29.0 + mkcert: + specifier: 'catalog:' + version: 3.2.0 node-vibrant: specifier: ^4.0.3 version: 4.0.3(encoding@0.1.13) @@ -1945,6 +1951,9 @@ importers: nanoid: specifier: 'catalog:' version: 5.1.6 + srvx: + specifier: ^0.10.1 + version: 0.10.1 packages/server-sdk: dependencies: @@ -13235,6 +13244,11 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkcert@3.2.0: + resolution: {integrity: sha512-026Eivq9RoOjOuLJGzbhGwXUAjBxRX11Z7Jbm4/7lqT/Av+XNy9SPrJte6+UpEt7i+W3e/HZYxQqlQcqXZWSzg==} + engines: {node: '>=16'} + hasBin: true + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -27981,6 +27995,11 @@ snapshots: mitt@3.0.1: {} + mkcert@3.2.0: + dependencies: + commander: 11.1.0 + node-forge: 1.3.3 + mkdirp-classic@0.5.3: {} mkdirp@0.5.6: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ccf0c14af..440270020 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -70,6 +70,7 @@ catalog: injeca: ^0.1.7 is-network-error: ^1.3.0 knip: ^5.82.1 + mkcert: ^3.2.0 nano-staged: ^0.9.0 nanoid: 5.1.6 ofetch: ^1.5.1