Add runtime environment detection

This commit is contained in:
Shantur Rathore
2025-11-22 21:46:53 +00:00
parent e9f3c4ee52
commit 3edb0ac09e
3 changed files with 110 additions and 11 deletions

View File

@@ -0,0 +1,82 @@
export type HostRuntime = "electron" | "tauri" | "web"
export type PlatformKind = "desktop" | "mobile"
export interface RuntimeEnvironment {
host: HostRuntime
platform: PlatformKind
}
declare global {
interface Window {
electronAPI?: unknown
__TAURI__?: {
invoke?: <T = unknown>(cmd: string, args?: Record<string, unknown>) => Promise<T>
event?: {
listen: (event: string, handler: (payload: { payload: unknown }) => void) => Promise<() => void>
}
}
}
}
function detectHost(): HostRuntime {
if (typeof window === "undefined") {
return "web"
}
const win = window as Window & { electronAPI?: unknown }
if (typeof win.electronAPI !== "undefined") {
return "electron"
}
if (typeof win.__TAURI__ !== "undefined") {
return "tauri"
}
if (typeof navigator !== "undefined" && /tauri/i.test(navigator.userAgent)) {
return "tauri"
}
return "web"
}
function detectPlatform(): PlatformKind {
if (typeof navigator === "undefined") {
return "desktop"
}
const uaData = (navigator as any).userAgentData
if (uaData?.mobile) {
return "mobile"
}
const ua = navigator.userAgent.toLowerCase()
if (/android|iphone|ipad|ipod|blackberry|mini|windows phone|mobile|silk/.test(ua)) {
return "mobile"
}
return "desktop"
}
let cachedEnv: RuntimeEnvironment | null = null
export function detectRuntimeEnvironment(): RuntimeEnvironment {
if (cachedEnv) {
return cachedEnv
}
cachedEnv = {
host: detectHost(),
platform: detectPlatform(),
}
if (typeof console !== "undefined") {
const message = `[runtime] host=${cachedEnv.host} platform=${cachedEnv.platform}`
console.info(message)
}
return cachedEnv
}
export const runtimeEnv = detectRuntimeEnvironment()
export const isElectronHost = () => runtimeEnv.host === "electron"
export const isTauriHost = () => runtimeEnv.host === "tauri"
export const isWebHost = () => runtimeEnv.host === "web"
export const isMobilePlatform = () => runtimeEnv.platform === "mobile"

View File

@@ -3,6 +3,7 @@ import App from "./App"
import { ThemeProvider } from "./lib/theme"
import { ConfigProvider } from "./stores/preferences"
import { InstanceConfigProvider } from "./stores/instance-config"
import { runtimeEnv } from "./lib/runtime-env"
import "./index.css"
import "@git-diff-view/solid/styles/diff-view-pure.css"
@@ -12,6 +13,11 @@ if (!root) {
throw new Error("Root element not found")
}
if (typeof document !== "undefined") {
document.documentElement.dataset.runtimeHost = runtimeEnv.host
document.documentElement.dataset.runtimePlatform = runtimeEnv.platform
}
render(
() => (
<ConfigProvider>

View File

@@ -1,6 +1,7 @@
import { createSignal, onCleanup, onMount } from "solid-js"
import { render } from "solid-js/web"
import iconUrl from "../../images/CodeNomad-Icon.png"
import { runtimeEnv, isTauriHost } from "../../lib/runtime-env"
import "../../index.css"
import "./loading.css"
@@ -17,6 +18,12 @@ const phrases = [
"Persuading the AI to give you keyboard control…",
]
const hostStatusMap: Record<typeof runtimeEnv.host, string> = {
electron: "Starting desktop shell…",
tauri: "Starting native shell…",
web: "Connecting to CodeNomad…",
}
interface CliStatus {
state?: string
url?: string | null
@@ -30,12 +37,6 @@ interface TauriBridge {
}
}
declare global {
interface Window {
__TAURI__?: TauriBridge
}
}
function pickPhrase(previous?: string) {
const filtered = phrases.filter((phrase) => phrase !== previous)
const source = filtered.length > 0 ? filtered : phrases
@@ -52,26 +53,34 @@ function getTauriBridge(): TauriBridge | null {
if (typeof window === "undefined") {
return null
}
const bridge = (window as any).__TAURI__ as TauriBridge | undefined
const bridge = (window as { __TAURI__?: TauriBridge }).__TAURI__
if (!bridge || !bridge.event || !bridge.invoke) {
return null
}
return bridge
}
function annotateDocument() {
if (typeof document === "undefined") {
return
}
document.documentElement.dataset.runtimeHost = runtimeEnv.host
document.documentElement.dataset.runtimePlatform = runtimeEnv.platform
}
function LoadingApp() {
const [phrase, setPhrase] = createSignal(pickPhrase())
const [error, setError] = createSignal<string | null>(null)
const [status, setStatus] = createSignal<string>("Starting services…")
const [status, setStatus] = createSignal<string>(hostStatusMap[runtimeEnv.host] ?? "Starting services…")
const changePhrase = () => setPhrase(pickPhrase(phrase()))
onMount(() => {
annotateDocument()
setPhrase(pickPhrase())
const tauriBridge = getTauriBridge()
const unsubscribers: Array<() => void> = []
async function bootstrapTauri() {
async function bootstrapTauri(tauriBridge: TauriBridge | null) {
if (!tauriBridge || !tauriBridge.event || !tauriBridge.invoke) {
return
}
@@ -115,7 +124,9 @@ function LoadingApp() {
}
}
void bootstrapTauri()
if (isTauriHost()) {
void bootstrapTauri(getTauriBridge())
}
onCleanup(() => {
unsubscribers.forEach((unsubscribe) => {