fix: slow get recipe memo
- fix: cannot adb command from blocking suspicious cmd - change: logging to logtape lib - change: adb payload handler format to send payload size as header for exact match payload Signed-off-by: pakintada@gmail.com <Pakin>
This commit is contained in:
parent
aa6414b1cc
commit
3cda8aa60b
12 changed files with 494 additions and 174 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -65,6 +65,7 @@
|
|||
"@dnd-kit-svelte/svelte": "0.1.3",
|
||||
"@dnd-kit/abstract": "^0.2.4",
|
||||
"@dnd-kit/helpers": "^0.2.4",
|
||||
"@logtape/logtape": "^2.2.2",
|
||||
"@tanstack/match-sorter-utils": "^8.19.4",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
|
|
@ -77,6 +78,7 @@
|
|||
"animejs": "^4.3.6",
|
||||
"firebase": "^12.14.0",
|
||||
"idb": "^8.0.3",
|
||||
"logtape": "https://github.com/dahlia/logtape.git",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"semver": "^7.8.4",
|
||||
"usb": "^2.17.0",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">import { logger } from '$lib/core/utils/logger';
|
||||
<script lang="ts">
|
||||
import { logger } from '$lib/core/utils/logger';
|
||||
|
||||
import { Button, type ButtonVariant } from './ui/button';
|
||||
import * as Card from './ui/card/index';
|
||||
|
|
@ -104,7 +105,9 @@
|
|||
if (instance) {
|
||||
try {
|
||||
// bypass
|
||||
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass');
|
||||
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass', {
|
||||
bypass: true
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
let result = await adb.executeCmd(
|
||||
|
|
@ -129,7 +132,9 @@
|
|||
|
||||
try {
|
||||
// bypass
|
||||
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass');
|
||||
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass', {
|
||||
bypass: true
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
setTimeout(async () => {
|
||||
|
|
|
|||
|
|
@ -18,10 +18,14 @@ import { WritableStream } from '@yume-chan/stream-extra';
|
|||
import { env } from '$env/dynamic/public';
|
||||
import { get } from 'svelte/store';
|
||||
import { adbConnectionStatus } from '../stores/adbConnectionStore';
|
||||
import { browser } from '$app/environment';
|
||||
import AdbWorker from '$lib/workers/androidPayload.worker?worker';
|
||||
|
||||
let _connectionPromise: Promise<boolean> | null = null;
|
||||
let syncConnection: any = null;
|
||||
|
||||
let worker: Worker | undefined;
|
||||
|
||||
/**
|
||||
* Centralized connection function that all pages and components should use.
|
||||
*
|
||||
|
|
@ -392,8 +396,8 @@ function sanitizeAdbCommand(command: string): string {
|
|||
return command;
|
||||
}
|
||||
|
||||
export async function executeCmd(command: string) {
|
||||
command = sanitizeAdbCommand(command);
|
||||
export async function executeCmd(command: string, { bypass = false }) {
|
||||
command = !bypass ? sanitizeAdbCommand(command) : command;
|
||||
let instance = getAdbInstance();
|
||||
|
||||
if (!instance) {
|
||||
|
|
@ -444,7 +448,7 @@ export async function executeCmd(command: string) {
|
|||
export async function goToMachineHome() {
|
||||
if (!getAdbInstance()) return;
|
||||
try {
|
||||
await executeCmd('input keyevent KEYCODE_HOME');
|
||||
await executeCmd('input keyevent KEYCODE_HOME', { bypass: true });
|
||||
} catch (e) {
|
||||
console.error('[goToMachineHome] error', e);
|
||||
}
|
||||
|
|
@ -687,52 +691,80 @@ async function connectToAndroidServer(maxRetries = 5) {
|
|||
const writer = stream.writable.getWriter();
|
||||
const reader = stream.readable.getReader();
|
||||
|
||||
if (browser) {
|
||||
worker = new AdbWorker();
|
||||
|
||||
worker.onmessage = (e) => {
|
||||
if (e.data.type === 'DATA') {
|
||||
const raw = e.data.payload;
|
||||
handleAdbPayload(new TextDecoder().decode(raw));
|
||||
}
|
||||
};
|
||||
|
||||
// logger.info("prep post ");
|
||||
// worker.postMessage({ stream }, [stream]);
|
||||
}
|
||||
|
||||
// const reader = stream.readable.getReader();
|
||||
|
||||
// logger.info('checking on writer ', writer);
|
||||
adbWriter.set(writer);
|
||||
|
||||
if (writer) {
|
||||
addNotification('INFO:Enable Brewing Mode T on machine');
|
||||
|
||||
const textDecoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
// decode chunk
|
||||
buffer += textDecoder.decode(value, { stream: true });
|
||||
// Transfer ONLY the buffer, not the stream
|
||||
// Type assertion (as any) fixes the overload error
|
||||
(worker as any).postMessage({ type: 'CHUNK', payload: value }, [value.buffer]);
|
||||
}
|
||||
})();
|
||||
|
||||
let lines = buffer.split('\n');
|
||||
// const textDecoder = new TextDecoder();
|
||||
// let buffer = '';
|
||||
|
||||
// save potential incomplete
|
||||
buffer = lines.pop() ?? '';
|
||||
// (async () => {
|
||||
// try {
|
||||
// while (true) {
|
||||
// const { value, done } = await reader.read();
|
||||
// if (done) break;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim() === '') continue;
|
||||
// // decode chunk
|
||||
// buffer += textDecoder.decode(value, { stream: true });
|
||||
|
||||
GlobalEventBus.emit('adb:raw-payload', line);
|
||||
handleAdbPayload(line);
|
||||
}
|
||||
// let lines = buffer.split('\n');
|
||||
|
||||
// GlobalEventBus.emit('adb:raw-payload', new TextDecoder().decode(value));
|
||||
// handleAdbPayload(new TextDecoder().decode(value));
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('read error', e);
|
||||
if (isRecoverableError(e)) {
|
||||
void connectToAndroidServer();
|
||||
}
|
||||
} finally {
|
||||
adbWriter.set(null);
|
||||
addNotification('WARN:Brewing Mode T Offline ...');
|
||||
reader.cancel().catch(() => {});
|
||||
}
|
||||
})();
|
||||
return;
|
||||
} else {
|
||||
addNotification('WARN:Brewing Mode T unavailable');
|
||||
// // save potential incomplete
|
||||
// buffer = lines.pop() ?? '';
|
||||
|
||||
// for (const line of lines) {
|
||||
// if (line.trim() === '') continue;
|
||||
|
||||
// GlobalEventBus.emit('adb:raw-payload', line);
|
||||
// handleAdbPayload(line);
|
||||
// }
|
||||
|
||||
// // GlobalEventBus.emit('adb:raw-payload', new TextDecoder().decode(value));
|
||||
// // handleAdbPayload(new TextDecoder().decode(value));
|
||||
// }
|
||||
// } catch (e) {
|
||||
// logger.error('read error', e);
|
||||
// if (isRecoverableError(e)) {
|
||||
// void connectToAndroidServer();
|
||||
// }
|
||||
// } finally {
|
||||
// adbWriter.set(null);
|
||||
// addNotification('WARN:Brewing Mode T Offline ...');
|
||||
// reader.cancel().catch(() => {});
|
||||
// }
|
||||
// })();
|
||||
return;
|
||||
} else {
|
||||
addNotification('WARN:Brewing Mode T unavailable');
|
||||
|
||||
if (attempt < maxRetries - 1) {
|
||||
const delay = Math.min(500 * Math.pow(2, attempt) + Math.random() * 500, 5000);
|
||||
|
|
|
|||
|
|
@ -347,8 +347,73 @@ async function handleAdbPayload(raw_payload: string) {
|
|||
|
||||
recipeFromMachine.set(recipeRawFromMachine);
|
||||
} else {
|
||||
if (sub_type == null) {
|
||||
try {
|
||||
let big_recipe = JSON.parse(JSON.stringify(payload.payload));
|
||||
logger.info(`get large recipe keys: ${Object.keys(big_recipe)}`);
|
||||
|
||||
let recipes = JSON.parse(big_recipe?.recipes);
|
||||
let materials = JSON.parse(big_recipe?.materials);
|
||||
let toppings = JSON.parse(big_recipe?.toppings);
|
||||
|
||||
let updated = {
|
||||
Recipe01: recipes,
|
||||
MaterialSetting: materials,
|
||||
Topping: toppings
|
||||
};
|
||||
|
||||
recipeFromMachine.set(updated);
|
||||
|
||||
// =====================================
|
||||
//
|
||||
// Build Overview
|
||||
//
|
||||
|
||||
let queries = [];
|
||||
for (let rp of recipes) {
|
||||
queries.push({
|
||||
productCode: rp.productCode ?? '<not set>',
|
||||
name: rp.name ? rp.name : (rp.otherName ?? '<not set>'),
|
||||
description: rp.description
|
||||
? rp.description
|
||||
: (rp.otherDescription ?? '<not set>'),
|
||||
tags: buildTags(rp),
|
||||
status: getMenuStatus(rp.MenuStatus)
|
||||
});
|
||||
}
|
||||
|
||||
let cached_query_from_machine = get(recipeFromMachineQuery);
|
||||
|
||||
cached_query_from_machine = {
|
||||
...cached_query_from_machine,
|
||||
recipe: queries
|
||||
};
|
||||
|
||||
recipeFromMachineQuery.set(cached_query_from_machine);
|
||||
|
||||
// =====================================
|
||||
//
|
||||
// Send finish signal
|
||||
|
||||
GlobalEventBus.emitUntilConsumed('recipe-event', {
|
||||
type: 'load-recipe',
|
||||
status: 'end',
|
||||
reload: true
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('some promise failed: ', e);
|
||||
|
||||
GlobalEventBus.emitUntilConsumed('recipe-event', {
|
||||
type: 'load-recipe-fail',
|
||||
status: 'end',
|
||||
reload: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
logger.info(`unhandled sub type: %${sub_type}%`);
|
||||
}
|
||||
|
||||
// unhandled sub type
|
||||
logger.info('unhandled sub type', payload);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -529,7 +529,7 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
heartbeat: (p) => {
|
||||
socketConnectionOfflineCount.set(0);
|
||||
socketAlreadySendHeartbeat.set(0);
|
||||
logger.info('heartbeat reset offline count');
|
||||
// logger.info('heartbeat reset offline count');
|
||||
},
|
||||
// Raw stream handlers for sheet data (e.g., price)
|
||||
raw_stream: (p) => {
|
||||
|
|
|
|||
|
|
@ -1,91 +1,216 @@
|
|||
/**
|
||||
* Lightweight logger that suppresses output in production.
|
||||
* Structured logger powered by LogTape (@logtape/logtape).
|
||||
*
|
||||
* 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.
|
||||
* Two tiers of logging:
|
||||
* - **logger** — general-purpose logging (suppressed in production for security)
|
||||
* - Dev: `trace`, `debug`, `info`, `warn`, `error` (configurable via PUBLIC_APP_LOG_LEVEL)
|
||||
* - Prod: `info` by default (or what PUBLIC_APP_LOG_LEVEL says)
|
||||
* - **inspector** — error-only logging, ALWAYS active (never suppressed).
|
||||
* Use for security-critical events that must appear in the browser
|
||||
* developer console (e.g. auth failures, API errors).
|
||||
*
|
||||
* Usage:
|
||||
* import { logger } from '$lib/core/utils/logger';
|
||||
* logger.info('hello', someVar);
|
||||
* logger.error('failed', err);
|
||||
* API (100 % backward compatible):
|
||||
* logger.info('message', arg1, …)
|
||||
* logger.error('Failed:', err)
|
||||
* inspector.error('auth failure', uid) // always visible
|
||||
*/
|
||||
import { configureSync, getConsoleSink, withFilter, getLogger } from '@logtape/logtape';
|
||||
|
||||
type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
||||
// ---------------------------------------------------------------------------
|
||||
// Environment helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LOG_LEVEL_RANK: Record<LogLevel, number> = {
|
||||
trace: -1,
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warn: 2,
|
||||
error: 3
|
||||
const IS_DEV = import.meta.env.DEV;
|
||||
const ENV_LOG_LEVEL = (import.meta.env.PUBLIC_APP_LOG_LEVEL as string) || (IS_DEV ? 'trace' : 'info');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LogTape configuration (singleton guard for Vite HMR / config reload)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let _configured = false;
|
||||
function ensureConfigured() {
|
||||
if (_configured) return;
|
||||
_configured = true;
|
||||
configureSync({
|
||||
sinks: {
|
||||
console: getConsoleSink(),
|
||||
errorsOnly: withFilter(getConsoleSink(), 'error'),
|
||||
},
|
||||
loggers: [
|
||||
{
|
||||
category: ['supra-app'],
|
||||
sinks: ['console'],
|
||||
lowestLevel: ENV_LOG_LEVEL as 'trace' | 'debug' | 'info' | 'warning' | 'error' | 'fatal',
|
||||
},
|
||||
{
|
||||
category: ['supra-app', 'inspector'],
|
||||
sinks: ['errorsOnly'],
|
||||
lowestLevel: 'error',
|
||||
},
|
||||
{
|
||||
category: ['logtape', 'meta'],
|
||||
sinks: ['console'],
|
||||
lowestLevel: 'error',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal LogTape loggers (lazy-init)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getBaseLogger() {
|
||||
ensureConfigured();
|
||||
return getLogger(['supra-app']);
|
||||
}
|
||||
|
||||
function getInspectorLogger() {
|
||||
ensureConfigured();
|
||||
return getLogger(['supra-app', 'inspector']);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function isRecord(v: unknown): v is Record<string, unknown> {
|
||||
return typeof v === 'object' && v !== null && !(v instanceof Error) && !Array.isArray(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert variadic args into a flat message string + optional extras.
|
||||
*
|
||||
* Patterns handled:
|
||||
* logger.info('text') → message, no props
|
||||
* logger.info('text', obj) → message, obj as props if obj is a plain Record
|
||||
* logger.info('text', error) → error overload
|
||||
* logger.info('text', arg1, arg2, …) → concatenated message
|
||||
*/
|
||||
function normalize(
|
||||
args: unknown[],
|
||||
): { message: string; error?: Error; properties?: Record<string, unknown> } {
|
||||
if (args.length === 0) return { message: '' };
|
||||
|
||||
const first = args[0];
|
||||
if (args.length === 1) {
|
||||
if (first instanceof Error) return { message: first.message, error: first };
|
||||
return { message: String(first) };
|
||||
}
|
||||
|
||||
if (args.length === 2 && isRecord(args[1])) {
|
||||
return { message: String(first), properties: args[1] };
|
||||
}
|
||||
|
||||
// error overload: logger.warn(msg, error) / logger.error('msg', err)
|
||||
if (args.length === 2 && args[1] instanceof Error) {
|
||||
return { message: String(first), error: args[1] as Error };
|
||||
}
|
||||
|
||||
// multi-arg: concatenate
|
||||
return {
|
||||
message: args.map((a) => (typeof a === 'object' ? safeStringify(a) : String(a))).join(' '),
|
||||
};
|
||||
}
|
||||
|
||||
function safeStringify(v: unknown): string {
|
||||
if (v instanceof Error) return `${v.name}: ${v.message}`;
|
||||
try {
|
||||
return JSON.stringify(v);
|
||||
} catch {
|
||||
return String(v);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const logger = {
|
||||
trace(...args: unknown[]) {
|
||||
const { message } = normalize(args);
|
||||
getBaseLogger().trace(message);
|
||||
},
|
||||
|
||||
debug(...args: unknown[]) {
|
||||
const { message, properties } = normalize(args);
|
||||
if (properties) {
|
||||
getBaseLogger().debug(message, properties);
|
||||
} else {
|
||||
getBaseLogger().debug(message);
|
||||
}
|
||||
},
|
||||
|
||||
info(...args: unknown[]) {
|
||||
const { message, properties } = normalize(args);
|
||||
if (properties) {
|
||||
getBaseLogger().info(message, properties);
|
||||
} else {
|
||||
getBaseLogger().info(message);
|
||||
}
|
||||
},
|
||||
|
||||
warn(...args: unknown[]) {
|
||||
const { message, error, properties } = normalize(args);
|
||||
if (properties) {
|
||||
getBaseLogger().warn(message, properties);
|
||||
} else if (error) {
|
||||
getBaseLogger().warn(message, error);
|
||||
} else {
|
||||
getBaseLogger().warn(message);
|
||||
}
|
||||
},
|
||||
|
||||
error(...args: unknown[]) {
|
||||
const { message, error, properties } = normalize(args);
|
||||
if (error && properties) {
|
||||
getBaseLogger().error(error, properties);
|
||||
} else if (error) {
|
||||
getBaseLogger().error(error);
|
||||
} else if (properties) {
|
||||
getBaseLogger().error(message, properties);
|
||||
} else {
|
||||
getBaseLogger().error(message);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
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.
|
||||
* Inspector logger — always active, error-only, never suppressed.
|
||||
* Messages are written via the same LogTape infrastructure but on a
|
||||
* separate category (supra-app → inspector) whose sink is hard-filtered to
|
||||
* `error` / `fatal` so security-sensitive events are always visible.
|
||||
*/
|
||||
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);
|
||||
export const inspector = {
|
||||
error(...args: unknown[]) {
|
||||
const { message, error, properties } = normalize(args);
|
||||
if (error && properties) {
|
||||
getInspectorLogger().error(error, properties);
|
||||
} else if (error) {
|
||||
getInspectorLogger().error(error);
|
||||
} else if (properties) {
|
||||
getInspectorLogger().error(message, properties);
|
||||
} else {
|
||||
getInspectorLogger().error(message);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
debug(...args: any[]) {
|
||||
if (shouldLog('debug')) {
|
||||
console.debug(...formatArgs('debug', args));
|
||||
warn(...args: unknown[]) {
|
||||
const { message, error } = normalize(args);
|
||||
// inspector only logs errors; escalate to error level
|
||||
if (error) {
|
||||
getInspectorLogger().error(error);
|
||||
} else if (message) {
|
||||
getInspectorLogger().error(message);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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();
|
||||
info(...args: unknown[]) {
|
||||
const { message } = normalize(args);
|
||||
// promote to error so it passes the filter
|
||||
if (message) getInspectorLogger().error(message);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
70
src/lib/core/utils/vite-logger.ts
Normal file
70
src/lib/core/utils/vite-logger.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Custom Vite logger that routes Vite's build/dev-server messages
|
||||
* through our LogTape-powered logger for consistent formatting.
|
||||
*
|
||||
* Usage in vite.config.ts:
|
||||
* import { createViteLogger } from './src/lib/core/utils/vite-logger';
|
||||
* export default defineConfig({
|
||||
* customLogger: createViteLogger(),
|
||||
* // ...
|
||||
* });
|
||||
*/
|
||||
import type { LogOptions, LogErrorOptions } from 'vite';
|
||||
import type { Logger } from 'vite';
|
||||
import { logger } from './logger';
|
||||
|
||||
export function createViteLogger(): Logger {
|
||||
const warnedMessages = new Set<string>();
|
||||
|
||||
return {
|
||||
get hasWarned() {
|
||||
return warnedMessages.size > 0;
|
||||
},
|
||||
|
||||
info(msg: string, options?: LogOptions) {
|
||||
const prefix = options?.environment ? `[${options.environment}]` : '';
|
||||
if (prefix) {
|
||||
logger.info(`${prefix} ${msg}`);
|
||||
} else if (options?.timestamp) {
|
||||
logger.info(`[vite] ${msg}`);
|
||||
} else {
|
||||
logger.info(msg);
|
||||
}
|
||||
},
|
||||
|
||||
warn(msg: string, options?: LogOptions) {
|
||||
warnedMessages.add(msg);
|
||||
const prefix = options?.environment ? `[${options.environment}]` : '';
|
||||
if (prefix) {
|
||||
logger.warn(`${prefix} ${msg}`);
|
||||
} else {
|
||||
logger.warn(msg);
|
||||
}
|
||||
},
|
||||
|
||||
warnOnce(msg: string, options?: LogOptions) {
|
||||
if (warnedMessages.has(msg)) return;
|
||||
this.warn(msg, options);
|
||||
},
|
||||
|
||||
error(msg: string, options?: LogErrorOptions) {
|
||||
const prefix = options?.environment ? `[${options.environment}]` : '';
|
||||
const err = options?.error;
|
||||
const fullMsg = prefix ? `${prefix} ${msg}` : msg;
|
||||
if (err && err instanceof Error) {
|
||||
logger.error(fullMsg, err);
|
||||
} else {
|
||||
logger.error(fullMsg);
|
||||
}
|
||||
},
|
||||
|
||||
clearScreen() {
|
||||
// Let Vite handle screen clearing itself
|
||||
},
|
||||
|
||||
hasErrorLogged(_error: Error | import('rollup').RollupError): boolean {
|
||||
// Don't keep error state — always re-report
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
63
src/lib/workers/androidPayload.worker.ts
Normal file
63
src/lib/workers/androidPayload.worker.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/// <reference lib="webworker" />
|
||||
import { logger } from '$lib/core/utils/logger';
|
||||
let chunks: Uint8Array[] = [];
|
||||
let totalBytes = 0;
|
||||
let expectedLength = -1;
|
||||
|
||||
self.onmessage = async (e) => {
|
||||
if (e.data.type === 'CHUNK') {
|
||||
const chunk = e.data.payload;
|
||||
chunks.push(chunk);
|
||||
totalBytes += chunk.length;
|
||||
|
||||
// Loop to process complete messages
|
||||
while (true) {
|
||||
// 1. Try to get length header (need 4 bytes)
|
||||
if (expectedLength === -1 && totalBytes >= 4) {
|
||||
// Need a flat buffer just to read the header
|
||||
const headerBuf = concatChunks(chunks, 4);
|
||||
expectedLength = new DataView(headerBuf.buffer).getInt32(0, false);
|
||||
|
||||
// Remove header bytes from our chunks array
|
||||
removeBytes(4);
|
||||
}
|
||||
|
||||
// 2. Try to get payload
|
||||
if (expectedLength !== -1 && totalBytes >= expectedLength) {
|
||||
const payload = concatChunks(chunks, expectedLength);
|
||||
removeBytes(expectedLength);
|
||||
|
||||
self.postMessage({ type: 'DATA', payload }, [payload.buffer]);
|
||||
|
||||
expectedLength = -1; // Reset for next message
|
||||
} else {
|
||||
break; // Wait for more data
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Helper: Concatenate only what we need
|
||||
function concatChunks(chunks: Uint8Array[], length: number): Uint8Array {
|
||||
const result = new Uint8Array(length);
|
||||
let offset = 0;
|
||||
for (let i = 0; i < chunks.length && offset < length; i++) {
|
||||
const chunk = chunks[i];
|
||||
const toCopy = Math.min(chunk.length, length - offset);
|
||||
result.set(chunk.slice(0, toCopy), offset);
|
||||
// If we only used part of the chunk, we keep the rest in the array
|
||||
if (toCopy < chunk.length) {
|
||||
chunks[i] = chunk.slice(toCopy);
|
||||
} else {
|
||||
chunks.shift();
|
||||
i--;
|
||||
}
|
||||
offset += toCopy;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper: Update total count
|
||||
function removeBytes(n: number) {
|
||||
totalBytes -= n;
|
||||
}
|
||||
|
|
@ -121,6 +121,7 @@
|
|||
try {
|
||||
addNotification('WARN:Load recipe from app memories ...');
|
||||
|
||||
logger.info('sending request get recipe');
|
||||
sendToAndroid({
|
||||
type: 'get_recipe',
|
||||
payload: {}
|
||||
|
|
@ -290,11 +291,14 @@
|
|||
if (instance) {
|
||||
try {
|
||||
// bypass
|
||||
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass');
|
||||
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass', {
|
||||
bypass: true
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
let result = await adb.executeCmd(
|
||||
'am start -n com.forthvending.coffeemain/com.forthvending.coffeemain.MainActivity'
|
||||
'am start -n com.forthvending.coffeemain/com.forthvending.coffeemain.MainActivity',
|
||||
{ bypass: true }
|
||||
);
|
||||
// if (result?.output) {
|
||||
// toast.success('Open app success!');
|
||||
|
|
@ -315,13 +319,15 @@
|
|||
|
||||
try {
|
||||
// bypass
|
||||
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass');
|
||||
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass', {
|
||||
bypass: true
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
// bypass
|
||||
await adb.executeCmd('input tap 336 795');
|
||||
await adb.executeCmd('input tap 336 795', { bypass: true });
|
||||
} catch (e) {}
|
||||
}, 3000);
|
||||
}
|
||||
|
|
@ -846,57 +852,6 @@
|
|||
recipeAutoLoadAttempted = true;
|
||||
void loadBrewDataFromConnectedAdb();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const brewAppStatusInterval = setInterval(async () => {
|
||||
// schedule status from .brew_web_status.log
|
||||
let inst = adb.getAdbInstance();
|
||||
if (inst && devRecipe && !recipeLoading) {
|
||||
await adb.executeCmd(
|
||||
'tail -n 1 /sdcard/coffeevending/.brew_web_status.log > /sdcard/coffeevending/.brew_web_status.latest.log'
|
||||
);
|
||||
|
||||
const latestStatusPath = env.PUBLIC_BREW_WEB_LATEST_STATUS;
|
||||
if (!latestStatusPath) return;
|
||||
|
||||
let brew_status_log = await adb.pull(latestStatusPath);
|
||||
if (brew_status_log) {
|
||||
let latest_log = brew_status_log;
|
||||
|
||||
if (brew_status !== latest_log) {
|
||||
brew_status = latest_log;
|
||||
|
||||
// noti from machine
|
||||
if (latest_log.includes('!!!')) {
|
||||
let log = latest_log.split('!!!');
|
||||
let noti_cfg = log[1];
|
||||
|
||||
if (noti_cfg.includes('=')) {
|
||||
let noti_level = noti_cfg.split('=')[1];
|
||||
|
||||
let spl = log[0].split(':');
|
||||
let pure_msg = spl[spl.length - 1];
|
||||
|
||||
// case special message
|
||||
if (pure_msg.includes('starting retry process')) {
|
||||
// is waiting/idle process
|
||||
pure_msg = 'Wait for brewing';
|
||||
}
|
||||
|
||||
addNotification(`${noti_level}:${pure_msg}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (inst && !devRecipe) {
|
||||
// try again
|
||||
// await startFetchRecipeFromMachine();
|
||||
}
|
||||
}, 1000);
|
||||
return () => {
|
||||
clearInterval(brewAppStatusInterval);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="mx-8 flex">
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ESNext", "DOM", "WebWorker"]
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import devtoolsJson from 'vite-plugin-devtools-json';
|
|||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { createViteLogger } from './src/lib/core/utils/vite-logger';
|
||||
|
||||
export default defineConfig({
|
||||
customLogger: createViteLogger(),
|
||||
plugins: [tailwindcss(), sveltekit(), devtoolsJson()],
|
||||
ssr: {
|
||||
noExternal: ['@dnd-kit/core', '@dnd-kit/sortable']
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue