/** * 微信小程序 MQTT 适配库 v2.0 * 修复 WebSocket 连接问题,增加调试日志 */ !function(root) { 'use strict'; if (typeof exports === 'object' && typeof module !== 'undefined') { module.exports = factory(); } else if (typeof define === 'function' && define.amd) { define(factory); } else { root.mqtt = factory(); } function factory() { // 微信小程序 WebSocket 适配器 class WxWebSocket { constructor(url, protocols) { console.log('[WxWebSocket] 初始化:', url, protocols); this.url = url; this.protocols = protocols; this.readyState = 0; // CONNECTING this._socket = null; this.onopen = null; this.onclose = null; this.onerror = null; this.onmessage = null; this._connect(); } _connect() { const that = this; console.log('[WxWebSocket] 开始连接:', this.url); try { // 使用 SocketTask 进行连接 const socketTask = wx.connectSocket({ url: this.url, protocols: this.protocols ? [this.protocols] : [], success: () => { console.log('[WxWebSocket] 连接请求已发送'); }, fail: (err) => { console.error('[WxWebSocket] 连接失败:', err); that.readyState = 3; that.onerror && that.onerror(new Error('WebSocket connection failed: ' + JSON.stringify(err))); } }); this._socket = socketTask; // 监听特定连接的事件 socketTask.onOpen((res) => { console.log('[WxWebSocket] 连接已打开'); that.readyState = 1; // OPEN that.onopen && that.onopen({ type: 'open' }); }); socketTask.onMessage((res) => { console.log('[WxWebSocket] 收到消息, 数据类型:', typeof res.data); if (that.onmessage) { let data = res.data; if (typeof data === 'string') { data = that._stringToArrayBuffer(data); } that.onmessage({ data }); } }); socketTask.onError((err) => { console.error('[WxWebSocket] 连接错误:', err); that.readyState = 3; that.onerror && that.onerror(new Error('WebSocket error: ' + JSON.stringify(err))); }); socketTask.onClose((res) => { console.log('[WxWebSocket] 连接关闭:', res); that.readyState = 3; that.onclose && that.onclose(res); }); } catch (err) { console.error('[WxWebSocket] 创建连接异常:', err); that.readyState = 3; that.onerror && that.onerror(err); } } _stringToArrayBuffer(str) { const buffer = new ArrayBuffer(str.length); const view = new Uint8Array(buffer); for (let i = 0; i < str.length; i++) { view[i] = str.charCodeAt(i); } return buffer; } send(data, callback) { if (this.readyState !== 1) { console.error('[WxWebSocket] 发送失败: 连接未打开, readyState:', this.readyState); callback && callback(new Error('WebSocket is not open')); return; } try { let sendData = data; if (data instanceof ArrayBuffer) { sendData = data; } else if (typeof data === 'string') { sendData = data; } console.log('[WxWebSocket] 发送数据, 大小:', sendData.byteLength || sendData.length); this._socket.send({ data: sendData, success: () => { console.log('[WxWebSocket] 发送成功'); callback && callback(); }, fail: (err) => { console.error('[WxWebSocket] 发送失败:', err); callback && callback(new Error('Send failed: ' + JSON.stringify(err))); } }); } catch (err) { console.error('[WxWebSocket] 发送异常:', err); callback && callback(err); } } close() { console.log('[WxWebSocket] 关闭连接, readyState:', this.readyState); if (this.readyState === 3) return; this.readyState = 2; // CLOSING this._socket.close({ success: () => { console.log('[WxWebSocket] 关闭成功'); } }); } } // MQTT 客户端 class MqttClient { constructor(url, options) { console.log('[MqttClient] 初始化客户端, URL:', url, 'Options:', options); this.url = url; this.options = options || {}; this.ws = null; this.connected = false; this.messageId = 1; this.subscriptions = new Map(); this.onConnect = null; this.onMessage = null; this.onError = null; this.onClose = null; this.onReconnect = null; // 连接参数 this.clientId = options.clientId || 'mqtt_' + Math.random().toString(16).substr(2, 8); this.username = options.username; this.password = options.password; this.keepalive = options.keepalive || 60; this.clean = options.clean !== false; this.reconnectPeriod = options.reconnectPeriod || 5000; this.connectTimeout = options.connectTimeout || 30000; this._connectTimer = null; this._reconnectTimer = null; this._keepaliveTimer = null; } connect() { return new Promise((resolve, reject) => { console.log('[MqttClient] 开始连接, 超时时间:', this.connectTimeout); const timeoutId = setTimeout(() => { if (!this.connected) { console.error('[MqttClient] 连接超时'); this.ws && this.ws.close(); reject(new Error('Connection timeout')); } }, this.connectTimeout); try { this.ws = new WxWebSocket(this.url, 'mqtt'); this.ws.onopen = () => { console.log('[MqttClient] WebSocket 已打开,发送 MQTT CONNECT'); clearTimeout(timeoutId); this._sendConnect(); }; this.ws.onmessage = (event) => { this._handleMessage(event.data); }; this.ws.onerror = (err) => { console.error('[MqttClient] WebSocket 错误:', err); clearTimeout(timeoutId); this.connected = false; this.onError && this.onError(err); reject(err); }; this.ws.onclose = () => { console.log('[MqttClient] WebSocket 关闭'); clearTimeout(timeoutId); this.connected = false; this.onClose && this.onClose(); this._scheduleReconnect(); }; } catch (err) { console.error('[MqttClient] 创建 WebSocket 异常:', err); clearTimeout(timeoutId); reject(err); } }); } _sendConnect() { console.log('[MqttClient] 构建 CONNECT 包'); const packet = this._buildConnectPacket(); console.log('[MqttClient] CONNECT 包大小:', packet.byteLength, 'bytes'); this.ws.send(packet, (err) => { if (err) { console.error('[MqttClient] 发送 CONNECT 失败:', err); this.onError && this.onError(err); } else { console.log('[MqttClient] CONNECT 包已发送,等待 CONNACK'); } }); } _buildConnectPacket() { const protocolId = 'MQTT'; const protocolVersion = 4; const clientId = this.clientId; // Flags: Clean Session + Username + Password let flags = 0x02; // Clean Session if (this.username) flags |= 0x80; if (this.password) flags |= 0x40; const keepalive = this.keepalive; // 构建 Variable Header const variableHeader = [ ...this._encodeString(protocolId), protocolVersion, flags, (keepalive >> 8) & 0xFF, keepalive & 0xFF ]; // 构建 Payload let payload = this._encodeString(clientId); if (this.username) { payload = payload.concat(this._encodeString(this.username)); } if (this.password) { payload = payload.concat(this._encodeString(this.password)); } // 构建 Fixed Header const remainingLength = variableHeader.length + payload.length; const fixedHeader = [0x10, ...this._encodeRemainingLength(remainingLength)]; // 合并为一个数组 const packet = fixedHeader.concat(variableHeader).concat(payload); // 转换为 ArrayBuffer return new Uint8Array(packet).buffer; } _encodeString(str) { const length = str.length; const bytes = []; bytes.push((length >> 8) & 0xFF); bytes.push(length & 0xFF); for (let i = 0; i < str.length; i++) { bytes.push(str.charCodeAt(i)); } return bytes; } _encodeRemainingLength(length) { const result = []; let x = length; do { let byte = x % 128; x = Math.floor(x / 128); if (x > 0) byte |= 0x80; result.push(byte); } while (x > 0); return result; } _handleMessage(data) { const view = new Uint8Array(data); const type = view[0] >> 4; console.log('[MqttClient] 收到消息, 类型:', type, '长度:', view.length); switch (type) { case 2: // CONNACK this._handleConnack(view); break; case 3: // PUBLISH this._handlePublish(view); break; case 4: // PUBACK console.log('[MqttClient] 收到 PUBACK'); break; case 9: // SUBACK console.log('[MqttClient] 收到 SUBACK'); break; case 13: // PINGRESP console.log('[MqttClient] 收到 PINGRESP'); break; } } _handleConnack(view) { console.log('[MqttClient] 收到 CONNACK, 长度:', view.length); const flags = view[2]; const returnCode = view[3]; console.log('[MqttClient] CONNACK flags:', flags, 'returnCode:', returnCode); if (returnCode === 0) { this.connected = true; console.log('[MqttClient] 连接成功'); this._startKeepalive(); this.onConnect && this.onConnect(); } else { const errors = [ '', 'Unacceptable protocol version', 'Identifier rejected', 'Server unavailable', 'Bad username or password', 'Not authorized' ]; const errorMsg = errors[returnCode] || 'Unknown error'; console.error('[MqttClient] 连接被拒绝:', errorMsg); this.onError && this.onError(new Error('Connection refused: ' + errorMsg)); } } _handlePublish(view) { console.log('[MqttClient] 收到 PUBLISH 消息'); let pos = 2; const topicLength = (view[pos] << 8) | view[pos + 1]; pos += 2; const topic = String.fromCharCode(...view.slice(pos, pos + topicLength)); pos += topicLength; const qos = (view[0] >> 1) & 0x03; if (qos > 0) pos += 2; // 跳过 messageId const payload = view.slice(pos); console.log('[MqttClient] 主题:', topic, '载荷长度:', payload.length); this.onMessage && this.onMessage(topic, { payload: payload, toString: () => String.fromCharCode(...payload) }); } _startKeepalive() { console.log('[MqttClient] 启动心跳, 间隔:', this.keepalive, '秒'); if (this._keepaliveTimer) clearInterval(this._keepaliveTimer); this._keepaliveTimer = setInterval(() => { if (this.connected) { this._sendPing(); } }, this.keepalive * 1000); } _sendPing() { console.log('[MqttClient] 发送 PINGREQ'); const packet = new Uint8Array([0xC0, 0x00]); this.ws.send(packet.buffer); } subscribe(topic, options, callback) { if (typeof options === 'function') { callback = options; options = { qos: 0 }; } console.log('[MqttClient] 订阅主题:', topic, 'QoS:', options.qos); const qos = options.qos || 0; const messageId = this.messageId++; // Variable Header: Message ID const variableHeader = [ (messageId >> 8) & 0xFF, messageId & 0xFF ]; // Payload: Topic Filter + QoS const topicData = this._encodeString(topic); const payload = topicData.concat([qos]); // Fixed Header const remainingLength = variableHeader.length + payload.length; const fixedHeader = [0x82, ...this._encodeRemainingLength(remainingLength)]; const packet = fixedHeader.concat(variableHeader).concat(payload); this.subscriptions.set(topic, callback); this.ws.send(new Uint8Array(packet).buffer, (err) => { if (err) { console.error('[MqttClient] 订阅失败:', err); this.onError && this.onError(err); } else { console.log('[MqttClient] 订阅包已发送'); } }); } publish(topic, message, options, callback) { if (typeof options === 'function') { callback = options; options = { qos: 0 }; } console.log('[MqttClient] 发布消息, 主题:', topic, 'QoS:', options.qos); const qos = options.qos || 0; const retain = options.retain ? 1 : 0; const dup = 0; let flags = (dup << 3) | (qos << 1) | retain; let messageId = 0; if (qos > 0) { messageId = this.messageId++; } // Topic const topicData = this._encodeString(topic); let variableHeader = topicData; if (qos > 0) { variableHeader = variableHeader.concat([ (messageId >> 8) & 0xFF, messageId & 0xFF ]); } // Payload let payload = []; if (typeof message === 'string') { for (let i = 0; i < message.length; i++) { payload.push(message.charCodeAt(i)); } } else if (message instanceof ArrayBuffer) { payload = Array.from(new Uint8Array(message)); } // Fixed Header const remainingLength = variableHeader.length + payload.length; const fixedHeader = [0x30 | flags, ...this._encodeRemainingLength(remainingLength)]; const packet = fixedHeader.concat(variableHeader).concat(payload); this.ws.send(new Uint8Array(packet).buffer, (err) => { callback && callback(err); }); } unsubscribe(topic, callback) { console.log('[MqttClient] 取消订阅:', topic); const messageId = this.messageId++; const topicData = this._encodeString(topic); const variableHeader = [ (messageId >> 8) & 0xFF, messageId & 0xFF ]; const remainingLength = variableHeader.length + topicData.length; const fixedHeader = [0xA2, ...this._encodeRemainingLength(remainingLength)]; const packet = fixedHeader.concat(variableHeader).concat(topicData); this.subscriptions.delete(topic); this.ws.send(new Uint8Array(packet).buffer, (err) => { callback && callback(err); }); } end(force, callback) { if (typeof force === 'function') { callback = force; force = false; } console.log('[MqttClient] 断开连接'); if (this._keepaliveTimer) clearInterval(this._keepaliveTimer); if (this._reconnectTimer) clearTimeout(this._reconnectTimer); this.connected = false; // 发送 DISCONNECT 包 const packet = new Uint8Array([0xE0, 0x00]); this.ws.send(packet.buffer); setTimeout(() => { this.ws.close(); callback && callback(); }, 100); } _scheduleReconnect() { if (this.reconnectPeriod > 0 && !this._reconnectTimer) { console.log('[MqttClient] 计划重连, 延迟:', this.reconnectPeriod); this._reconnectTimer = setTimeout(() => { this._reconnectTimer = null; this.onReconnect && this.onReconnect(); this.connect().catch(err => { console.error('[MqttClient] 重连失败:', err); this.onError && this.onError(err); }); }, this.reconnectPeriod); } } on(event, callback) { switch (event) { case 'connect': this.onConnect = callback; break; case 'message': this.onMessage = callback; break; case 'error': this.onError = callback; break; case 'close': this.onClose = callback; break; case 'reconnect': this.onReconnect = callback; break; } } } // 导出连接函数 function connect(url, options) { console.log('[MQTT] 创建客户端连接:', url); const client = new MqttClient(url, options); client.connect(); return client; } return { connect, MqttClient }; } }(typeof self !== 'undefined' ? self : this);