Refactor: Rename NanoKVM to BatchuKVM and update server URL

This commit is contained in:
2025-12-09 20:35:38 +09:00
commit 8cf674c9e5
396 changed files with 54380 additions and 0 deletions

23
web/src/lib/cookie.ts Normal file
View File

@@ -0,0 +1,23 @@
import Cookies from 'js-cookie';
const COOKIE_TOKEN_KEY = 'nano-kvm-token';
export function existToken() {
const token = Cookies.get(COOKIE_TOKEN_KEY);
return !!token;
}
export function getToken() {
const token = Cookies.get(COOKIE_TOKEN_KEY);
if (!token) return null;
return token;
}
export function setToken(token: string) {
Cookies.set(COOKIE_TOKEN_KEY, token, { expires: 30 });
}
export function removeToken() {
Cookies.remove(COOKIE_TOKEN_KEY);
}

9
web/src/lib/encrypt.ts Normal file
View File

@@ -0,0 +1,9 @@
import CryptoJS from 'crypto-js';
// This key is only used to prevent the data from being transmitted in plaintext.
const SECRET_KEY = 'NanoKVM-KOREA-TestKey-2512092155';
export function encrypt(data: string) {
const dataEncrypt = CryptoJS.AES.encrypt(data, SECRET_KEY).toString();
return encodeURIComponent(dataEncrypt);
}

74
web/src/lib/http.ts Normal file
View File

@@ -0,0 +1,74 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { removeToken } from '@/lib/cookie.ts';
import { getBaseUrl } from '@/lib/service.ts';
type Response = {
code: number;
msg: string;
data: any;
};
class Http {
private instance: AxiosInstance;
constructor() {
const baseURL = getBaseUrl('http');
const withCredentials = (import.meta.env.VITE_WITH_CREDENTIALS as string) !== 'false';
this.instance = axios.create({
baseURL,
withCredentials,
timeout: 60 * 1000
});
this.setInterceptors();
}
private setInterceptors() {
this.instance.interceptors.request.use((config) => {
if (config.headers) {
config.headers.Accept = 'application/json';
}
return config;
});
this.instance.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
console.log(error);
const code = error.response?.status;
if (code === 401) {
removeToken();
window.location.reload();
}
return Promise.reject(error);
}
);
}
public get(url: string, params?: any): Promise<Response> {
return this.instance.request({
method: 'get',
url,
params
});
}
public post(url: string, data?: any): Promise<Response> {
return this.instance.request({
method: 'post',
url,
data
});
}
public request(config: AxiosRequestConfig): Promise<Response> {
return this.instance.request(config);
}
}
export const http = new Http();

196
web/src/lib/localstorage.ts Normal file
View File

