Update App version to 0.1.1

Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
luckfox-eng29
2026-02-05 11:28:14 +08:00
parent 5e17c52afc
commit 9a4e604c61
289 changed files with 23077 additions and 12474 deletions

View File

@@ -38,7 +38,8 @@ const appendStatToMap = <T extends { timestamp: number }>(
};
// Constants and types
export type AvailableSidebarViews = "connection-stats";
export type AvailableSidebarViews ="ConsoleLogViewer"|"MacroMoreList"|"Fullscreen"|"TerminalTabsMobile"|"SettingsModal"|"ClipboardMobile"|"KeyboardPanel"|"MousePanel"|"SettingsVideo"
|"connection-stats"|"Clipboard"|"PowerControl"|"Macros"|"VirtualMedia"|"SharedFolders"|null;
export type AvailableTerminalTypes = "kvm" | "serial" | "none";
export interface User {
@@ -56,6 +57,11 @@ interface UIState {
sidebarView: AvailableSidebarViews | null;
setSidebarView: (view: AvailableSidebarViews | null) => void;
topBarView: AvailableSidebarViews | null;
setTopBarView: (view: AvailableSidebarViews | null) => void;
isAnimationComplete: boolean;
setIsAnimationComplete: (enabled: boolean) => void;
disableVideoFocusTrap: boolean;
setDisableVideoFocusTrap: (enabled: boolean) => void;
@@ -63,12 +69,15 @@ interface UIState {
setWakeOnLanModalVisibility: (enabled: boolean) => void;
toggleSidebarView: (view: AvailableSidebarViews) => void;
toggleTopBarView: (view: AvailableSidebarViews) => void;
isAttachedVirtualKeyboardVisible: boolean;
setAttachedVirtualKeyboardVisibility: (enabled: boolean) => void;
terminalType: AvailableTerminalTypes;
setTerminalType: (enabled: UIState["terminalType"]) => void;
otherSession:boolean;
setOtherSession: (enabled: boolean) => void;
}
export const useUiStore = create<UIState>(set => ({
@@ -77,9 +86,13 @@ export const useUiStore = create<UIState>(set => ({
sidebarView: null,
setSidebarView: view => set({ sidebarView: view }),
topBarView: null,
setTopBarView: view => set({ topBarView: view }),
disableVideoFocusTrap: false,
setDisableVideoFocusTrap: enabled => set({ disableVideoFocusTrap: enabled }),
isAnimationComplete: false,
setIsAnimationComplete: enabled => set({ isAnimationComplete: enabled }),
isWakeOnLanModalVisible: false,
setWakeOnLanModalVisibility: enabled => set({ isWakeOnLanModalVisible: enabled }),
@@ -87,16 +100,30 @@ export const useUiStore = create<UIState>(set => ({
toggleSidebarView: view =>
set(state => {
if (state.sidebarView === view) {
return { sidebarView: null };
return { sidebarView: null , topBarView: null };
} else {
return { sidebarView: view };
return { sidebarView: view , topBarView: null };
}
}),
toggleTopBarView: view =>
set(state => {
if (state.topBarView === view) {
return { topBarView: null,sidebarView: null };
} else {
return { topBarView: view ,sidebarView: null };
}
}),
otherSession:false,
setOtherSession: enabled => set({ otherSession: enabled }),
isAttachedVirtualKeyboardVisible: true,
setAttachedVirtualKeyboardVisibility: enabled =>
set({ isAttachedVirtualKeyboardVisible: enabled }),
}));
}),
);
interface RTCState {
peerConnection: RTCPeerConnection | null;
@@ -151,7 +178,13 @@ interface RTCState {
appendDiskDataChannelStats: (stat: RTCDataChannelStats) => void;
terminalChannel: RTCDataChannel | null;
setTerminalChannel: (channel: RTCDataChannel) => void;
setTerminalChannel: (channel: RTCDataChannel | null) => void;
kvmTerminal: RTCDataChannel | null;
setKvmTerminal: (channel: RTCDataChannel | null) => void;
serialConsole: RTCDataChannel | null;
setSerialConsole: (channel: RTCDataChannel | null) => void;
}
export const useRTCStore = create<RTCState>(set => ({
@@ -227,6 +260,11 @@ export const useRTCStore = create<RTCState>(set => ({
// Add these new properties to the store implementation
terminalChannel: null,
setTerminalChannel: channel => set({ terminalChannel: channel }),
kvmTerminal: null,
setKvmTerminal: channel => set({ kvmTerminal: channel }),
serialConsole: null,
setSerialConsole: channel => set({ serialConsole: channel }),
}));
interface MouseMove {
@@ -356,6 +394,9 @@ interface SettingsState {
setVideoBrightness: (value: number) => void;
videoContrast: number;
setVideoContrast: (value: number) => void;
forceHttp: boolean;
setForceHttp: (enabled: boolean) => void;
}
export const useSettingsStore = create(
@@ -422,6 +463,9 @@ export const useSettingsStore = create(
setVideoBrightness: value => set({ videoBrightness: value }),
videoContrast: 1.0,
setVideoContrast: value => set({ videoContrast: value }),
forceHttp: false,
setForceHttp: enabled => set({ forceHttp: enabled }),
}),
{
name: "settings",
@@ -530,11 +574,21 @@ export interface HidState {
usbState: "configured" | "attached" | "not attached" | "suspended" | "addressed" | "default";
setUsbState: (state: HidState["usbState"]) => void;
isReinitializingGadget: boolean;
setIsReinitializingGadget: (reinitializing: boolean) => void;
}
export interface SerialState {
isConnected: boolean;
setIsConnected: (connected: boolean) => void;
}
export const useSerialStore = create<SerialState>(set => ({
isConnected: false,
setIsConnected: connected => set({ isConnected: connected }),
}));
export const useHidStore = create<HidState>((set, get) => ({
activeKeys: [],
activeModifiers: [],
@@ -580,7 +634,7 @@ export const useHidStore = create<HidState>((set, get) => ({
// Add these new properties for USB state
usbState: "not attached",
setUsbState: state => set({ usbState: state }),
isReinitializingGadget: false,
setIsReinitializingGadget: reinitializing => set({ isReinitializingGadget: reinitializing }),
}));
@@ -662,7 +716,15 @@ export const useUpdateStore = create<UpdateState>(set => ({
isUpdatePending: false,
setIsUpdatePending: isPending => set({ isUpdatePending: isPending }),
setOtaState: state => set({ otaState: state }),
setOtaState: state =>
set(current => {
const definedEntries = Object.entries(state).filter(([, value]) => value !== undefined);
const merged = {
...current.otaState,
...Object.fromEntries(definedEntries),
} as UpdateState["otaState"];
return { otaState: merged };
}),
otaState: {
updating: false,
error: null,
@@ -714,7 +776,7 @@ export const useUsbConfigModalStore = create<UsbConfigModalState>(set => ({
setErrorMessage: message => set({ errorMessage: message }),
}));
interface LocalAuthModalState {
export interface LocalAuthModalState {
modalView:
| "createPassword"
| "deletePassword"
@@ -1065,3 +1127,19 @@ export const useVpnStore = create<VpnState>(set => ({
setZeroTierIP: networkID => set({ zeroTierIP: networkID }),
}));
export interface MacrosSideState {
sideTitle: string;
setSideTitle: (title: string) => void;
};
export const useMacrosSideTitleState = create<MacrosSideState>(set => ({
sideTitle: "Keyboard Macros",
setSideTitle: title => set({ sideTitle: title }),
}));
export interface LogEntry {
timestamp: string;
level: 'log' | 'error' | 'warn' | 'info';
message: string;
originalArgs: any[];
}

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect } from "react";
import { useRTCStore } from "@/hooks/stores";
import { useRTCStore, useSettingsStore } from "@/hooks/stores";
export interface JsonRpcRequest {
jsonrpc: string;
@@ -31,12 +31,111 @@ export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
const callbackStore = new Map<number | string, (resp: JsonRpcResponse) => void>();
let requestCounter = 0;
let httpSessionId: string | null = null;
let httpSessionInvalidated = false;
function getHttpSessionId() {
if (httpSessionId) return httpSessionId;
try {
const existing = window.sessionStorage.getItem("httpSessionId");
if (existing) {
httpSessionId = existing;
return httpSessionId;
}
const generated = typeof crypto !== "undefined" && "randomUUID" in crypto
? crypto.randomUUID()
: `${Date.now()}-${Math.random().toString(16).slice(2)}`;
window.sessionStorage.setItem("httpSessionId", generated);
httpSessionId = generated;
return httpSessionId;
} catch {
const fallback = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
httpSessionId = fallback;
return httpSessionId;
}
}
export function resetHttpSessionId() {
try {
window.sessionStorage.removeItem("httpSessionId");
} catch {
void 0;
}
httpSessionId = null;
httpSessionInvalidated = true;
}
export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
const forceHttp = useSettingsStore(state => state.forceHttp);
const send = useCallback(
(method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => {
if (forceHttp) {
if (httpSessionInvalidated) {
requestCounter++;
const payloadId = requestCounter;
if (callback) {
callback({
jsonrpc: "2.0",
error: { code: -32002, message: "HTTP session invalidated on client" },
id: payloadId,
} as JsonRpcErrorResponse);
}
return;
}
requestCounter++;
const payload = { jsonrpc: "2.0", method, params, id: requestCounter };
fetch("/api/rpc", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': getHttpSessionId(),
},
body: JSON.stringify(payload)
})
.then(res => res.json())
.then((data: unknown) => {
const handleEvent = (event: JsonRpcRequest) => {
if (event.method === "refreshPage") {
const currentUrl = new URL(window.location.href);
currentUrl.searchParams.set("networkChanged", "true");
window.location.href = currentUrl.toString();
return;
}
if (onRequest) onRequest(event);
};
if (data && typeof data === "object" && ("response" in data || "event" in data)) {
const wrapper = data as { response: JsonRpcResponse; event?: JsonRpcRequest };
if (wrapper.event) {
handleEvent(wrapper.event);
}
if (callback) callback(wrapper.response);
return;
}
if (data && typeof data === "object" && "method" in data) {
handleEvent(data as JsonRpcRequest);
return;
}
if (callback) callback(data as JsonRpcResponse);
})
.catch(err => {
console.error("RPC over HTTP failed", err);
if (callback) {
callback({
jsonrpc: "2.0",
error: { code: -32000, message: "HTTP RPC failed", data: err.toString() },
id: payload.id
});
}
});
return;
}
if (rpcDataChannel?.readyState !== "open") return;
requestCounter++;
const payload = { jsonrpc: "2.0", method, params, id: requestCounter };
@@ -45,7 +144,7 @@ export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
rpcDataChannel.send(JSON.stringify(payload));
},
[rpcDataChannel],
[rpcDataChannel, forceHttp, onRequest],
);
useEffect(() => {

View File

@@ -1,7 +1,7 @@
import { useCallback } from "react";
import notifications from "@/notifications";
import { useHidStore, useRTCStore } from "@/hooks/stores";
import notifications from "@/notifications";
import { useHidStore, useRTCStore, useSettingsStore } from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { keys, modifiers } from "@/keyboardMappings";
@@ -9,6 +9,7 @@ export default function useKeyboard() {
const [send] = useJsonRpc();
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
const forceHttp = useSettingsStore(state => state.forceHttp);
const updateActiveKeysAndModifiers = useHidStore(
state => state.updateActiveKeysAndModifiers,
);
@@ -17,11 +18,10 @@ export default function useKeyboard() {
const sendKeyboardEvent = useCallback(
(keys: number[], modifiers: number[]) => {
if (rpcDataChannel?.readyState !== "open") return;
if (!forceHttp && rpcDataChannel?.readyState !== "open") return;
// Don't send keyboard events while reinitializing gadget
if (isReinitializingGadget) return;
if (usbState !== "configured") return;
const accModifier = modifiers.reduce((acc, val) => acc + val, 0);
send("keyboardReport", { keys, modifier: accModifier }, resp => {
@@ -36,7 +36,7 @@ export default function useKeyboard() {
// We do this for the info bar to display the currently pressed keys for the user
updateActiveKeysAndModifiers({ keys: keys, modifiers: modifiers });
},
[rpcDataChannel?.readyState, send, updateActiveKeysAndModifiers, isReinitializingGadget, usbState],
[forceHttp, rpcDataChannel?.readyState, send, updateActiveKeysAndModifiers, isReinitializingGadget, usbState],
);
const resetKeyboardState = useCallback(() => {