|
|
ʵսʹÓÃ×ÀÃæ¿ØÖÆWebSocket
- /**
- * WebSocket ¸ß¼¶¼¼Çɺϼ¯ - AIWROK °æ
- *
- * ws-multi-pool Á¬½Ó³Ø / JetSocket / Binary Transfer Protocol /
- * Android WebSocket ʵս / ´óÎļþ·ÖƬÉÏ´«£©
- *
- * °üº¬ 8 ´óÌØÊâģʽ£º
- * 1. ACK È·ÈÏ + ³¬Ê±ÖØ´« ¡ª¡ª ÏûÏ¢²»¶ª£¨·Â TCP ¿É¿¿Í¶µÝ£©
- * 2. ÇëÇó-ÏìÓ¦Åä¶Ô ¡ª¡ª ·¢Ö¸ÁîµÈ»ØÓ¦£¨·Â HTTP ÇëÇóģʽ£¬´ø³¬Ê±£©
- * 3. ÏûÏ¢ÐòÁкŠ+ ¶ÏÏß²¹³¥ ¡ª¡ª ÖØÁ¬ºó²¹·¢È±Ê§ÏûÏ¢
- * 4. ÀëÏßÏûÏ¢¶ÓÁÐ ¡ª¡ª ¶ÏÏß»º´æ£¬ÖØÁ¬ºó×Ô¶¯ flush
- * 5. ·ÖƬ´«Êä´ó½ØÍ¼ ¡ª¡ª 64KB ·Ö¿é£¬±ÜÃâ´óÖ¡×èÈû
- * 6. ×ÔÊÊÓ¦ÐÄÌø ¡ª¡ª ÈõÍøÀ³¤ÐÄÌø¼ä¸ô£¬Ê¡µç·À¶Ï
- * 7. ±³Ñ¹¿ØÖÆ ¡ª¡ª ·¢ËÍËÙÂÊÏÞÖÆ£¬·ÀÖ¹ÑÍû¶Ô¶Ë
- * 8. ƵµÀ¶©ÔÄģʽ ¡ª¡ª ¶¯Ì¬¶©ÔÄ/È¡Ïû¶©ÔÄʼþÁ÷
- *
- * AIWROK ÔÉú WebSocket API£º
- * setSocketID(String) / event(onopen,onmsg,onerr,onclose) / connet(String) / send(Object) / close()
- * ¶¨Ê±Æ÷£ºrunTime.setTimeout(fn, ms) / runTime.stopTimeout(id)
- */
- // ============================================================
- // ÅäÖÃ
- // ============================================================
- var CONFIG = {
- // WebSocket ·þÎñÆ÷µÄ¾ÖÓòÍø IP µØÖ·£¬Ðè¸ÄΪÄã PC µÄʵ¼Ê IP
- // ²é¿´·½Ê½£ºWindows ÏÂÔËÐÐ ipconfig ¡ú ÕÒ IPv4 µØÖ·£¨Í¨³£ÊÇ 192.168.x.x£©
- // ⚠️ ÊÖ»úºÍ PC ±ØÐëÁ¬½Óµ½Í¬Ò»¸ö WiFi Íø¶Î
- SERVER_IP: "192.168.10.104",
- // WebSocket ·þÎñÆ÷¼àÌýµÄ¶Ë¿ÚºÅ£¬ÐèÓë·þÎñÆ÷¶Ë±£³ÖÒ»ÖÂ
- // ±¾ÏîÄ¿ÅäÌ×µÄ WebSocket²âÊÔ·þÎñÆ÷.py ĬÈÏʹÓà 8886
- SERVER_PORT: "8886",
- // WebSocket Á¬½ÓµÄ URL ·¾¶£¨¸ù·¾¶Îª "/"£©
- // Èç¹û·þÎñÆ÷ÔÚÌØ¶¨Â·¾¶ÉÏÌṩ·þÎñ£¨Èç "/ws"¡¢"/api/v1/socket"£©£¬Ð޸Ĵ˴¦
- // ×îÖÕÁ¬½ÓµØÖ·¸ñʽ: ws://{SERVER_IP}:{SERVER_PORT}{WS_PATH}
- WS_PATH: "/",
- // 1. ACK È·ÈÏ
- ACK_TIMEOUT_MS: 5000, // µÈ´ý ACK ³¬Ê±
- ACK_MAX_RETRY: 3, // ×î¶àÖØ´« 3 ´Î
- // 2. ÇëÇó-ÏìÓ¦Åä¶Ô
- REQ_TIMEOUT_MS: 10000, // ÇëÇó³¬Ê± 10 Ãë
- // 3. ÐòÁкÅ
- RESEND_ON_RECONNECT: true, // ÖØÁ¬ºóÊÇ·ñÇëÇó²¹·¢È±Ê§ÏûÏ¢
- // 4. ÀëÏß¶ÓÁÐ
- OFFLINE_QUEUE_MAX: 1000, // ÀëÏß¶ÓÁÐ×î´ó»º´æÌõÊý£¨·ÖƬ´«ÊäʱÐèÒª½Ï´ó»º³å£©
- // 5. ·ÖƬ´«Êä
- CHUNK_SIZE: 65536, // 64KB ÿƬ£¨RFC ±ê×¼·ÖƬ´óС£©
- LARGE_IMG_THRESHOLD_KB: 100, // ³¬¹ý 100KB µÄ½ØÍ¼×ß·ÖÆ¬
- CHUNK_RETAIN_LAST_MS: 60000, // ±£Áô×î½üÒ»´Î·ÖƬÔʼÊý¾ÝÓÃÓÚÖØ´«£¬Ä¬ÈÏ 60 Ãë
- // 6. ×ÔÊÊÓ¦ÐÄÌø
- HEARTBEAT_MIN_MS: 15000, // ÍøÂçºÃʱ 15 Ãë
- HEARTBEAT_MAX_MS: 60000, // ÍøÂç²îʱ 60 Ãë
- HEARTBEAT_TIMEOUT_MS: 15000,
- NETWORK_CHECK_INTERVAL_MS: 30000, // ÿ 30 ÃëÆÀ¹ÀÒ»´ÎÍøÂçÖÊÁ¿
- // 7. ±³Ñ¹
- SEND_RATE_LIMIT_MS: 5, // Á½´Î·¢ËÍÖ®¼ä×îÉÙ¼ä¸ô 5ms£¨¡Ö200Ìõ/ÃëÉÏÏÞ£¬·ÖƬ²»×èÈû£©
- SEND_BURST_MAX: 50, // ÔÊÐíÍ»·¢ 50 Ìõ£¨Ò»´Î 23 Ƭ²»±»±³Ñ¹ÏÞÁ÷£©
- // 8. ½ØÍ¼²ÎÊý£¨½µµÍ´ó³ß´çÊÖ»úµÄ´ø¿íÏûºÄ£©
- SCREENSHOT_JPEG_QUALITY: 80, // 0-100£¬Ô½¸ßÖÊÁ¿Ô½ºÃÌå»ýÔ½´ó
- SCREENSHOT_MAX_WIDTH: 0, // ×î´ó¿í¶È£¨ÏñËØ£©£¬0=²»Ëõ·Å£»½¨ÒéµÍ¶Ë»ú»òÊ¡Á÷Á¿Éè 1080
- // 9. ƵµÀ¶©ÔÄ
- AUTO_SUBSCRIBE_CHANNELS: ["system", "cmd", "screenshot"],
- // ͨÓÃ
- MAX_RETRY: 999,
- RETRY_BASE_MS: 2000,
- RETRY_MAX_MS: 30000,
- DEBUG_LOG: true, // ±¾½Å±¾ÎªÑÝʾ£¬Ä¬ÈÏ¿ªÈÕÖ¾
- SHOW_LOG_WINDOW: false,
- // ============================================================
- // ½µ¼¶Ä£Ê½£¨Ä¬ÈÏ¿ªÆô£¬±ÜÃâ AIWROK »·¾³ ACK Ç¿ÖÆ + ÐÄÌø¹ýÑϵ¼ÖµĻìÂÒ£©
- // RELAXED_MODE = true ¡ú ËùÓÐÏûϢĬÈÏ no_ack¡¢ACK ÖØ´«²»±¨´í¡¢ÐÄÌø³¬Ê±Ö»¾¯¸æ²»Ö÷¶¯¶Ï
- // RELAXED_MODE = false ¡ú ÍêÈ«ÆôÓà TCP ʽ¿É¿¿Í¶µÝ£¨ÐèÒª·þÎñÆ÷ÍêÈ«ÑϸñÆ¥ÅäÐÒ飩
- // ============================================================
- RELAXED_MODE: true,
- RELAXED_ACK_REQUIRED: false, // false = ²»Ç¿ÖÆÒªÇó ACK£¬³¬Ê±Ö±½Ó¶ªÆú£¬²»´òÓ¡ ERROR
- RELAXED_HEARTBEAT_SOFT_FAIL: true // true = ÐÄÌø³¬Ê±½ö½µ¼¶ÍøÂçÖÊÁ¿£¬²»Ö÷¶¯ close
- };
- // ============================================================
- // ÈÕÖ¾
- // ============================================================
- function LOG(tag, msg) {
- if (!CONFIG.DEBUG_LOG && (tag === "DEBUG" || tag === "RECV" || tag === "SEND")) return;
- try { printl("[" + tag + "] " + msg); } catch (e) {}
- }
- if (CONFIG.SHOW_LOG_WINDOW) {
- try { logWindow.show(); } catch (e) {}
- try { logWindow.setNoClickModel(); } catch (e) {}
- }
- // ============================================================
- // È«¾Ö״̬
- // ============================================================
- var G = {
- ws: null,
- socketID: "phone_" + new Date().getTime(),
- connected: false,
- retryCount: 0,
- running: true,
- seq: 0, // ·¢ËÍÐòÁкÅ
- lastReceivedSeq: 0, // ÊÕµ½·þÎñ¶ËÏûÏ¢µÄ×îºóÐòÁкÅ
- // 1. ACK È·ÈÏ
- pendingAcks: {}, // seq -> { msg, retryCount, timerId, sentAt }
- // 2. ÇëÇó-ÏìÓ¦Åä¶Ô
- pendingRequests: {}, // reqId -> { resolve, reject, timerId }
- // 4. ÀëÏß¶ÓÁÐ
- offlineQueue: [],
- // 5. ·ÖƬ´«Êä
- chunkTransfers: {}, // transferId -> { chunks:[], totalChunks, received, ts }
- lastSentChunks: {}, // transferId -> { b64, chunkSize, width, height, ts } £¨·¢ËͶ˱£Áô£¬ÓÃÓÚ CHUNK_RESEND ÖØ´«£©
- // 6. ×ÔÊÊÓ¦ÐÄÌø
- heartbeatTimer: null,
- currentHeartbeatMs: 25000,
- networkQuality: "good", // good / fair / poor
- lastPongAt: 0,
- pingRtt: 0,
- // 7. ±³Ñ¹
- lastSendAt: 0,
- sendBurstCount: 0,
- // 8. ƵµÀ¶©ÔÄ
- subscribedChannels: {}
- };
- // ============================================================
- // ÏûÏ¢¹¹½¨ & ·¢ËÍ£¨º¬±³Ñ¹¿ØÖÆ£©
- // ============================================================
- function buildMsg(type, data, opts) {
- G.seq++;
- var msg = {
- type: type,
- seq: G.seq,
- ts: new Date().getTime(),
- device: { brand: "", model: "" },
- data: data || {}
- };
- try { msg.device.brand = device.getBrand(); } catch (e) {}
- try { msg.device.model = device.getModel(); } catch (e) {}
- if (opts) {
- if (opts.req_id) msg.req_id = opts.req_id;
- if (opts.ack_for) msg.ack_for = opts.ack_for;
- if (opts.channel) msg.channel = opts.channel;
- if (opts.no_ack) msg.no_ack = true; // ±ê¼Ç²»ÐèÒª ACK
- }
- return msg;
- }
- /**
- * ·¢ËÍÏûÏ¢£¨Ä£Ê½ 7£º±³Ñ¹¿ØÖÆ£©
- * Á½´Î·¢ËÍÖ®¼äÖÁÉÙ¼ä¸ô SEND_RATE_LIMIT_MS£¬µ«ÔÊÐíÍ»·¢ SEND_BURST_MAX Ìõ
- */
- function sendMsg(type, data, opts) {
- // ½µ¼¶Ä£Ê½£ºÄ¬ÈÏËùÓÐÏûÏ¢¶¼ÎÞÐè ACK£¬³ý·ÇÏÔʽҪÇó
- if (CONFIG.RELAXED_MODE && !opts) opts = {};
- if (CONFIG.RELAXED_MODE && opts && !opts.__force_ack) {
- if (type !== "REGISTER" && type !== "SUBSCRIBE" && type !== "UNSUBSCRIBE") {
- opts.no_ack = true;
- }
- // ¶ÔÓÚ SUBSCRIBE / REGISTER£º½µ¼¶Ä£Ê½ÏÂÒ²×ß no_ack£¬¼õÉÙ»ìÂÒ
- if (!CONFIG.RELAXED_ACK_REQUIRED) {
- opts.no_ack = true;
- }
- }
- if (!G.ws || !G.connected) {
- // ģʽ 4£ºÀëÏß¶ÓÁÐ ¡ª¡ª ¶ÏÏßʱ»º´æ
- enqueueOffline(type, data, opts);
- return -1;
- }
- // ·ÖƬÏûÏ¢£¨CHUNK_START/CHUNK_DATA/CHUNK_END£©Óë RESPONSE/STATUS£ºÈƹý±³Ñ¹
- // ±ÜÃâ½ØÍ¼·ÖƬ±»ÏÞÁ÷ºóÈë¶Óµ¼Ö¶ªÆ¬
- var bypassBackpressure = (
- type === "CHUNK_START" || type === "CHUNK_DATA" || type === "CHUNK_END" ||
- type === "RESPONSE" || type === "STATUS" || type === "SCREENSHOT_DATA" ||
- type === "SCREENSHOT_FAIL" || type === "ACK" || type === "PONG"
- );
- if (!bypassBackpressure) {
- // ±³Ñ¹¿ØÖÆ£¨½ö¶Ô¿ØÖÆÀàÏûÏ¢ÉúЧ£©
- var now = new Date().getTime();
- var elapsed = now - G.lastSendAt;
- if (G.sendBurstCount >= CONFIG.SEND_BURST_MAX && elapsed < CONFIG.SEND_RATE_LIMIT_MS) {
- // ³¬¹ýÍ»·¢ÉÏÏÞÇÒ¼ä¸ô²»¹» ¡ú Èë¶ÓµÈ´ý
- LOG("WARN", "±³Ñ¹ÏÞÖÆ£¬ÏûÏ¢Èë¶ÓµÈ´ý type=" + type);
- enqueueOffline(type, data, opts);
- return -1;
- }
- if (elapsed >= CONFIG.SEND_RATE_LIMIT_MS) {
- G.sendBurstCount = 0; // ¼ä¸ô¹»ÁË£¬ÖØÖÃÍ»·¢¼ÆÊý
- }
- G.sendBurstCount++;
- G.lastSendAt = now;
- }
- var msg = buildMsg(type, data, opts);
- var raw = JSON.stringify(msg);
- try {
- G.ws.send(raw);
- LOG("SEND", type + " seq=" + msg.seq);
- // ģʽ 1£ºÐèÒª ACK µÄÏûÏ¢ ¡ú Æô¶¯³¬Ê±ÖØ´«
- if (!opts || !opts.no_ack) {
- startAckTimer(msg);
- }
- return msg.seq;
- } catch (e) {
- LOG("ERROR", "sendÒì³£: " + e.message);
- enqueueOffline(type, data, opts);
- return -1;
- }
- }
- // ============================================================
- // ģʽ 1£ºACK È·ÈÏ + ³¬Ê±ÖØ´«
- //
- // ·¢ËÍÏûÏ¢ºó£¬µÈ¶Ô¶Ë»Ø ACK(seq)¡£
- // ³¬Ê±Î´ÊÕµ½ ¡ú ÖØ´«£¨×î¶à ACK_MAX_RETRY ´Î£©¡£
- // ³¬¹ý×î´óÖØ´«´ÎÊý ¡ú ֪ͨÉϲãʧ°Ü¡£
- // ============================================================
- function startAckTimer(msg) {
- var entry = {
- raw: JSON.stringify(msg),
- retryCount: 0,
- sentAt: new Date().getTime(),
- timerId: null
- };
- G.pendingAcks[msg.seq] = entry;
- entry.timerId = runTime.setTimeout(function () {
- onAckTimeout(msg.seq);
- }, CONFIG.ACK_TIMEOUT_MS);
- }
- function onAckTimeout(seq) {
- var entry = G.pendingAcks[seq];
- if (!entry) return; // ÒѾÊÕµ½ ACK ÁË
- entry.retryCount++;
- if (entry.retryCount > CONFIG.ACK_MAX_RETRY) {
- if (CONFIG.RELAXED_MODE) {
- LOG("DEBUG", "ACK δȷÈÏ seq=" + seq + "£¨½µ¼¶Ä£Ê½×Ô¶¯ºöÂÔ£©");
- } else {
- LOG("ERROR", "ACK ÖØ´«³¬ÏÞ seq=" + seq + " ·ÅÆú");
- }
- delete G.pendingAcks[seq];
- // ֪ͨÉϲ㣨¿ÉÑ¡£º´¥·¢»Øµ÷£©
- return;
- }
- if (CONFIG.RELAXED_MODE) {
- LOG("DEBUG", "ACK µÈ´ý seq=" + seq + " µÚ " + entry.retryCount + " ´Î£¨½µ¼¶Ä£Ê½²»ÖØ´«£¬½öµÈ´ý£©");
- // ½µ¼¶Ä£Ê½£º²»ÖØ´«£¬¼ÌÐøµÈÒ»´Î£¬ÏÂÒ»´Î³¬Ê±¾Í discard
- entry.timerId = runTime.setTimeout(function () {
- LOG("DEBUG", "ACK ×îÖÕδ·µ»Ø seq=" + seq + "£¨½µ¼¶Ä£Ê½¶ªÆú£©");
- delete G.pendingAcks[seq];
- }, CONFIG.ACK_TIMEOUT_MS);
- return;
- }
- LOG("WARN", "ACK ³¬Ê± seq=" + seq + " µÚ " + entry.retryCount + " ´ÎÖØ´«");
- if (G.ws && G.connected) {
- try { G.ws.send(entry.raw); } catch (e) {}
- // ÖØÐÂÆô¶¯³¬Ê±¼ÆÊ±
- entry.timerId = runTime.setTimeout(function () {
- onAckTimeout(seq);
- }, CONFIG.ACK_TIMEOUT_MS);
- } else {
- // Á¬½Ó¶ÏÁË£¬·Å»ØÀëÏß¶ÓÁÐ
- delete G.pendingAcks[seq];
- // ÀëÏß¶ÓÁлáÔÚÖØÁ¬ºó flush
- }
- }
- function handleAck(seq) {
- var entry = G.pendingAcks[seq];
- if (!entry) {
- LOG("DEBUG", "ÊÕµ½Öظ´ ACK seq=" + seq);
- return;
- }
- if (entry.timerId) {
- try { runTime.stopTimeout(entry.timerId); } catch (e) {}
- }
- var rtt = new Date().getTime() - entry.sentAt;
- LOG("DEBUG", "ACK È·ÈÏ seq=" + seq + " RTT=" + rtt + "ms");
- delete G.pendingAcks[seq];
- }
- // ============================================================
- // ģʽ 2£ºÇëÇó-ÏìÓ¦Åä¶Ô£¨·Â HTTP ÇëÇóģʽ£©
- //
- // sendRequest("GET_STATUS", { detailed: true })
- // ¡ú µÈ·þÎñ¶Ë»Ø { type: "RESPONSE", req_id: "xxx", data: {...} }
- // ¡ú ³¬Ê±Ôò reject
- //
- // AIWROK ²»Ö§³Ö Promise£¬ÕâÀïÓûص÷·½Ê½£º
- // sendRequest(type, data, onOk, onTimeout)
- // ============================================================
- var _reqCounter = 0;
- function sendRequest(type, data, onOk, onTimeout) {
- _reqCounter++;
- var reqId = "req_" + new Date().getTime() + "_" + _reqCounter;
- var entry = {
- reqId: reqId,
- onOk: onOk,
- onTimeout: onTimeout,
- timerId: null
- };
- G.pendingRequests[reqId] = entry;
- // ³¬Ê±¼ÆÊ±
- entry.timerId = runTime.setTimeout(function () {
- if (G.pendingRequests[reqId]) {
- delete G.pendingRequests[reqId];
- LOG("WARN", "ÇëÇó³¬Ê± type=" + type + " reqId=" + reqId);
- if (onTimeout) onTimeout("timeout");
- }
- }, CONFIG.REQ_TIMEOUT_MS);
- sendMsg(type, data, { req_id: reqId });
- LOG("DEBUG", "ÇëÇó·¢³ö type=" + type + " reqId=" + reqId);
- return reqId;
- }
- function handleResponse(resp) {
- var reqId = resp.req_id;
- var entry = G.pendingRequests[reqId];
- if (!entry) {
- LOG("DEBUG", "ÊÕµ½¹ýÆÚÏìÓ¦ reqId=" + reqId);
- return;
- }
- if (entry.timerId) {
- try { runTime.stopTimeout(entry.timerId); } catch (e) {}
- }
- delete G.pendingRequests[reqId];
- if (entry.onOk) entry.onOk(resp.data || resp);
- LOG("DEBUG", "ÏìÓ¦ÊÕµ½ reqId=" + reqId);
- }
- // ============================================================
- // ģʽ 3£ºÏûÏ¢ÐòÁкŠ+ ¶ÏÏß²¹³¥
- //
- // ÿÌõÊÕµ½µÄÏûÏ¢¶¼´ø seq¡£
- // ÖØÁ¬ºó·¢ËÍ RESYNC(lastSeq)£¬ÇëÇó·þÎñ¶Ë²¹·¢ lastSeq Ö®ºóµÄÏûÏ¢¡£
- // ============================================================
- function handleIncomingSeq(seq) {
- if (seq > 0) {
- if (seq <= G.lastReceivedSeq) {
- LOG("WARN", "ÖØ¸´ÏûÏ¢ seq=" + seq + " (last=" + G.lastReceivedSeq + ")£¬¶ªÆú");
- return false; // ÖØ¸´ÏûÏ¢
- }
- if (G.lastReceivedSeq > 0 && seq > G.lastReceivedSeq + 1) {
- var gap = seq - G.lastReceivedSeq - 1;
- LOG("WARN", "ÏûÏ¢¼ä϶ seq=" + seq + " ȱÉÙ " + gap + " Ìõ");
- // ¿ÉÑ¡£ºÇëÇó²¹·¢
- // sendMsg("RESYNC", { from: G.lastReceivedSeq + 1, to: seq - 1 });
- }
- G.lastReceivedSeq = seq;
- }
- return true;
- }
- function requestResyncOnReconnect() {
- if (!CONFIG.RESEND_ON_RECONNECT) return;
- if (G.lastReceivedSeq > 0) {
- LOG("INFO", "ÖØÁ¬ºóÇëÇó²¹·¢ seq>" + G.lastReceivedSeq);
- sendMsg("RESYNC", { last_seq: G.lastReceivedSeq });
- }
- }
- // ============================================================
- // ģʽ 4£ºÀëÏßÏûÏ¢¶ÓÁÐ
- //
- // ¶ÏÏßʱËùÓз¢Ë͵ÄÏûÏ¢»º´æµ½¶ÓÁС£
- // ÖØÁ¬ºó×Ô¶¯ flush£¨ÏȽøÏȳö£©¡£
- // ============================================================
- function enqueueOffline(type, data, opts) {
- if (G.offlineQueue.length >= CONFIG.OFFLINE_QUEUE_MAX) {
- G.offlineQueue.shift(); // ¶ª×îÀϵÄ
- LOG("WARN", "ÀëÏß¶ÓÁÐÒÑÂú£¬¶ªÆú×î¾ÉÏûÏ¢");
- }
- G.offlineQueue.push({ type: type, data: data, opts: opts });
- LOG("DEBUG", "ÈëÀëÏß¶ÓÁÐ type=" + type + " ¶ÓÁ㤶È=" + G.offlineQueue.length);
- }
- function flushOfflineQueue() {
- if (G.offlineQueue.length === 0) return;
- LOG("INFO", "¿ªÊ¼ flush ÀëÏß¶ÓÁÐ " + G.offlineQueue.length + " Ìõ");
- var count = 0;
- while (G.offlineQueue.length > 0 && G.connected) {
- var item = G.offlineQueue.shift();
- // Ö±½Ó·¢ËÍ£¬²»×ß sendMsg µÄÀëÏßÅжÏ
- var msg = buildMsg(item.type, item.data, item.opts);
- try {
- G.ws.send(JSON.stringify(msg));
- if (!item.opts || !item.opts.no_ack) startAckTimer(msg);
- count++;
- } catch (e) {
- // ·¢ËÍʧ°Ü£¬·Å»Ø¶ÓÁÐÍ·²¿
- G.offlineQueue.unshift(item);
- break;
- }
- // ±³Ñ¹£ºÃ¿ÌõÖ®¼äÉÔ΢ͣ¶Ù
- // AIWROK ÖÐ sleep.millisecond »á×èÈû£¬ÕâÀïÓü«¶Ì¼ä¸ô
- }
- LOG("INFO", "flush Íê³É " + count + " Ìõ");
- }
- // ============================================================
- // ============================================================
- // ģʽ 5£º·ÖƬ´«Êä´ó½ØÍ¼
- //
- // ³¬¹ý LARGE_IMG_THRESHOLD_KB µÄ½ØÍ¼£¬·Ö CHUNK_SIZE ´óСµÄ¿é·¢ËÍ¡£
- // ÐÒ飺
- // 1. send { type: "CHUNK_START", transfer_id, total_chunks, mime, width, height }
- // 2. send { type: "CHUNK_DATA", transfer_id, chunk_index, data_base64 }
- // 3. send { type: "CHUNK_END", transfer_id }
- //
- // ±¾º¯Êý¶Ô½ØÍ¼ API ×ö 4 ¼¶ fallback£¬Ê§°Üʱ¾¡Á¿¸ø³öÔÒò¶ø²»ÊÇ #0
- // ============================================================
- function takeScreenshotRobust() {
- /**
- * AIWROK ÕýÈ·½ØÍ¼ API£ºÓÅÏÈ screen.screenShot(w,h,quality) ¿ÉËõ·Å+¿ØÖÊÁ¿
- * ʧ°Üʱ»ØÍË screen.screenShotFull() ¡ú .toBase64()
- * ·µ»Ø { base64, size_kb, width, height }
- * ʧ°Üʱ·µ»Ø null
- */
- try {
- var errs = [];
- // ¶ÁÈ¡ÅäÖÃ
- var maxW = parseInt(CONFIG.SCREENSHOT_MAX_WIDTH) || 0;
- var quality = parseInt(CONFIG.SCREENSHOT_JPEG_QUALITY);
- if (!quality || quality < 10 || quality > 100) quality = 80;
- // 0) ¼ì²é½ØÍ¼È¨ÏÞ
- try {
- if (typeof permissionManger !== "undefined" && permissionManger) {
- var hasPerm = permissionManger.checkScreenShotPremission();
- if (hasPerm === false) {
- LOG("ERROR", "½ØÍ¼È¨ÏÞ먦Æô£¡ÇëÔÚ AIWROK ȨÏÞ¹ÜÀíÖпªÆô¡¸½ØÍ¼/ͶÆÁ¡¹È¨ÏÞ");
- return null;
- }
- }
- } catch (ePerm) { errs.push("perm_check:" + ePerm.message); }
- // ¼ÆËãÄ¿±ê³ß´ç£¨µÈ±ÈËõ·Å£©
- var realW = 0, realH = 0;
- try { realW = screen.getScreenWidth(); } catch (ew) {}
- try { realH = screen.getScreenHeight(); } catch (eh) {}
- var targetW = realW, targetH = realH;
- if (maxW > 0 && realW > 0 && realH > 0 && realW > maxW) {
- targetW = maxW;
- targetH = Math.floor(realH * (maxW / realW));
- }
- // 1) ÓÅÏÈ screen.screenShot(w, h, quality)£º¿Éͬʱ¿ØÖÆ·Ö±æÂÊºÍ JPEG ÖÊÁ¿£¨¼õÉÙ´ø¿í£©
- if (typeof screen !== "undefined" && screen && typeof screen.screenShot === "function") {
- try {
- if (!targetW || !targetH) { targetW = realW || 1080; targetH = realH || 1920; }
- var imgA = screen.screenShot(targetW, targetH, quality);
- if (!imgA) throw new Error("screenShot ·µ»Ø null");
- var b64A = "";
- if (typeof imgA.toBase64 === "function") {
- b64A = "" + imgA.toBase64();
- } else if (typeof imgA.toBase64Jpg === "function") {
- b64A = "" + imgA.toBase64Jpg();
- } else {
- throw new Error("screenShot ·µ»Ø¶ÔÏóÎÞ toBase64 ·½·¨");
- }
- try { imgA.recycle(); } catch (er) {}
- if (!b64A || b64A.length < 10) throw new Error("Base64 Ϊ¿Õ len=" + b64A.length);
- var sizeKBA = Math.floor((b64A.length * 0.75) / 1024);
- LOG("INFO", "½ØÍ¼ OK (screenShot scaled) size=" + sizeKBA + "KB " + targetW + "x" + targetH + " q=" + quality);
- return { base64: b64A, size_kb: sizeKBA, width: targetW, height: targetH };
- } catch (eA) {
- errs.push("screenShot(" + targetW + "," + targetH + "," + quality + "):" + (eA.message || eA));
- }
- }
- // 2) »ØÍË screen.screenShotFull()£¨AIWROK Ö÷Òª½ØÍ¼ API£©
- if (typeof screen !== "undefined" && screen && typeof screen.screenShotFull === "function") {
- try {
- var img = screen.screenShotFull();
- if (!img) throw new Error("screenShotFull ·µ»Ø null");
- var w = realW, h = realH;
- if (!w || !h) {
- try { w = img.getWidth(); } catch (ew2) {}
- try { h = img.getHeight(); } catch (eh2) {}
- }
- var b64 = "";
- if (typeof img.toBase64 === "function") {
- b64 = "" + img.toBase64();
- } else if (typeof img.toBase64Jpg === "function") {
- b64 = "" + img.toBase64Jpg();
- } else {
- throw new Error("½ØÍ¼¶ÔÏóûÓÐ toBase64 ·½·¨");
- }
- try { img.recycle(); } catch (er) {}
- if (!b64 || b64.length < 10) throw new Error("Base64 Ϊ¿Õ»ò¹ý¶Ì len=" + (b64 ? b64.length : 0));
- var sizeKB = Math.floor((b64.length * 0.75) / 1024);
- LOG("INFO", "½ØÍ¼ OK size=" + sizeKB + "KB width=" + w + " height=" + h);
- return { base64: b64, size_kb: sizeKB, width: w, height: h };
- } catch (e1) {
- errs.push("screenShotFull:" + (e1.message || e1));
- }
- } else {
- errs.push("screenShotFull_not_found");
- }
- // 3) ×îºó¶µµ×£º³¢ÊÔ image.* ϵÁÐ
- if (typeof image !== "undefined" && image) {
- var tryList = ["fastCapture", "screenshot", "captureScreen", "capture"];
- for (var ti = 0; ti < tryList.length; ti++) {
- var fnName = tryList[ti];
- try {
- if (typeof image[fnName] === "function") {
- var img3 = image[fnName]();
- if (img3) {
- var w3 = 0, h3 = 0;
- try { w3 = img3.getWidth(); } catch (ew3) {}
- try { h3 = img3.getHeight(); } catch (eh3) {}
- var b64_3 = "" + img3.toBase64();
- try { img3.recycle(); } catch (er) {}
- var sizeKB3 = Math.floor((b64_3.length * 0.75) / 1024);
- LOG("INFO", "½ØÍ¼ OK (image." + fnName + ") size=" + sizeKB3 + "KB");
- return { base64: b64_3, size_kb: sizeKB3, width: w3, height: h3 };
- }
- }
- } catch (e3) { errs.push(fnName + ":" + e3.message); }
- }
- }
- LOG("ERROR", "ËùÓнØÍ¼ API ¾ùʧ°Ü: " + errs.join(" | "));
- LOG("ERROR", "³£¼ûÔÒò£º(1) AIWROK 먦Æô¡¸Í¶ÆÁ/½ØÍ¼¡¹È¨ÏÞ£»(2) Ó¦ÓÃδÆô¶¯Ç°Ì¨·þÎñ£»(3) Android 10+ ÐèÒª MediaProjection ÊÚȨ");
- return null;
- } catch (eOuter) {
- try { LOG("ERROR", "½ØÍ¼×îÍâ²ãÒì³£: " + (eOuter.message || String(eOuter))); } catch (eLog) {}
- return null;
- }
- }
- function sendScreenshotChunked(shotID) {
- // ⚠️ ×îÍâ²ã try-catch£º·ÀÖ¹ java/Android µ×²ãÒì³£Ö±½Ó±ä³É¡¾´íÎó¡¿#0
- try {
- var shot = takeScreenshotRobust();
- if (!shot) {
- // °Ñʧ°ÜÔÒò»Ø´«¸ø¿ØÖÆÃæ°å£¨±ãÓÚÏÔʾ£©
- try { sendMsg("SCREENSHOT_FAIL", { id: shotID || "", reason: "capture_failed" }, { no_ack: true }); } catch (eSend) {}
- return { ok: false, reason: "capture_failed", error_msg: "ËùÓнØÍ¼API¾ùʧ°Ü£¬Çë¼ì²éAIWROKͶÆÁȨÏÞ" };
- }
- var sizeKB = shot.size_kb;
- var b64 = shot.base64;
- if (sizeKB < CONFIG.LARGE_IMG_THRESHOLD_KB) {
- // Сͼֱ½Ó·¢
- sendMsg("SCREENSHOT", {
- id: shotID, format: "jpeg",
- width: shot.width, height: shot.height,
- size_kb: sizeKB, image_base64: b64
- }, { no_ack: true });
- return { ok: true, size_kb: sizeKB, width: shot.width, height: shot.height, chunked: false };
- }
- // ´óͼ·ÖƬ
- var transferId = "tf_" + new Date().getTime();
- var chunkSize = CONFIG.CHUNK_SIZE;
- var totalChunks = Math.ceil(b64.length / chunkSize);
- LOG("INFO", "·ÖƬ´«Êä transferId=" + transferId + " ×ÜÆ¬Êý=" + totalChunks + " size=" + sizeKB + "KB");
- // ±£ÁôÔʼ Base64 ºÍ²ÎÊý£¬ÓÃÓÚ CHUNK_RESEND ÖØ´«
- G.lastSentChunks[transferId] = {
- b64: b64,
- chunkSize: chunkSize,
- width: shot.width,
- height: shot.height,
- sizeKB: sizeKB,
- totalChunks: totalChunks,
- ts: new Date().getTime()
- };
- // ÇåÀí¹ýÆÚ¼Ç¼£¬±ÜÃâÄÚ´æÎÞÏÞÔö³¤
- var _keepMs = CONFIG.CHUNK_RETAIN_LAST_MS || 60000;
- var _now = new Date().getTime();
- var _keys = [];
- for (var _k in G.lastSentChunks) if (G.lastSentChunks.hasOwnProperty(_k)) _keys.push(_k);
- for (var _ki = 0; _ki < _keys.length; _ki++) {
- if (_now - (G.lastSentChunks[_keys[_ki]].ts || 0) > _keepMs) delete G.lastSentChunks[_keys[_ki]];
- }
- sendMsg("CHUNK_START", {
- transfer_id: transferId,
- total_chunks: totalChunks,
- mime: "jpeg",
- width: shot.width,
- height: shot.height,
- size_kb: sizeKB
- });
- for (var i = 0; i < totalChunks; i++) {
- var start = i * chunkSize;
- var end = Math.min(start + chunkSize, b64.length);
- var chunk = b64.substring(start, end);
- sendMsg("CHUNK_DATA", {
- transfer_id: transferId,
- chunk_index: i,
- data_base64: chunk
- });
- // ·ÖƬ֮¼ä 1ms Èò½¼´¿É£¬²»ÔÙ 5 Ƭ˯ 10ms£¨Èƹý±³Ñ¹ºó²»ÐèÒª¶îÍâ½ÚÁ÷£©
- if ((i + 1) % 20 === 0) {
- try { sleep.millisecond(1); } catch (e) {}
- }
- }
- sendMsg("CHUNK_END", { transfer_id: transferId });
- LOG("INFO", "·ÖƬ´«ÊäÍê³É transferId=" + transferId);
- return { ok: true, size_kb: sizeKB, width: shot.width, height: shot.height, chunked: true, transfer_id: transferId, total_chunks: totalChunks };
- } catch (eOuter) {
- try { LOG("ERROR", "sendScreenshotChunked ×îÍâ²ãÒì³£: " + (eOuter.message || String(eOuter))); } catch (eLog) {}
- try { sendMsg("SCREENSHOT_FAIL", { id: shotID || "", reason: "exception", error_msg: (eOuter.message || String(eOuter)) }, { no_ack: true }); } catch (eSend) {}
- return { ok: false, reason: "exception", error_msg: (eOuter.message || String(eOuter)) };
- }
- }
- // ============================================================
- // ¸¨Öúº¯Êý Polyfill£¨AIWROK »·¾³È«¾Öº¯Êýȱʧ¶µµ×£©
- // ⚠️ ¹Ø¼ü²ßÂÔ£ºËùÓк¯Êýͬʱ¹ÒÔØµ½È«¾Ö±äÁ¿ + PhoneCtrl ¶ÔÏó
- // ¼´Ê¹±äÁ¿ÃûÓë AIWROK ÒýÇæ±£ÁôÃû³åÍ»µ¼Ö¸³ÖµÊ§°Ü£¬
- // PhoneCtrl ¶ÔÏóÉϵĺ¯ÊýÈÔÈ»¿ÉÓã¬handleRemoteCommand »áÓÅÏÈʹÓÃ
- // ============================================================
- var PhoneCtrl = PhoneCtrl || {};
- (function _polyfill_helpers() {
- try {
- function _has(obj, name) { return !!(obj && typeof obj[name] === "function"); }
- // ³¢ÊÔÔÚÈ«¾Ö×÷ÓÃÓò¸³Öµ£¨°²È«·½Ê½£¬Ê¹ÓÃÈ«¾Ö¶ÔÏóÊôÐÔ£©
- function _tryAssignGlobal(name, fn) {
- try {
- var g = (typeof window !== "undefined") ? window :
- (typeof globalThis !== "undefined") ? globalThis :
- (typeof runtime !== "undefined") ? runtime : this;
- g[name] = fn;
- return true;
- } catch (e) {
- return false;
- }
- }
- // ³¢ÊÔÔÚÈ«¾Ö×÷ÓÃÓò¶ÁÈ¡£¨°²È«·½Ê½£©
- function _tryGetGlobal(name) {
- try {
- var g = (typeof window !== "undefined") ? window :
- (typeof globalThis !== "undefined") ? globalThis :
- (typeof runtime !== "undefined") ? runtime : this;
- return g[name];
- } catch (e) { return undefined; }
- }
- // ͨÓãºauto API Ö±½Óµ÷Óöµµ×
- var _autoClick = function (x, y) {
- if (_has(auto, "clickPoint")) return auto.clickPoint(x, y);
- if (_has(auto, "clickPercent")) {
- var sw = screen.getScreenWidth(), sh = screen.getScreenHeight();
- return auto.clickPercent(x / sw, y / sh);
- }
- throw new Error("no_click_api");
- };
- var _autoSwipe = function (x1, y1, x2, y2, dur) {
- if (_has(auto, "swip")) return auto.swip(x1, y1, x2, y2, dur, 0);
- throw new Error("no_swipe_api");
- };
- var _autoInput = function (text) {
- if (_has(auto, "input")) return auto.input(text);
- if (_has(auto, "sendText")) return auto.sendText(text);
- if (_has(auto, "inputText")) return auto.inputText(text);
- throw new Error("no_input_api");
- };
- var _autoPress = function (fnName) {
- if (_has(auto, fnName)) return auto[fnName]();
- throw new Error("auto." + fnName + "_not_found");
- };
- var _keyPress = function (keyName, keyCode) {
- if (_has(device, keyName)) return device[keyName]();
- if (_has(device, "pressKey")) return device.pressKey(keyCode);
- if (_has(device, "press")) return device.press(keyCode);
- try {
- var KeyEvent = android.view.KeyEvent;
- if (_has(runtime, "postEvent")) return runtime.postEvent(KeyEvent.ACTION_DOWN, keyCode);
- } catch (e) {}
- throw new Error("no_" + keyName + "_api");
- };
- // click
- PhoneCtrl.click = function (x, y) {
- x = parseInt(x || 0); y = parseInt(y || 0);
- try { return _autoClick(x, y); } catch (e1) {}
- try { if (_has(gesture, "click")) return gesture.click(x, y); } catch (e2) {}
- try { if (_has(device, "click")) return device.click(x, y); } catch (e3) {}
- throw new Error("click_failed");
- };
- _tryAssignGlobal("click", PhoneCtrl.click);
- // swipe
- PhoneCtrl.swipe = function (x1, y1, x2, y2, duration) {
- x1 = parseInt(x1 || 0); y1 = parseInt(y1 || 0);
- x2 = parseInt(x2 || 0); y2 = parseInt(y2 || 0);
- duration = parseInt(duration || 400);
- try { return _autoSwipe(x1, y1, x2, y2, duration); } catch (e1) {}
- try {
- if (_has(auto, "dispatchGesture")) {
- gesture.setDurTime(duration);
- gesture.addPoint(x1, y1);
- gesture.addPoint(x2, y2);
- return auto.dispatchGesture([gesture]);
- }
- } catch (e2) {}
- try { if (_has(gesture, "swipe")) return gesture.swipe(x1, y1, x2, y2, duration); } catch (e3) {}
- try { if (_has(device, "swipe")) return device.swipe(x1, y1, x2, y2, duration); } catch (e4) {}
- throw new Error("swipe_failed");
- };
- _tryAssignGlobal("swipe", PhoneCtrl.swipe);
- // input
- PhoneCtrl.input = function (text) {
- text = String(text == null ? "" : text);
- try { return _autoInput(text); } catch (e1) {}
- try { if (_has(device, "input")) return device.input(text); } catch (e2) {}
- throw new Error("input_failed");
- };
- _tryAssignGlobal("input", PhoneCtrl.input);
- // home / back / menu / power / search / camera
- PhoneCtrl.home = function () {
- try { return _autoPress("home"); } catch (e) {}
- return _keyPress("home", 3);
- };
- PhoneCtrl.back = function () {
- try { return _autoPress("back"); } catch (e) {}
- return _keyPress("back", 4);
- };
- PhoneCtrl.menu = function () {
- try { return _autoPress("menu"); } catch (e) {}
- return _keyPress("menu", 82);
- };
- PhoneCtrl.power = function () {
- try { return _autoPress("power"); } catch (e) {}
- return _keyPress("power", 26);
- };
- PhoneCtrl.search = function () {
- try { return _autoPress("search"); } catch (e) {}
- return _keyPress("search", 84);
- };
- PhoneCtrl.camera = function () {
- try { return _autoPress("camera"); } catch (e) {}
- return _keyPress("camera", 27);
- };
- // recentApps
- PhoneCtrl.recentApps = function () {
- try { return _autoPress("recents"); } catch (e1) {}
- try { return _autoPress("recentApps"); } catch (e2) {}
- try {
- var sw = screen.getScreenWidth(), sh = screen.getScreenHeight();
- return PhoneCtrl.swipe(Math.floor(sw/2), sh - 20, Math.floor(sw/2), Math.floor(sh/2), 300);
- } catch (e3) {}
- return _keyPress("recentApps", 187);
- };
- // expandNotificationBar
- PhoneCtrl.expandNotificationBar = function () {
- try { return _autoPress("statusBar"); } catch (e1) {}
- try { return _autoPress("notification"); } catch (e2) {}
- try {
- var sw = screen.getScreenWidth();
- return PhoneCtrl.swipe(Math.floor(sw/2), 10, Math.floor(sw/2), 400, 300);
- } catch (e3) { throw new Error("no_noti_api"); }
- };
- // openApp
- PhoneCtrl.openApp = function (pkg) {
- pkg = String(pkg || "");
- try {
- if (typeof app !== "undefined" && app) {
- if (_has(app, "openApp")) return app.openApp(pkg);
- if (_has(app, "openAppv2")) return app.openAppv2(pkg);
- if (_has(app, "openAppSeting")) return app.openAppSeting(pkg);
- if (_has(app, "launch")) return app.launch(pkg);
- if (_has(app, "open")) return app.open(pkg);
- }
- } catch (e1) {}
- try {
- if (typeof device !== "undefined" && device) {
- if (_has(device, "startApp")) return device.startApp(pkg);
- if (_has(device, "launchApp")) return device.launchApp(pkg);
- if (_has(device, "openApp")) return device.openApp(pkg);
- }
- } catch (e2) {}
- try {
- var ctxt = context || runtime.getContext();
- var Intent = android.content.Intent;
- var pm = ctxt.getPackageManager();
- var launchIntent = pm.getLaunchIntentForPackage(pkg);
- if (launchIntent) {
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- ctxt.startActivity(launchIntent);
- return true;
- }
- throw new Error("no_launch_intent");
- } catch (e3) { throw new Error("openApp_fail: " + e3.message); }
- };
- // openAppName
- PhoneCtrl.openAppName = function (name) {
- name = String(name || "");
- try {
- if (typeof app !== "undefined" && app) {
- if (_has(app, "openApp")) return app.openApp(name);
- if (_has(app, "openAppv2")) return app.openAppv2(name);
- if (_has(app, "launchByName")) return app.launchByName(name);
- if (_has(app, "openByName")) return app.openByName(name);
- }
- } catch (e1) {}
- try {
- if (typeof device !== "undefined" && device) {
- if (_has(device, "openAppByName")) return device.openAppByName(name);
- if (_has(device, "launchByName")) return device.launchByName(name);
- }
- } catch (e2) {}
- try {
- var ctxt = context || runtime.getContext();
- var pm = ctxt.getPackageManager();
- var apps = pm.getInstalledApplications(0);
- var target = name.toLowerCase();
- var iter = apps.iterator();
- while (iter.hasNext()) {
- var ai = iter.next();
- var label = String(pm.getApplicationLabel(ai) || "").toLowerCase();
- var pkg = String(ai.packageName || "").toLowerCase();
- if (label === target || pkg === target || label.indexOf(target) !== -1 || pkg.indexOf(target) !== -1) {
- return PhoneCtrl.openApp(ai.packageName);
- }
- }
- throw new Error("pkg_not_found_for_name_" + name);
- } catch (e3) { throw new Error("openAppName_fail: " + e3.message); }
- };
- // killApp
- PhoneCtrl.killApp = function (pkg) {
- pkg = String(pkg || "");
- try {
- if (typeof app !== "undefined" && app) {
- if (_has(app, "forceStop")) return app.forceStop(pkg);
- if (_has(app, "kill")) return app.kill(pkg);
- if (_has(app, "close")) return app.close(pkg);
- }
- } catch (e1) {}
- try {
- if (typeof device !== "undefined" && device) {
- if (_has(device, "forceStopApp")) return device.forceStopApp(pkg);
- if (_has(device, "killApp")) return device.killApp(pkg);
- if (_has(device, "killBackgroundProcesses")) return device.killBackgroundProcesses(pkg);
- }
- } catch (e2) {}
- try {
- var ctxt = context || runtime.getContext();
- var actMan = ctxt.getSystemService(android.content.Context.ACTIVITY_SERVICE);
- actMan.killBackgroundProcesses(pkg);
- return true;
- } catch (e3) { throw new Error("killApp_fail: " + e3.message); }
- };
- // È«¾Ö±äÁ¿¸³Öµ£¨¾¡Á¦¶øÎª£¬Ê§°Ü²»Ó°Ïì PhoneCtrl Éϵĺ¯Êý£©
- _tryAssignGlobal("home", PhoneCtrl.home);
- _tryAssignGlobal("back", PhoneCtrl.back);
- _tryAssignGlobal("menu", PhoneCtrl.menu);
- _tryAssignGlobal("power", PhoneCtrl.power);
- _tryAssignGlobal("search", PhoneCtrl.search);
- _tryAssignGlobal("camera", PhoneCtrl.camera);
- _tryAssignGlobal("recentApps", PhoneCtrl.recentApps);
- _tryAssignGlobal("expandNotificationBar", PhoneCtrl.expandNotificationBar);
- _tryAssignGlobal("openApp", PhoneCtrl.openApp);
- _tryAssignGlobal("openAppName", PhoneCtrl.openAppName);
- _tryAssignGlobal("killApp", PhoneCtrl.killApp);
- LOG("INFO", "Polyfill ³õʼ»¯Íê³É PhoneCtrl.click/swipe/input/home/back µÈÒѾÍÐ÷");
- } catch (ePolyOuter) {
- try { LOG("WARN", "polyfill Òì³£: " + (ePolyOuter.message || String(ePolyOuter))); } catch (eLog) {}
- }
- })();
- /**
- * ½ÓÊÕ¶Ë£º×é×°·ÖƬ£¨Èç¹ûÊÖ»úÒ²ÐèÒª½ÓÊÕ´óÎļþµÄ»°£©
- */
- function handleChunkStart(data) {
- G.chunkTransfers[data.transfer_id] = {
- chunks: {},
- totalChunks: data.total_chunks,
- received: 0,
- meta: data
- };
- LOG("DEBUG", "·ÖƬ½ÓÊÕ¿ªÊ¼ transferId=" + data.transfer_id + " ×ÜÆ¬Êý=" + data.totalChunks);
- }
- function handleChunkData(data) {
- var transfer = G.chunkTransfers[data.transfer_id];
- if (!transfer) {
- LOG("WARN", "δ֪·ÖƬ´«Êä transferId=" + data.transfer_id);
- return;
- }
- transfer.chunks[data.chunk_index] = data.data_base64;
- transfer.received++;
- // ½ø¶È
- var pct = Math.floor(transfer.received / transfer.totalChunks * 100);
- if (transfer.received % 10 === 0 || transfer.received === transfer.totalChunks) {
- LOG("DEBUG", "·ÖƬ½ÓÊÕ½ø¶È " + pct + "% (" + transfer.received + "/" + transfer.totalChunks + ")");
- }
- }
- function handleChunkEnd(data) {
- var transfer = G.chunkTransfers[data.transfer_id];
- if (!transfer) return;
- if (transfer.received < transfer.totalChunks) {
- LOG("WARN", "·ÖƬ²»ÍêÕû " + transfer.received + "/" + transfer.totalChunks);
- // ¿ÉÇëÇóÖØ´«È±Ê§µÄƬ
- var missing = [];
- for (var i = 0; i < transfer.totalChunks; i++) {
- if (!transfer.chunks[i]) missing.push(i);
- }
- sendMsg("CHUNK_RESEND", {
- transfer_id: data.transfer_id,
- missing_indices: missing
- });
- return;
- }
- // ×é×°
- var fullB64 = "";
- for (var i = 0; i < transfer.totalChunks; i++) {
- fullB64 += transfer.chunks[i];
- }
- LOG("INFO", "·ÖƬ×é×°Íê³É transferId=" + data.transfer_id + " ×Ü´óС=" + Math.floor(fullB64.length / 1024) + "KB");
- // ÕâÀï¿ÉÒÔ½âÂë Base64 ¡ú Bitmap ¡ú ±£´æÎļþµÈ
- delete G.chunkTransfers[data.transfer_id];
- }
- /**
- * ´¦Àí¶Ô¶Ë·¢À´µÄ CHUNK_RESEND£¨È±Ê§·ÖÆ¬ÖØ´«ÇëÇó£©
- * ´Ó G.lastSentChunks ÖÐÈ¡³ö¸Ã transferId µÄÔʼ b64£¬°´Ë÷ÒýÖØÐ·¢ËͶÔӦƬ
- */
- function handleChunkResend(data) {
- var transferId = data.transfer_id;
- var missing = data.missing_indices || [];
- var cached = G.lastSentChunks[transferId];
- if (!cached) {
- LOG("WARN", "ÊÕµ½ CHUNK_RESEND µ«±¾µØÒÑÎÞ»º´æ transferId=" + transferId);
- return;
- }
- LOG("INFO", "ÊÕµ½ CHUNK_RESEND transferId=" + transferId + " ȱʧƬÊý=" + missing.length);
- var b64 = cached.b64;
- var chunkSize = cached.chunkSize;
- var reSentCount = 0;
- for (var mi = 0; mi < missing.length; mi++) {
- var idx = parseInt(missing[mi]);
- if (isNaN(idx) || idx < 0) continue;
- var start = idx * chunkSize;
- if (start >= b64.length) continue;
- var end = Math.min(start + chunkSize, b64.length);
- var chunk = b64.substring(start, end);
- sendMsg("CHUNK_DATA", {
- transfer_id: transferId,
- chunk_index: idx,
- data_base64: chunk
- });
- reSentCount++;
- // ÿ 20 ƬÈà 1ms
- if (reSentCount % 20 === 0) { try { sleep.millisecond(1); } catch (e) {} }
- }
- LOG("INFO", "CHUNK_RESEND ÒÑÖØ´« " + reSentCount + " Ƭ transferId=" + transferId);
- }
- // ============================================================
- // ģʽ 6£º×ÔÊÊÓ¦ÐÄÌø
- //
- // ¸ù¾ÝÍøÂçÖÊÁ¿¶¯Ì¬µ÷ÕûÐÄÌø¼ä¸ô£º
- // good ¡ú 15s fair ¡ú 30s poor ¡ú 60s
- // ÍøÂçÖÊÁ¿ÆÀ¹ÀÒÀ¾Ý£ºPING RTT + ×î½üÊÇ·ñ¶ÏÏ߯µ·±
- // ============================================================
- function assessNetworkQuality() {
- var rtt = G.pingRtt || 0;
- var oldQ = G.networkQuality;
- if (rtt === 0 || rtt < 100) {
- G.networkQuality = "good";
- G.currentHeartbeatMs = CONFIG.HEARTBEAT_MIN_MS;
- } else if (rtt < 500) {
- G.networkQuality = "fair";
- G.currentHeartbeatMs = Math.floor((CONFIG.HEARTBEAT_MIN_MS + CONFIG.HEARTBEAT_MAX_MS) / 2);
- } else {
- G.networkQuality = "poor";
- G.currentHeartbeatMs = CONFIG.HEARTBEAT_MAX_MS;
- }
- if (oldQ !== G.networkQuality) {
- LOG("INFO", "ÍøÂçÖÊÁ¿±ä»¯ " + oldQ + " ¡ú " + G.networkQuality + " (RTT=" + rtt + "ms) ÐÄÌø=" + (G.currentHeartbeatMs / 1000) + "s");
- // ÖØÆôÐÄÌø
- startHeartbeat();
- }
- }
- function startHeartbeat() {
- stopHeartbeat();
- G.lastPongAt = new Date().getTime();
- function tick() {
- if (!G.connected) return;
- var now = new Date().getTime();
- var timeoutThresh = G.currentHeartbeatMs + CONFIG.HEARTBEAT_TIMEOUT_MS;
- var sincePong = now - G.lastPongAt;
- if (sincePong > timeoutThresh) {
- if (CONFIG.RELAXED_MODE && CONFIG.RELAXED_HEARTBEAT_SOFT_FAIL) {
- // ½µ¼¶Ä£Ê½£º²»Ö÷¶¯¶Ï¿ª£¬Ö»½µ¼¶ÍøÂçÖÊÁ¿ + ·¢¾¯¸æ
- if (G.networkQuality === "good") { G.networkQuality = "fair"; G.currentHeartbeatMs = Math.floor((CONFIG.HEARTBEAT_MIN_MS + CONFIG.HEARTBEAT_MAX_MS) / 2); }
- else if (G.networkQuality === "fair") { G.networkQuality = "poor"; G.currentHeartbeatMs = CONFIG.HEARTBEAT_MAX_MS; }
- LOG("WARN", "ÐÄÌøÎ´ÊÕµ½ PONG ÒÑ " + (sincePong / 1000).toFixed(0) + "s£¬½µ¼¶ÍøÂçÖÊÁ¿Îª " + G.networkQuality + "£¨½µ¼¶Ä£Ê½²»¶ÏÏߣ©");
- G.lastPongAt = now; // ÖØÖüÆÊ±£¬±ÜÃâ·´¸´¸æ¾¯·ç±©
- } else {
- LOG("WARN", "ÐÄÌø³¬Ê± Ö÷¶¯¶Ï¿ª");
- try { G.ws.close(); } catch (e) {}
- return;
- }
- }
- var pingTs = new Date().getTime();
- sendMsg("PING", { t: pingTs }, { no_ack: true });
- // ÏÂÒ»´ÎÑÓ³Ù = µ±Ç°×ÔÊÊÓ¦ÐÄÌø¼ä¸ô
- G.heartbeatTimer = runTime.setTimeout(tick, G.currentHeartbeatMs);
- }
- // ×¢Ò⣺AIWROK ÖÐ onopen »Øµ÷Ö´ÐÐ˳Ðò¿ÉÄÜÑÓ³Ù£¬ÕâÀïÒ²ÉèÖà lastPongAt
- G.lastPongAt = new Date().getTime();
- G.heartbeatTimer = runTime.setTimeout(tick, G.currentHeartbeatMs);
- LOG("DEBUG", "ÐÄÌøÒÑÆô¶¯ ¼ä¸ô=" + (G.currentHeartbeatMs / 1000) + "s ÖÊÁ¿=" + G.networkQuality + (CONFIG.RELAXED_MODE ? " ½µ¼¶Ä£Ê½=¿ª" : ""));
- }
- function stopHeartbeat() {
- if (G.heartbeatTimer) {
- try { runTime.stopTimeout(G.heartbeatTimer); } catch (e) {}
- G.heartbeatTimer = null;
- }
- }
- // ¶¨Ê±ÆÀ¹ÀÍøÂçÖÊÁ¿
- var networkCheckTimer = null;
- function startNetworkCheck() {
- stopNetworkCheck();
- networkCheckTimer = runTime.setTimeout(function check() {
- assessNetworkQuality();
- // ÿ 30 Ãëͬ²½Ò»´Î×îÐÂÉ豸״̬¸ø¿ØÖÆÃæ°å£¨µçÁ¿/´æ´¢/ÍøÂçµÈʵʱ¸üУ©
- if (G.connected) {
- try {
- var s = collectStatusFull();
- if (s) sendMsg("STATUS", s, { no_ack: true });
- } catch (eStatus) {
- try { LOG("DEBUG", "ÖÜÆÚ״̬Éϱ¨Ê§°Ü: " + eStatus.message); } catch (eLog) {}
- }
- }
- networkCheckTimer = runTime.setTimeout(check, CONFIG.NETWORK_CHECK_INTERVAL_MS);
- }, CONFIG.NETWORK_CHECK_INTERVAL_MS);
- }
- function stopNetworkCheck() {
- if (networkCheckTimer) {
- try { runTime.stopTimeout(networkCheckTimer); } catch (e) {}
- networkCheckTimer = null;
- }
- }
- // ============================================================
- // ģʽ 7£º±³Ñ¹¿ØÖÆ
- // ÒÑÔÚ sendMsg() ÖÐʵÏÖ£¬ºËÐÄÂß¼£º
- // - Á½´Î·¢ËÍÖÁÉÙ¼ä¸ô SEND_RATE_LIMIT_MS
- // - ÔÊÐíÍ»·¢ SEND_BURST_MAX Ìõ
- // - ³¬ÏÞÔòÈëÀëÏß¶ÓÁеȴý
- // ============================================================
- // ============================================================
- // ģʽ 8£ºÆµµÀ¶©ÔÄģʽ
- //
- // ¿Í»§¶Ë¿ÉÒÔ¶¯Ì¬¶©ÔÄ/È¡Ïû¶©ÔÄʼþƵµÀ¡£
- // ·þÎñ¶ËÖ»ÍÆËÍÒѶ©ÔÄÆµµÀµÄÏûÏ¢¡£
- //
- // subscribe("cmd") ¡ú ·þÎñ¶Ë¿ªÊ¼ÍÆËÍ cmd ƵµÀÏûÏ¢
- // unsubscribe("cmd") ¡ú ·þÎñ¶ËÍ£Ö¹ÍÆËÍ
- // ============================================================
- function subscribe(channel) {
- G.subscribedChannels[channel] = true;
- sendMsg("SUBSCRIBE", { channel: channel });
- LOG("INFO", "¶©ÔÄÆµµÀ " + channel);
- }
- function unsubscribe(channel) {
- delete G.subscribedChannels[channel];
- sendMsg("UNSUBSCRIBE", { channel: channel });
- LOG("INFO", "È¡Ïû¶©ÔÄ " + channel);
- }
- function autoSubscribeChannels() {
- for (var i = 0; i < CONFIG.AUTO_SUBSCRIBE_CHANNELS.length; i++) {
- subscribe(CONFIG.AUTO_SUBSCRIBE_CHANNELS[i]);
- }
- }
- // ============================================================
- // ·þÎñÆ÷ÏûÏ¢´¦Àí
- // ============================================================
- function handleServerMessage(raw) {
- // AIWROK Öлص÷²ÎÊý¿ÉÄÜÊÇ bytes / java object / string£¬ÏÈͳһת×Ö·û´®
- if (raw === null || raw === undefined) return;
- if (typeof raw !== "string") {
- try {
- if (typeof raw === "object" && typeof raw.toString === "function") {
- var s = raw.toString();
- // Java ×Ö½ÚÊý×é»òÆäËû¶ÔÏó
- if (s && (s.indexOf("[B@") === 0 || s.indexOf("[object") === 0)) {
- // ³¢ÊÔͨ¹ýÆäËû·½Ê½½âÂë
- try { s = String(new java.lang.String(raw, "UTF-8")); } catch (e) {}
- }
- raw = s;
- } else {
- raw = String(raw);
- }
- } catch (e) {
- LOG("DEBUG", "ÎÞ·¨×ª»»ÏûÏ¢ÀàÐÍ: " + (typeof raw));
- return;
- }
- }
- if (!raw || typeof raw !== "string") return;
- // Ö»ÒªÊÕµ½·þÎñÆ÷ÈκÎÓÐЧÏûÏ¢£¬¾ÍˢРlastPongAt£¨±ÈµÈ PONG ¸ü¿É¿¿£©
- var nowTs = new Date().getTime();
- G.lastPongAt = nowTs;
- // ¼æÈݾɰæÎı¾ÐÒé
- if (raw.indexOf("WELCOME|") === 0) {
- LOG("INFO", "·þÎñÆ÷»¶Ó " + raw.split("|")[1]);
- return;
- }
- if (raw === "PONG" || raw.indexOf("ECHO|") === 0) {
- return;
- }
- // JSON ÐÒé
- var obj = null;
- try { obj = JSON.parse(raw); } catch (e) {
- LOG("DEBUG", "·ÇJSONÏûÏ¢: " + raw.substring(0, 60));
- return;
- }
- if (!obj || typeof obj !== "object") return;
- // ģʽ 1£º´¦Àí ACK
- if (obj.type === "ACK" && obj.ack_for) {
- handleAck(obj.ack_for);
- return;
- }
- // ģʽ 2£º´¦ÀíÏìÓ¦
- if (obj.type === "RESPONSE" && obj.req_id) {
- handleResponse(obj);
- return;
- }
- // ģʽ 3£ºÐòÁкżì²é
- if (obj.seq && !handleIncomingSeq(obj.seq)) {
- return; // ÖØ¸´ÏûÏ¢£¬¶ªÆú
- }
- // °´ÀàÐÍ·Ö·¢£¨Íâ²ã try-catch ·ÀÖ¹ #0 ±ÀÀ££©
- try {
- switch (obj.type) {
- case "PONG":
- var pingSentAt = (obj.data && obj.data.t) ? obj.data.t : (obj.ack_for ? obj.ack_for : nowTs);
- G.pingRtt = (pingSentAt && pingSentAt < nowTs) ? (nowTs - pingSentAt) : 0;
- LOG("DEBUG", "PONG RTT=" + G.pingRtt + "ms");
- break;
- case "PING":
- sendMsg("PONG", { echo_ts: obj.ts || 0, t: new Date().getTime() }, { no_ack: true });
- break;
- case "CMD":
- handleRemoteCommand(obj.cmd, obj.params, obj.req_id);
- break;
- // ģʽ 5£º·ÖƬ½ÓÊÕ
- case "CHUNK_START":
- handleChunkStart(obj.data);
- break;
- case "CHUNK_DATA":
- handleChunkData(obj.data);
- break;
- case "CHUNK_END":
- handleChunkEnd(obj.data);
- break;
- // ģʽ 5 ²¹³ä£º·þÎñ¶Ë/¿ØÖÆÃæ°åÇëÇóÖØ´«È±Ê§·ÖƬ
- case "CHUNK_RESEND":
- handleChunkResend(obj.data || {});
- break;
- // ƵµÀÏûÏ¢
- case "CHANNEL":
- LOG("INFO", "ƵµÀÏûÏ¢ ch=" + (obj.data.channel || "?") + " content=" + JSON.stringify(obj.data.payload || "").substring(0, 100));
- break;
- case "RESYNC":
- // ·þÎñ¶Ë²¹·¢µÄÏûÏ¢
- LOG("INFO", "ÊÕµ½²¹·¢ÏûÏ¢ seq=" + obj.seq);
- break;
- case "RESPONSE":
- // ·þÎñÆ÷ת·¢µÄÆäËû¿Í»§¶ËµÄÏìÓ¦£¨ºöÂÔ£¬ÎÒÃÇÒÑÔÚ handleResponse ´¦Àí£©
- LOG("DEBUG", "ÊÕµ½ RESPONSE req_id=" + (obj.req_id || "?"));
- break;
- case "WELCOME":
- LOG("INFO", "·þÎñÆ÷»¶Ó " + (obj.data ? obj.data.client_id : "?"));
- break;
- default:
- LOG("DEBUG", "δ֪ÏûÏ¢ type=" + obj.type);
- }
- } catch (eSwitch) {
- LOG("WARN", "ÏûÏ¢·Ö·¢Òì³£ type=" + obj.type + " err=" + (eSwitch.message || String(eSwitch)));
- }
- // ģʽ 1£ºÊÕµ½ÏûÏ¢ºó×Ô¶¯»Ø ACK£¨³ý·Ç±ê¼Ç no_ack£©
- if (obj.seq && !obj.no_ack) {
- sendMsg("ACK", { ack_for: obj.seq }, { no_ack: true });
- }
- }
- // Ô¶³ÌÖ¸Á¶ÔÆë WebSocketÊÖ»ú¶Ë³£ÓÃʵÀý.js µÄ 9 Àà CMD£¬Ê¹¿ØÖÆÃæ°åÄÜÖ±½Ó²Ù×÷ÊÖ»ú£©
- function handleRemoteCommand(cmd, params, reqId) {
- params = params || {};
- LOG("CMD", cmd + " params=" + JSON.stringify(params).substring(0, 200));
- var result = { ok: true, data: null, error: "" };
- try {
- // ÖÕ¼«²ßÂÔ£ºÖ±½ÓʹÓà auto/device/app ¶ÔÏóµÄÔÉú API
- // ³¹µ×ÈÆ¿ª click/swipe µÈ AIWROK ÒýÇæ±£ÁôÃû³åÍ»
- function _HAS(obj, name) { return !!(obj && typeof obj[name] === "function"); }
- // ===== ´¥¿Ø =====
- function _doClick(x, y) {
- x = parseInt(x || 0); y = parseInt(y || 0);
- if (_HAS(auto, "clickPoint")) return auto.clickPoint(x, y);
- if (_HAS(auto, "clickPercent")) {
- var sw = screen.getScreenWidth(), sh = screen.getScreenHeight();
- return auto.clickPercent(x / sw, y / sh);
- }
- if (_HAS(gesture, "click")) return gesture.click(x, y);
- if (_HAS(device, "click")) return device.click(x, y);
- throw new Error("click_api_not_found");
- }
- function _doSwipe(x1, y1, x2, y2, d) {
- x1 = parseInt(x1 || 0); y1 = parseInt(y1 || 0);
- x2 = parseInt(x2 || 0); y2 = parseInt(y2 || 0);
- d = parseInt(d || 400);
- if (_HAS(auto, "swip")) return auto.swip(x1, y1, x2, y2, d, 0);
- if (_HAS(auto, "dispatchGesture") && _HAS(gesture, "setDurTime")) {
- gesture.setDurTime(d);
- gesture.addPoint(x1, y1);
- gesture.addPoint(x2, y2);
- return auto.dispatchGesture([gesture]);
- }
- if (_HAS(gesture, "swipe")) return gesture.swipe(x1, y1, x2, y2, d);
- if (_HAS(device, "swipe")) return device.swipe(x1, y1, x2, y2, d);
- throw new Error("swipe_api_not_found");
- }
- function _doInput(t) {
- t = String(t == null ? "" : t);
- if (_HAS(auto, "input")) return auto.input(t);
- if (_HAS(auto, "sendText")) return auto.sendText(t);
- if (_HAS(auto, "inputText")) return auto.inputText(t);
- if (_HAS(device, "input")) return device.input(t);
- throw new Error("input_api_not_found");
- }
- // ===== °´¼ü =====
- function _doKey(fnName, keyCode, keyName) {
- if (_HAS(auto, fnName)) return auto[fnName]();
- if (_HAS(device, keyName)) return device[keyName]();
- if (_HAS(device, "pressKey")) return device.pressKey(keyCode);
- if (_HAS(device, "press")) return device.press(keyCode);
- if (_HAS(runtime, "postEvent")) {
- var KEv = android.view.KeyEvent;
- return runtime.postEvent(KEv.ACTION_DOWN, keyCode);
- }
- throw new Error("key_" + fnName + "_not_found");
- }
- // ===== Ó¦ÓùÜÀí =====
- function _openAppDirect(pkg) {
- pkg = String(pkg || "");
- if (_HAS(app, "openApp")) return app.openApp(pkg);
- if (_HAS(app, "openAppv2")) return app.openAppv2(pkg);
- if (_HAS(app, "openAppSeting")) return app.openAppSeting(pkg);
- if (_HAS(app, "launch")) return app.launch(pkg);
- if (_HAS(app, "open")) return app.open(pkg);
- if (_HAS(device, "startApp")) return device.startApp(pkg);
- if (_HAS(device, "launchApp")) return device.launchApp(pkg);
- if (_HAS(device, "openApp")) return device.openApp(pkg);
- var ctxt = context || runtime.getContext();
- var Intent = android.content.Intent;
- var pm = ctxt.getPackageManager();
- var li = pm.getLaunchIntentForPackage(pkg);
- if (li) { li.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ctxt.startActivity(li); return true; }
- throw new Error("openApp_intent_fail");
- }
- function _openByNameDirect(name) {
- name = String(name || "");
- if (_HAS(app, "openApp")) { try { return app.openApp(name); } catch (e1) {} }
- if (_HAS(app, "openAppv2")) { try { return app.openAppv2(name); } catch (e2) {} }
- if (_HAS(app, "launchByName")) { try { return app.launchByName(name); } catch (e3) {} }
- if (_HAS(app, "openByName")) { try { return app.openByName(name); } catch (e4) {} }
- if (_HAS(device, "openAppByName")) { try { return device.openAppByName(name); } catch (e5) {} }
- if (_HAS(device, "launchByName")) { try { return device.launchByName(name); } catch (e6) {} }
- // Ä£ºýÆ¥Åä
- var ctxt2 = context || runtime.getContext();
- var pm2 = ctxt2.getPackageManager();
- var apps = pm2.getInstalledApplications(0);
- var target = name.toLowerCase();
- var iter = apps.iterator();
- while (iter.hasNext()) {
- var ai = iter.next();
- var label = String(pm2.getApplicationLabel(ai) || "").toLowerCase();
- var pkg = String(ai.packageName || "").toLowerCase();
- if (label === target || pkg === target || label.indexOf(target) !== -1 || pkg.indexOf(target) !== -1) {
- return _openAppDirect(ai.packageName);
- }
- }
- throw new Error("app_name_not_found:" + name);
- }
- function _killAppDirect(pkg) {
- pkg = String(pkg || "");
- if (_HAS(app, "forceStop")) return app.forceStop(pkg);
- if (_HAS(app, "kill")) return app.kill(pkg);
- if (_HAS(app, "close")) return app.close(pkg);
- if (_HAS(device, "forceStopApp")) return device.forceStopApp(pkg);
- if (_HAS(device, "killApp")) return device.killApp(pkg);
- if (_HAS(device, "killBackgroundProcesses")) return device.killBackgroundProcesses(pkg);
- var ctxt3 = context || runtime.getContext();
- var am = ctxt3.getSystemService(android.content.Context.ACTIVITY_SERVICE);
- am.killBackgroundProcesses(pkg);
- return true;
- }
- switch (cmd) {
- case "SCREENSHOT":
- var shotRes = sendScreenshotChunked(params.id || ("shot_" + new Date().getTime()));
- if (shotRes && shotRes.ok) {
- result.data = shotRes;
- } else {
- result.ok = false;
- result.error = (shotRes && shotRes.error_msg) ? shotRes.error_msg : "screenshot_failed";
- result.data = shotRes || {};
- }
- break;
- case "CLICK":
- if (params.x != null && params.y != null) {
- var cx = parseInt(params.x);
- var cy = parseInt(params.y);
- try { _doClick(cx, cy); result.data = { x: cx, y: cy }; }
- catch (e) { result.ok = false; result.error = e.message; }
- } else {
- result.ok = false; result.error = "missing_xy";
- }
- break;
- case "SWIPE":
- if (params.x1 != null && params.y1 != null && params.x2 != null && params.y2 != null) {
- var x1 = parseInt(params.x1); var y1 = parseInt(params.y1);
- var x2 = parseInt(params.x2); var y2 = parseInt(params.y2);
- var dur = parseInt(params.duration_ms || 400);
- try { _doSwipe(x1, y1, x2, y2, dur); result.data = { x1: x1, y1: y1, x2: x2, y2: y2, duration_ms: dur }; }
- catch (e) { result.ok = false; result.error = e.message; }
- } else {
- result.ok = false; result.error = "missing_points";
- }
- break;
- case "INPUT_TEXT":
- if (params.text != null) {
- var txt = String(params.text);
- try { _doInput(txt); result.data = { text: txt }; }
- catch (e) { result.ok = false; result.error = e.message; }
- } else {
- result.ok = false; result.error = "missing_text";
- }
- break;
- case "KEYCODE":
- if (params.code) {
- var code = String(params.code).toUpperCase();
- try {
- switch (code) {
- case "HOME": _doKey("home", 3, "home"); break;
- case "BACK": _doKey("back", 4, "back"); break;
- case "RECENT":
- case "APP_SWITCH":
- case "TASK":
- try { _doKey("recents", 187, "recentApps"); } catch (eA) { _doKey("recentApps", 187, "recentApps"); }
- break;
- case "NOTIFICATION":
- case "STATUS_BAR":
- try { _doKey("statusBar", 83, "statusBar"); } catch (eB) { _doKey("notification", 83, "notification"); }
- break;
- case "POWER": _doKey("power", 26, "power"); break;
- case "VOL_UP":
- try { device.setMusicVolume(device.getMusicVolume() + 1); } catch (e) { throw new Error("vol_up_failed"); }
- break;
- case "VOL_DOWN":
- try { device.setMusicVolume(Math.max(0, device.getMusicVolume() - 1)); } catch (e) { throw new Error("vol_down_failed"); }
- break;
- case "MENU": _doKey("menu", 82, "menu"); break;
- case "SEARCH": _doKey("search", 84, "search"); break;
- case "CAMERA": _doKey("camera", 27, "camera"); break;
- case "ENTER": try { _doInput("\n"); } catch (e) {} break;
- case "DEL":
- case "BACKSPACE":
- try { var et = device.getFocusEditText(); if (et) { et.setText(""); } } catch (e) { throw new Error("del_failed"); }
- break;
- default:
- throw new Error("unknown_keycode_" + code);
- }
- result.data = { code: code };
- } catch (e) {
- result.ok = false; result.error = e.message;
- }
- } else {
- result.ok = false; result.error = "missing_code";
- }
- break;
- case "OPEN_APP":
- try {
- if (params.pkg) {
- _openAppDirect(String(params.pkg));
- result.data = { opened: "pkg:" + params.pkg };
- } else if (params.name) {
- _openByNameDirect(String(params.name));
- result.data = { opened: "name:" + params.name };
- } else if (params.package_name) {
- _openAppDirect(String(params.package_name));
- result.data = { opened: "pkg:" + params.package_name };
- } else {
- result.ok = false; result.error = "missing_pkg_or_name";
- }
- } catch (e) {
- result.ok = false; result.error = e.message;
- }
- break;
- case "KILL_APP":
- try {
- var pkg = params.pkg || params.package_name;
- if (pkg) {
- _killAppDirect(String(pkg));
- result.data = { killed: pkg };
- } else {
- result.ok = false; result.error = "missing_pkg";
- }
- } catch (e) {
- result.ok = false; result.error = e.message;
- }
- break;
- case "REPORT_STATUS":
- case "GET_STATUS":
- result.data = collectStatusFull();
- break;
- case "RUN_SCRIPT":
- if (params.code != null) {
- try {
- var codeStr = String(params.code);
- var r = (new Function(codeStr))();
- result.data = { returned: (r === undefined ? null : (typeof r === "object" ? r : String(r))) };
- } catch (e) {
- result.ok = false; result.error = e.message;
- }
- } else {
- result.ok = false; result.error = "missing_code";
- }
- break;
- case "SUBSCRIBE":
- if (params.channel) subscribe(params.channel);
- result.data = "subscribed";
- break;
- case "UNSUBSCRIBE":
- if (params.channel) unsubscribe(params.channel);
- result.data = "unsubscribed";
- break;
- case "ECHO":
- result.data = params;
- break;
- case "QUIT":
- result.data = "bye";
- sendMsg("RESPONSE", result, { req_id: reqId, no_ack: true });
- G.running = false;
- try { G.ws.close(); } catch (e) {}
- return;
- default:
- result.ok = false;
- result.error = "unknown_cmd_" + cmd;
- }
- } catch (e) {
- result.ok = false;
- result.error = e.message;
- }
- // ÒÔ RESPONSE ÐÎʽ»Ø¸´£¨Ä£Ê½ 2£ºÇëÇó-ÏìÓ¦Åä¶Ô£©
- // ⚠️ ±ØÐëͨ¹ý opts.req_id °Ñ req_id ·Åµ½ÏûÏ¢¶¥²ã£¬·þÎñÆ÷ obj.get("req_id") ²ÅÄÜÈ¡µ½
- // ²»Òª°Ñ req_id °üÔÚµÚ¶þ¸ö²ÎÊý data À·ñÔò buildMsg »á±ä³É msg.data.req_id
- if (reqId) {
- sendMsg("RESPONSE", result, { req_id: reqId, no_ack: true });
- }
- }
- function collectStatus() {
- // ¼æÈݾɵ÷Ó÷½£º×Ö¶ÎÃûÈ«²¿¶ÔÆë"й淶"£¬±ÜÃâ·µ»ØÀÏÃû×Ö battery/charging
- var s = {
- battery_level: 0,
- battery_charging: false,
- network_type: "unknown",
- storage_free_mb: 0
- };
- try { s.battery_level = device.getBatteryLevel(); } catch (e) {}
- try { s.battery_charging = device.isCharging(); } catch (e) {}
- try { s.network_type = device.getNetworkType(); } catch (e) {}
- // ¾É×ֶΣ¨±£Áô·ÀÖ¹ÀÏ´úÂëÒÀÀµ£©
- try { s.battery = s.battery_level; } catch (e) {}
- try { s.charging = s.battery_charging; } catch (e) {}
- try { s.network = s.network_type; } catch (e) {}
- return s;
- }
- // ÍêÕûµÄ״̬Éϱ¨£¨ËùÓÐÇé¿ö϶¼±£Ö¤×Ö¶ÎÃûÒÔ"й淶"ÓÅÏȳöÏÖ£¬²»»áÓÐ null£©
- function collectStatusFull() {
- var s = {
- battery_level: 0,
- battery_charging: false,
- network_type: "unknown",
- signal_level: 0,
- storage_total_mb: 0,
- storage_free_mb: 0,
- ram_free_mb: 0,
- screen_on: true,
- brightness: 0,
- volume: 0,
- sdk: 0,
- brand: "",
- model: "",
- debug: ""
- };
- var errors = [];
- var gotInfo = false;
- try {
- var info = device.getInfo && device.getInfo();
- if (info && typeof info === "object") {
- for (var k in info) if (info.hasOwnProperty(k)) {
- try { s[k] = info[k]; } catch (e) {}
- }
- gotInfo = true;
- }
- } catch (e) { errors.push("getInfo:" + e.message); }
- // Öð×ֶζµµ×£¨getInfo ÀïµÄ×Ö¶ÎÃû¿ÉÄÜÎ廨°ËÃÅ£¬ËùÒÔÿ¸ö¶¼ÖØÐµ¥¶À¶ÁÒ»´Î¸²¸Ç£©
- try { s.battery_level = device.getBatteryLevel(); } catch (e) { errors.push("battery:" + e.message); }
- try { s.battery_charging = device.isCharging(); } catch (e) { errors.push("charging:" + e.message); }
- try { s.network_type = device.getNetworkType(); } catch (e) { errors.push("net:" + e.message); }
- try { s.signal_level = device.getSignalLevel(); } catch (e) { errors.push("signal:" + e.message); }
- try { s.storage_total_mb = device.getTotalInternalMemorySize() / 1048576; } catch (e) { errors.push("stotal:" + e.message); }
- try { s.storage_free_mb = device.getAvailableInternalMemorySize() / 1048576; } catch (e) { errors.push("sfree:" + e.message); }
- try { s.ram_free_mb = device.getAvailableMemorySize() / 1048576; } catch (e) { errors.push("ram:" + e.message); }
- try { s.screen_on = device.isScreenOn(); } catch (e) { errors.push("screen:" + e.message); }
- try { s.brightness = device.getScreenBrightness(); } catch (e) { errors.push("bright:" + e.message); }
- try { s.volume = device.getMusicVolume(); } catch (e) { errors.push("vol:" + e.message); }
- try { s.sdk = device.getSdkVersion(); } catch (e) { errors.push("sdk:" + e.message); }
- try { s.brand = device.getBrand(); } catch (e) { errors.push("brand:" + e.message); }
- try { s.model = device.getModel(); } catch (e) { errors.push("model:" + e.message); }
- // ¹Ø¼ü£ºÀÏ×Ö¶ÎÃû ¡ú ÐÂ×Ö¶ÎÃû ·´Ïòͬ²½£¨getInfo Àï¿ÉÄÜÖ»ÓÐÀÏÃû×Ö£¬¶øµ¥¶ÀAPIÓÖÅ×Ò쳣ʧ°Ü£©
- try {
- if ((s.battery_level === 0 || s.battery_level == null) && s.battery && typeof s.battery === "number") {
- s.battery_level = s.battery;
- }
- } catch (e) {}
- try {
- if (s.battery_charging == null && s.charging != null) {
- s.battery_charging = !!s.charging;
- }
- } catch (e) {}
- try {
- if ((!s.network_type || s.network_type === "unknown") && s.network) {
- s.network_type = String(s.network);
- }
- } catch (e) {}
- try {
- if (s.signal_level === 0 && s.signal != null && typeof s.signal === "number") {
- s.signal_level = s.signal;
- }
- } catch (e) {}
- try {
- if (s.storage_free_mb === 0 && s.storage_free != null && typeof s.storage_free === "number") {
- s.storage_free_mb = s.storage_free;
- }
- } catch (e) {}
- try {
- if (s.ram_free_mb === 0 && s.ram_free != null && typeof s.ram_free === "number") {
- s.ram_free_mb = s.ram_free;
- }
- } catch (e) {}
- // ͬʱ±£Ö¤"ÀÏ×Ö¶ÎÃû"Ò²´æÔÚ£¨¼æÈݾɵÄÉϲãÂß¼ / ¾É¿ØÖÆÃæ°å£©
- try { if (s.battery == null) s.battery = s.battery_level; } catch (e) {}
- try { if (s.charging == null) s.charging = s.battery_charging; } catch (e) {}
- try { if (!s.network) s.network = s.network_type; } catch (e) {}
- if (errors.length) s.debug = errors.join("; ");
- s._info_ok = gotInfo;
- return s;
- }
- function clearPendingTimers() {
- // ÇåÀíËùÓÐ ACK µÈ´ýÖеĶ¨Ê±Æ÷£¨¶ÏÏߺó²»Ó¦¼ÌÐøÖØ´«£¬µÈÖØÁ¬ºóÓÉÀëÏß¶ÓÁÐÖØ·¢£©
- var keys = [];
- for (var k in G.pendingAcks) if (G.pendingAcks.hasOwnProperty(k)) keys.push(k);
- for (var i = 0; i < keys.length; i++) {
- var entry = G.pendingAcks[keys[i]];
- if (entry && entry.timerId) {
- try { runTime.stopTimeout(entry.timerId); } catch (e) {}
- }
- }
- G.pendingAcks = {};
- // ÇåÀíËùÓÐÇëÇóµÈ´ýÖеĶ¨Ê±Æ÷
- var rkeys = [];
- for (var rk in G.pendingRequests) if (G.pendingRequests.hasOwnProperty(rk)) rkeys.push(rk);
- for (var j = 0; j < rkeys.length; j++) {
- var rentry = G.pendingRequests[rkeys[j]];
- if (rentry && rentry.timerId) {
- try { runTime.stopTimeout(rentry.timerId); } catch (e) {}
- // ²»É¾³ý entry£¬Èà flushOfflineQueue ¿ÉÒÔ°ÑÇëÇóÖØÐ·¢³ö
- }
- }
- }
- // ============================================================
- // Á¬½Ó & ÖØÁ¬
- // ============================================================
- function doConnect() {
- G.connected = false;
- var ws = new websocket();
- G.ws = ws;
- try { ws.setSocketID(G.socketID); } catch (e) {}
- try {
- ws.event(
- function () { // onopen
- G.connected = true;
- G.retryCount = 0;
- LOG("OK", "Á¬½Ó³É¹¦");
- // ģʽ 3£ºÖØÁ¬ºóÇëÇó²¹·¢
- requestResyncOnReconnect();
- // ģʽ 4£ºflush ÀëÏß¶ÓÁÐ
- flushOfflineQueue();
- // ģʽ 6£ºÆô¶¯×ÔÊÊÓ¦ÐÄÌø
- startHeartbeat();
- startNetworkCheck();
- // ģʽ 8£º×Ô¶¯¶©ÔÄÆµµÀ
- autoSubscribeChannels();
- // ·¢ËÍ×¢²á£¨Ð¯´øÉ豸ÐÅÏ¢¸ø¿ØÖÆÃæ°åÕ¹Ê¾Æ·ÅÆ/Ðͺţ©
- var brand = ""; var model = "";
- try { brand = device.getBrand(); } catch (e) {}
- try { model = device.getModel(); } catch (e) {}
- sendMsg("REGISTER", {
- socket_id: G.socketID,
- version: "2.0",
- features: ["ack", "req_resp", "resync", "offline_queue",
- "chunked", "adaptive_heartbeat", "backpressure", "channels"]
- });
- // ÉÏÏߺóÁ¢¿Ì±¨Ò»´Î״̬£¬ÈÿØÖÆÃæ°åÄÜ¿´µ½
- try {
- var s = collectStatusFull();
- if (s) sendMsg("STATUS", s, { no_ack: true });
- } catch (e) {}
- },
- function (msg) { // onmessage
- try { handleServerMessage(msg); }
- catch (e) { LOG("ERROR", "ÏûÏ¢´¦ÀíÒì³£: " + e.message); }
- },
- function (err) { // onerror
- LOG("WARN", "Á¬½Óʧ°Ü");
- G.connected = false;
- stopHeartbeat();
- stopNetworkCheck();
- clearPendingTimers();
- },
- function () { // onclose
- G.connected = false;
- stopHeartbeat();
- stopNetworkCheck();
- clearPendingTimers();
- }
- );
- } catch (e) {
- LOG("ERROR", "event×¢²áʧ°Ü: " + e.message);
- return false;
- }
- var url = "ws://" + CONFIG.SERVER_IP + ":" + CONFIG.SERVER_PORT + CONFIG.WS_PATH;
- try {
- ws.connet(url);
- return true;
- } catch (e) {
- LOG("ERROR", "connetÒì³£: " + e.message);
- return false;
- }
- }
- function computeRetryMs(n) {
- var ms = CONFIG.RETRY_BASE_MS * Math.pow(2, n);
- return Math.min(ms, CONFIG.RETRY_MAX_MS) + Math.floor(Math.random() * 1000);
- }
- // ============================================================
- // Ö÷Ñ»·
- // ============================================================
- function main() {
- LOG("INFO", "=== WebSocket ¸ß¼¶¼¼Çɺϼ¯ v2.0 Æô¶¯ ===");
- LOG("INFO", "8´óģʽ: ACKÖØ´« / ÇëÇóÏìÓ¦ / ÐòÁкŲ¹³¥ / ÀëÏß¶ÓÁÐ / ·ÖƬ´«Êä / ×ÔÊÊÓ¦ÐÄÌø / ±³Ñ¹ / ƵµÀ¶©ÔÄ");
- while (G.running) {
- G.retryCount++;
- doConnect();
- var wait = 0;
- while (!G.connected && wait < 150) {
- sleep.millisecond(100);
- wait++;
- }
- if (!G.connected) {
- var ms = computeRetryMs(G.retryCount - 1);
- LOG("WARN", "Á¬½Óʧ°Ü " + (ms / 1000).toFixed(1) + "s ºóÖØÊÔ");
- try { G.ws.close(); } catch (e) {}
- sleep.millisecond(ms);
- continue;
- }
- while (G.running && G.connected) {
- sleep.millisecond(500);
- }
- if (!G.running) break;
- var ms2 = computeRetryMs(G.retryCount - 1);
- LOG("WARN", "¶ÏÏß " + (ms2 / 1000).toFixed(1) + "s ºóÖØÁ¬");
- try { G.ws.close(); } catch (e) {}
- sleep.millisecond(ms2);
- }
- // ÇåÀí
- stopHeartbeat();
- stopNetworkCheck();
- try { G.ws.close(); } catch (e) {}
- LOG("INFO", "½Å±¾½áÊø");
- exit();
- }
- // Æô¶¯
- try {
- main();
- } catch (e) {
- LOG("FATAL", e.message + "\n" + (e.stack || ""));
- try { G.ws.close(); } catch (e2) {}
- exit();
- }
¸´ÖÆ´úÂë
|
|