fastbee/yack-mini/miniprogram/libs/mqtt-wx.min.js
2026-09-27 14:46:05 +08:00

888 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 微信小程序 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;
// 心跳探活:PINGREQ 发出后等 PINGRESP 的时限(默认 keepalive/3,至少 3s)
this._pingTimer = null;
this._pingTimeout = options.pingTimeout || Math.max(3000, Math.floor((options.keepalive || 60) * 1000 / 3));
// 终止标记:end() 置位,此后一切自动重连都必须停手
// (这是"手动断开后 5s 又连回来"的根因防线)
this._ended = false;
// 连接代号:每次 connect() 自增,用于作废上一代 socket 的迟到回调
this._connToken = 0;
// 建连 Promise 的结算句柄(原实现成功路径永不 resolve)
this._pendingConnect = null;
// QoS>0 未确认报文:messageId -> { topic, message, qos, retain, messageId, attempts, timer, callback }
this._inflight = new Map();
this._pubackTimeout = options.pubackTimeout || 5000;
this._maxInflightRetry = options.maxInflightRetry || 3;
this._maxInflight = options.maxInflight || 50;
}
connect() {
// 新建连必须清掉终止标记,否则 end() 之后永远连不上
this._ended = false;
const token = ++this._connToken;
return new Promise((resolve, reject) => {
console.log('[MqttClient] 开始连接, 超时时间:', this.connectTimeout);
this._pendingConnect = { resolve, reject };
const timeoutId = setTimeout(() => {
if (this.connected) return;
console.error('[MqttClient] 连接超时');
const ws = this.ws;
if (ws) {
try { ws.close(); } catch (e) { /* 忽略 */ }
}
const err = new Error('Connection timeout');
this.onError && this.onError(err);
if (this._pendingConnect) {
const pending = this._pendingConnect;
this._pendingConnect = null;
pending.reject(err);
}
}, this.connectTimeout);
try {
this.ws = new WxWebSocket(this.url, 'mqtt');
this.ws.onopen = () => {
if (token !== this._connToken) return; // 上一代 socket 的迟到回调,丢弃
console.log('[MqttClient] WebSocket 已打开,发送 MQTT CONNECT');
clearTimeout(timeoutId);
this._sendConnect();
};
this.ws.onmessage = (event) => {
if (token !== this._connToken) return;
this._handleMessage(event.data);
};
this.ws.onerror = (err) => {
if (token !== this._connToken) return;
console.error('[MqttClient] WebSocket 错误:', err);
clearTimeout(timeoutId);
this.connected = false;
this.onError && this.onError(err);
if (this._pendingConnect) {
const pending = this._pendingConnect;
this._pendingConnect = null;
pending.reject(err);
}
};
this.ws.onclose = () => {
if (token !== this._connToken) return;
console.log('[MqttClient] WebSocket 关闭');
clearTimeout(timeoutId);
this.connected = false;
this._stopKeepalive();
this._failInflight('连接已关闭');
this.onClose && this.onClose();
// 主动 end() 后不得自连:_ended 是 end() 设下的终止标记
if (!this._ended) this._scheduleReconnect();
};
} catch (err) {
console.error('[MqttClient] 创建 WebSocket 异常:', err);
clearTimeout(timeoutId);
if (this._pendingConnect) {
const pending = this._pendingConnect;
this._pendingConnect = null;
pending.reject(err);
}
}
});
}
_sendConnect() {
console.log('[MqttClient] 构建 CONNECT 包');
const packet = this._buildConnectPacket();
console.log('[MqttClient] CONNECT 包大小:', packet.byteLength, 'bytes');
const ws = this.ws;
if (!ws) {
this.onError && this.onError(new Error('WebSocket 未就绪'));
return;
}
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
this._handlePuback(view);
break;
case 9: // SUBACK
console.log('[MqttClient] 收到 SUBACK');
break;
case 13: // PINGRESP
console.log('[MqttClient] 收到 PINGRESP');
if (this._pingTimer) {
clearTimeout(this._pingTimer);
this._pingTimer = null;
}
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();
if (this._pendingConnect) {
const pending = this._pendingConnect;
this._pendingConnect = null;
pending.resolve();
}
this.onConnect && this.onConnect();
return;
}
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);
const err = new Error('Connection refused: ' + errorMsg);
// 凭证类拒绝(4=用户名密码错 / 5=未授权):重试一万次也不会好,
// 必须置终止标记挡住自动重连,否则会以 reconnectPeriod 为间隔无限撞 broker
const credentialRejected = returnCode === 4 || returnCode === 5;
if (credentialRejected) this._ended = true;
if (this._pendingConnect) {
const pending = this._pendingConnect;
this._pendingConnect = null;
pending.reject(err);
}
this.onError && this.onError(err);
if (credentialRejected) {
const ws = this.ws;
if (ws) {
try { ws.close(); } catch (e) { /* 忽略 */ }
}
}
}
/**
* 解析 MQTT Remaining Length(变长编码,最多 4 字节)
* 原实现硬编码 "pos = 2",topic 稍长(Remaining Length > 127)就会解析错位
* @returns {{ value: number, bytes: number }}
*/
_decodeRemainingLength(view, start) {
let multiplier = 1;
let value = 0;
let pos = start;
let encodedByte = 0;
do {
if (pos >= view.length) break;
encodedByte = view[pos++];
value += (encodedByte & 0x7F) * multiplier;
multiplier *= 128;
} while ((encodedByte & 0x80) !== 0);
return { value, bytes: pos - start };
}
/**
* 字节数组转字符串(分块)
* 原实现 String.fromCharCode(...payload) 在载荷较大时会超出调用栈参数上限直接抛栈溢出
*/
_bytesToString(bytes) {
let out = '';
const CHUNK = 4096;
for (let i = 0; i < bytes.length; i += CHUNK) {
out += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));
}
return out;
}
_handlePublish(view) {
console.log('[MqttClient] 收到 PUBLISH 消息');
const head = this._decodeRemainingLength(view, 1);
let pos = 1 + head.bytes;
const topicLength = (view[pos] << 8) | view[pos + 1];
pos += 2;
const topic = this._bytesToString(view.subarray(pos, pos + topicLength));
pos += topicLength;
const qos = (view[0] >> 1) & 0x03;
if (qos > 0) pos += 2; // 跳过 messageId
const payload = view.subarray(pos);
console.log('[MqttClient] 主题:', topic, '载荷长度:', payload.length);
const self = this;
const message = {
payload: payload,
toString: () => self._bytesToString(payload),
};
this.onMessage && this.onMessage(topic, message);
// 订阅自带的回调也要分发(原实现 subscriptions 只写不读,等于死代码)
const sub = this.subscriptions.get(topic);
if (sub) sub(topic, message);
}
/** PUBACK:结算对应在途 QoS1 报文 */
_handlePuback(view) {
const messageId = (view[2] << 8) | view[3];
const entry = this._inflight.get(messageId);
if (!entry) {
console.log('[MqttClient] 收到 PUBACK(无匹配在途消息), id:', messageId);
return;
}
console.log('[MqttClient] 收到 PUBACK, id:', messageId);
if (entry.timer) clearTimeout(entry.timer);
entry.timer = null;
this._inflight.delete(messageId);
entry.callback && entry.callback(null);
}
/** 链路断开:所有在途 QoS1 报文立即回调失败,交给上层落离线缓存(至少一次投递) */
_failInflight(reason) {
if (this._inflight.size === 0) return;
const entries = Array.from(this._inflight.values());
this._inflight.clear();
entries.forEach((entry) => {
if (entry.timer) clearTimeout(entry.timer);
entry.timer = null;
entry.callback && entry.callback(new Error(reason || '链路已断开'));
});
}
_startKeepalive() {
const interval = Math.max(5, this.keepalive) * 1000;
console.log('[MqttClient] 启动心跳, 间隔:', this.keepalive, '秒');
this._stopKeepalive();
this._keepaliveTimer = setInterval(() => {
if (!this.connected) return;
this._sendPing();
}, interval);
}
_stopKeepalive() {
if (this._keepaliveTimer) {
clearInterval(this._keepaliveTimer);
this._keepaliveTimer = null;
}
if (this._pingTimer) {
clearTimeout(this._pingTimer);
this._pingTimer = null;
}
}
/**
* 发送 PINGREQ 并启动 PINGRESP 等待
*
* 工业现场必须能识别"假在线":基站切换 / NAT 超时 / 设备侧断电等场景下
* TCP 不会立刻关闭,只等 socket 事件会让 connected 永远为 true。
* 原实现只发不等,PINGRESP 判死完全没做。
*/
_sendPing() {
const ws = this.ws;
if (!ws) return;
console.log('[MqttClient] 发送 PINGREQ');
ws.send(new Uint8Array([0xC0, 0x00]).buffer);
if (this._pingTimer) return;
this._pingTimer = setTimeout(() => {
this._pingTimer = null;
if (!this.connected) return;
console.warn('[MqttClient] PINGRESP 超时(' + this._pingTimeout + 'ms),判定链路已死');
this._onLinkDead();
}, this._pingTimeout);
}
/** 心跳判死:清理本地状态,按"连接关闭"语义交给上层重连 */
_onLinkDead() {
const err = new Error('心跳超时,链路判定为断开');
this._connToken++; // 作废当前 socket 的迟到回调
this.connected = false;
this._stopKeepalive();
this._failInflight('心跳超时');
this.onError && this.onError(err);
this.onClose && this.onClose();
const ws = this.ws;
this.ws = null;
if (ws) {
try { ws.close(); } catch (e) { /* 忽略 */ }
}
if (!this._ended) this._scheduleReconnect();
}
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);
const ws = this.ws;
if (!ws) {
callback && callback(new Error('WebSocket 未就绪'));
return;
}
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 };
}
options = options || {};
const qos = options.qos || 0;
const retain = options.retain ? 1 : 0;
console.log('[MqttClient] 发布消息, 主题:', topic, 'QoS:', qos);
if (qos === 0) {
// QoS0:无确认语义,send 成功即视为已发出
const messageId = 0;
let packet;
try {
packet = this._buildPublishPacket(topic, message, qos, retain, 0, messageId);
} catch (e) {
callback && callback(e);
return;
}
const ws = this.ws;
if (!ws) {
callback && callback(new Error('WebSocket 未就绪'));
return;
}
ws.send(packet, (err) => { callback && callback(err); });
return;
}
// QoS1:必须等 PUBACK 才算投递成功。未确认前保留报文,超时按 dup 重发,
// 重试用尽才回调失败 —— 上层据此把数据落离线缓存,实现"至少一次"投递。
// 原实现发完就回调成功,QoS1 是名不副实的假保证。
if (this._inflight.size >= this._maxInflight) {
callback && callback(new Error('在途消息过多(' + this._inflight.size + '),请稍后重试'));
return;
}
const messageId = this.messageId++;
const entry = {
topic: topic,
message: message,
qos: qos,
retain: retain,
messageId: messageId,
attempts: 0,
timer: null,
callback: callback || null,
};
this._inflight.set(messageId, entry);
this._sendInflight(entry);
}
/** 按 QoS/dup/messageId 拼 PUBLISH 报文 */
_buildPublishPacket(topic, message, qos, retain, dup, messageId) {
const flags = (dup << 3) | (qos << 1) | retain;
const topicData = this._encodeString(topic);
let variableHeader = topicData;
if (qos > 0) {
variableHeader = variableHeader.concat([
(messageId >> 8) & 0xFF,
messageId & 0xFF
]);
}
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));
} else if (message && message.buffer instanceof ArrayBuffer) {
payload = Array.from(new Uint8Array(message.buffer));
}
const remainingLength = variableHeader.length + payload.length;
const fixedHeader = [0x30 | flags, ...this._encodeRemainingLength(remainingLength)];
const packet = fixedHeader.concat(variableHeader).concat(payload);
return new Uint8Array(packet).buffer;
}
/** 发送(或重发)一条在途 QoS1 报文,并挂上 PUBACK 超时计时器 */
_sendInflight(entry) {
entry.attempts++;
const dup = entry.attempts > 1 ? 1 : 0;
let packet;
try {
packet = this._buildPublishPacket(entry.topic, entry.message, entry.qos, entry.retain, dup, entry.messageId);
} catch (e) {
this._failInflightEntry(entry, e);
return;
}
const ws = this.ws;
if (!ws) {
this._failInflightEntry(entry, new Error('WebSocket 未就绪'));
return;
}
ws.send(packet, (err) => {
if (err) {
this._failInflightEntry(entry, err);
return;
}
if (entry.timer) clearTimeout(entry.timer);
entry.timer = setTimeout(() => {
entry.timer = null;
if (!this._inflight.has(entry.messageId)) return;
if (entry.attempts >= this._maxInflightRetry) {
this._inflight.delete(entry.messageId);
entry.callback && entry.callback(new Error('PUBACK 超时,已重试 ' + entry.attempts + ' 次'));
return;
}
console.warn('[MqttClient] PUBACK 超时,重发 id:', entry.messageId, '第', entry.attempts + 1, '次');
this._sendInflight(entry);
}, this._pubackTimeout);
});
}
_failInflightEntry(entry, err) {
if (entry.timer) clearTimeout(entry.timer);
entry.timer = null;
this._inflight.delete(entry.messageId);
entry.callback && entry.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);
const ws = this.ws;
if (!ws) {
callback && callback(null);
return;
}
ws.send(new Uint8Array(packet).buffer, (err) => {
callback && callback(err);
});
}
/**
* 断开连接(终止性操作)
*
* 关键:必须在关闭 socket 之前置 _ended。ws.close() 会触发 onclose,
* onclose 里会排一次自动重连 —— 晚置位就拦不住,这正是一度出现
* "点断开后 5s 又自己连回来"的根因。force 语义统一:本实现一律彻底终止。
*/
end(force, callback) {
if (typeof force === 'function') {
callback = force;
force = false;
}
console.log('[MqttClient] 断开连接');
this._ended = true;
this._connToken++; // 作废当前 socket 的迟到回调
this._stopKeepalive();
if (this._reconnectTimer) {
clearTimeout(this._reconnectTimer);
this._reconnectTimer = null;
}
this._failInflight('连接已主动关闭');
this.connected = false;
if (this._pendingConnect) {
const pending = this._pendingConnect;
this._pendingConnect = null;
pending.reject(new Error('连接已主动关闭'));
}
const ws = this.ws;
this.ws = null; // 立即脱钩:后续 send/close 不再打到半死 socket
const finish = () => { callback && callback(); };
if (!ws) {
finish();
return;
}
// 发送 DISCONNECT 包(尽力而为,失败不影响终止)
try {
ws.send(new Uint8Array([0xE0, 0x00]).buffer);
} catch (e) { /* 忽略 */ }
setTimeout(() => {
try { ws.close(); } catch (e) { /* 忽略 */ }
finish();
}, 100);
}
_scheduleReconnect() {
// 主动 end() 后不再自连:这是"断开"能真正生效的唯一致命防线
if (this._ended) return;
if (this.reconnectPeriod > 0 && !this._reconnectTimer) {
console.log('[MqttClient] 计划重连, 延迟:', this.reconnectPeriod);
this._reconnectTimer = setTimeout(() => {
this._reconnectTimer = null;
if (this._ended) return;
this.onReconnect && this.onReconnect();
this.connect().catch(err => {
console.error('[MqttClient] 重连失败:', err && err.message);
this.onError && this.onError(err);
});
}, this.reconnectPeriod);
}
}
/** 是否正处于"等待自动重连"状态(供上层查询,替代原不存在的同名属性) */
get reconnecting() {
return !!this._reconnectTimer;
}
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);
// 首次建连结果由 onConnect / onError 事件交付;这里吞掉 Promise rejection,
// 避免"事件 + Promise"双通道产生未处理的 Promise 拒绝
client.connect().catch(() => {});
return client;
}
return {
connect,
MqttClient
};
}
}(typeof self !== 'undefined' ? self : this);