@@ -0,0 +1,196 @@
import { Resolution } from '@/types';
const LANGUAGE_KEY = 'nano-kvm-language';
const VIDEO_MODE_KEY = 'nano-kvm-vide-mode';
const WEB_RESOLUTION_KEY = 'nano-kvm-web-resolution';
const FPS_KEY = 'nano-kvm-fps';
const QUALITY_KEY = 'nano-kvm-quality';
const GOP_KEY = 'nano-kvm-gop';
const FRAME_DETECT_KEY = 'nano-kvm-frame-detect';
const MOUSE_STYLE_KEY = 'nano-kvm-mouse-style';
const MOUSE_MODE_KEY = 'nano-kvm-mouse-mode';
const MOUSE_SCROLL_INTERVAL_KEY = 'nanokvm-kvm-mouse-scroll-interval';
const SKIP_UPDATE_KEY = 'nano-kvm-check-update';
const KEYBOARD_SYSTEM_KEY = 'nano-kvm-keyboard-system';
const KEYBOARD_LANGUAGE_KEY = 'nano-kvm-keyboard-language';
const SKIP_MODIFY_PASSWORD_KEY = 'nano-kvm-skip-modify-password';
const MENU_DISABLED_ITEMS_KEY = 'nano-kvm-menu-disabled-items';
const POWER_CONFIRM_KEY = 'nano-kvm-power-confirm';
type ItemWithExpiry = {
value: string;
expiry: number;
};
// set the value with expiration time (unit: milliseconds)
function setWithExpiry(key: string, value: string, ttl: number) {
const now = new Date();
const item: ItemWithExpiry = {
value: value,
expiry: now.getTime() + ttl
};
localStorage.setItem(key, JSON.stringify(item));
}
// get the value with expiration time
function getWithExpiry(key: string) {
const itemStr = localStorage.getItem(key);
if (!itemStr) return null;
const item: ItemWithExpiry = JSON.parse(itemStr);
const now = new Date();
if (now.getTime() > item.expiry) {
localStorage.removeItem(key);
return null;
}
return item.value;
}
export function getLanguage() {
return localStorage.getItem(LANGUAGE_KEY);
}
export function setLanguage(language: string) {
localStorage.setItem(LANGUAGE_KEY, language);
}
export function getVideoMode() {
return localStorage.getItem(VIDEO_MODE_KEY);
}
export function setVideoMode(mode: string) {
localStorage.setItem(VIDEO_MODE_KEY, mode);
}
export function getResolution(): Resolution | null {
const resolution = localStorage.getItem(WEB_RESOLUTION_KEY);
if (resolution) {
const obj = JSON.parse(window.atob(resolution));
return obj as Resolution;
}
return null;
}
export function setResolution(resolution: Resolution) {
localStorage.setItem(WEB_RESOLUTION_KEY, window.btoa(JSON.stringify(resolution)));
}
export function getFps() {
const fps = localStorage.getItem(FPS_KEY);
return fps ? Number(fps) : null;
}
export function setFps(fps: number) {
localStorage.setItem(FPS_KEY, String(fps));
}
export function getQuality() {
const quality = localStorage.getItem(QUALITY_KEY);
return quality ? Number(quality) : null;
}
export function setQuality(quality: number) {
localStorage.setItem(QUALITY_KEY, String(quality));
}
export function getGop() {
const gop = localStorage.getItem(GOP_KEY);
return gop ? Number(gop) : null;
}
export function setGop(gop: number) {
localStorage.setItem(GOP_KEY, String(gop));
}
export function getFrameDetect(): boolean {
const enabled = localStorage.getItem(FRAME_DETECT_KEY);
return enabled === 'true';
}
export function setFrameDetect(enabled: boolean) {
localStorage.setItem(FRAME_DETECT_KEY, String(enabled));
}
export function getMouseStyle() {
return localStorage.getItem(MOUSE_STYLE_KEY);
}
export function setMouseStyle(mouse: string) {
localStorage.setItem(MOUSE_STYLE_KEY, mouse);
}
export function getMouseMode() {
return localStorage.getItem(MOUSE_MODE_KEY);
}
export function setMouseMode(mouse: string) {
localStorage.setItem(MOUSE_MODE_KEY, mouse);
}
export function getMouseScrollInterval() {
const interval = localStorage.getItem(MOUSE_SCROLL_INTERVAL_KEY);
return interval ? Number(interval) : null;
}
export function setMouseScrollInterval(interval: number): void {
localStorage.setItem(MOUSE_SCROLL_INTERVAL_KEY, String(interval));
}
export function getSkipUpdate() {
const skip = getWithExpiry(SKIP_UPDATE_KEY);
return skip === 'true';
}
export function setSkipUpdate(skip: boolean) {
const expiry = 3 * 24 * 60 * 60 * 1000; // 3 days
setWithExpiry(SKIP_UPDATE_KEY, String(skip), expiry);
}
export function setKeyboardSystem(system: string) {
localStorage.setItem(KEYBOARD_SYSTEM_KEY, system);
}
export function getKeyboardSystem() {
return localStorage.getItem(KEYBOARD_SYSTEM_KEY);
}
export function setKeyboardLanguage(language: string) {
localStorage.setItem(KEYBOARD_LANGUAGE_KEY, language);
}
export function getKeyboardLanguage() {
return localStorage.getItem(KEYBOARD_LANGUAGE_KEY);
}
export function setSkipModifyPassword(skip: boolean) {
const expiry = 3 * 24 * 60 * 60 * 1000; // 3 days
setWithExpiry(SKIP_MODIFY_PASSWORD_KEY, String(skip), expiry);
}
export function getSkipModifyPassword() {
const skip = getWithExpiry(SKIP_MODIFY_PASSWORD_KEY);
return skip === 'true';
}
export function setMenuDisabledItems(items: string[]) {
const value = JSON.stringify(items);
localStorage.setItem(MENU_DISABLED_ITEMS_KEY, value);
}
export function getMenuDisabledItems(): string[] {
const value = localStorage.getItem(MENU_DISABLED_ITEMS_KEY);
return value ? JSON.parse(value) : [];
}
export function getPowerConfirm() {
const enabled = localStorage.getItem(POWER_CONFIRM_KEY);
return enabled === 'true';
}
export function setPowerConfirm(enabled: boolean) {
localStorage.setItem(POWER_CONFIRM_KEY, String(enabled));
}

21
web/src/lib/service.ts Normal file
View File

@@ -0,0 +1,21 @@
export function getHostname(): string {
const ip = import.meta.env.VITE_SERVER_IP as string;
return ip ? ip : window.location.hostname;
}
export function getPort(): string {
const port = import.meta.env.VITE_SERVER_PORT as string;
return port ? port : window.location.port;
}
export function getBaseUrl(type: 'http' | 'ws'): string {
let protocol = window.location.protocol;
if (type === 'ws') {
protocol = protocol === 'https:' ? 'wss:' : 'ws:';
}
const hostname = getHostname();
const port = getPort();
return `${protocol}//${hostname}:${port}`;
}

66
web/src/lib/websocket.ts Normal file
View File

@@ -0,0 +1,66 @@
import { IMessageEvent, w3cwebsocket as W3cWebSocket } from 'websocket';
import { getBaseUrl } from '@/lib/service.ts';
type Event = (message: IMessageEvent) => void;
const eventMap: Map<string, Event> = new Map<string, Event>();
class WsClient {
private readonly url: string;
private instance: W3cWebSocket;
constructor() {
this.url = `${getBaseUrl('ws')}/api/ws`;
this.instance = new W3cWebSocket(this.url);
this.setEvents();
}
public connect() {
this.close();
this.instance = new W3cWebSocket(this.url);
this.setEvents();
}
public send(data: number[]) {
if (this.instance.readyState !== W3cWebSocket.OPEN) {
return;
}
const message = JSON.stringify(data);
this.instance.send(message);
}
public close() {
if (this.instance.readyState === W3cWebSocket.OPEN) {
this.instance.close();
}
}
public register(type: string, fn: (message: IMessageEvent) => void) {
eventMap.set(type, fn);
this.setEvents();
}
public unregister(type: string) {
eventMap.delete(type);
this.setEvents();
}
private setEvents() {
this.instance.onmessage = (message) => {
const data = JSON.parse(message.data as string);
if (!data) return;
const fn = eventMap.get(data.type);
if (!fn) return;
fn(message);
};
}
}
export const client = new WsClient();