Commit 8fb3c00c authored by Skye Yu's avatar Skye Yu

refactor: init

parent 4786339d
.idea
.vscode
dist
node_modules
\ No newline at end of file
dist
node_modules
.idea
\ No newline at end of file
const os = require('os');
module.exports = {
printWidth: 140,
singleQuote: true,
tabWidth: 2,
trailingComma: "none", // 确保数组末尾没有尾随逗号
printWidth: 140, // 换行长度
semi: true, // 使用分号
singleQuote: true, // 使用单引号
tabWidth: 2, // tab长度
trailingComma: 'none', // 确保数组末尾没有尾随逗号
bracketSpacing: true, // 数组括号内的空格(根据需要调整)
proseWrap: "never", // 确保不会因文档换行规则影响数组
proseWrap: 'never', // 确保不会因文档换行规则影响数组
endOfLine: os.platform() === 'win32' ? 'crlf' : 'lf' // 同一换行符格式,可选值: "lf", "crlf", "auto"
};
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jensen Page</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/App.tsx"></script>
</body>
</html>
import { Jensen } from './src/utils/jensen';
export default Jensen;
import { Jensen as Jen } from './src/jensen';
import { Logger } from './src/utils/utils';
export type DeviceInfo = {
sn: string;
versionNumber: number;
......@@ -28,53 +25,67 @@ export type ScheduleInfo = {
platform: string;
};
declare class JensenType {
constructor(log?: typeof Logger);
onconnect: Function;
onmessage: (data: any) => void | null;
connect: () => Promise<void>;
export enum Level {
debug = 'debug',
info = 'info',
error = 'error'
}
export type Log = {
level: Level;
module: string;
procedure: string;
message: string;
time: number;
};
export type Logger = {
messages: Log[];
consoleOutput: boolean;
info(module: string, procedure: string, message: string): void;
debug(module: string, procedure: string, message: string): void;
error(module: string, procedure: string, message: string): void;
_append(level: Level, module: string, procedure: string, message: string): void;
_print(log: Log): void;
filter(module: string, procedure: string): void;
enableConsoleOutput(): void;
disableConsoleOutput(): void;
peek(rows: number): void;
search(module: string, procedure: string, keyword: string): Log[];
};
declare class Jensen {
constructor(log?: Logger);
init: () => Promise<boolean>;
connect: () => Promise<void>;
onconnect: () => void;
disconnect: () => void;
ondisconnect?: () => void;
isStopConnectionCheck: boolean;
isConnected: () => boolean;
getModel: () => string;
tryconnect: (disableOnConnect?: boolean) => Promise<boolean>;
getDeviceInfo: (time?: number) => Promise<DeviceInfo>;
listFiles: (time?: number) => Promise<FileInfo[]>;
tryconnect: (disableOnConnect?: boolean) => Promise<boolean>;
getFile: (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void, onprogress?: (size: number) => void) => void;
getFileBlock: (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void) => Promise<ReturnStruct['common']>;
requestFirmwareUpgrade: (
vn: number,
length: number,
time?: number
) => Promise<{
result: 'accepted' | 'fail';
}>;
requestFirmwareUpgrade: (vn: number, length: number, time?: number) => Promise<{ result: 'accepted' | 'fail' }>;
beginBNC: (time?: number) => Promise<ReturnStruct['common']>;
endBNC: (time?: number) => Promise<ReturnStruct['common']>;
setTime: (date: Date, timeout?: number) => Promise<ReturnStruct['common']>;
deleteFile: (fileName: string) => Promise<{ result: string }>;
deleteFile: (fileName: string) => Promise<{ result: 'failed' | 'success' | 'not-exists' }>;
uploadFirmware: (data: number[], seconds?: number, onProgress?: (cur: number, total: number) => void) => Promise<ReturnStruct['common']>;
getTime: (time?: number) => Promise<{
time: string;
}>;
getSettings: (time?: number) => Promise<{
autoRecord: boolean;
autoPlay: boolean;
notification?: boolean;
} | null>;
getTime: (time?: number) => Promise<{ time: string }>;
getSettings: (time?: number) => Promise<{ autoRecord: boolean; autoPlay: boolean; notification?: boolean } | null>;
setAutoRecord: (enable: boolean, time?: number) => Promise<ReturnStruct['common']>;
setAutoPlay: (enable: boolean, time?: number) => Promise<ReturnStruct['common']>;
isConnected: () => boolean;
setNotification: (state: boolean, time?: number) => Promise<ReturnStruct['common']>;
ondisconnect?: Function;
isStopConnectionCheck: boolean;
getRecordingFile: () => Promise<{
recording: null | string;
createTime: string;
createDate: string;
}>;
getRecordingFile: () => Promise<{ recording: null | string; createTime: string; createDate: string }>;
getCardInfo: (seconds?: number) => Promise<{ used: number; capacity: number; status: string }>;
formatCard: (seconds?: number) => Promise<ReturnStruct['common']>;
factoryReset: (seconds?: number) => Promise<ReturnStruct['common']>;
restoreFactorySettings: (seconds?: number) => Promise<ReturnStruct['common']>;
getModel: () => string;
getFileCount: (seconds?: number) => Promise<{ count: number } | null>;
recordTestStart: (type: number, seconds?: number) => Promise<ReturnStruct['common']>;
recordTestEnd: (type: number, seconds?: number) => Promise<ReturnStruct['common']>;
......@@ -84,7 +95,4 @@ declare class JensenType {
sendScheduleInfo: (info: ScheduleInfo) => Promise<ReturnStruct['common']>;
}
//@ts-ignore
const Jensen = Jen as new (log?: typeof Logger) => JensenType;
export default Jensen;
export = Jensen;
This diff is collapsed.
{
"name": "jensen",
"version": "1.0.1",
"description": "webUSB Tool",
"scripts": {
"prettier": "prettier --write ."
},
"keywords": [
"WebUSB"
],
"author": "Skye",
"license": "ISC",
"devDependencies": {
"prettier": "^3.3.3"
}
"name": "jensen",
"version": "1.0.0",
"description": "WebUSB Helper",
"author": "Skye Yu",
"license": "ISC",
"main": "index.js",
"types": "jensen.d.ts",
"keywords": [
"WebUSB"
],
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prettier": "prettier --write ."
},
"devDependencies": {
"@types/node": "^22.10.2",
"@types/react-dom": "^19.0.2",
"@vitejs/plugin-react-swc": "^3.7.2",
"prettier": "^3.3.3",
"typescript": "^5.7.2",
"vite": "^6.0.3"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
import { createRoot } from 'react-dom/client';
import { Home } from '.';
createRoot(document.getElementById('root') as HTMLDivElement).render(<Home />);
* {
margin: 0;
padding: 0;
}
import { useEffect } from 'react';
import Jensen from '..';
import './index.css';
const jensen = new Jensen();
export function Home() {
useEffect(() => {
jensen.connect();
jensen.onconnect = () => {
console.log('connect successfully');
};
}, []);
return <button onClick={() => jensen.connect()}>Click to connect</button>;
}
import { Logger, Logger as internalLogger, getTimeStr, shortcutKeys } from './utils/utils';
import { Logger as internalLogger, formatTime, shortcutKeys } from './utils';
const INVAILD = 0x00;
const QUERY_DEVICE_INFO = 0x01;
const QUERY_DEVICE_TIME = 0x02;
const SET_DEVICE_TIME = 0x03;
......@@ -22,31 +23,35 @@ const GET_FILE_BLOCK = 0x0d;
const FACTORY_RESET = 0xf00b;
const TEST_SN_WRITE = 0xf007;
const RECORD_TEST_START = 0xf008; // 录音测试开始
const RECORD_TEST_END = 0xf009; // 录音测试结束
const COMMAND_NAMES = [
'invalid-0',
'get-device-info',
'get-device-time',
'set-device-time',
'get-file-list',
'transfer-file',
'get-file-count',
'delete-file',
'request-firmware-upgrade',
'firmware-upload',
'read card info',
'format card',
'get recording file',
'restore factory settings',
'device msg test',
'bnc demo test',
'get-settings',
'set-settings',
'get file block',
'factory reset'
];
const RECORD_TEST_START = 0xf008;
const RECORD_TEST_END = 0xf009;
const COMMAND_NAMES = {
[INVAILD]: 'invalid-0',
[QUERY_DEVICE_INFO]: 'get-device-info',
[QUERY_DEVICE_TIME]: 'get-device-time',
[SET_DEVICE_TIME]: 'set-device-time',
[QUERY_FILE_LIST]: 'get-file-list',
[TRANSFER_FILE]: 'transfer-file',
[QUERY_FILE_COUNT]: 'get-file-count',
[DELETE_FILE]: 'delete-file',
[REQUEST_FIRMWARE_UPGRADE]: 'request-firmware-upgrade',
[FIRMWARE_UPLOAD]: 'firmware-upload',
[READ_CARD_INFO]: 'read card info',
[FORMAT_CARD]: 'format card',
[GET_RECORDING_FILE]: 'get recording file',
[RESTORE_FACTORY_SETTINGS]: 'restore factory settings',
[SCHEDULE_INFO]: 'send meeting schedule info',
[DEVICE_MSG_TEST]: 'device msg test',
[BNC_DEMO_TEST]: 'bnc demo test',
[GET_SETTINGS]: 'get-settings',
[SET_SETTINGS]: 'set-settings',
[GET_FILE_BLOCK]: 'get file block',
[FACTORY_RESET]: 'factory reset',
[TEST_SN_WRITE]: 'test sn write',
[RECORD_TEST_START]: 'record test start',
[RECORD_TEST_END]: 'record test end'
};
function Jensen(log) {
const Logger = log || internalLogger;
......@@ -75,7 +80,6 @@ function Jensen(log) {
this.onreceive = null;
const RECV_BUFF_SIZE = 51200;
let worker = null;;
const _check_conn_status = () => {
if (device?.opened === false) {
......@@ -146,10 +150,6 @@ function Jensen(log) {
Logger.error('jensen', 'init', 'webusb not supported');
return;
}
if (window.worker) {
worker = new Worker('./utils/worker.js');
Worker.postMessage('Hello, World.');
}
navigator.usb.onconnect = function (e) {
self.tryconnect();
};
......@@ -299,7 +299,6 @@ function Jensen(log) {
if (device)
device.transferIn(2, RECV_BUFF_SIZE).then((r) => {
Logger.save?.('jensen', 'tryReceive', r?.data);
worker.postMessage(r);
receive(r);
});
};
......@@ -317,37 +316,18 @@ function Jensen(log) {
// 做一个回调,怎么样找到它呢?
blocks.push(result.data);
tryReceive();
if (self.decodeTimeout) clearTimeout(self.decodeTimeout);
self.decodeTimeout = setTimeout(function () {
tryDecode();
}, self.timewait);
tryDecode();
if (self.onreceive) {
try {
self.onreceive(totalBytes);
} catch (e) {}
} else {
// if (self.decodeTimeout) clearTimeout(self.decodeTimeout);
// self.decodeTimeout = setTimeout(function () {
// tryDecode();
// }, self.timewait);
}
};
// 持续解码
const continueDecode = (block) => {
const buff = new ArrayBuffer(RECV_BUFF_SIZE * 2);
let bview = new Uint8Array(buff);
for (let k = 0; k < block.byteLength; k++) {
bview[k] = block.getInt8(k);
}
let result = null;
try {
result = decodeMessage(bview, 0, block.byteLength);
} catch (err) {
console.error('ci decode', err);
}
sendNext();
const msg = result.message;
return result && msg.id === TRANSFER_FILE ? msg.body : null;
};
const tryDecode = function () {
// 一个容器,比单独任意一个小包要大一点儿,好像还差一点儿
let stime = new Date();
......@@ -567,15 +547,18 @@ Jensen.prototype.getTime = async function (seconds) {
Jensen.prototype.getFileCount = async function (seconds) {
return this.send(new Command(QUERY_FILE_COUNT), seconds);
};
Jensen.prototype.factoryReset = async function (seconds) {
if (this.versionNumber < 327705) return null;
return this.send(new Command(FACTORY_RESET), seconds);
};
Jensen.prototype.restoreFactorySettings = async function (seconds) {
if (this.model === 'hidock-h1e' && this.versionNumber < 393476) return null;
if (this.model === 'hidock-h1' && this.versionNumber < 327944) return null;
return this.send(new Command(RESTORE_FACTORY_SETTINGS).body([0x01, 0x02, 0x03, 0x04]), seconds);
};
Jensen.prototype.listFiles = async function () {
let tag = 'filelist';
if (this[tag] != null) return null;
......@@ -707,7 +690,7 @@ Jensen.prototype.setTime = async function (time, seconds) {
return this.send(new Command(SET_DEVICE_TIME).body(this.to_bcd(str)), seconds);
};
Jensen.prototype.streaming = async function (filename, length, ondata, onprogress) {
Jensen.prototype.streaming = async function (filename, length, ondata, onprogress) {
if (typeof length != 'number') throw new Error('parameter `length` required');
if (length <= 0) throw new Error('parameter `length` must greater than zero');
......@@ -846,18 +829,22 @@ Jensen.prototype.getSettings = async function (seconds) {
}
return this.send(new Command(GET_SETTINGS), seconds);
};
Jensen.prototype.setAutoRecord = function (enable, seconds) {
if (this.versionNumber < 327714) return { result: false };
return this.send(new Command(SET_SETTINGS).body([0, 0, 0, enable ? 1 : 2]), seconds);
};
Jensen.prototype.setAutoPlay = function (enable, seconds) {
if (this.versionNumber < 327714) return { result: false };
return this.send(new Command(SET_SETTINGS).body([0, 0, 0, 0, 0, 0, 0, enable ? 1 : 2]), seconds);
};
Jensen.prototype.setNotification = function (enable, seconds) {
if (this.versionNumber < 327714) return { result: false };
return this.send(new Command(SET_SETTINGS).body([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, enable ? 1 : 2]), seconds);
};
Jensen.prototype.setBluetoothPromptPlay = function (enable, seconds) {
// h1e 6.1.4 393476
// h1 5.1.4 327940
......@@ -870,10 +857,12 @@ Jensen.prototype.getCardInfo = function (seconds) {
if (this.versionNumber < 327733) return null;
return this.send(new Command(READ_CARD_INFO), seconds);
};
Jensen.prototype.formatCard = function (seconds) {
if (this.versionNumber < 327733) return null;
return this.send(new Command(FORMAT_CARD).body([0x01, 0x02, 0x03, 0x04]), seconds);
};
Jensen.prototype.getRecordingFile = function (seconds) {
if (this.versionNumber < 327733) return null;
return this.send(new Command(GET_RECORDING_FILE), seconds);
......@@ -929,8 +918,8 @@ Jensen.prototype.sendScheduleInfo = function (info) {
let startDate = new Array(7).fill(0);
let endDate = new Array(7).fill(0);
if (info.startDate && info.endDate) {
startDate = this.to_bcd(getTimeStr(info.startDate));
endDate = this.to_bcd(getTimeStr(info.endDate));
startDate = formatTime(info.startDate);
endDate = formatTime(info.endDate);
}
const reserved = 0x00; // 预留
const body = [...startDate, ...endDate, reserved, ...codes];
......@@ -999,7 +988,11 @@ Jensen.registerHandler(GET_SETTINGS, (msg) => {
let r1 = msg.body[3];
let r2 = msg.body[7];
let r4 = msg.body[15];
let rst = { autoRecord: r1 === 1, autoPlay: r2 === 1, bluetoothTone: !(r4 === 1) };
let rst = {
autoRecord: r1 === 1,
autoPlay: r2 === 1,
bluetoothTone: !(r4 === 1)
};
if (msg.body.length >= 12) {
let r3 = msg.body[11] === 1;
rst['notification'] = r3;
......
const Level = {
const level = {
debug: 'debug',
info: 'info',
error: 'error'
......@@ -8,42 +8,29 @@ export const Logger = {
messages: [],
consoleOutput: true,
info(module, procedure, message) {
this._append(Level.info, module, procedure, message);
this._append(level.info, module, procedure, message);
},
debug(module, procedure, message) {
this._append(Level.debug, module, procedure, message);
this._append(level.debug, module, procedure, message);
},
error(module, procedure, message) {
this._append(Level.error, module, procedure, message);
this._append(level.error, module, procedure, message);
},
_append(level, module, procedure, message) {
let log = {
const log = {
level,
module,
procedure,
message: String(message),
message: message,
time: new Date().getTime()
};
this.messages.push(log);
if (this.consoleOutput) {
this._print(log);
}
if (this.consoleOutput) this._print(log);
if (this.messages.length > 15000) this.messages.shift();
},
_print(log) {
let time = new Date(log.time);
console.info(
'[' +
(log.level === 'error' ? 'x' : '*') +
'][' +
time.toLocaleString() +
'](' +
log.module +
' - ' +
log.procedure +
') ' +
log.message
);
console.info(`[${log.level === 'error' ? 'x' : '*'}][${time.toLocaleString()}](${log.module} - ${log.procedure}) ${log.message}`);
},
filter(module, procedure) {
return this.messages.filter((i) => i.module === module && i.procedure === procedure);
......@@ -72,7 +59,7 @@ export const Logger = {
}
};
export const getTimeStr = (date: Date) => {
export const formatTime = (date) => {
let str =
date.getFullYear() +
'-0' +
......@@ -89,7 +76,7 @@ export const getTimeStr = (date: Date) => {
return str;
};
const keyMap: { [key: string]: number } = {
const keyMap = {
CUSTOM_1: 0x01,
A: 0x04,
B: 0x05,
......@@ -144,15 +131,15 @@ const HotKeyBuilder = {
HotKeyBuilder.guiKey = true;
return HotKeyBuilder;
},
withKey: (key: string) => {
withKey: (key) => {
if (HotKeyBuilder.keys.length >= 2) throw new Error('exceed max key bindings');
HotKeyBuilder.keys.push(HotKeyBuilder.__mapping(key));
return HotKeyBuilder;
},
__mapping: (key: string) => {
__mapping: (key) => {
return keyMap[key];
},
build: (REG_8: number = 0x03, REG_9: number = 0x00) => {
build: (REG_8 = 0x03, REG_9 = 0x00) => {
let key1 = REG_9;
if (HotKeyBuilder.control) key1 |= 0x01 << 0;
if (HotKeyBuilder.shift) key1 |= 0x01 << 1;
......@@ -177,7 +164,7 @@ const HotKeyBuilder = {
}
};
const enterKeyCode = (answer: 0 | 1 = 0, hangup: 0 | 1 = 0, reject: 0 | 1 = 0, micMute: 0 | 1 = 0) => {
const enterKeyCode = (answer = 0, hangup = 0, reject = 0, micMute = 0) => {
let code = 0;
if (answer) code |= 0x01 << 0;
if (hangup) code |= 0x01 << 1;
......@@ -189,7 +176,7 @@ const enterKeyCode = (answer: 0 | 1 = 0, hangup: 0 | 1 = 0, reject: 0 | 1 = 0, m
const emptyCodes = [0, 0, 0, 0, 0, 0, 0, 0];
export const shortcutKeys = {
'zoom': {
zoom: {
Windows: [
enterKeyCode(0, 1),
...HotKeyBuilder.build(0x04, 0x01),
......@@ -206,7 +193,7 @@ export const shortcutKeys = {
],
Linux: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...emptyCodes]
},
'teams': {
teams: {
Windows: [
enterKeyCode(),
...HotKeyBuilder.withControl().withShift().withKey('A').build(),
......@@ -228,7 +215,7 @@ export const shortcutKeys = {
Mac: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...HotKeyBuilder.withGuiKey().withKey('D').build()],
Linux: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...emptyCodes]
},
'webex': {
webex: {
Windows: [
enterKeyCode(),
...HotKeyBuilder.withControl().withShift().withKey('C').build(),
......@@ -245,22 +232,22 @@ export const shortcutKeys = {
],
Linux: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...emptyCodes]
},
'feishu': {
feishu: {
Windows: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...HotKeyBuilder.withControl().withShift().withKey('D').build()],
Mac: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...HotKeyBuilder.withGuiKey().withShift().withKey('D').build()],
Linux: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...emptyCodes]
},
'lark': {
lark: {
Windows: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...HotKeyBuilder.withControl().withShift().withKey('D').build()],
Mac: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...HotKeyBuilder.withGuiKey().withShift().withKey('D').build()],
Linux: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...emptyCodes]
},
'weChat': {
weChat: {
Windows: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...emptyCodes],
Mac: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...emptyCodes],
Linux: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...emptyCodes]
},
'line': {
line: {
Windows: [
enterKeyCode(0, 1, 1),
...emptyCodes,
......@@ -288,7 +275,7 @@ export const shortcutKeys = {
],
Linux: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...emptyCodes]
},
'slack': {
slack: {
Windows: [
enterKeyCode(),
...emptyCodes,
......@@ -299,7 +286,7 @@ export const shortcutKeys = {
Mac: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...HotKeyBuilder.withGuiKey().withShift().withKey('SPACE').build()],
Linux: [enterKeyCode(), ...emptyCodes, ...emptyCodes, ...emptyCodes, ...emptyCodes]
},
'discord': {
discord: {
Windows: [
enterKeyCode(),
HotKeyBuilder.withControl().withKey('ENTER').build(),
......
// worker.js
self.onmessage = function (e) {
console.log('Worker received data:', e.data);
// const result = fibonacci(e.data);
// self.postMessage(result);
};
\ No newline at end of file
This diff is collapsed.
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: { open: true, hmr: true, port: 6300 }
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment