feat: migrate recipe editor
- using eventbus in recipe editor - migrate to logging instead of console log - fix case swap not saved, value not update after change, topping slot bug Signed-off-by: pakintada@gmail.com <Pakin>
This commit is contained in:
parent
270faf6b34
commit
f0619c5a10
65 changed files with 1600 additions and 557 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { Adb, AdbDaemonTransport, encodeUtf8 } from '@yume-chan/adb';
|
||||
import AdbWebCredentialStore from '@yume-chan/adb-credential-web';
|
||||
import {
|
||||
|
|
@ -138,9 +139,9 @@ async function connectWithRetry<T>(
|
|||
|
||||
export async function connnectViaWebUSB(connectAndroidServer = true) {
|
||||
const device = await AdbDaemonWebUsbDeviceManager.BROWSER?.requestDevice();
|
||||
console.log('usb ok', globalThis.navigator.usb);
|
||||
logger.info('usb ok', globalThis.navigator.usb);
|
||||
if (device) {
|
||||
console.log('connect ', device.name);
|
||||
logger.info('connect ', device.name);
|
||||
|
||||
try {
|
||||
const credentialStore = new AdbWebCredentialStore();
|
||||
|
|
@ -161,7 +162,7 @@ export async function connnectViaWebUSB(connectAndroidServer = true) {
|
|||
// save device info
|
||||
await deviceCredentialManager.saveDeviceInfo(device);
|
||||
} catch (e: any) {
|
||||
console.error('error on connect', e);
|
||||
logger.error('error on connect', e);
|
||||
|
||||
if (e instanceof AdbDaemonWebUsbDevice.DeviceBusyError) {
|
||||
addNotification(
|
||||
|
|
@ -218,9 +219,9 @@ export async function connectRecipeMenuViaWebUSB() {
|
|||
if (recipeMenuAdbConnectPromise) return await recipeMenuAdbConnectPromise;
|
||||
|
||||
const device = await AdbDaemonWebUsbDeviceManager.BROWSER?.requestDevice();
|
||||
console.log('recipe menu usb ok', 'usb' in globalThis.navigator);
|
||||
logger.info('recipe menu usb ok', 'usb' in globalThis.navigator);
|
||||
if (device) {
|
||||
console.log('recipe menu connect ', device.name);
|
||||
logger.info('recipe menu connect ', device.name);
|
||||
|
||||
try {
|
||||
const credentialStore = new AdbWebCredentialStore();
|
||||
|
|
@ -230,7 +231,7 @@ export async function connectRecipeMenuViaWebUSB() {
|
|||
await deviceCredentialManager.saveDeviceInfo(device);
|
||||
return adb;
|
||||
} catch (e: any) {
|
||||
console.error('recipe menu connect error', e);
|
||||
logger.error('recipe menu connect error', e);
|
||||
|
||||
if (e instanceof AdbDaemonWebUsbDevice.DeviceBusyError) {
|
||||
addNotification(
|
||||
|
|
@ -306,10 +307,10 @@ export async function sendRecipeMenuMessageToAndroid(message: any) {
|
|||
try {
|
||||
const encoder = new TextEncoder();
|
||||
await writer.write(encoder.encode(JSON.stringify(message) + '\n'));
|
||||
console.log('recipe menu sent! ', JSON.stringify(message).length);
|
||||
logger.info('recipe menu sent! ', JSON.stringify(message).length);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('recipe menu write failed', error);
|
||||
logger.error('recipe menu write failed', error);
|
||||
addNotification(`ERR:Failed to send recipe menu\n${error}`);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -319,7 +320,7 @@ export async function executeCmd(command: string) {
|
|||
let instance = getAdbInstance();
|
||||
|
||||
if (!instance) {
|
||||
console.error('instance not found');
|
||||
logger.error('instance not found');
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -348,7 +349,7 @@ export async function executeCmd(command: string) {
|
|||
};
|
||||
}
|
||||
} catch (e: any) {
|
||||
// console.log(e.message);
|
||||
// logger.info(e.message);
|
||||
//ExactReadable ended
|
||||
if (e.message.includes('ExactReadable ended')) {
|
||||
return {
|
||||
|
|
@ -358,7 +359,7 @@ export async function executeCmd(command: string) {
|
|||
};
|
||||
}
|
||||
|
||||
console.error('error while execute command', e);
|
||||
logger.error('error while execute command', e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
@ -445,7 +446,7 @@ export async function disconnect() {
|
|||
if (instance) {
|
||||
try {
|
||||
await instance.close();
|
||||
console.log('close instance');
|
||||
logger.info('close instance');
|
||||
} finally {
|
||||
await saveAdbInstance(undefined);
|
||||
}
|
||||
|
|
@ -457,7 +458,7 @@ export async function cleanupSync() {
|
|||
try {
|
||||
await syncConnection.dispose();
|
||||
} catch (e) {
|
||||
console.error('error on dispose sync', e);
|
||||
logger.error('error on dispose sync', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -489,7 +490,7 @@ export async function pull(filename: string, timeoutMs: number = 5000) {
|
|||
return result_string;
|
||||
}
|
||||
} catch (pull_error: any) {
|
||||
console.log('pulling error', pull_error);
|
||||
logger.info('pulling error', pull_error);
|
||||
} finally {
|
||||
await cleanupSync();
|
||||
}
|
||||
|
|
@ -511,14 +512,14 @@ export async function push(path: string, obj: string) {
|
|||
});
|
||||
|
||||
try {
|
||||
console.log('support push v2', sync.supportsSendReceiveV2);
|
||||
logger.info('support push v2', sync.supportsSendReceiveV2);
|
||||
|
||||
await sync.write({
|
||||
filename: path,
|
||||
file
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('error while trying to write to machine', error);
|
||||
logger.info('error while trying to write to machine', error);
|
||||
} finally {
|
||||
await sync.dispose();
|
||||
}
|
||||
|
|
@ -563,7 +564,7 @@ export async function pushBinary(
|
|||
onProgress?.(total, total);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log('error while pushing binary to machine', error);
|
||||
logger.info('error while pushing binary to machine', error);
|
||||
return false;
|
||||
} finally {
|
||||
await sync.dispose();
|
||||
|
|
@ -582,7 +583,7 @@ async function connectToAndroidServer(maxRetries = 5) {
|
|||
try {
|
||||
let inst = getAdbInstance();
|
||||
if (!inst) {
|
||||
console.warn('adb instance not found');
|
||||
logger.warn('adb instance not found');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -599,7 +600,7 @@ async function connectToAndroidServer(maxRetries = 5) {
|
|||
const writer = stream.writable.getWriter();
|
||||
const reader = stream.readable.getReader();
|
||||
|
||||
console.log('checking on writer ', writer);
|
||||
// logger.info('checking on writer ', writer);
|
||||
adbWriter.set(writer);
|
||||
if (writer) {
|
||||
addNotification('INFO:Enable Brewing Mode T on machine');
|
||||
|
|
@ -632,7 +633,7 @@ async function connectToAndroidServer(maxRetries = 5) {
|
|||
// handleAdbPayload(new TextDecoder().decode(value));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('read error', e);
|
||||
logger.error('read error', e);
|
||||
if (isRecoverableError(e)) {
|
||||
void connectToAndroidServer();
|
||||
}
|
||||
|
|
@ -671,7 +672,7 @@ async function connectToAndroidServer(maxRetries = 5) {
|
|||
}
|
||||
|
||||
if (lastError) {
|
||||
console.error('Connection failed. Suspect java running or not', lastError);
|
||||
logger.error('Connection failed. Suspect java running or not', lastError);
|
||||
addNotification(`ERR:Fail to enable brewing mode T\n${lastError.message ?? ''}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -693,7 +694,7 @@ async function connectToAndroidRecipeMenuServerOnce(notifyFailure = true, retryO
|
|||
try {
|
||||
let inst = getAdbInstance();
|
||||
if (!inst) {
|
||||
console.warn('recipe menu adb instance not found');
|
||||
logger.warn('recipe menu adb instance not found');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -704,7 +705,7 @@ async function connectToAndroidRecipeMenuServerOnce(notifyFailure = true, retryO
|
|||
const writer = stream.writable.getWriter();
|
||||
const reader = stream.readable.getReader();
|
||||
|
||||
console.log('checking recipe menu writer ', writer);
|
||||
logger.info('checking recipe menu writer ', writer);
|
||||
adbWriter.set(writer);
|
||||
if (writer) {
|
||||
addNotification('INFO:Enable Android recipe menu channel');
|
||||
|
|
@ -712,7 +713,7 @@ async function connectToAndroidRecipeMenuServerOnce(notifyFailure = true, retryO
|
|||
addNotification('WARN:Android recipe menu channel unavailable');
|
||||
|
||||
setTimeout(async () => {
|
||||
console.log('reconnecting android recipe menu server');
|
||||
logger.info('reconnecting android recipe menu server');
|
||||
await connectToAndroidRecipeMenuServer();
|
||||
}, 5000);
|
||||
}
|
||||
|
|
@ -726,7 +727,7 @@ async function connectToAndroidRecipeMenuServerOnce(notifyFailure = true, retryO
|
|||
if (done) break;
|
||||
|
||||
const decoded = decoder.decode(value, { stream: true });
|
||||
console.log('[ADB Reader] Received raw:', decoded.slice(0, 200));
|
||||
logger.info('[ADB Reader] Received raw:', decoded.slice(0, 200));
|
||||
messageBuffer += decoded;
|
||||
const messages = messageBuffer.split('\n');
|
||||
messageBuffer = messages.pop() ?? '';
|
||||
|
|
@ -734,7 +735,7 @@ async function connectToAndroidRecipeMenuServerOnce(notifyFailure = true, retryO
|
|||
for (const message of messages) {
|
||||
const trimmedMessage = message.trim();
|
||||
if (trimmedMessage) {
|
||||
console.log('[ADB Reader] Processing message:', trimmedMessage.slice(0, 200));
|
||||
logger.info('[ADB Reader] Processing message:', trimmedMessage.slice(0, 200));
|
||||
GlobalEventBus.emit('adb:raw-payload', trimmedMessage);
|
||||
handleAdbPayload(trimmedMessage);
|
||||
}
|
||||
|
|
@ -747,7 +748,7 @@ async function connectToAndroidRecipeMenuServerOnce(notifyFailure = true, retryO
|
|||
handleAdbPayload(remainingMessage);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('recipe menu read error', e);
|
||||
logger.error('recipe menu read error', e);
|
||||
} finally {
|
||||
adbWriter.set(null);
|
||||
addNotification('WARN:Android recipe menu channel offline ...');
|
||||
|
|
@ -757,7 +758,7 @@ async function connectToAndroidRecipeMenuServerOnce(notifyFailure = true, retryO
|
|||
}
|
||||
})();
|
||||
} catch (err) {
|
||||
console.error('Recipe menu connection failed. Suspect java running or not', err);
|
||||
logger.error('Recipe menu connection failed. Suspect java running or not', err);
|
||||
adbWriter.set(null);
|
||||
if (notifyFailure) addNotification('ERR:Fail to enable Android recipe menu channel');
|
||||
if (retryOnFailure) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import AdbWebCredentialStore from '@yume-chan/adb-credential-web';
|
||||
|
||||
export class DeviceCredentialManager {
|
||||
|
|
@ -20,9 +21,9 @@ export class DeviceCredentialManager {
|
|||
storedDevices[device.serial] = deviceInfo;
|
||||
|
||||
localStorage.setItem('adb_device_infos', JSON.stringify(storedDevices));
|
||||
console.log('save device info', deviceInfo);
|
||||
logger.info('save device info', deviceInfo);
|
||||
} catch (error) {
|
||||
console.error('save device info error', error);
|
||||
logger.error('save device info error', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +32,7 @@ export class DeviceCredentialManager {
|
|||
const stored = localStorage.getItem('adb_device_infos');
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
} catch (error) {
|
||||
console.error('unable to get stored device info', error);
|
||||
logger.error('unable to get stored device info', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +44,7 @@ export class DeviceCredentialManager {
|
|||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('check stored keys fail', error);
|
||||
logger.error('check stored keys fail', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +58,7 @@ export class DeviceCredentialManager {
|
|||
|
||||
return count;
|
||||
} catch (error) {
|
||||
console.error('get key stored count error', error);
|
||||
logger.error('get key stored count error', error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +75,7 @@ export class DeviceCredentialManager {
|
|||
try {
|
||||
clearedCount++;
|
||||
} catch (error) {
|
||||
console.error('clear error', error);
|
||||
logger.error('clear error', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,16 +89,16 @@ export class DeviceCredentialManager {
|
|||
request.onsuccess = () => resolve(null);
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onblocked = () => {
|
||||
console.warn('request delete got blocked');
|
||||
logger.warn('request delete got blocked');
|
||||
resolve(null);
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
}
|
||||
return clearedCount;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
0
src/lib/core/brew/machineStatusDefinition.ts
Normal file
0
src/lib/core/brew/machineStatusDefinition.ts
Normal file
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { get } from 'svelte/store';
|
||||
import { departmentStore } from '../stores/departments';
|
||||
import { sendMessage } from '../handlers/ws_messageSender';
|
||||
|
|
@ -10,7 +11,7 @@ import { recipeData, recipeOverviewData } from '../stores/recipeStore';
|
|||
|
||||
export async function getRecipes() {
|
||||
if (browser && !get(departmentStore)) {
|
||||
console.log('cannot get dep', get(departmentStore));
|
||||
logger.info('cannot get dep', get(departmentStore));
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ export async function getRecipes() {
|
|||
// construct path. fetch (GET) {server}/recipe/{countryTarget}/{version}
|
||||
let idToken = await get(auth)?.getIdToken();
|
||||
|
||||
console.log('country target get recipe', countryTarget);
|
||||
logger.info('country target get recipe', countryTarget);
|
||||
|
||||
recipeData.set([]);
|
||||
recipeOverviewData.set([]);
|
||||
|
|
@ -53,7 +54,7 @@ export async function getRecipes() {
|
|||
|
||||
export async function getRecipeWithVersion(version: string) {
|
||||
if (browser && !get(departmentStore)) {
|
||||
console.log('cannot get dep', get(departmentStore));
|
||||
logger.info('cannot get dep', get(departmentStore));
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ export async function getRecipeWithVersion(version: string) {
|
|||
// construct path. fetch (GET) {server}/recipe/{countryTarget}/{version}
|
||||
let idToken = await get(auth)?.getIdToken();
|
||||
|
||||
console.log('country target get recipe', countryTarget);
|
||||
logger.info('country target get recipe', countryTarget);
|
||||
|
||||
recipeData.set([]);
|
||||
recipeOverviewData.set([]);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { get } from 'svelte/store';
|
||||
import { updateMachineStatus } from '../stores/machineInfoStore';
|
||||
import { addNotification } from '../stores/noti';
|
||||
|
|
@ -19,13 +20,13 @@ type AdbPayload = { type: string; payload: any };
|
|||
let queuedPromises = new Array<Promise<void>>();
|
||||
|
||||
async function handleAdbPayload(raw_payload: string) {
|
||||
// console.log('[ADB] Received payload:', raw_payload.slice(0, 300));
|
||||
// logger.info('[ADB] Received payload:', raw_payload.slice(0, 300));
|
||||
const APP_VERSION = env.PUBLIC_APP_SEMVER;
|
||||
// const bus = useEventBus();
|
||||
|
||||
try {
|
||||
const payload: AdbPayload = JSON.parse(raw_payload);
|
||||
// console.log('[ADB] Parsed type:', payload.type, 'payload:', payload.payload);
|
||||
// logger.info('[ADB] Parsed type:', payload.type, 'payload:', payload.payload);
|
||||
|
||||
// Emit payload event for terminal drawer payload viewer
|
||||
GlobalEventBus.emit('adb:payload', payload);
|
||||
|
|
@ -46,7 +47,7 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
let log_message = payload.payload['msg'] ?? '';
|
||||
|
||||
if (log_message !== '') {
|
||||
console.log('[ADB LOG]', log_level, log_message);
|
||||
logger.info('[ADB LOG]', log_level, log_message);
|
||||
addNotification(`${log_level}:${log_message}`);
|
||||
}
|
||||
break;
|
||||
|
|
@ -65,7 +66,7 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
let action = res[2] ?? '';
|
||||
let uiAction = res[3] ?? '';
|
||||
|
||||
console.log('[ADB] Save response parsed:', { pd, action, uiAction, raw_payload });
|
||||
// logger.info('[ADB] Save response parsed:', { pd, action, uiAction, raw_payload });
|
||||
|
||||
// Track menu save status
|
||||
if (raw_payload.startsWith('save_recipe_menu_file') && pd) {
|
||||
|
|
@ -93,6 +94,9 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
// }
|
||||
// })
|
||||
// );
|
||||
if (pd.length > 0) {
|
||||
setMenuSaved(pd);
|
||||
}
|
||||
}
|
||||
} else if (raw_payload.startsWith('state')) {
|
||||
let res = raw_payload.split('/');
|
||||
|
|
@ -108,7 +112,7 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
// acknowledge response from app
|
||||
if (payload.payload !== 'OK') {
|
||||
// abnormal
|
||||
console.error('error from ACK', payload.payload);
|
||||
logger.error('error from ACK', payload.payload);
|
||||
addNotification('ERR:Request rejected');
|
||||
}
|
||||
break;
|
||||
|
|
@ -119,7 +123,7 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
let curr = states[0].replace('MACHINE_STATE_', '');
|
||||
let next = states[1].replace('MACHINE_STATE_', '');
|
||||
|
||||
console.log('current state', curr, 'next state', next);
|
||||
logger.info('current state', curr, 'next state', next);
|
||||
|
||||
addNotification('INFO:Machine Status Updated, ' + next);
|
||||
updateMachineStatus(next);
|
||||
|
|
@ -133,7 +137,7 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
let mode_ref = plist[2] ?? '';
|
||||
|
||||
// update recipe data store
|
||||
console.log('brewing finish', pd, 'total time', total_time);
|
||||
logger.info('brewing finish', pd, 'total time', total_time);
|
||||
|
||||
// update recipe from brew now
|
||||
let recipeDevSnapshot = get(recipeFromMachineQuery) ?? {};
|
||||
|
|
@ -190,7 +194,7 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
// only update recipe from memory of brew app
|
||||
if (!semver.satisfies(APP_VERSION, '^0.0.3')) {
|
||||
// reject
|
||||
console.log('unsupported version');
|
||||
logger.info('unsupported version');
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -281,11 +285,11 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
} else if (sub_type == 'end' && payload.payload.includes('finish')) {
|
||||
let force_reload = payload.payload.includes('reload');
|
||||
|
||||
console.log('queued recipes: ', queuedPromises.length);
|
||||
logger.info('queued recipes: ', queuedPromises.length);
|
||||
if (queuedPromises.length > 0) {
|
||||
try {
|
||||
await Promise.all(queuedPromises);
|
||||
console.log('clear all recipe promises');
|
||||
logger.info('clear all recipe promises');
|
||||
queuedPromises = new Array();
|
||||
|
||||
GlobalEventBus.emitUntilConsumed('recipe-event', {
|
||||
|
|
@ -294,7 +298,7 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
reload: force_reload
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('some promise failed: ', e);
|
||||
logger.error('some promise failed: ', e);
|
||||
|
||||
GlobalEventBus.emitUntilConsumed('recipe-event', {
|
||||
type: 'load-recipe-fail',
|
||||
|
|
@ -323,13 +327,13 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
};
|
||||
}
|
||||
|
||||
console.log('checking add mat', recipeRawFromMachine);
|
||||
// logger.info('checking add mat', recipeRawFromMachine);
|
||||
recipeFromMachine.set(recipeRawFromMachine);
|
||||
} else if (sub_type == 'toppings') {
|
||||
let recipeRawFromMachine = get(recipeFromMachine);
|
||||
|
||||
let topping_raw = JSON.parse(payload.payload);
|
||||
console.log('receive topping', topping_raw);
|
||||
logger.info('receive topping', topping_raw);
|
||||
|
||||
//
|
||||
recipeRawFromMachine = {
|
||||
|
|
@ -344,7 +348,7 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
recipeFromMachine.set(recipeRawFromMachine);
|
||||
} else {
|
||||
// unhandled sub type
|
||||
console.log('unhandled sub type', payload);
|
||||
logger.info('unhandled sub type', payload);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import { addNotification, notiStore } from '../stores/noti';
|
||||
import {
|
||||
|
|
@ -51,6 +52,7 @@ import { env } from '$env/dynamic/public';
|
|||
import * as semver from 'semver';
|
||||
import { WebCryptoHelper } from '../utils/crypto';
|
||||
import { GlobalEventBus } from '../utils/eventBus';
|
||||
import { handleIncomingRemoteShell } from '../stores/remoteShellStore';
|
||||
|
||||
export const messages = writable<string[]>([]);
|
||||
|
||||
|
|
@ -60,7 +62,7 @@ type WSMessage = { type: string; payload: any };
|
|||
// MAXIMUM LIMIT = 1814355
|
||||
const handlers: Record<string, (payload: any) => void> = {
|
||||
chat: (p) => messages.update((m) => [...m, p]),
|
||||
ping: (p) => console.log('ping from server'),
|
||||
ping: (p) => logger.info('ping from server'),
|
||||
recipeResponse: (p) => {
|
||||
let recipe_result = p.result;
|
||||
let recipe_request = p.request;
|
||||
|
|
@ -109,7 +111,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
},
|
||||
stream_data_chunk: (p) => {
|
||||
let current_meta = get(recipeStreamMeta);
|
||||
// console.log('current meta', current_meta);
|
||||
// logger.info('current meta', current_meta);
|
||||
if (current_meta) {
|
||||
let stream_id = current_meta.id;
|
||||
|
||||
|
|
@ -146,7 +148,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
// build overview for recipe from server
|
||||
//
|
||||
|
||||
// console.log('ending stream');
|
||||
// logger.info('ending stream');
|
||||
buildOverviewFromServer();
|
||||
|
||||
let current_meta = get(recipeStreamMeta);
|
||||
|
|
@ -201,7 +203,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
curr_mat_query.push(m);
|
||||
}
|
||||
|
||||
// // console.log('current materials: ', JSON.stringify(curr_mat_query));
|
||||
// // logger.info('current materials: ', JSON.stringify(curr_mat_query));
|
||||
materialFromServerQuery.set(curr_mat_query);
|
||||
break;
|
||||
case 'topplist':
|
||||
|
|
@ -285,7 +287,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
addNotification(`ERR:Gen Layout error: ${msg}`);
|
||||
break;
|
||||
default:
|
||||
console.log('[GenService] Received:', level, msg);
|
||||
logger.info('[GenService] Received:', level, msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -319,7 +321,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
break;
|
||||
default:
|
||||
// Handle other content notifications from sheet-service
|
||||
console.log('[Sheet] Received content:', p.content);
|
||||
logger.info('[Sheet] Received content:', p.content);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
|
@ -368,7 +370,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
|
||||
let content: RecipePrice[] = p.content ?? [];
|
||||
|
||||
console.log('get price length: ', content.length);
|
||||
logger.info('get price length: ', content.length);
|
||||
|
||||
let current_price = get(priceRecipeData);
|
||||
let lastRequestPriceInstance = get(lastRequestSheetPrice);
|
||||
|
|
@ -385,7 +387,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
|
||||
priceRecipeData.set(current_price);
|
||||
|
||||
console.log('check length', saved_product_code_to_get_from_sheet.length);
|
||||
logger.info('check length', saved_product_code_to_get_from_sheet.length);
|
||||
// set command request to stream mode so
|
||||
let request_id = uuidv4();
|
||||
|
||||
|
|
@ -450,7 +452,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
// streamingRawData.set(streamRawInstance);
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.log(`end price process error: ${e}`);
|
||||
// logger.info(`end price process error: ${e}`);
|
||||
// }
|
||||
|
||||
// break;
|
||||
|
|
@ -460,7 +462,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
heartbeat: (p) => {
|
||||
socketConnectionOfflineCount.set(0);
|
||||
socketAlreadySendHeartbeat.set(0);
|
||||
console.log('heartbeat reset offline count');
|
||||
logger.info('heartbeat reset offline count');
|
||||
},
|
||||
// Raw stream handlers for sheet data (e.g., price)
|
||||
raw_stream: (p) => {
|
||||
|
|
@ -486,6 +488,10 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
announce: (p) => {
|
||||
// Server-pushed announcement (e.g., closing maintenance)
|
||||
GlobalEventBus.emit('announce', p);
|
||||
},
|
||||
remote_shell: (p) => {
|
||||
// Server requests command execution on connected Android device
|
||||
handleIncomingRemoteShell(p);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -493,7 +499,7 @@ export async function handleIncomingMessages(raw: string, clientPrivateKey: Cryp
|
|||
const APP_VERSION = env.PUBLIC_APP_SEMVER;
|
||||
|
||||
const ack: HandshakeAck = JSON.parse(raw);
|
||||
// console.log(`[WS MSG] type=${msg.type}`, msg.payload);
|
||||
// logger.info(`[WS MSG] type=${msg.type}`, msg.payload);
|
||||
if (ack != null && ack.status === 'authenticated') {
|
||||
// has server response
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { get } from 'svelte/store';
|
||||
import { lastRequestSheetPrice } from '../stores/recipeStore';
|
||||
|
||||
|
|
@ -37,11 +38,11 @@ export function handleSheetResponseFromNoti(raw_payload: any, ref: string, count
|
|||
switch (ref) {
|
||||
case 'price':
|
||||
let price_contents: PayloadFromSheet[] = raw_payload.content;
|
||||
console.log(`price content length: ${price_contents.length}`);
|
||||
logger.info(`price content length: ${price_contents.length}`);
|
||||
let header_idx = PRICE_SHEET_DEFINITION_BY_COUNTRY[country ?? 'unknown'].get_header_idx(
|
||||
price_contents[0].header
|
||||
);
|
||||
console.log(`header idx: ${header_idx}`);
|
||||
logger.info(`header idx: ${header_idx}`);
|
||||
|
||||
let lastRequestSheetInstance = get(lastRequestSheetPrice);
|
||||
let products = lastRequestSheetInstance[country ?? 'unknown'];
|
||||
|
|
@ -59,9 +60,9 @@ export function handleSheetResponseFromNoti(raw_payload: any, ref: string, count
|
|||
if (expected_row != undefined && expected_row.cells != undefined) {
|
||||
let price_col = expected_row.cells[price_idx];
|
||||
products[curr_product_code] = price_col;
|
||||
console.log(`[handleSheetPrice][country] ${curr_product_code} --> ${price_col}`);
|
||||
logger.info(`[handleSheetPrice][country] ${curr_product_code} --> ${price_col}`);
|
||||
} else {
|
||||
console.log(
|
||||
logger.info(
|
||||
`[handleSheetPrice][country] ${curr_product_code} not found cell, ${JSON.stringify(price_rows)}`
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import type { OutMessage } from '../types/outMessage';
|
||||
import { sharedKey, socketStore } from '../stores/websocketStore';
|
||||
|
|
@ -54,10 +55,10 @@ export async function sendMessage(
|
|||
const socket = get(socketStore);
|
||||
let data = JSON.stringify(msg);
|
||||
|
||||
// console.log('try sending ', data);
|
||||
// logger.info('try sending ', data);
|
||||
|
||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||
// console.warn('WebSocket not connected, put to queue');
|
||||
// logger.warn('WebSocket not connected, put to queue');
|
||||
|
||||
// let currentQueue = get(queue);
|
||||
// if (currentQueue.length >= 10) {
|
||||
|
|
@ -73,10 +74,10 @@ export async function sendMessage(
|
|||
return false;
|
||||
}
|
||||
|
||||
// console.log('send v2', APP_VERSION, semver.satisfies(APP_VERSION, '^0.0.2'));
|
||||
// logger.info('send v2', APP_VERSION, semver.satisfies(APP_VERSION, '^0.0.2'));
|
||||
|
||||
if (semver.satisfies(APP_VERSION, '>=0.0.2')) {
|
||||
// console.log('sending secured');
|
||||
// logger.info('sending secured');
|
||||
let sharedKeyRes = get(sharedKey);
|
||||
|
||||
// do encrypt
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { browser } from '$app/environment';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
|
|
@ -108,7 +109,7 @@ export async function loadCachedAndroidRecipeExport(): Promise<AndroidRecipeExpo
|
|||
androidRecipeExportPayload.set(payload);
|
||||
return payload;
|
||||
} catch (error) {
|
||||
console.error('failed to load cached android recipe export from IndexedDB', error);
|
||||
logger.error('failed to load cached android recipe export from IndexedDB', error);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -123,7 +124,7 @@ export async function loadCachedAndroidRecipeExport(): Promise<AndroidRecipeExpo
|
|||
localStorage.removeItem(ANDROID_RECIPE_EXPORT_CACHE_KEY);
|
||||
return payload;
|
||||
} catch (error) {
|
||||
console.error('failed to load legacy android recipe export cache', error);
|
||||
logger.error('failed to load legacy android recipe export cache', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -134,7 +135,7 @@ export function saveAndroidRecipeExportPayload(payload: AndroidRecipeExportPaylo
|
|||
if (!browser) return;
|
||||
|
||||
void writeCachedPayloadToIndexedDb(payload).catch((error) => {
|
||||
console.error('failed to cache android recipe export', error);
|
||||
logger.error('failed to cache android recipe export', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -145,7 +146,7 @@ export function clearCachedAndroidRecipeExport() {
|
|||
|
||||
localStorage.removeItem(ANDROID_RECIPE_EXPORT_CACHE_KEY);
|
||||
void deleteCachedPayloadFromIndexedDb().catch((error) => {
|
||||
console.error('failed to clear android recipe export cache', error);
|
||||
logger.error('failed to clear android recipe export cache', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { sendCommandRequest, sendMessage } from '../handlers/ws_messageSender';
|
||||
import { get } from 'svelte/store';
|
||||
import { auth } from '../stores/auth';
|
||||
|
|
@ -87,14 +88,14 @@ export async function updateMenu(
|
|||
}
|
||||
|
||||
export async function addMenu(country: string, catalog: string, content: any[]): Promise<boolean> {
|
||||
console.log('[sheetService] Adding menu:', { country, catalog, content });
|
||||
logger.info('[sheetService] Adding menu:', { country, catalog, content });
|
||||
const sent = await sendCommandRequest('sheet', {
|
||||
country: country,
|
||||
catalog: catalog,
|
||||
content: content,
|
||||
param: 'add/menu'
|
||||
});
|
||||
console.log('[sheetService] Add menu sent:', sent);
|
||||
logger.info('[sheetService] Add menu sent:', sent);
|
||||
return sent;
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +141,7 @@ export async function requestListMenu(country: string, boxid?: string): Promise<
|
|||
productCodesLoading.set(true);
|
||||
setPendingProductCodesCountry(country);
|
||||
|
||||
console.log('[sheetService] Sending list_menu request for country:', country, 'boxid:', boxid);
|
||||
logger.info('[sheetService] Sending list_menu request for country:', country, 'boxid:', boxid);
|
||||
|
||||
return await sendMessage({
|
||||
type: 'list_menu',
|
||||
|
|
@ -166,7 +167,7 @@ export async function requestGenLayout(country: string): Promise<boolean> {
|
|||
|
||||
setGenLayoutGenerating();
|
||||
|
||||
console.log('[sheetService] Sending gen-layout request for country:', country);
|
||||
logger.info('[sheetService] Sending gen-layout request for country:', country);
|
||||
|
||||
return await sendMessage({
|
||||
type: 'command',
|
||||
|
|
@ -190,12 +191,12 @@ export async function requestGenLayout(country: string): Promise<boolean> {
|
|||
export async function requestSheetPrice(country: string, productCodes: string[]): Promise<boolean> {
|
||||
// Check if already sent
|
||||
if (hasSheetPriceBeenSent('price')) {
|
||||
console.warn('[sheetService] Price request already sent, skipping');
|
||||
logger.warn('[sheetService] Price request already sent, skipping');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!productCodes || productCodes.length === 0) {
|
||||
console.warn('[sheetService] No product codes to request price for');
|
||||
logger.warn('[sheetService] No product codes to request price for');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +219,7 @@ export async function requestSheetPrice(country: string, productCodes: string[])
|
|||
// Convert to array of objects (backend expects objects, not strings)
|
||||
const content = productCodes.map((code) => ({ product_code: code }));
|
||||
|
||||
console.log(
|
||||
logger.info(
|
||||
'[sheetService] Sending sheet price request for country:',
|
||||
country,
|
||||
'codes:',
|
||||
|
|
@ -253,11 +254,11 @@ export async function updateSheetPrice(
|
|||
content: { row_index: number; cells: { value: string; coord: { row: number; col: number } }[] }[]
|
||||
): Promise<boolean> {
|
||||
if (!content || content.length === 0) {
|
||||
console.warn('[sheetService] No content to update');
|
||||
logger.warn('[sheetService] No content to update');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(
|
||||
logger.info(
|
||||
'[sheetService] Updating sheet price for country:',
|
||||
country,
|
||||
'items:',
|
||||
|
|
@ -280,11 +281,11 @@ export async function addSheetPrice(
|
|||
content: { cells: string[] }[]
|
||||
): Promise<boolean> {
|
||||
if (!content || content.length === 0) {
|
||||
console.warn('[sheetService] No content to add');
|
||||
logger.warn('[sheetService] No content to add');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(
|
||||
logger.info(
|
||||
'[sheetService] Adding price rows for country:',
|
||||
country,
|
||||
'items:',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import { addNotification } from './noti';
|
||||
|
||||
|
|
@ -5,7 +6,7 @@ const adbWriter: any = writable(null);
|
|||
|
||||
async function sendToAndroid(message: any) {
|
||||
let writer: any = get(adbWriter);
|
||||
console.log('writer', writer);
|
||||
// logger.info('writer', writer);
|
||||
if (!writer) {
|
||||
addNotification('ERR:No active Android connection');
|
||||
return false;
|
||||
|
|
@ -14,18 +15,18 @@ async function sendToAndroid(message: any) {
|
|||
const encoder = new TextEncoder();
|
||||
const serializedMessage = JSON.stringify(message);
|
||||
await writer.write(encoder.encode(serializedMessage + '\n'));
|
||||
console.log('[ADB] sent', {
|
||||
type: message?.type,
|
||||
bytes: serializedMessage.length,
|
||||
productCode: message?.payload?.data?.productCode,
|
||||
batchCount: Array.isArray(message?.payload?.data) ? message.payload.data.length : undefined,
|
||||
batchProductCodes: Array.isArray(message?.payload?.data)
|
||||
? message.payload.data.map((menu: any) => menu?.productCode)
|
||||
: undefined
|
||||
});
|
||||
// logger.info('[ADB] sent', {
|
||||
// type: message?.type,
|
||||
// bytes: serializedMessage.length,
|
||||
// productCode: message?.payload?.data?.productCode,
|
||||
// batchCount: Array.isArray(message?.payload?.data) ? message.payload.data.length : undefined,
|
||||
// batchProductCodes: Array.isArray(message?.payload?.data)
|
||||
// ? message.payload.data.map((menu: any) => menu?.productCode)
|
||||
// : undefined
|
||||
// });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('write failed', error);
|
||||
logger.error('write failed', error);
|
||||
addNotification('ERR:Failed to send message to Android');
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { writable, get } from 'svelte/store';
|
||||
|
||||
export interface GenLayoutFile {
|
||||
|
|
@ -59,7 +60,7 @@ export function handleGenLayoutBatchStart(payload: {
|
|||
status: 'receiving',
|
||||
files: []
|
||||
});
|
||||
console.log('[GenLayout] Batch started:', payload.batch_id, 'total files:', payload.total_files);
|
||||
logger.info('[GenLayout] Batch started:', payload.batch_id, 'total files:', payload.total_files);
|
||||
}
|
||||
|
||||
export function handleGenLayoutFile(payload: {
|
||||
|
|
@ -96,7 +97,7 @@ export function handleGenLayoutFile(payload: {
|
|||
// Store this chunk
|
||||
tracker.parts.set(partIndex, payload.content);
|
||||
|
||||
console.log(
|
||||
logger.info(
|
||||
'[GenLayout] Received chunk:',
|
||||
partIndex + 1,
|
||||
'/',
|
||||
|
|
@ -127,7 +128,7 @@ export function handleGenLayoutFile(payload: {
|
|||
// Clean up tracker
|
||||
chunkedFiles.delete(fileIndex);
|
||||
|
||||
console.log(
|
||||
logger.info(
|
||||
'[GenLayout] Assembled chunked file:',
|
||||
fileIndex + 1,
|
||||
'/',
|
||||
|
|
@ -143,7 +144,7 @@ export function handleGenLayoutFile(payload: {
|
|||
file_index: payload.file_index
|
||||
}, payload.total_files);
|
||||
|
||||
console.log(
|
||||
logger.info(
|
||||
'[GenLayout] Received file:',
|
||||
payload.file_index + 1,
|
||||
'/',
|
||||
|
|
@ -191,7 +192,7 @@ export function handleGenLayoutBatchEnd(payload: { batch_id: string; total_files
|
|||
files: sortedFiles,
|
||||
error
|
||||
}));
|
||||
console.warn('[GenLayout] Batch incomplete:', error, sortedFiles);
|
||||
logger.warn('[GenLayout] Batch incomplete:', error, sortedFiles);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +203,7 @@ export function handleGenLayoutBatchEnd(payload: { batch_id: string; total_files
|
|||
files: sortedFiles
|
||||
}));
|
||||
|
||||
console.log('[GenLayout] Batch complete, received', sortedFiles.length, 'files');
|
||||
logger.info('[GenLayout] Batch complete, received', sortedFiles.length, 'files');
|
||||
|
||||
if (onBatchCompleteCallback) {
|
||||
onBatchCompleteCallback(sortedFiles);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export function getNotification() {
|
|||
if (first) {
|
||||
let msg_p = first.split(':');
|
||||
let msg_level_type = msg_p[0];
|
||||
let msg = msg_p[1];
|
||||
let msg = msg_p.slice(1).join('');
|
||||
|
||||
switch (msg_level_type) {
|
||||
case 'ERR':
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ export const toppingGroupFromServerQuery = writable<any>([]);
|
|||
export const latestRecipeToppingData = writable<any>([]);
|
||||
|
||||
// edit data update
|
||||
/// NOTE: Will be obsolete in future, and replace with `EventBus` style.
|
||||
/// NOTE: DEPRECATED — replaced by `GlobalEventBus` + recipe-editor-events.ts.
|
||||
/// All components now use the typed event bus pattern.
|
||||
/// Kept only to avoid breaking any external references.
|
||||
export const recipeDataEvent = writable<{
|
||||
event_type: string;
|
||||
payload: any;
|
||||
|
|
|
|||
261
src/lib/core/stores/remoteShellStore.ts
Normal file
261
src/lib/core/stores/remoteShellStore.ts
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
import { get, writable } from 'svelte/store';
|
||||
import { executeCmd, executeStreamingCmd, getAdbInstance } from '../adb/adb';
|
||||
import { sendMessage } from '../handlers/ws_messageSender';
|
||||
import { addNotification } from './noti';
|
||||
|
||||
/**
|
||||
* Represents an incoming remote-shell request from the WebSocket server
|
||||
* asking the client to execute a command on the connected Android device.
|
||||
*/
|
||||
export interface RemoteShellRequest {
|
||||
requestId: string;
|
||||
commandInput: string;
|
||||
purposeDeclaration: string;
|
||||
userInfo: any;
|
||||
status: 'pending' | 'confirmed' | 'rejected' | 'executing' | 'completed' | 'failed';
|
||||
output?: string;
|
||||
error?: string;
|
||||
exitCode?: number;
|
||||
timestamp: number;
|
||||
/** Optional timeout in seconds from the server. 0 or undefined = no timeout. */
|
||||
timeoutSeconds?: number;
|
||||
/** True if the command was aborted by timeout rather than natural completion. */
|
||||
timedOut?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive store holding the current pending/active remote-shell request.
|
||||
* null when no request is active.
|
||||
*/
|
||||
export const remoteShellRequest = writable<RemoteShellRequest | null>(null);
|
||||
|
||||
/**
|
||||
* Called by the WebSocket message handler when a 'remote-shell' message arrives.
|
||||
* If requireConfirmation is true, sets status to 'pending' — the UI will
|
||||
* prompt the user. Otherwise, immediately executes the command.
|
||||
*/
|
||||
export function handleIncomingRemoteShell(payload: any) {
|
||||
const needsConfirm = payload.requireConfirmation === true;
|
||||
const rawTimeout = payload.timeout;
|
||||
const timeoutSeconds =
|
||||
rawTimeout != null && typeof rawTimeout === 'number' && rawTimeout > 0 ? rawTimeout : undefined;
|
||||
|
||||
const req: RemoteShellRequest = {
|
||||
requestId: payload.generatedRequestId,
|
||||
commandInput: payload.commandInput,
|
||||
purposeDeclaration: payload.purposeDeclaration,
|
||||
userInfo: payload.userInfo,
|
||||
status: needsConfirm ? 'pending' : 'confirmed',
|
||||
timestamp: Date.now(),
|
||||
timeoutSeconds
|
||||
};
|
||||
|
||||
remoteShellRequest.set(req);
|
||||
|
||||
if (!needsConfirm) {
|
||||
const suffix = timeoutSeconds ? ` (timeout: ${timeoutSeconds}s)` : '';
|
||||
addNotification(`INFO:Remote shell command received – executing automatically${suffix}`);
|
||||
executeAndRespond(req);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the UI when the user confirms they want to run the command.
|
||||
*/
|
||||
export function confirmRemoteShell() {
|
||||
const req = get(remoteShellRequest);
|
||||
if (!req || req.status !== 'pending') return;
|
||||
|
||||
addNotification('INFO:Remote shell command confirmed by user');
|
||||
remoteShellRequest.set({ ...req, status: 'confirmed' });
|
||||
executeAndRespond({ ...req, status: 'confirmed' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the UI when the user rejects the command.
|
||||
*/
|
||||
export function rejectRemoteShell() {
|
||||
const req = get(remoteShellRequest);
|
||||
if (!req || req.status !== 'pending') return;
|
||||
|
||||
addNotification('WARN:Remote shell command rejected by user');
|
||||
remoteShellRequest.set(null);
|
||||
sendResponse(req.requestId, false, '', 'User rejected the command', -1, req.userInfo, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the ADB shell command and send the response back to the server.
|
||||
* Uses executeStreamingCmd with an optional timeout to handle long-running
|
||||
* commands like `logcat` that would otherwise never complete.
|
||||
*/
|
||||
async function executeAndRespond(req: RemoteShellRequest) {
|
||||
remoteShellRequest.set({ ...req, status: 'executing' });
|
||||
|
||||
// Check ADB connection
|
||||
const instance = getAdbInstance();
|
||||
if (!instance) {
|
||||
addNotification('ERR:No Android device connected for remote shell');
|
||||
remoteShellRequest.set({
|
||||
...req,
|
||||
status: 'failed',
|
||||
error: 'No Android device connected',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
sendResponse(req.requestId, false, '', 'No Android device connected', -1, req.userInfo, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Decide which execution path to use
|
||||
if (req.timeoutSeconds && req.timeoutSeconds > 0) {
|
||||
await executeWithTimeout(req);
|
||||
} else {
|
||||
await executeDirect(req);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command that is expected to complete on its own.
|
||||
* Uses the simpler executeCmd() which properly captures exitCode and stderr.
|
||||
*/
|
||||
async function executeDirect(req: RemoteShellRequest) {
|
||||
const result = await executeCmd(req.commandInput);
|
||||
const output = result.output ?? '';
|
||||
const error = result.error ?? '';
|
||||
const exitCode = result.exitCode ?? (error ? 1 : 0);
|
||||
const success = exitCode === 0;
|
||||
|
||||
addNotification(`${success ? 'INFO' : 'ERR'}:Remote shell command ${success ? 'completed' : 'failed'}`);
|
||||
|
||||
remoteShellRequest.set({
|
||||
...req,
|
||||
status: success ? 'completed' : 'failed',
|
||||
output,
|
||||
error,
|
||||
exitCode,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
sendResponse(req.requestId, success, output, error, exitCode, req.userInfo, false);
|
||||
scheduleAutoClear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command with a timeout using executeStreamingCmd().
|
||||
* The streaming variant supports abort via its cleanup function, making it
|
||||
* suitable for long-running commands like `logcat`.
|
||||
*
|
||||
* When the timeout fires, the stream is aborted and whatever output was
|
||||
* captured so far is returned with timedOut=true.
|
||||
*/
|
||||
async function executeWithTimeout(req: RemoteShellRequest) {
|
||||
const timeoutMs = req.timeoutSeconds! * 1000;
|
||||
|
||||
let output = '';
|
||||
let error = '';
|
||||
let exitCode: number | undefined;
|
||||
let timedOut = false;
|
||||
let resolved = false;
|
||||
|
||||
const cleanup = await executeStreamingCmd(req.commandInput, {
|
||||
onData: (chunk: string) => {
|
||||
output += chunk;
|
||||
},
|
||||
onError: (err: string) => {
|
||||
if (resolved) return;
|
||||
resolved = true;
|
||||
error = err;
|
||||
exitCode = 1;
|
||||
finish(req, output, error, exitCode, false);
|
||||
},
|
||||
onExit: (code: number | undefined) => {
|
||||
if (resolved) return;
|
||||
resolved = true;
|
||||
exitCode = code ?? 0;
|
||||
finish(req, output, error, exitCode, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Set the timeout timer
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (resolved) return;
|
||||
resolved = true;
|
||||
timedOut = true;
|
||||
cleanup(); // Abort the ADB stream
|
||||
addNotification(`WARN:Remote shell command timed out after ${req.timeoutSeconds}s`);
|
||||
finish(req, output, 'Command timed out after ' + req.timeoutSeconds + 's', -1, true);
|
||||
}, timeoutMs);
|
||||
|
||||
// If the command completes naturally, cancel the timeout timer
|
||||
const originalFinish = finish;
|
||||
function finish(
|
||||
r: RemoteShellRequest,
|
||||
out: string,
|
||||
err: string,
|
||||
code: number,
|
||||
timedOut: boolean
|
||||
) {
|
||||
clearTimeout(timeoutId);
|
||||
const success = !timedOut && code === 0;
|
||||
|
||||
addNotification(
|
||||
timedOut
|
||||
? 'WARN:Remote shell command timed out'
|
||||
: `${success ? 'INFO' : 'ERR'}:Remote shell command ${success ? 'completed' : 'failed'}`
|
||||
);
|
||||
|
||||
remoteShellRequest.set({
|
||||
...r,
|
||||
status: success ? 'completed' : 'failed',
|
||||
output: out,
|
||||
error: err,
|
||||
exitCode: code,
|
||||
timestamp: Date.now(),
|
||||
timedOut
|
||||
});
|
||||
|
||||
sendResponse(r.requestId, success, out, err, code, r.userInfo, timedOut);
|
||||
scheduleAutoClear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-clear the store after 8 seconds so the UI result state is visible.
|
||||
*/
|
||||
let autoClearTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function scheduleAutoClear() {
|
||||
clearTimeout(autoClearTimer);
|
||||
autoClearTimer = setTimeout(() => {
|
||||
const current = get(remoteShellRequest);
|
||||
if (current && (current.status === 'completed' || current.status === 'failed')) {
|
||||
remoteShellRequest.set(null);
|
||||
}
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the execution result back to the WebSocket server.
|
||||
*/
|
||||
async function sendResponse(
|
||||
requestId: string,
|
||||
success: boolean,
|
||||
output: string,
|
||||
error: string,
|
||||
exitCode: number,
|
||||
userInfo: any,
|
||||
timedOut: boolean
|
||||
) {
|
||||
await sendMessage({
|
||||
type: 'remote-shell-response',
|
||||
payload: {
|
||||
requestId,
|
||||
success,
|
||||
output,
|
||||
error,
|
||||
exitCode,
|
||||
userInfo,
|
||||
timestamp: Date.now(),
|
||||
...(timedOut ? { timedOut: true } : {})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { writable, get } from 'svelte/store';
|
||||
|
||||
// Catalog types
|
||||
|
|
@ -392,7 +393,7 @@ export function getPriceFromCells(
|
|||
): string | null {
|
||||
const headers = get(sheetPriceHeader)[country];
|
||||
if (!headers || headers.length === 0) {
|
||||
console.warn(`[getPriceFromCells] No header found for country: ${country}`);
|
||||
logger.warn(`[getPriceFromCells] No header found for country: ${country}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -402,16 +403,16 @@ export function getPriceFromCells(
|
|||
|
||||
// Find the column index for this price type
|
||||
const colIdx = findHeaderIndex(headers, possibleNames);
|
||||
//console.log(`[getPriceFromCells] ${country} ${priceType}: colIdx=${colIdx}, headers=`, headers, 'possibleNames=', possibleNames);
|
||||
//logger.info(`[getPriceFromCells] ${country} ${priceType}: colIdx=${colIdx}, headers=`, headers, 'possibleNames=', possibleNames);
|
||||
|
||||
if (colIdx < 0) {
|
||||
console.warn(`[getPriceFromCells] No ${priceType} column found for ${country}, tried:`, possibleNames);
|
||||
logger.warn(`[getPriceFromCells] No ${priceType} column found for ${country}, tried:`, possibleNames);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the cell with matching column index
|
||||
const priceCell = cells.find((c) => c.coord?.col === colIdx);
|
||||
//console.log(`[getPriceFromCells] Found cell for col ${colIdx}:`, priceCell);
|
||||
//logger.info(`[getPriceFromCells] Found cell for col ${colIdx}:`, priceCell);
|
||||
return priceCell?.value ?? null;
|
||||
}
|
||||
|
||||
|
|
@ -444,7 +445,7 @@ export const streamingRawData = writable<
|
|||
|
||||
// Handler: raw_stream header (e.g., raw_stream_price)
|
||||
export function handleRawStreamHeader(subtype: string, payload: any) {
|
||||
console.log(`[RawStream] Header for ${subtype}:`, payload);
|
||||
logger.info(`[RawStream] Header for ${subtype}:`, payload);
|
||||
|
||||
// Get existing stream data to preserve country from request
|
||||
const currentData = get(streamingRawData);
|
||||
|
|
@ -473,13 +474,13 @@ export function handleRawStreamHeader(subtype: string, payload: any) {
|
|||
|
||||
// Handler: raw_stream chunk (e.g., raw_stream_chunk_price)
|
||||
export function handleRawStreamChunk(subtype: string, payload: any) {
|
||||
console.log(`[RawStream] Chunk ${payload.idx} for ${subtype}, raw length:`, payload.raw?.length);
|
||||
logger.info(`[RawStream] Chunk ${payload.idx} for ${subtype}, raw length:`, payload.raw?.length);
|
||||
|
||||
const currentData = get(streamingRawData);
|
||||
const streamData = currentData[subtype];
|
||||
|
||||
if (!streamData || streamData.request_id !== payload.request_id) {
|
||||
console.warn(`[RawStream] Chunk received for unknown stream: ${subtype}`);
|
||||
logger.warn(`[RawStream] Chunk received for unknown stream: ${subtype}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -494,7 +495,7 @@ export function handleRawStreamChunk(subtype: string, payload: any) {
|
|||
rawParts: [...(streamData.rawParts || []), payload.raw]
|
||||
}
|
||||
}));
|
||||
console.log(`[RawStream] Accumulated chunk ${payload.idx} for ${subtype}`);
|
||||
logger.info(`[RawStream] Accumulated chunk ${payload.idx} for ${subtype}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -511,18 +512,18 @@ export function handleRawStreamChunk(subtype: string, payload: any) {
|
|||
}
|
||||
}));
|
||||
|
||||
console.log(`[RawStream] Chunk for ${subtype}: +${contentArray.length} items`);
|
||||
logger.info(`[RawStream] Chunk for ${subtype}: +${contentArray.length} items`);
|
||||
}
|
||||
|
||||
// Handler: raw_stream end (e.g., raw_stream_end_price)
|
||||
export function handleRawStreamEnd(subtype: string, payload: any) {
|
||||
console.log(`[RawStream] End payload for ${subtype}:`, payload);
|
||||
logger.info(`[RawStream] End payload for ${subtype}:`, payload);
|
||||
|
||||
const currentData = get(streamingRawData);
|
||||
const streamData = currentData[subtype];
|
||||
|
||||
if (!streamData || streamData.request_id !== payload.request_id) {
|
||||
console.warn(`[RawStream] End received for unknown stream: ${subtype}`);
|
||||
logger.warn(`[RawStream] End received for unknown stream: ${subtype}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -533,13 +534,13 @@ export function handleRawStreamEnd(subtype: string, payload: any) {
|
|||
let chunks = streamData.chunks || [];
|
||||
if (streamData.rawParts && streamData.rawParts.length > 0) {
|
||||
const fullRawJson = streamData.rawParts.join('');
|
||||
console.log(
|
||||
logger.info(
|
||||
`[RawStream] Joining ${streamData.rawParts.length} raw parts, total length: ${fullRawJson.length}`
|
||||
);
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(fullRawJson);
|
||||
console.log(`[RawStream] Parsed combined raw data, keys:`, Object.keys(parsed));
|
||||
logger.info(`[RawStream] Parsed combined raw data, keys:`, Object.keys(parsed));
|
||||
|
||||
// Extract content from nested structure: { payload: { content: [...] } }
|
||||
const content = parsed?.payload?.content || parsed?.content || parsed || [];
|
||||
|
|
@ -547,14 +548,14 @@ export function handleRawStreamEnd(subtype: string, payload: any) {
|
|||
|
||||
// Log first item to see actual structure
|
||||
if (chunks.length > 0) {
|
||||
console.log(`[RawStream] First content item:`, JSON.stringify(chunks[0]).substring(0, 200));
|
||||
logger.info(`[RawStream] First content item:`, JSON.stringify(chunks[0]).substring(0, 200));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[RawStream] Failed to parse combined raw JSON:`, e);
|
||||
logger.error(`[RawStream] Failed to parse combined raw JSON:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[RawStream] End for ${subtype}: total ${chunks.length} items, country: ${country}`);
|
||||
logger.info(`[RawStream] End for ${subtype}: total ${chunks.length} items, country: ${country}`);
|
||||
|
||||
if (subtype === 'price') {
|
||||
processSheetPriceData(country, streamData.header || [], chunks);
|
||||
|
|
@ -572,11 +573,11 @@ export function handleRawStreamEnd(subtype: string, payload: any) {
|
|||
|
||||
// Process and store sheet price data
|
||||
function processSheetPriceData(country: string, header: string[], chunks: any[]) {
|
||||
console.log(`[SheetPrice] Processing data for ${country}:`, {
|
||||
logger.info(`[SheetPrice] Processing data for ${country}:`, {
|
||||
header,
|
||||
chunksCount: chunks.length
|
||||
});
|
||||
console.log(`[SheetPrice] Sample chunk:`, chunks[0]);
|
||||
logger.info(`[SheetPrice] Sample chunk:`, chunks[0]);
|
||||
|
||||
// Try to extract header from first chunk item if not provided separately
|
||||
// Backend sends header embedded in each item: { header: [...], key: "...", payload: [...] }
|
||||
|
|
@ -585,7 +586,7 @@ function processSheetPriceData(country: string, header: string[], chunks: any[])
|
|||
const firstChunk = chunks[0];
|
||||
if (firstChunk?.header && Array.isArray(firstChunk.header)) {
|
||||
effectiveHeader = firstChunk.header;
|
||||
console.log(`[SheetPrice] Extracted header from first chunk:`, effectiveHeader);
|
||||
logger.info(`[SheetPrice] Extracted header from first chunk:`, effectiveHeader);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -595,13 +596,13 @@ function processSheetPriceData(country: string, header: string[], chunks: any[])
|
|||
...data,
|
||||
[country]: effectiveHeader
|
||||
}));
|
||||
console.log(`[SheetPrice] Saved header for ${country}:`, effectiveHeader);
|
||||
logger.info(`[SheetPrice] Saved header for ${country}:`, effectiveHeader);
|
||||
}
|
||||
|
||||
// Find column indices dynamically from header
|
||||
// product_code header is typically "ProductCode" or similar
|
||||
const productCodeIdx = findHeaderIndex(effectiveHeader, ['ProductCode', 'Product_Code', 'product_code', 'Code']);
|
||||
console.log(`[SheetPrice] productCodeIdx from header:`, productCodeIdx, 'header:', effectiveHeader);
|
||||
logger.info(`[SheetPrice] productCodeIdx from header:`, productCodeIdx, 'header:', effectiveHeader);
|
||||
|
||||
const priceByProductCode: Record<string, GristCell[]> = {};
|
||||
// Track ALL rows per product code (for duplicates)
|
||||
|
|
@ -609,7 +610,7 @@ function processSheetPriceData(country: string, header: string[], chunks: any[])
|
|||
|
||||
for (const row of chunks) {
|
||||
if (!row) {
|
||||
console.log(`[SheetPrice] Row is null/undefined`);
|
||||
logger.info(`[SheetPrice] Row is null/undefined`);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -655,7 +656,7 @@ function processSheetPriceData(country: string, header: string[], chunks: any[])
|
|||
let cells: GristCell[] = row.cells || row;
|
||||
|
||||
if (!Array.isArray(cells)) {
|
||||
console.log(`[SheetPrice] Unknown row structure:`, row);
|
||||
logger.info(`[SheetPrice] Unknown row structure:`, row);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -695,18 +696,18 @@ function processSheetPriceData(country: string, header: string[], chunks: any[])
|
|||
}
|
||||
}));
|
||||
|
||||
console.log(
|
||||
logger.info(
|
||||
`[SheetPrice] Processed ${Object.keys(priceByProductCode).length} prices for ${country}`
|
||||
);
|
||||
console.log(`[SheetPrice] Sample product codes:`, Object.keys(priceByProductCode).slice(0, 5));
|
||||
logger.info(`[SheetPrice] Sample product codes:`, Object.keys(priceByProductCode).slice(0, 5));
|
||||
// Log duplicates info
|
||||
const duplicates = Object.entries(allRowsByProductCode).filter(([_, rows]) => rows.length > 1);
|
||||
if (duplicates.length > 0) {
|
||||
console.log(`[SheetPrice] Found ${duplicates.length} product codes with duplicate rows:`, duplicates.slice(0, 3));
|
||||
logger.info(`[SheetPrice] Found ${duplicates.length} product codes with duplicate rows:`, duplicates.slice(0, 3));
|
||||
}
|
||||
if (chunks.length > 0 && Object.keys(priceByProductCode).length > 0) {
|
||||
const sampleKey = Object.keys(priceByProductCode)[0];
|
||||
console.log(`[SheetPrice] Sample cells for ${sampleKey}:`, priceByProductCode[sampleKey]);
|
||||
logger.info(`[SheetPrice] Sample cells for ${sampleKey}:`, priceByProductCode[sampleKey]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -745,7 +746,7 @@ export function addGeneratedProductCode(code: string) {
|
|||
newSet.add(code);
|
||||
return newSet;
|
||||
});
|
||||
console.log('[sheetStore] Added generated code:', code);
|
||||
logger.info('[sheetStore] Added generated code:', code);
|
||||
}
|
||||
|
||||
const PRODUCT_CODES_STORAGE_KEY = 'supra_product_codes';
|
||||
|
|
@ -757,7 +758,7 @@ let pendingProductCodesCountry = '';
|
|||
// Set pending country when making a request
|
||||
export function setPendingProductCodesCountry(country: string) {
|
||||
pendingProductCodesCountry = country;
|
||||
console.log('[sheetStore] Pending product codes country:', country);
|
||||
logger.info('[sheetStore] Pending product codes country:', country);
|
||||
}
|
||||
|
||||
// Load product codes from localStorage for specific country
|
||||
|
|
@ -769,19 +770,19 @@ export function loadProductCodesFromCache(country?: string): boolean {
|
|||
// Only load if country matches (or no country filter specified)
|
||||
if (data.codes && Array.isArray(data.codes)) {
|
||||
if (country && data.country && data.country !== country) {
|
||||
console.log('[sheetStore] Cache is for different country:', data.country, '!= requested:', country);
|
||||
logger.info('[sheetStore] Cache is for different country:', data.country, '!= requested:', country);
|
||||
// Clear the store for different country
|
||||
existingProductCodes.set(new Set());
|
||||
return false;
|
||||
}
|
||||
existingProductCodes.set(new Set(data.codes));
|
||||
currentProductCodesCountry = data.country || '';
|
||||
console.log('[sheetStore] Loaded', data.codes.length, 'product codes from cache for', data.country || 'unknown');
|
||||
logger.info('[sheetStore] Loaded', data.codes.length, 'product codes from cache for', data.country || 'unknown');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[sheetStore] Failed to load from cache:', e);
|
||||
logger.warn('[sheetStore] Failed to load from cache:', e);
|
||||
}
|
||||
// Clear store if no valid cache
|
||||
existingProductCodes.set(new Set());
|
||||
|
|
@ -792,13 +793,13 @@ export function loadProductCodesFromCache(country?: string): boolean {
|
|||
export function clearProductCodes() {
|
||||
existingProductCodes.set(new Set());
|
||||
currentProductCodesCountry = '';
|
||||
console.log('[sheetStore] Cleared product codes');
|
||||
logger.info('[sheetStore] Cleared product codes');
|
||||
}
|
||||
|
||||
export function handleListMenuResponse(payload: { codes: string[]; country?: string }) {
|
||||
// Use pending country if not in payload
|
||||
const country = payload.country || pendingProductCodesCountry;
|
||||
console.log('[sheetStore] Received list_menu_response for', country, ':', payload.codes?.length, 'codes');
|
||||
logger.info('[sheetStore] Received list_menu_response for', country, ':', payload.codes?.length, 'codes');
|
||||
|
||||
if (payload && payload.codes) {
|
||||
existingProductCodes.set(new Set(payload.codes));
|
||||
|
|
@ -814,9 +815,9 @@ export function handleListMenuResponse(payload: { codes: string[]; country?: str
|
|||
timestamp: Date.now()
|
||||
})
|
||||
);
|
||||
console.log('[sheetStore] Saved', payload.codes.length, 'product codes to cache for', country);
|
||||
logger.info('[sheetStore] Saved', payload.codes.length, 'product codes to cache for', country);
|
||||
} catch (e) {
|
||||
console.warn('[sheetStore] Failed to save to cache:', e);
|
||||
logger.warn('[sheetStore] Failed to save to cache:', e);
|
||||
}
|
||||
}
|
||||
productCodesLoading.set(false);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { logger } from '$lib/core/utils/logger';
|
||||
import { browser } from '$app/environment';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { get, writable } from 'svelte/store';
|
||||
|
|
@ -55,7 +56,7 @@ export function waitForOpenSocket(timeoutMs = 8000): Promise<WebSocket | null> {
|
|||
|
||||
export async function connectToWebsocket(id_token?: string) {
|
||||
if (browser) {
|
||||
// console.log('connecting to ', env.PUBLIC_WSS);
|
||||
// logger.info('connecting to ', env.PUBLIC_WSS);
|
||||
try {
|
||||
if (socket != null) {
|
||||
return;
|
||||
|
|
@ -89,7 +90,7 @@ export async function connectToWebsocket(id_token?: string) {
|
|||
sendAuthInfoInterval = setInterval(async () => {
|
||||
if (get(sharedKey)) {
|
||||
// Debug: check if auth_data has uid
|
||||
console.log('[WS Auth] Sending auth info with:', {
|
||||
logger.info('[WS Auth] Sending auth info with:', {
|
||||
uid: auth_data?.uid,
|
||||
name: auth_data?.displayName,
|
||||
email: auth_data?.email
|
||||
|
|
@ -109,7 +110,7 @@ export async function connectToWebsocket(id_token?: string) {
|
|||
}
|
||||
}, 3000);
|
||||
}
|
||||
console.log(socket);
|
||||
logger.info(socket);
|
||||
|
||||
// heartbeat 10s
|
||||
socketCheck = setInterval(async () => {
|
||||
|
|
@ -140,12 +141,12 @@ export async function connectToWebsocket(id_token?: string) {
|
|||
socketAlreadySendHeartbeat.set(heartbeat_count + 1);
|
||||
} else {
|
||||
// may send reconnect
|
||||
console.log('check on socket health', socket);
|
||||
logger.info('check on socket health', socket);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
if (auth.currentUser && socket == null) {
|
||||
console.log('try reconnect websocket ...');
|
||||
logger.info('try reconnect websocket ...');
|
||||
// retry again
|
||||
reconnectTimeout = setTimeout(async () => {
|
||||
id_token = await auth.currentUser?.getIdToken(true);
|
||||
|
|
@ -166,7 +167,7 @@ export async function connectToWebsocket(id_token?: string) {
|
|||
clearInterval(socketCheck);
|
||||
|
||||
if (auth.currentUser && !socket) {
|
||||
console.log('try reconnect websocket ...');
|
||||
logger.info('try reconnect websocket ...');
|
||||
// retry again
|
||||
reconnectTimeout = setTimeout(async () => {
|
||||
id_token = await auth.currentUser?.getIdToken(true);
|
||||
|
|
@ -176,13 +177,13 @@ export async function connectToWebsocket(id_token?: string) {
|
|||
});
|
||||
|
||||
socket.addEventListener('error', (e) => {
|
||||
// console.log('WebSocket error: ', e);
|
||||
// logger.info('WebSocket error: ', e);
|
||||
socketStore.set(null);
|
||||
sharedKey.set(null);
|
||||
});
|
||||
} catch (socket_error: any) {
|
||||
if (ENABLE_WS_DEBUG) {
|
||||
console.error('WS_ERR', socket_error);
|
||||
logger.error('WS_ERR', socket_error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,4 +78,17 @@ export type OutMessage =
|
|||
override_file?: string;
|
||||
user_info: any;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: 'remote-shell-response';
|
||||
payload: {
|
||||
requestId: string;
|
||||
success: boolean;
|
||||
output: string;
|
||||
error: string;
|
||||
exitCode: number;
|
||||
userInfo: any;
|
||||
timestamp: number;
|
||||
timedOut?: boolean;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
91
src/lib/core/utils/logger.ts
Normal file
91
src/lib/core/utils/logger.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Lightweight logger that suppresses output in production.
|
||||
*
|
||||
* In development (npm run dev): logs are shown with a prefix tag.
|
||||
* In production (npm run build): all console output is suppressed
|
||||
* so logs are never exposed to the browser inspector.
|
||||
*
|
||||
* Usage:
|
||||
* import { logger } from '$lib/core/utils/logger';
|
||||
* logger.info('hello', someVar);
|
||||
* logger.error('failed', err);
|
||||
*/
|
||||
|
||||
type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
||||
|
||||
const LOG_LEVEL_RANK: Record<LogLevel, number> = {
|
||||
trace: -1,
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warn: 2,
|
||||
error: 3
|
||||
};
|
||||
|
||||
const ENV_LOG_LEVEL: LogLevel =
|
||||
(import.meta.env.PUBLIC_APP_LOG_LEVEL as LogLevel) || 'info';
|
||||
const IS_DEV = import.meta.env.DEV;
|
||||
|
||||
function shouldLog(level: LogLevel): boolean {
|
||||
return IS_DEV && LOG_LEVEL_RANK[level] >= LOG_LEVEL_RANK[ENV_LOG_LEVEL];
|
||||
}
|
||||
|
||||
function formatArgs(level: LogLevel, args: any[]): any[] {
|
||||
return [`[${level.toUpperCase()}]`, ...args];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract caller location (file:line) from a stack trace.
|
||||
* Stack frame format: " at method (file:///path/file.ts:10:20)"
|
||||
* The 3rd frame (index 2) is the caller of the method that created the Error.
|
||||
*/
|
||||
function getCallerLocation(): string {
|
||||
try {
|
||||
const stack = new Error().stack?.split('\n');
|
||||
if (!stack || stack.length < 3) return 'unknown';
|
||||
// stack[0] = "Error", stack[1] = " at getCallerLocation (...)",
|
||||
// stack[2] = " at Logger.trace (...)" — skip both
|
||||
const caller = stack[2]?.trim();
|
||||
// Extract path from common formats:
|
||||
// "at method (file:///path/file.ts:10:20)" or "at file:///path/file.ts:10:20"
|
||||
const match = caller.match(/(?:\(|at\s+)([^(]+?\.\w+):(\d+)/);
|
||||
if (match) return `${match[1]}:${match[2]}`;
|
||||
return caller || 'unknown';
|
||||
} catch {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
class Logger {
|
||||
trace(...args: any[]) {
|
||||
if (shouldLog('trace')) {
|
||||
const location = getCallerLocation();
|
||||
console.debug(`[TRACE:${location}]`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
debug(...args: any[]) {
|
||||
if (shouldLog('debug')) {
|
||||
console.debug(...formatArgs('debug', args));
|
||||
}
|
||||
}
|
||||
|
||||
info(...args: any[]) {
|
||||
if (shouldLog('info')) {
|
||||
console.info(...formatArgs('info', args));
|
||||
}
|
||||
}
|
||||
|
||||
warn(...args: any[]) {
|
||||
if (shouldLog('warn')) {
|
||||
console.warn(...formatArgs('warn', args));
|
||||
}
|
||||
}
|
||||
|
||||
error(...args: any[]) {
|
||||
if (shouldLog('error')) {
|
||||
console.error(...formatArgs('error', args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger();
|
||||
Loading…
Add table
Add a link
Reference in a new issue