Better xtermjs api

This commit is contained in:
EthanShoeDev
2025-09-18 21:36:40 -04:00
parent 9e789d23be
commit 519de821e2
6 changed files with 439 additions and 403 deletions

View File

@@ -49,7 +49,7 @@
"expo-haptics": "~15.0.7", "expo-haptics": "~15.0.7",
"expo-image": "~3.0.8", "expo-image": "~3.0.8",
"expo-linking": "~8.0.8", "expo-linking": "~8.0.8",
"expo-router": "6.0.6", "expo-router": "6.0.7",
"expo-secure-store": "~15.0.7", "expo-secure-store": "~15.0.7",
"expo-splash-screen": "~31.0.10", "expo-splash-screen": "~31.0.10",
"expo-status-bar": "~3.0.8", "expo-status-bar": "~3.0.8",
@@ -72,28 +72,28 @@
}, },
"devDependencies": { "devDependencies": {
"@epic-web/config": "^1.21.3", "@epic-web/config": "^1.21.3",
"@types/react": "~19.1.12",
"cmd-ts": "^0.14.1",
"eslint": "^9.35.0",
"@eslint/js": "^9.35.0",
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0", "@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
"@eslint-react/eslint-plugin": "^1.53.0", "@eslint-react/eslint-plugin": "^1.53.0",
"@eslint/js": "^9.35.0",
"@tanstack/eslint-plugin-query": "^5.86.0", "@tanstack/eslint-plugin-query": "^5.86.0",
"@types/react": "~19.1.12",
"@typescript-eslint/parser": "^8.44.0", "@typescript-eslint/parser": "^8.44.0",
"@typescript-eslint/utils": "^8.43.0", "@typescript-eslint/utils": "^8.43.0",
"cmd-ts": "^0.14.1",
"eslint": "^9.35.0",
"eslint-config-expo": "~10.0.0",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-compiler": "19.1.0-rc.2", "eslint-plugin-react-compiler": "19.1.0-rc.2",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"globals": "^16.4.0",
"eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-react-refresh": "^0.4.20",
"typescript-eslint": "^8.44.0", "globals": "^16.4.0",
"eslint-config-expo": "~10.0.0",
"jiti": "^2.5.1", "jiti": "^2.5.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"prettier-plugin-organize-imports": "^4.2.0", "prettier-plugin-organize-imports": "^4.2.0",
"tsx": "^4.20.5", "tsx": "^4.20.5",
"typescript": "~5.9.2" "typescript": "~5.9.2",
"typescript-eslint": "^8.44.0"
}, },
"expo": { "expo": {
"doctor": { "doctor": {

View File

@@ -50,6 +50,8 @@ function RouteSkeleton() {
); );
} }
const encoder = new TextEncoder();
function ShellDetail() { function ShellDetail() {
const xtermRef = useRef<XtermWebViewHandle>(null); const xtermRef = useRef<XtermWebViewHandle>(null);
const terminalReadyRef = useRef(false); const terminalReadyRef = useRef(false);
@@ -151,100 +153,72 @@ function ShellDetail() {
<XtermJsWebView <XtermJsWebView
ref={xtermRef} ref={xtermRef}
style={{ flex: 1 }} style={{ flex: 1 }}
// WebView behavior that suits terminals logger={{
keyboardDisplayRequiresUserAction={false} log: console.log,
setSupportMultipleWindows={false} debug: console.log,
overScrollMode="never" warn: console.warn,
pullToRefreshEnabled={false} error: console.error,
bounces={false} }}
setBuiltInZoomControls={false}
setDisplayZoomControls={false}
textZoom={100}
allowsLinkPreview={false}
textInteractionEnabled={false}
// xterm options // xterm options
options={{ xtermOptions={{
fontFamily: 'Menlo, ui-monospace, monospace',
fontSize: 80,
cursorBlink: true,
scrollback: 10000,
theme: { theme: {
background: theme.colors.background, background: theme.colors.background,
foreground: theme.colors.textPrimary, foreground: theme.colors.textPrimary,
}, },
}} }}
onRenderProcessGone={() => { onInitialized={() => {
console.log('WebView render process gone -> clear()'); if (terminalReadyRef.current) return;
const xr = xtermRef.current; terminalReadyRef.current = true;
if (xr) xr.clear();
}}
onContentProcessDidTerminate={() => {
console.log('WKWebView content process terminated -> clear()');
const xr = xtermRef.current;
if (xr) xr.clear();
}}
onLoadEnd={() => {
console.log('WebView onLoadEnd');
}}
onMessage={(m) => {
console.log('received msg', m);
if (m.type === 'initialized') {
if (terminalReadyRef.current) return;
terminalReadyRef.current = true;
// Replay from head, then attach live listener // Replay from head, then attach live listener
if (shell) { if (shell) {
void (async () => { void (async () => {
const res = await shell.readBuffer({ mode: 'head' }); const res = await shell.readBuffer({ mode: 'head' });
console.log('readBuffer(head)', { console.log('readBuffer(head)', {
chunks: res.chunks.length, chunks: res.chunks.length,
nextSeq: res.nextSeq, nextSeq: res.nextSeq,
dropped: res.dropped, dropped: res.dropped,
});
if (res.chunks.length) {
const chunks = res.chunks.map((c) => c.bytes);
const xr = xtermRef.current;
if (xr) {
xr.writeMany(chunks);
xr.flush();
}
}
const id = shell.addListener(
(ev: ListenerEvent) => {
if ('kind' in ev) {
console.log('listener.dropped', ev);
return;
}
const chunk = ev;
const xr3 = xtermRef.current;
if (xr3) xr3.write(chunk.bytes);
},
{ cursor: { mode: 'seq', seq: res.nextSeq } },
);
console.log('shell listener attached', id.toString());
listenerIdRef.current = id;
})();
}
// Focus to pop the keyboard (iOS needs the prop we set)
const xr2 = xtermRef.current;
if (xr2) xr2.focus();
return;
}
if (m.type === 'data') {
console.log('xterm->SSH', { len: m.data.length });
const { buffer, byteOffset, byteLength } = m.data;
const ab = buffer.slice(byteOffset, byteOffset + byteLength);
if (shell) {
shell.sendData(ab as ArrayBuffer).catch((e: unknown) => {
console.warn('sendData failed', e);
router.back();
}); });
} if (res.chunks.length) {
return; const chunks = res.chunks.map((c) => c.bytes);
} else { const xr = xtermRef.current;
console.log('xterm.debug', m.message); if (xr) {
xr.writeMany(chunks);
xr.flush();
}
}
const id = shell.addListener(
(ev: ListenerEvent) => {
if ('kind' in ev) {
console.log('listener.dropped', ev);
return;
}
const chunk = ev;
const xr3 = xtermRef.current;
if (xr3) xr3.write(chunk.bytes);
},
{ cursor: { mode: 'seq', seq: res.nextSeq } },
);
console.log('shell listener attached', id.toString());
listenerIdRef.current = id;
})();
} }
// Focus to pop the keyboard (iOS needs the prop we set)
const xr2 = xtermRef.current;
if (xr2) xr2.focus();
return;
}}
onData={(terminalMessage) => {
if (!shell) return;
const bytes = encoder.encode(terminalMessage);
if (shell) {
shell.sendData(bytes.buffer).catch((e: unknown) => {
console.warn('sendData failed', e);
router.back();
});
}
return;
}} }}
/> />
</SafeAreaView> </SafeAreaView>

View File

@@ -1,8 +1,8 @@
import { FitAddon } from '@xterm/addon-fit'; import { FitAddon } from '@xterm/addon-fit';
import { Terminal, type ITerminalOptions, type ITheme } from '@xterm/xterm'; import { Terminal } from '@xterm/xterm';
import { Base64 } from 'js-base64';
import '@xterm/xterm/css/xterm.css'; import '@xterm/xterm/css/xterm.css';
import { import {
bStrToBinary,
type BridgeInboundMessage, type BridgeInboundMessage,
type BridgeOutboundMessage, type BridgeOutboundMessage,
} from '../src/bridge'; } from '../src/bridge';
@@ -14,14 +14,13 @@ declare global {
terminalWriteBase64?: (data: string) => void; terminalWriteBase64?: (data: string) => void;
ReactNativeWebView?: { postMessage?: (data: string) => void }; ReactNativeWebView?: { postMessage?: (data: string) => void };
__FRESSH_XTERM_BRIDGE__?: boolean; __FRESSH_XTERM_BRIDGE__?: boolean;
__FRESSH_XTERM_MSG_HANDLER__?: (e: MessageEvent<string>) => void; __FRESSH_XTERM_MSG_HANDLER__?: (
e: MessageEvent<BridgeOutboundMessage>,
) => void;
} }
} }
/** const sendToRn = (msg: BridgeInboundMessage) =>
* Post typed messages to React Native
*/
const post = (msg: BridgeInboundMessage) =>
window.ReactNativeWebView?.postMessage?.(JSON.stringify(msg)); window.ReactNativeWebView?.postMessage?.(JSON.stringify(msg));
/** /**
@@ -29,7 +28,7 @@ const post = (msg: BridgeInboundMessage) =>
* If the script happens to run twice (dev reloads, double-mounts), we bail out early. * If the script happens to run twice (dev reloads, double-mounts), we bail out early.
*/ */
if (window.__FRESSH_XTERM_BRIDGE__) { if (window.__FRESSH_XTERM_BRIDGE__) {
post({ sendToRn({
type: 'debug', type: 'debug',
message: 'bridge already installed; ignoring duplicate boot', message: 'bridge already installed; ignoring duplicate boot',
}); });
@@ -55,152 +54,84 @@ if (window.__FRESSH_XTERM_BRIDGE__) {
window.fitAddon = fitAddon; window.fitAddon = fitAddon;
// Encode helper // Encode helper
const enc = new TextEncoder(); // const enc = new TextEncoder();
// Initial handshake (send once)
setTimeout(() => post({ type: 'initialized' }), 500);
// User input from xterm -> RN (SSH) as UTF-8 bytes (Base64) // User input from xterm -> RN (SSH) as UTF-8 bytes (Base64)
term.onData((data /* string */) => { term.onData((data) => {
const bytes = enc.encode(data); // const bytes = enc.encode(data);
const b64 = Base64.fromUint8Array(bytes); // const bStr = binaryToBStr(bytes);
post({ type: 'input', b64 }); sendToRn({ type: 'input', str: data });
}); });
// Remove old handler if any (just in case) // Remove old handler if any (just in case)
if (window.__FRESSH_XTERM_MSG_HANDLER__) { if (window.__FRESSH_XTERM_MSG_HANDLER__)
window.removeEventListener('message', window.__FRESSH_XTERM_MSG_HANDLER__!); window.removeEventListener('message', window.__FRESSH_XTERM_MSG_HANDLER__!);
}
// RN -> WebView handler (write, resize, setFont, setTheme, setOptions, clear, focus) // RN -> WebView handler (write, resize, setFont, setTheme, setOptions, clear, focus)
const handler = (e: MessageEvent<string>) => { const handler = (e: MessageEvent<BridgeOutboundMessage>) => {
try { try {
const msg = JSON.parse(e.data) as BridgeOutboundMessage; const msg = e.data;
if (!msg || typeof msg.type !== 'string') return; if (!msg || typeof msg.type !== 'string') return;
// TODO: https://xtermjs.org/docs/guides/flowcontrol/#ideas-for-a-better-mechanism
const termWrite = (bStr: string) => {
const bytes = bStrToBinary(bStr);
term.write(bytes);
};
switch (msg.type) { switch (msg.type) {
case 'write': { case 'write': {
if ('b64' in msg) { termWrite(msg.bStr);
const bytes = Base64.toUint8Array(msg.b64); break;
term.write(bytes); }
post({ type: 'debug', message: `write(bytes=${bytes.length})` }); case 'writeMany': {
} else if ('chunks' in msg && Array.isArray(msg.chunks)) { for (const bStr of msg.chunks) {
for (const b64 of msg.chunks) { termWrite(bStr);
const bytes = Base64.toUint8Array(b64);
term.write(bytes);
}
post({
type: 'debug',
message: `write(chunks=${msg.chunks.length})`,
});
} }
break; break;
} }
case 'resize': { case 'resize': {
if (typeof msg.cols === 'number' && typeof msg.rows === 'number') { term.resize(msg.cols, msg.rows);
term.resize(msg.cols, msg.rows); break;
post({ type: 'debug', message: `resize(${msg.cols}x${msg.rows})` }); }
} case 'fit': {
fitAddon.fit(); fitAddon.fit();
break; break;
} }
case 'setFont': {
const { family, size } = msg;
const patch: Partial<ITerminalOptions> = {};
if (family) patch.fontFamily = family;
if (typeof size === 'number') patch.fontSize = size;
if (Object.keys(patch).length) {
term.options = patch; // never spread existing options (avoids cols/rows setters)
post({
type: 'debug',
message: `setFont(${family ?? ''}, ${size ?? ''})`,
});
fitAddon.fit();
}
break;
}
case 'setTheme': {
const { background, foreground } = msg;
const theme: Partial<ITheme> = {};
if (background) {
theme.background = background;
document.body.style.backgroundColor = background;
}
if (foreground) theme.foreground = foreground;
if (Object.keys(theme).length) {
term.options = { theme }; // set only theme
post({
type: 'debug',
message: `setTheme(bg=${background ?? ''}, fg=${foreground ?? ''})`,
});
}
break;
}
case 'setOptions': { case 'setOptions': {
const incoming = (msg.opts ?? {}) as Record<string, unknown>; const newOpts = msg.opts;
type PatchRecord = Partial< term.options = newOpts;
Record< if (
keyof ITerminalOptions, 'theme' in newOpts &&
ITerminalOptions[keyof ITerminalOptions] newOpts.theme &&
> 'background' in newOpts.theme &&
>; newOpts.theme.background
const patch: PatchRecord = {}; ) {
for (const [k, v] of Object.entries(incoming)) { document.body.style.backgroundColor = newOpts.theme.background;
// Avoid touching cols/rows via options setters here
if (k === 'cols' || k === 'rows') continue;
// Theme: also mirror background to page for seamless visuals
if (k === 'theme' && v && typeof v === 'object') {
const theme = v as ITheme;
if (theme.background) {
document.body.style.backgroundColor = theme.background;
}
patch.theme = theme;
continue;
}
const key = k as keyof ITerminalOptions;
patch[key] = v as ITerminalOptions[keyof ITerminalOptions];
}
if (Object.keys(patch).length) {
term.options = patch;
post({
type: 'debug',
message: `setOptions(${Object.keys(patch).join(',')})`,
});
// If dimensions-affecting options changed, refit
if (
patch.fontFamily !== undefined ||
patch.fontSize !== undefined ||
patch.letterSpacing !== undefined ||
patch.lineHeight !== undefined
) {
fitAddon.fit();
}
} }
break; break;
} }
case 'clear': { case 'clear': {
term.clear(); term.clear();
post({ type: 'debug', message: 'clear()' });
break; break;
} }
case 'focus': { case 'focus': {
term.focus(); term.focus();
post({ type: 'debug', message: 'focus()' });
break; break;
} }
} }
} catch (err) { } catch (err) {
post({ type: 'debug', message: `message handler error: ${String(err)}` }); sendToRn({
type: 'debug',
message: `message handler error: ${String(err)}`,
});
} }
}; };
window.__FRESSH_XTERM_MSG_HANDLER__ = handler; window.__FRESSH_XTERM_MSG_HANDLER__ = handler;
window.addEventListener('message', handler); window.addEventListener('message', handler);
// Initial handshake (send once)
setTimeout(() => sendToRn({ type: 'initialized' }), 50);
} }

View File

@@ -1,25 +1,22 @@
import { Base64 } from 'js-base64';
type ITerminalOptions = import('@xterm/xterm').ITerminalOptions; type ITerminalOptions = import('@xterm/xterm').ITerminalOptions;
// Messages posted from the WebView (xterm page) to React Native // Messages posted from the WebView (xterm page) to React Native
export type BridgeInboundMessage = export type BridgeInboundMessage =
| { type: 'initialized' } | { type: 'initialized' }
| { type: 'input'; b64: string } | { type: 'input'; str: string }
| { type: 'debug'; message: string }; | { type: 'debug'; message: string };
// Messages injected from React Native into the WebView (xterm page) // Messages injected from React Native into the WebView (xterm page)
export type BridgeOutboundMessage = export type BridgeOutboundMessage =
| { type: 'write'; b64: string } | { type: 'write'; bStr: string }
| { type: 'write'; chunks: string[] } | { type: 'writeMany'; chunks: string[] }
| { type: 'resize'; cols?: number; rows?: number } | { type: 'resize'; cols: number; rows: number }
| { type: 'setFont'; family?: string; size?: number } | { type: 'fit' }
| { type: 'setTheme'; background?: string; foreground?: string }
| { type: 'setOptions'; opts: Partial<ITerminalOptions> } | { type: 'setOptions'; opts: Partial<ITerminalOptions> }
| { type: 'clear' } | { type: 'clear' }
| { type: 'focus' }; | { type: 'focus' };
export type TerminalOptionsPatch = BridgeOutboundMessage extends { export const binaryToBStr = (binary: Uint8Array): string =>
type: 'setOptions'; Base64.fromUint8Array(binary);
opts: infer O; export const bStrToBinary = (bStr: string): Uint8Array =>
} Base64.toUint8Array(bStr);
? O
: never;

View File

@@ -1,27 +1,26 @@
type ITerminalOptions = import('@xterm/xterm').ITerminalOptions; import React, {
import { Base64 } from 'js-base64'; useEffect,
import React, { useEffect, useImperativeHandle, useRef } from 'react'; useImperativeHandle,
import { WebView } from 'react-native-webview'; useMemo,
useRef,
useCallback,
} from 'react';
import { WebView, type WebViewMessageEvent } from 'react-native-webview';
import htmlString from '../dist-internal/index.html?raw'; import htmlString from '../dist-internal/index.html?raw';
import { import {
binaryToBStr,
bStrToBinary,
type BridgeInboundMessage, type BridgeInboundMessage,
type BridgeOutboundMessage, type BridgeOutboundMessage,
type TerminalOptionsPatch,
} from './bridge'; } from './bridge';
// Re-exported shared types live in src/bridge.ts for library build
// Internal page imports the same file via ../src/bridge export { bStrToBinary, binaryToBStr };
type StrictOmit<T, K extends keyof T> = Omit<T, K>; type StrictOmit<T, K extends keyof T> = Omit<T, K>;
type ITerminalOptions = import('@xterm/xterm').ITerminalOptions;
type WebViewOptions = React.ComponentProps<typeof WebView>;
/** const defaultCoalescingThreshold = 8 * 1024;
* Message from the webview to RN
*/
type InboundMessage = BridgeInboundMessage;
/**
* Message from RN to the webview
*/
type OutboundMessage = BridgeOutboundMessage;
/** /**
* Message from this pkg to calling RN * Message from this pkg to calling RN
@@ -36,105 +35,160 @@ export type XtermWebViewHandle = {
// Efficiently write many chunks in one postMessage (for initial replay) // Efficiently write many chunks in one postMessage (for initial replay)
writeMany: (chunks: Uint8Array[]) => void; writeMany: (chunks: Uint8Array[]) => void;
flush: () => void; // force-flush outgoing writes flush: () => void; // force-flush outgoing writes
resize: (cols?: number, rows?: number) => void;
setFont: (family?: string, size?: number) => void;
setTheme: (background?: string, foreground?: string) => void;
setOptions: (opts: TerminalOptionsPatch) => void;
clear: () => void; clear: () => void;
focus: () => void; focus: () => void;
resize: (size: { cols: number; rows: number }) => void;
fit: () => void;
}; };
export interface XtermJsWebViewProps const defaultWebViewProps: WebViewOptions = {
extends StrictOmit< // WebView behavior that suits terminals
React.ComponentProps<typeof WebView>, // ios
'source' | 'originWhitelist' | 'onMessage' keyboardDisplayRequiresUserAction: false,
> { pullToRefreshEnabled: false,
ref: React.RefObject<XtermWebViewHandle | null>; bounces: false,
onMessage?: (msg: XtermInbound) => void; textInteractionEnabled: false,
allowsLinkPreview: false,
// android
setSupportMultipleWindows: false,
overScrollMode: 'never',
setBuiltInZoomControls: false,
setDisplayZoomControls: false,
textZoom: 100,
// both
originWhitelist: ['*'],
scalesPageToFit: false,
contentMode: 'mobile',
};
// xterm Terminal.setOptions props (typed from @xterm/xterm) const defaultXtermOptions: Partial<ITerminalOptions> = {
options?: Partial<ITerminalOptions>; fontFamily: 'Menlo, ui-monospace, monospace',
fontSize: 80,
cursorBlink: true,
scrollback: 10000,
};
type UserControllableWebViewProps = StrictOmit<
WebViewOptions,
'source' | 'style'
>;
export type XtermJsWebViewProps = {
ref: React.RefObject<XtermWebViewHandle | null>;
style?: WebViewOptions['style'];
webViewOptions?: UserControllableWebViewProps;
xtermOptions?: Partial<ITerminalOptions>;
onInitialized?: () => void;
onData?: (data: string) => void;
logger?: {
debug?: (...args: unknown[]) => void;
log?: (...args: unknown[]) => void;
warn?: (...args: unknown[]) => void;
error?: (...args: unknown[]) => void;
};
coalescingThreshold?: number;
size?: {
cols: number;
rows: number;
};
autoFit?: boolean;
};
function xTermOptionsEquals(
a: Partial<ITerminalOptions> | null,
b: Partial<ITerminalOptions> | null,
): boolean {
if (a == b) return true;
if (a == null && b == null) return true;
if (a == null || b == null) return false;
const keys = new Set<string>([
...Object.keys(a as object),
...Object.keys(b as object),
]);
for (const k of keys) {
const key = k as keyof ITerminalOptions;
if (a[key] !== b[key]) return false;
}
return true;
} }
export function XtermJsWebView({ export function XtermJsWebView({
ref, ref,
onMessage, style,
options, webViewOptions = defaultWebViewProps,
...props xtermOptions = defaultXtermOptions,
onInitialized,
onData,
coalescingThreshold = defaultCoalescingThreshold,
logger,
size,
autoFit = true,
}: XtermJsWebViewProps) { }: XtermJsWebViewProps) {
const webRef = useRef<WebView>(null); const webRef = useRef<WebView>(null);
// ---- RN -> WebView message sender // ---- RN -> WebView message sender
const send = (obj: OutboundMessage) => { const sendToWebView = useCallback(
const payload = JSON.stringify(obj); (obj: BridgeOutboundMessage) => {
console.log('sending msg', payload); const webViewRef = webRef.current;
const js = `window.dispatchEvent(new MessageEvent('message',{data:${JSON.stringify( if (!webViewRef) return;
payload, const payload = JSON.stringify(obj);
)}})); true;`; logger?.log?.(`sending msg to webview: ${payload}`);
webRef.current?.injectJavaScript(js); const js = `window.dispatchEvent(new MessageEvent('message',{data:${payload}})); true;`;
}; webViewRef.injectJavaScript(js);
},
[logger],
);
// ---- rAF + 8KB coalescer for writes // ---- rAF + 8KB coalescer for writes
const bufRef = useRef<Uint8Array | null>(null); const bufRef = useRef<Uint8Array | null>(null);
const rafRef = useRef<number | null>(null); const rafRef = useRef<number | null>(null);
const THRESHOLD = 8 * 1024;
const flush = () => { const flush = useCallback(() => {
if (!bufRef.current) return; if (!bufRef.current) return;
const b64 = Base64.fromUint8Array(bufRef.current); const bStr = binaryToBStr(bufRef.current);
bufRef.current = null; bufRef.current = null;
if (rafRef.current != null) { if (rafRef.current != null) {
cancelAnimationFrame(rafRef.current); cancelAnimationFrame(rafRef.current);
rafRef.current = null; rafRef.current = null;
} }
send({ type: 'write', b64 }); sendToWebView({ type: 'write', bStr });
}; }, [sendToWebView]);
const schedule = () => { const schedule = useCallback(() => {
if (rafRef.current != null) return; if (rafRef.current != null) return;
rafRef.current = requestAnimationFrame(() => { rafRef.current = requestAnimationFrame(() => {
rafRef.current = null; rafRef.current = null;
flush(); flush();
}); });
}; }, [flush]);
const write = (data: Uint8Array) => { const write = useCallback(
if (!data || data.length === 0) return; (data: Uint8Array) => {
if (!bufRef.current) { if (!data || data.length === 0) return;
bufRef.current = data; if (!bufRef.current) {
} else { bufRef.current = data;
const a = bufRef.current; } else {
const merged = new Uint8Array(a.length + data.length); const a = bufRef.current;
merged.set(a, 0); const merged = new Uint8Array(a.length + data.length);
merged.set(data, a.length); merged.set(a, 0);
bufRef.current = merged; merged.set(data, a.length);
} bufRef.current = merged;
if ((bufRef.current?.length ?? 0) >= THRESHOLD) flush(); }
else schedule(); if ((bufRef.current?.length ?? 0) >= coalescingThreshold) flush();
}; else schedule();
},
[coalescingThreshold, flush, schedule],
);
const writeMany = (chunks: Uint8Array[]) => { const writeMany = useCallback(
if (!chunks || chunks.length === 0) return; (chunks: Uint8Array[]) => {
// Ensure any pending small buffered write is flushed before bulk write if (!chunks || chunks.length === 0) return;
flush(); flush(); // Ensure any pending small buffered write is flushed before bulk write
const b64s = chunks.map((c) => Base64.fromUint8Array(c)); const bStrs = chunks.map(binaryToBStr);
send({ type: 'write', chunks: b64s }); sendToWebView({ type: 'writeMany', chunks: bStrs });
}; },
[flush, sendToWebView],
useImperativeHandle(ref, () => ({ );
write,
writeMany,
flush,
resize: (cols?: number, rows?: number) =>
send({ type: 'resize', cols, rows }),
setFont: (family?: string, size?: number) =>
send({ type: 'setFont', family, size }),
setTheme: (background?: string, foreground?: string) =>
send({ type: 'setTheme', background, foreground }),
setOptions: (opts) => send({ type: 'setOptions', opts }),
clear: () => send({ type: 'clear' }),
focus: () => send({ type: 'focus' }),
}));
// Cleanup pending rAF on unmount // Cleanup pending rAF on unmount
useEffect(() => { useEffect(() => {
@@ -145,69 +199,149 @@ export function XtermJsWebView({
}; };
}, []); }, []);
// Apply options changes via setOptions without remounting const fit = useCallback(() => {
const prevOptsRef = useRef<Partial<ITerminalOptions> | null>(null); sendToWebView({ type: 'fit' });
useEffect(() => { }, [sendToWebView]);
const merged: Partial<ITerminalOptions> = {
...(options ?? {}),
};
// Compute shallow patch of changed keys to reduce noise const autoFitFn = useCallback(() => {
const prev: Partial<ITerminalOptions> = (prevOptsRef.current ?? if (!autoFit) return;
{}) as Partial<ITerminalOptions>; fit();
type PatchRecord = Partial< }, [autoFit, fit]);
Record<keyof ITerminalOptions, ITerminalOptions[keyof ITerminalOptions]>
>; const appliedSizeRef = useRef<{ cols: number; rows: number } | null>(null);
const patch: PatchRecord = {};
const keys = new Set<string>([ useEffect(() => {
...Object.keys(prev as object), const appliedSize = appliedSizeRef.current;
...Object.keys(merged as object), if (!size) return;
]); if (appliedSize?.cols === size.cols && appliedSize?.rows === size.rows)
let changed = false; return;
for (const k of keys) {
const key = k as keyof ITerminalOptions; logger?.log?.(`calling resize`, size);
const prevVal = prev[key]; sendToWebView({ type: 'resize', cols: size.cols, rows: size.rows });
const nextVal = merged[key]; autoFitFn();
if (prevVal !== nextVal) {
patch[key] = nextVal as ITerminalOptions[keyof ITerminalOptions]; appliedSizeRef.current = size;
changed = true; }, [size, sendToWebView, logger, autoFitFn]);
useImperativeHandle(ref, () => ({
write,
writeMany,
flush,
clear: () => sendToWebView({ type: 'clear' }),
focus: () => {
sendToWebView({ type: 'focus' });
webRef.current?.requestFocus();
},
resize: (size: { cols: number; rows: number }) => {
sendToWebView({ type: 'resize', cols: size.cols, rows: size.rows });
autoFitFn();
appliedSizeRef.current = size;
},
fit,
}));
const mergedXTermOptions = useMemo(
() => ({
...defaultXtermOptions,
...xtermOptions,
}),
[xtermOptions],
);
const appliedXtermOptionsRef = useRef<Partial<ITerminalOptions> | null>(null);
useEffect(() => {
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]);
const onMessage = useCallback(
(e: WebViewMessageEvent) => {
try {
const msg: BridgeInboundMessage = JSON.parse(e.nativeEvent.data);
logger?.log?.(`received msg from webview: `, msg);
if (msg.type === 'initialized') {
onInitialized?.();
autoFitFn();
return;
}
if (msg.type === 'input') {
// const bytes = bStrToBinary(msg.bStr);
// onData?.(bytes);
onData?.(msg.str);
return;
}
if (msg.type === 'debug') {
logger?.log?.(`received debug msg from webview: `, msg.message);
return;
}
webViewOptions?.onMessage?.(e);
} catch (error) {
logger?.warn?.(
`received unknown msg from webview: `,
e.nativeEvent.data,
error,
);
} }
} },
if (changed) { [logger, webViewOptions, onInitialized, autoFitFn, onData],
send({ type: 'setOptions', opts: patch }); );
prevOptsRef.current = merged;
} const onContentProcessDidTerminate = useCallback<
}, [options]); NonNullable<WebViewOptions['onContentProcessDidTerminate']>
>(
(e) => {
logger?.warn?.('WebView Crashed on iOS! onContentProcessDidTerminate');
webViewOptions?.onContentProcessDidTerminate?.(e);
},
[logger, webViewOptions],
);
const onRenderProcessGone = useCallback<
NonNullable<WebViewOptions['onRenderProcessGone']>
>(
(e) => {
logger?.warn?.('WebView Crashed on Android! onRenderProcessGone');
webViewOptions?.onRenderProcessGone?.(e);
},
[logger, webViewOptions],
);
const onLoadEnd = useCallback<NonNullable<WebViewOptions['onLoadEnd']>>(
(e) => {
logger?.log?.('WebView onLoadEnd');
webViewOptions?.onLoadEnd?.(e);
},
[logger, webViewOptions],
);
const mergedWebViewOptions = useMemo(
() => ({
...defaultWebViewProps,
...webViewOptions,
onContentProcessDidTerminate,
onRenderProcessGone,
onLoadEnd,
}),
[
webViewOptions,
onContentProcessDidTerminate,
onRenderProcessGone,
onLoadEnd,
],
);
return ( return (
<WebView <WebView
ref={webRef} ref={webRef}
originWhitelist={['*']}
scalesPageToFit={false}
contentMode="mobile"
source={{ html: htmlString }} source={{ html: htmlString }}
onMessage={(e) => { onMessage={onMessage}
try { style={style}
const msg: InboundMessage = JSON.parse(e.nativeEvent.data); {...mergedWebViewOptions}
console.log('received msg', msg);
if (msg.type === 'initialized') {
onMessage?.({ type: 'initialized' });
return;
}
if (msg.type === 'input') {
const bytes = Base64.toUint8Array(msg.b64);
onMessage?.({ type: 'data', data: bytes });
return;
}
if (msg.type === 'debug') {
onMessage?.({ type: 'debug', message: msg.message });
return;
}
} catch {
// ignore unknown payloads
}
}}
{...props}
/> />
); );
} }

82
pnpm-lock.yaml generated
View File

@@ -78,7 +78,7 @@ importers:
version: 4.1.0 version: 4.1.0
expo: expo:
specifier: 54.0.8 specifier: 54.0.8
version: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) version: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-clipboard: expo-clipboard:
specifier: ~8.0.7 specifier: ~8.0.7
version: 8.0.7(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) version: 8.0.7(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
@@ -113,8 +113,8 @@ importers:
specifier: ~8.0.8 specifier: ~8.0.8
version: 8.0.8(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) version: 8.0.8(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-router: expo-router:
specifier: 6.0.6 specifier: 6.0.7
version: 6.0.6(795f20997b5e74199ec1f471a585ccc7) version: 6.0.7(795f20997b5e74199ec1f471a585ccc7)
expo-secure-store: expo-secure-store:
specifier: ~15.0.7 specifier: ~15.0.7
version: 15.0.7(expo@54.0.8) version: 15.0.7(expo@54.0.8)
@@ -5261,8 +5261,8 @@ packages:
react: '*' react: '*'
react-native: '*' react-native: '*'
expo-router@6.0.6: expo-router@6.0.7:
resolution: {integrity: sha512-uSuKQanivBI9RtwmAznLI7It5aPwQLVL2tVBPAOJ70tv6BzP62SpVCf0I8o0j9PmEzORPRLrU2LbQOL962yBHg==} resolution: {integrity: sha512-dP/35aQadCuplEP99CZ0sLrVpnCFCQGnCBtFlI0Tph75PbepdWhI7XC0Vzt7MoNBLF9NW80q5CeZdXTvybc+4w==}
peerDependencies: peerDependencies:
'@expo/metro-runtime': ^6.1.2 '@expo/metro-runtime': ^6.1.2
'@react-navigation/drawer': ^7.5.0 '@react-navigation/drawer': ^7.5.0
@@ -10646,7 +10646,7 @@ snapshots:
'@eslint/core': 0.15.2 '@eslint/core': 0.15.2
levn: 0.4.1 levn: 0.4.1
'@expo/cli@54.0.6(expo-router@6.0.6)(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))': '@expo/cli@54.0.6(expo-router@6.0.7)(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))':
dependencies: dependencies:
'@0no-co/graphql.web': 1.2.0 '@0no-co/graphql.web': 1.2.0
'@expo/code-signing-certificates': 0.0.5 '@expo/code-signing-certificates': 0.0.5
@@ -10682,7 +10682,7 @@ snapshots:
connect: 3.7.0 connect: 3.7.0
debug: 4.4.1 debug: 4.4.1
env-editor: 0.4.2 env-editor: 0.4.2
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
freeport-async: 2.0.0 freeport-async: 2.0.0
getenv: 2.0.0 getenv: 2.0.0
glob: 10.4.5 glob: 10.4.5
@@ -10714,7 +10714,7 @@ snapshots:
wrap-ansi: 7.0.0 wrap-ansi: 7.0.0
ws: 8.18.3 ws: 8.18.3
optionalDependencies: optionalDependencies:
expo-router: 6.0.6(795f20997b5e74199ec1f471a585ccc7) expo-router: 6.0.7(795f20997b5e74199ec1f471a585ccc7)
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@modelcontextprotocol/sdk' - '@modelcontextprotocol/sdk'
@@ -10903,7 +10903,7 @@ snapshots:
postcss: 8.4.49 postcss: 8.4.49
resolve-from: 5.0.0 resolve-from: 5.0.0
optionalDependencies: optionalDependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- supports-color - supports-color
@@ -10912,7 +10912,7 @@ snapshots:
'@expo/metro-runtime@6.1.1(expo@54.0.8)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': '@expo/metro-runtime@6.1.1(expo@54.0.8)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)':
dependencies: dependencies:
anser: 1.4.10 anser: 1.4.10
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
pretty-format: 29.7.0 pretty-format: 29.7.0
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
@@ -10975,7 +10975,7 @@ snapshots:
'@expo/json-file': 10.0.7 '@expo/json-file': 10.0.7
'@react-native/normalize-colors': 0.81.4 '@react-native/normalize-colors': 0.81.4
debug: 4.4.3 debug: 4.4.3
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
resolve-from: 5.0.0 resolve-from: 5.0.0
semver: 7.7.2 semver: 7.7.2
xml2js: 0.6.0 xml2js: 0.6.0
@@ -13827,7 +13827,7 @@ snapshots:
resolve-from: 5.0.0 resolve-from: 5.0.0
optionalDependencies: optionalDependencies:
'@babel/runtime': 7.28.3 '@babel/runtime': 7.28.3
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- supports-color - supports-color
@@ -14808,7 +14808,7 @@ snapshots:
'@typescript-eslint/eslint-plugin': 8.41.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/eslint-plugin': 8.41.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)
'@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)
eslint: 9.35.0(jiti@2.5.1) eslint: 9.35.0(jiti@2.5.1)
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.5.1)))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.5.1)))(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1))
eslint-plugin-expo: 1.0.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) eslint-plugin-expo: 1.0.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1))
eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1))
@@ -14839,7 +14839,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.5.1)))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.5.1)))(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)):
dependencies: dependencies:
'@nolyfill/is-core-module': 1.0.39 '@nolyfill/is-core-module': 1.0.39
debug: 4.4.1 debug: 4.4.1
@@ -14862,7 +14862,7 @@ snapshots:
'@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)
eslint: 9.35.0(jiti@2.5.1) eslint: 9.35.0(jiti@2.5.1)
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.5.1)))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.35.0(jiti@2.5.1)))(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -15316,7 +15316,7 @@ snapshots:
expo-asset@12.0.8(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-asset@12.0.8(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
'@expo/image-utils': 0.8.7 '@expo/image-utils': 0.8.7
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-constants: 18.0.9(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)) expo-constants: 18.0.9(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
@@ -15325,7 +15325,7 @@ snapshots:
expo-clipboard@8.0.7(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-clipboard@8.0.7(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
@@ -15333,7 +15333,7 @@ snapshots:
dependencies: dependencies:
'@expo/config': 12.0.9 '@expo/config': 12.0.9
'@expo/env': 2.0.7 '@expo/env': 2.0.7
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -15341,11 +15341,11 @@ snapshots:
expo-crypto@15.0.7(expo@54.0.8): expo-crypto@15.0.7(expo@54.0.8):
dependencies: dependencies:
base64-js: 1.5.1 base64-js: 1.5.1
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-dev-client@6.0.12(expo@54.0.8): expo-dev-client@6.0.12(expo@54.0.8):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-dev-launcher: 6.0.11(expo@54.0.8) expo-dev-launcher: 6.0.11(expo@54.0.8)
expo-dev-menu: 7.0.11(expo@54.0.8) expo-dev-menu: 7.0.11(expo@54.0.8)
expo-dev-menu-interface: 2.0.0(expo@54.0.8) expo-dev-menu-interface: 2.0.0(expo@54.0.8)
@@ -15356,7 +15356,7 @@ snapshots:
expo-dev-launcher@6.0.11(expo@54.0.8): expo-dev-launcher@6.0.11(expo@54.0.8):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-dev-menu: 7.0.11(expo@54.0.8) expo-dev-menu: 7.0.11(expo@54.0.8)
expo-manifests: 1.0.8(expo@54.0.8) expo-manifests: 1.0.8(expo@54.0.8)
transitivePeerDependencies: transitivePeerDependencies:
@@ -15364,42 +15364,42 @@ snapshots:
expo-dev-menu-interface@2.0.0(expo@54.0.8): expo-dev-menu-interface@2.0.0(expo@54.0.8):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-dev-menu@7.0.11(expo@54.0.8): expo-dev-menu@7.0.11(expo@54.0.8):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-dev-menu-interface: 2.0.0(expo@54.0.8) expo-dev-menu-interface: 2.0.0(expo@54.0.8)
expo-document-picker@14.0.7(expo@54.0.8): expo-document-picker@14.0.7(expo@54.0.8):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-file-system@19.0.14(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)): expo-file-system@19.0.14(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
expo-font@14.0.8(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-font@14.0.8(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
fontfaceobserver: 2.3.0 fontfaceobserver: 2.3.0
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
expo-glass-effect@0.1.4(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-glass-effect@0.1.4(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
expo-haptics@15.0.7(expo@54.0.8): expo-haptics@15.0.7(expo@54.0.8):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-image@3.0.8(expo@54.0.8)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-image@3.0.8(expo@54.0.8)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
optionalDependencies: optionalDependencies:
@@ -15409,7 +15409,7 @@ snapshots:
expo-keep-awake@15.0.7(expo@54.0.8)(react@19.1.0): expo-keep-awake@15.0.7(expo@54.0.8)(react@19.1.0):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react: 19.1.0 react: 19.1.0
expo-linking@8.0.8(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-linking@8.0.8(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
@@ -15425,7 +15425,7 @@ snapshots:
expo-manifests@1.0.8(expo@54.0.8): expo-manifests@1.0.8(expo@54.0.8):
dependencies: dependencies:
'@expo/config': 12.0.8 '@expo/config': 12.0.8
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-json-utils: 0.15.0 expo-json-utils: 0.15.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -15445,7 +15445,7 @@ snapshots:
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
expo-router@6.0.6(795f20997b5e74199ec1f471a585ccc7): expo-router@6.0.7(795f20997b5e74199ec1f471a585ccc7):
dependencies: dependencies:
'@expo/metro-runtime': 6.1.1(expo@54.0.8)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) '@expo/metro-runtime': 6.1.1(expo@54.0.8)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
'@expo/schema-utils': 0.1.7 '@expo/schema-utils': 0.1.7
@@ -15456,9 +15456,9 @@ snapshots:
'@react-navigation/native': 7.1.17(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) '@react-navigation/native': 7.1.17(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
'@react-navigation/native-stack': 7.3.26(@react-navigation/native@7.1.17(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) '@react-navigation/native-stack': 7.3.26(@react-navigation/native@7.1.17(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
client-only: 0.0.1 client-only: 0.0.1
debug: 4.4.1 debug: 4.4.3
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-constants: 18.0.9(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)) expo-constants: 18.0.9(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))
expo-linking: 8.0.8(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo-linking: 8.0.8(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
@@ -15490,12 +15490,12 @@ snapshots:
expo-secure-store@15.0.7(expo@54.0.8): expo-secure-store@15.0.7(expo@54.0.8):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-splash-screen@31.0.10(expo@54.0.8): expo-splash-screen@31.0.10(expo@54.0.8):
dependencies: dependencies:
'@expo/prebuild-config': 54.0.3(expo@54.0.8) '@expo/prebuild-config': 54.0.3(expo@54.0.8)
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -15507,7 +15507,7 @@ snapshots:
expo-symbols@1.0.7(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)): expo-symbols@1.0.7(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
sf-symbols-typescript: 2.1.0 sf-symbols-typescript: 2.1.0
@@ -15515,7 +15515,7 @@ snapshots:
dependencies: dependencies:
'@react-native/normalize-colors': 0.81.4 '@react-native/normalize-colors': 0.81.4
debug: 4.4.1 debug: 4.4.1
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
optionalDependencies: optionalDependencies:
react-native-web: 0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-native-web: 0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -15524,12 +15524,12 @@ snapshots:
expo-updates-interface@2.0.0(expo@54.0.8): expo-updates-interface@2.0.0(expo@54.0.8):
dependencies: dependencies:
expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo@54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.6)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo@54.0.8(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.7)(react-native-webview@13.15.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
'@babel/runtime': 7.28.3 '@babel/runtime': 7.28.3
'@expo/cli': 54.0.6(expo-router@6.0.6)(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)) '@expo/cli': 54.0.6(expo-router@6.0.7)(expo@54.0.8)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))
'@expo/config': 12.0.9 '@expo/config': 12.0.9
'@expo/config-plugins': 54.0.1 '@expo/config-plugins': 54.0.1
'@expo/devtools': 0.1.7(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) '@expo/devtools': 0.1.7(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)