diff --git a/apps/mobile/src/app/(tabs)/shell/detail.tsx b/apps/mobile/src/app/(tabs)/shell/detail.tsx index 19f82b0..10e3c5d 100644 --- a/apps/mobile/src/app/(tabs)/shell/detail.tsx +++ b/apps/mobile/src/app/(tabs)/shell/detail.tsx @@ -151,14 +151,14 @@ function ShellDetail() { style={{ flex: 1 }} logger={{ log: console.log, - debug: console.log, + // debug: console.log, warn: console.warn, error: console.error, }} // xterm options xtermOptions={{ theme: { - background: 'red', + background: theme.colors.background, foreground: theme.colors.textPrimary, }, }} diff --git a/apps/mobile/src/lib/query-fns.ts b/apps/mobile/src/lib/query-fns.ts index cc9b9eb..d281a3e 100644 --- a/apps/mobile/src/lib/query-fns.ts +++ b/apps/mobile/src/lib/query-fns.ts @@ -19,15 +19,15 @@ export const useSshConnMutation = (opts?: { const security = connectionDetails.security.type === 'password' ? { - type: 'password' as const, - password: connectionDetails.security.password, - } + type: 'password' as const, + password: connectionDetails.security.password, + } : { - type: 'key' as const, - privateKey: await secretsManager.keys.utils - .getPrivateKey(connectionDetails.security.keyId) - .then((e) => e.value), - }; + type: 'key' as const, + privateKey: await secretsManager.keys.utils + .getPrivateKey(connectionDetails.security.keyId) + .then((e) => e.value), + }; const sshConnection = await connect({ host: connectionDetails.host, diff --git a/apps/mobile/src/lib/secrets-manager.ts b/apps/mobile/src/lib/secrets-manager.ts index 1187c1e..f68c2ed 100644 --- a/apps/mobile/src/lib/secrets-manager.ts +++ b/apps/mobile/src/lib/secrets-manager.ts @@ -82,9 +82,9 @@ function makeBetterSecureStore< const unsafedRootManifest: unknown = rawRootManifestString ? JSON.parse(rawRootManifestString) : { - manifestVersion: rootManifestVersion, - manifestChunksIds: [], - }; + manifestVersion: rootManifestVersion, + manifestChunksIds: [], + }; const rootManifest = rootManifestSchema.parse(unsafedRootManifest); const manifestChunks = await Promise.all( rootManifest.manifestChunksIds.map(async (manifestChunkId) => { @@ -409,7 +409,11 @@ async function upsertConnection(params: { priority: number; label?: string; }) { - const id = `${params.details.username}-${params.details.host}-${params.details.port}`.replaceAll('.', '_'); + const id = + `${params.details.username}-${params.details.host}-${params.details.port}`.replaceAll( + '.', + '_', + ); await betterConnectionStorage.upsertEntry({ id, metadata: { diff --git a/packages/react-native-xtermjs-webview/src-internal/main.tsx b/packages/react-native-xtermjs-webview/src-internal/main.tsx index aa85d81..2e8b324 100644 --- a/packages/react-native-xtermjs-webview/src-internal/main.tsx +++ b/packages/react-native-xtermjs-webview/src-internal/main.tsx @@ -94,7 +94,7 @@ if (window.__FRESSH_XTERM_BRIDGE__) { break; } case 'setOptions': { - const newOpts: ITerminalOptions = { + const newOpts: ITerminalOptions & { cols?: never; rows?: never } = { ...term.options, ...msg.opts, theme: { @@ -102,6 +102,8 @@ if (window.__FRESSH_XTERM_BRIDGE__) { ...msg.opts.theme, }, }; + delete newOpts.cols; + delete newOpts.rows; term.options = newOpts; if ( 'theme' in newOpts && @@ -109,7 +111,7 @@ if (window.__FRESSH_XTERM_BRIDGE__) { 'background' in newOpts.theme && newOpts.theme.background ) { - document.body.style.backgroundColor = 'blue'; // TODO: Just for debugging + document.body.style.backgroundColor = newOpts.theme.background; } break; } diff --git a/packages/react-native-xtermjs-webview/src/bridge.ts b/packages/react-native-xtermjs-webview/src/bridge.ts index 7b913d1..ad16b80 100644 --- a/packages/react-native-xtermjs-webview/src/bridge.ts +++ b/packages/react-native-xtermjs-webview/src/bridge.ts @@ -1,5 +1,6 @@ import { Base64 } from 'js-base64'; type ITerminalOptions = import('@xterm/xterm').ITerminalOptions; +type ITerminalInitOnlyOptions = import('@xterm/xterm').ITerminalInitOnlyOptions; // Messages posted from the WebView (xterm page) to React Native export type BridgeInboundMessage = | { type: 'initialized' } @@ -12,7 +13,11 @@ export type BridgeOutboundMessage = | { type: 'writeMany'; chunks: string[] } | { type: 'resize'; cols: number; rows: number } | { type: 'fit' } - | { type: 'setOptions'; opts: Partial } + | { + type: 'setOptions'; opts: Partial< + Omit + > + } | { type: 'clear' } | { type: 'focus' }; diff --git a/packages/react-native-xtermjs-webview/src/index.tsx b/packages/react-native-xtermjs-webview/src/index.tsx index ff1fbab..4f87c60 100644 --- a/packages/react-native-xtermjs-webview/src/index.tsx +++ b/packages/react-native-xtermjs-webview/src/index.tsx @@ -4,6 +4,7 @@ import React, { useMemo, useRef, useCallback, + useState, } from 'react'; import { WebView, type WebViewMessageEvent } from 'react-native-webview'; import htmlString from '../dist-internal/index.html?raw'; @@ -63,7 +64,7 @@ const defaultWebViewProps: WebViewOptions = { const defaultXtermOptions: Partial = { fontFamily: 'Menlo, ui-monospace, monospace', - fontSize: 80, + fontSize: 20, cursorBlink: true, scrollback: 10000, }; @@ -125,6 +126,7 @@ export function XtermJsWebView({ autoFit = true, }: XtermJsWebViewProps) { const webRef = useRef(null); + const [initialized, setInitialized] = useState(false); // ---- RN -> WebView message sender const sendToWebView = useCallback( @@ -132,7 +134,7 @@ export function XtermJsWebView({ const webViewRef = webRef.current; if (!webViewRef) return; const payload = JSON.stringify(obj); - logger?.log?.(`sending msg to webview: ${payload}`); + logger?.debug?.(`sending msg to webview: ${payload}`); const js = `window.dispatchEvent(new MessageEvent('message',{data:${payload}})); true;`; webViewRef.injectJavaScript(js); }, @@ -211,6 +213,7 @@ export function XtermJsWebView({ const appliedSizeRef = useRef<{ cols: number; rows: number } | null>(null); useEffect(() => { + if (!initialized) return; const appliedSize = appliedSizeRef.current; if (!size) return; if (appliedSize?.cols === size.cols && appliedSize?.rows === size.rows) @@ -221,7 +224,7 @@ export function XtermJsWebView({ autoFitFn(); appliedSizeRef.current = size; - }, [size, sendToWebView, logger, autoFitFn]); + }, [size, sendToWebView, logger, autoFitFn, initialized]); useImperativeHandle(ref, () => ({ write, @@ -251,13 +254,14 @@ export function XtermJsWebView({ const appliedXtermOptionsRef = useRef | null>(null); useEffect(() => { + if (!initialized) return; const appliedXtermOptions = appliedXtermOptionsRef.current; if (xTermOptionsEquals(appliedXtermOptions, mergedXTermOptions)) return; logger?.log?.(`setting options: `, mergedXTermOptions); sendToWebView({ type: 'setOptions', opts: mergedXTermOptions }); appliedXtermOptionsRef.current = mergedXTermOptions; - }, [mergedXTermOptions, sendToWebView, logger]); + }, [mergedXTermOptions, sendToWebView, logger, initialized]); const onMessage = useCallback( (e: WebViewMessageEvent) => { @@ -267,6 +271,7 @@ export function XtermJsWebView({ if (msg.type === 'initialized') { onInitialized?.(); autoFitFn(); + setInitialized(true); return; } if (msg.type === 'input') { diff --git a/packages/react-native-xtermjs-webview/turbo.jsonc b/packages/react-native-xtermjs-webview/turbo.jsonc index a86dc5d..8fceb36 100644 --- a/packages/react-native-xtermjs-webview/turbo.jsonc +++ b/packages/react-native-xtermjs-webview/turbo.jsonc @@ -8,12 +8,12 @@ // Special tasks "build:main": { - "inputs": ["src/**"], + "inputs": ["src/**", "vite.config.ts"], "dependsOn": ["build:internal"], "outputs": ["dist/**"], }, "build:internal": { - "inputs": ["src-internal/**"], + "inputs": ["src-internal/**", "index.html", "vite.config.internal.ts"], "outputs": ["dist-internal/**"], }, },