1
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,212 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>files</key>
|
||||
<dict>
|
||||
<key>Headers/unimoduleXMqttS-Swift.h</key>
|
||||
<data>
|
||||
us3c7FdOtvvpxAdVgLk4pWNuwOA=
|
||||
</data>
|
||||
<key>Headers/unimoduleXMqttS.h</key>
|
||||
<data>
|
||||
FH2SRnQAHIO8AbDZPa5/Bz/CcdA=
|
||||
</data>
|
||||
<key>Info.plist</key>
|
||||
<data>
|
||||
UQNJ7jyX3QAsqknIpAm6FCxq3WA=
|
||||
</data>
|
||||
<key>Modules/module.modulemap</key>
|
||||
<data>
|
||||
GNmBhr+kPC7jCw5hmJ/XWlcQ90o=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo</key>
|
||||
<data>
|
||||
5PJpCvFI3hp5mYermg/rG/zQlIk=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
|
||||
<data>
|
||||
j8wYwGynekAGd/D50u+hNETt9Uw=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
|
||||
<data>
|
||||
DlWqI5eYTqgv8qwdIj208giOfrE=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
|
||||
<data>
|
||||
1K+l3rq/J9hvut58H8vgiWTjR/I=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
|
||||
<data>
|
||||
DlWqI5eYTqgv8qwdIj208giOfrE=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
|
||||
<data>
|
||||
nUELKkTPB2lwDB0G0YefuZFhBZ8=
|
||||
</data>
|
||||
<key>config.json</key>
|
||||
<data>
|
||||
86zzbbpH14xDJT7XFihyGwNWXhM=
|
||||
</data>
|
||||
</dict>
|
||||
<key>files2</key>
|
||||
<dict>
|
||||
<key>Headers/unimoduleXMqttS-Swift.h</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
Md5kS0WFiaXtbeTSKO/8du82DglwsOS5TY+BDmh79tI=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/unimoduleXMqttS.h</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
XAsze1m7Z4PhyI2gj+RafZVS4xyqWjgvqraTawdVPMY=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/module.modulemap</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
SM0R5ohUl4WWA6FqKV7y4wdGy+fRoO/nHbdNP7N7xdo=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
BVFOw0ORUqXAggKQ8xJIR5bEa9t+1V0HfJITJkZG1yQ=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
NKt0ctJcSMY3hJt34jWIJU6abzw2KGbtnpNeGTT2cOE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
yjSDkB3gJt5oO5/gvlxhx+RCJpV1ImEsfQ02JY3k8gA=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
6rl/O8hn+LP0gdZdxwkODD88w3vBOthyKojL1GlpeH0=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
yjSDkB3gJt5oO5/gvlxhx+RCJpV1ImEsfQ02JY3k8gA=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMqttS.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
trB/R39AmC8X7aLYvod7ARDXu7T+ztP6Fm8XVQfjcD0=
|
||||
</data>
|
||||
</dict>
|
||||
<key>config.json</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
UHsnIaXxl3l3rh1rB45DjCJk/E1Wnk5h+/9qtjl7vfQ=
|
||||
</data>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>rules</key>
|
||||
<dict>
|
||||
<key>^.*</key>
|
||||
<true/>
|
||||
<key>^.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^Base\.lproj/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>1010</real>
|
||||
</dict>
|
||||
<key>^version.plist$</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>rules2</key>
|
||||
<dict>
|
||||
<key>.*\.dSYM($|/)</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>11</real>
|
||||
</dict>
|
||||
<key>^(.*/)?\.DS_Store$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>2000</real>
|
||||
</dict>
|
||||
<key>^.*</key>
|
||||
<true/>
|
||||
<key>^.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^Base\.lproj/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>1010</real>
|
||||
</dict>
|
||||
<key>^Info\.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^PkgInfo$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^embedded\.provisionprofile$</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^version\.plist$</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
|
||||
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
|
||||
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
|
||||
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
|
||||
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
|
||||
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
|
||||
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
|
||||
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
|
||||
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
|
||||
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
|
||||
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
|
||||
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
|
||||
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
|
||||
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
|
||||
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
|
||||
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
|
||||
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,285 @@
|
||||
import {
|
||||
CONNECT_STATUS, MQTT_EVENT_TYPE, MQTT_EVENT_CALL, MQTT_EVENT_PUBLISH, MQTT_SUBSCRIBE, MQTT_EVENTS_CALL,
|
||||
MQTT_PUBLISH_TOPIC, MQTT_CONNECT_OPTS
|
||||
} from "../interface.uts"
|
||||
import {
|
||||
CocoaMQTT, CocoaMQTTWebSocket, CocoaMQTT5Delegate, CocoaMQTT5, MqttConnectProperties,
|
||||
CocoaMQTTDISCONNECTReasonCode,
|
||||
CocoaMQTTAUTHReasonCode,
|
||||
MqttDecodeUnsubAck,
|
||||
MqttDecodeSubAck,
|
||||
CocoaMQTT5Message,
|
||||
MqttDecodePublish,
|
||||
MqttDecodePubAck,
|
||||
MqttDecodePubRec,
|
||||
MqttDecodeConnAck,
|
||||
CocoaMQTTCONNACKReasonCode,
|
||||
CocoaMQTTConnState,
|
||||
CocoaMQTTQoS, MqttPublishProperties, CocoaMQTTError, CocoaMQTTMessage
|
||||
|
||||
} from "CocoaMQTT"
|
||||
|
||||
// https://github.com/anatoliykant/SwiftMQTT
|
||||
import { UInt16 } from 'Swift';
|
||||
import { Bundle, CFArray, SecPKCS12Import } from 'Foundation';
|
||||
import { kCFStreamSSLCertificates } from 'CFNetwork';
|
||||
import { NSObject } from 'ObjectiveC';
|
||||
|
||||
|
||||
// https://github.com/emqx/CocoaMQTT
|
||||
// https://cocoapods.org/pods/CocoaMQTT
|
||||
// 文档:https://www.emqx.com/en/blog/ios-mqtt5-client
|
||||
|
||||
|
||||
class MQTTDELETED implements CocoaMQTT5Delegate {
|
||||
// 授权状态
|
||||
mqtt5(mqtt5 : CocoaMQTT5, @argumentLabel("didReceiveAuthReasonCode") reasonCode : CocoaMQTTAUTHReasonCode) {
|
||||
console.log(4)
|
||||
}
|
||||
// 连接丢失状态
|
||||
mqtt5(mqtt5 : CocoaMQTT5, @argumentLabel("didReceiveDisconnectReasonCode") reasonCode : CocoaMQTTDISCONNECTReasonCode) {
|
||||
console.log(3)
|
||||
}
|
||||
// 取消订阅 MqttDecodeUnsubAck
|
||||
mqtt5(mqtt5 : CocoaMQTT5, @argumentLabel("didUnsubscribeTopics") topics : string[], unsubAckData : any) {
|
||||
console.log(2)
|
||||
}
|
||||
// 订阅 MqttDecodeSubAck
|
||||
mqtt5(mqtt5 : CocoaMQTT5, @argumentLabel("didSubscribeTopics") success : NSDictionary, failed : string[], subAckData : any) {
|
||||
console.log(1)
|
||||
}
|
||||
// 收到消息 MqttDecodePublish
|
||||
mqtt5(mqtt5 : CocoaMQTT5, @argumentLabel("didReceiveMessage") message : CocoaMQTT5Message, id : number, publishData : any) {
|
||||
console.log(8)
|
||||
}
|
||||
// 重复发送消息 MqttDecodePubRec
|
||||
mqtt5(mqtt5 : CocoaMQTT5, @argumentLabel("didPublishRec") id : number, pubRecData : any) {
|
||||
console.log(8)
|
||||
}
|
||||
// 推送消息异常 MqttDecodePubAck
|
||||
mqtt5(mqtt5 : CocoaMQTT5, @argumentLabel("didPublishAck") id : number, pubAckData : any) {
|
||||
console.log(8)
|
||||
}
|
||||
// 推送消息
|
||||
mqtt5(mqtt5 : CocoaMQTT5, @argumentLabel("didPublishMessage") message : CocoaMQTT5Message, id : number) {
|
||||
console.log(8)
|
||||
}
|
||||
|
||||
// 推送消息 MqttDecodeConnAck
|
||||
mqtt5(mqtt5 : CocoaMQTT5, @argumentLabel("didConnectAck") ack : CocoaMQTTCONNACKReasonCode, connAckData : any) {
|
||||
console.log(8)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
let xmqtt : CocoaMQTT | null = null
|
||||
const connectDeleted : MQTTDELETED | null = null;
|
||||
export class xMqtt {
|
||||
mqtt : CocoaMQTT | null = null;
|
||||
mqttConnectOptions : MqttConnectProperties | null = null;
|
||||
connectStatus : CONNECT_STATUS = 'wait'
|
||||
events = new Map<string, MQTT_EVENTS_CALL>();
|
||||
constructor() { }
|
||||
|
||||
|
||||
/**
|
||||
* @param type {MQTT_EVENT_TYPE} 事件名称
|
||||
* @param call {MQTT_EVENT_CALL} 事件回调
|
||||
*/
|
||||
@UTSJS.keepAlive
|
||||
addEventListener(type : MQTT_EVENT_TYPE, call : MQTT_EVENT_CALL) : string {
|
||||
let id = Date.now().toString() + (Math.random() * 100).toString()
|
||||
this.events.set(id,
|
||||
{
|
||||
type,
|
||||
value: call
|
||||
} as MQTT_EVENTS_CALL
|
||||
)
|
||||
|
||||
return id;
|
||||
}
|
||||
/**
|
||||
* @param id {string} addEventListener返回的事件id
|
||||
*/
|
||||
removeEventListener(id : string) : xMqtt {
|
||||
this.events.delete(id)
|
||||
return this;
|
||||
}
|
||||
|
||||
private buildCallEvents(type : MQTT_EVENT_TYPE, toppic : string | null, str : string) {
|
||||
this.events.forEach((value : MQTT_EVENTS_CALL, key : string) => {
|
||||
if (value.type == type) {
|
||||
value.value(type, toppic, str)
|
||||
}
|
||||
})
|
||||
}
|
||||
create(opts : MQTT_CONNECT_OPTS) : xMqtt {
|
||||
let t = this;
|
||||
|
||||
let clientID = opts.clientId!
|
||||
let websocket = CocoaMQTTWebSocket(uri = opts.path)
|
||||
let mqtt5 = new CocoaMQTT(clientID = clientID, host = opts.server, port = opts.port!.toUInt16(), socket = websocket)
|
||||
mqtt5.willMessage = new CocoaMQTTMessage(topic = "/will", string = "dieout")
|
||||
|
||||
// let connectProperties = MqttConnectProperties()
|
||||
// connectProperties.topicAliasMaximum = 0
|
||||
// connectProperties.sessionExpiryInterval = 0
|
||||
// connectProperties.receiveMaximum = 100
|
||||
// connectProperties.maximumPacketSize = 5000
|
||||
// mqtt5.connectProperties = connectProperties
|
||||
mqtt5.username = opts.userName
|
||||
mqtt5.password = opts.passWord
|
||||
mqtt5.keepAlive = opts.keepAliveInterval.toUInt16()
|
||||
mqtt5.enableSSL = opts.useSSL
|
||||
mqtt5.autoReconnect = opts.reconnect
|
||||
mqtt5.allowUntrustCACertificate = true
|
||||
let sslCart = opts.certName==null?"":(opts.certName)
|
||||
let isP12 = false;
|
||||
// let sslSettings = new Map<string, NSObject>()
|
||||
if (sslCart != "" && opts.useSSL) {
|
||||
isP12 = sslCart.lastIndexOf(".p12") > -1;
|
||||
if (isP12) {
|
||||
let passwordSsl = opts.certPassword == null ? "" : (opts.certPassword)
|
||||
if (passwordSsl != "") {
|
||||
let crtpath = Bundle.main.path(forResource = String(sslCart.substring(0, sslCart.lastIndexOf("."))), ofType = "p12")
|
||||
let temsslSettings = getClientCertFromP12File(resourcePath = crtpath == null ? "" : (crtpath!), certPassword = String(passwordSsl))
|
||||
if (temsslSettings != null) {
|
||||
mqtt5.sslSettings = temsslSettings!
|
||||
} else {
|
||||
console.error("提供的P12证书有误或者文件不存在.")
|
||||
}
|
||||
} else {
|
||||
console.error("提供了p12证书,但未提供证书密码.")
|
||||
}
|
||||
} else {
|
||||
let crtpath = Bundle.main.path(forResource = String(sslCart.substring(0, sslCart.lastIndexOf("."))), ofType = "crt")
|
||||
|
||||
// ca文件.
|
||||
let temsslSettings = getClientCertFromCrtFile(resourcePath = crtpath == null ? "" : (crtpath!))
|
||||
if (temsslSettings != null) {
|
||||
// console.log(temsslSettings)
|
||||
mqtt5.sslSettings = temsslSettings!
|
||||
} else {
|
||||
console.error("提供的Ca证书有误或者文件不存在.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mqtt5.cleanSession = true
|
||||
// mqtt5.delegate = new MQTTDELETED()
|
||||
mqtt5.didDisconnect = (client : CocoaMQTT, errorcode ?: any) => {
|
||||
let code = errorcode as CocoaMQTTError | null
|
||||
|
||||
if (errorcode instanceof CocoaMQTTError) {
|
||||
t.connectStatus = 'error'
|
||||
t.buildCallEvents('error', null, '连接错误')
|
||||
|
||||
} else {
|
||||
t.connectStatus = 'error'
|
||||
t.buildCallEvents('error', null, '地址错误,或者没有网络。')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mqtt5.didConnectAck = (client : CocoaMQTT, cack ?: any) => {
|
||||
t.connectStatus = 'open'
|
||||
t.buildCallEvents('open', null, '已连接')
|
||||
|
||||
|
||||
console.log('success')
|
||||
}
|
||||
mqtt5.didChangeState = (client : CocoaMQTT, state : CocoaMQTTConnState) => {
|
||||
if (state == CocoaMQTTConnState.connecting) {
|
||||
console.log('连接中')
|
||||
}
|
||||
if (state == CocoaMQTTConnState.connected) {
|
||||
console.log('连接成功')
|
||||
}
|
||||
if (state == CocoaMQTTConnState.disconnected) {
|
||||
console.log('连接失败')
|
||||
t.connectStatus = 'dissconnect'
|
||||
t.buildCallEvents('dissconnect', null, '已断开连接')
|
||||
}
|
||||
}
|
||||
|
||||
mqtt5.didReceiveMessage = (client : CocoaMQTT, message : CocoaMQTTMessage, id : UInt16) => {
|
||||
let topic = message.topic
|
||||
let msg = message.string
|
||||
t.connectStatus = 'message'
|
||||
t.buildCallEvents('message', topic, msg == null ? '' : (msg!))
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.mqtt = mqtt5;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
connect() : xMqtt {
|
||||
if (this.mqtt == null) return this;
|
||||
this.mqtt!.connect()
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 推送消息
|
||||
* @param call {MQTT_EVENT_PUBLISH}
|
||||
*/
|
||||
publish(msg : MQTT_PUBLISH_TOPIC, call : MQTT_EVENT_PUBLISH) : xMqtt {
|
||||
if (this.mqtt == null) return this;
|
||||
let topic = msg.topic
|
||||
let message = msg.message
|
||||
let qos : CocoaMQTTQoS = CocoaMQTTQoS.qos0
|
||||
if (msg.qos == 1) {
|
||||
qos = CocoaMQTTQoS.qos1
|
||||
} else if (msg.qos == 2) {
|
||||
qos = CocoaMQTTQoS.qos2
|
||||
}
|
||||
this.mqtt!.publish(new CocoaMQTTMessage(topic = topic!, string = message!, qos = qos, retained = msg.retained))
|
||||
call(true)
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 订阅
|
||||
* @param data {MQTT_SUBSCRIBE[]} 订阅的消息数组
|
||||
*/
|
||||
subscribe(data : MQTT_SUBSCRIBE[]) : xMqtt {
|
||||
let t = this;
|
||||
if (this.mqtt == null) return this;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let qos : CocoaMQTTQoS = CocoaMQTTQoS.qos0
|
||||
if (data[i].qos == 1) {
|
||||
qos = CocoaMQTTQoS.qos1
|
||||
} else if (data[i].qos == 2) {
|
||||
qos = CocoaMQTTQoS.qos2
|
||||
}
|
||||
this.mqtt!.subscribe(data[i].topic, qos = qos)
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅
|
||||
* @param topics {string[]} 主题数组
|
||||
*/
|
||||
unsubscribe(topics : string[]) : xMqtt {
|
||||
if (this.mqtt == null) return this;
|
||||
for (let i = 0; i < topics.length; i++) {
|
||||
this.mqtt!.unsubscribe(topics[i])
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
disconnect() : xMqtt {
|
||||
if (this.mqtt == null) return this;
|
||||
this.mqtt!.disconnect()
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"deploymentTarget": "12",
|
||||
"dependencies-pods": [
|
||||
{
|
||||
"name": "CocoaMQTT/WebSockets",
|
||||
"version": "2.1.6"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
CONNECT_STATUS, MQTT_EVENT_TYPE, MQTT_EVENT_CALL, MQTT_EVENT_PUBLISH, MQTT_SUBSCRIBE, MQTT_EVENTS_CALL,
|
||||
MQTT_PUBLISH_TOPIC, MQTT_CONNECT_OPTS
|
||||
} from "../interface.uts"
|
||||
import {
|
||||
CocoaMQTT, CocoaMQTTWebSocket, CocoaMQTT5Delegate, CocoaMQTT5, MqttConnectProperties,
|
||||
CocoaMQTTDISCONNECTReasonCode,
|
||||
CocoaMQTTAUTHReasonCode,
|
||||
MqttDecodeUnsubAck,
|
||||
MqttDecodeSubAck,
|
||||
CocoaMQTT5Message,
|
||||
MqttDecodePublish,
|
||||
MqttDecodePubAck,
|
||||
MqttDecodePubRec,
|
||||
MqttDecodeConnAck,
|
||||
CocoaMQTTCONNACKReasonCode,
|
||||
CocoaMQTTConnState,
|
||||
CocoaMQTTQoS, MqttPublishProperties, CocoaMQTTError, CocoaMQTTMessage
|
||||
|
||||
} from "CocoaMQTT"
|
||||
|
||||
// https://github.com/anatoliykant/SwiftMQTT
|
||||
import { UInt16 } from 'Swift';
|
||||
import { Bundle, CFArray, NSDictionary, SecPKCS12Import } from 'Foundation';
|
||||
import { kCFStreamSSLCertificates } from 'CFNetwork';
|
||||
import { NSObject } from 'ObjectiveC';
|
||||
|
||||
|
||||
// https://github.com/emqx/CocoaMQTT
|
||||
// https://cocoapods.org/pods/CocoaMQTT
|
||||
// 文档:https://www.emqx.com/en/blog/ios-mqtt5-client
|
||||
|
||||
|
||||
|
||||
export class xMqtt {
|
||||
mqtt:XMqttHelp = new XMqttHelp();
|
||||
mqttConnectOptions : MqttConnectProperties | null = null;
|
||||
connectStatus : CONNECT_STATUS = 'wait'
|
||||
constructor() { }
|
||||
/**
|
||||
* @param type {MQTT_EVENT_TYPE} 事件名称
|
||||
* @param call {MQTT_EVENT_CALL} 事件回调
|
||||
*/
|
||||
@UTSJS.keepAlive
|
||||
addEventListener(type : MQTT_EVENT_TYPE, call : MQTT_EVENT_CALL) : string {
|
||||
@escaping
|
||||
return this.mqtt.addEventListener(
|
||||
type,
|
||||
callback = call);
|
||||
}
|
||||
/**
|
||||
* @param id {string} addEventListener返回的事件id
|
||||
*/
|
||||
removeEventListener(id : string) : xMqtt {
|
||||
this.mqtt.removeEventListener(id)
|
||||
return this;
|
||||
}
|
||||
|
||||
private buildCallEvents() {
|
||||
@escaping
|
||||
this.mqtt.setCallBack(()=>{
|
||||
this.connectStatus = this.mqtt.getStatus();
|
||||
console.log(this.connectStatus)
|
||||
})
|
||||
}
|
||||
create(opts : MQTT_CONNECT_OPTS) : xMqtt {
|
||||
let optsjson = JSON.parseObject(JSON.stringify(opts)!)!;
|
||||
const realJson = {...optsjson,allowUntrustCACertificate:opts.allowUntrustCACertificate==true,protocol:opts.useSSL?'wss':'ws'}
|
||||
this.mqtt.create(realJson.toMap() as NSDictionary)
|
||||
this.buildCallEvents()
|
||||
return this;
|
||||
}
|
||||
connect() : xMqtt {
|
||||
// if (this.mqtt.getStatus()!='dissconnect'&&this.mqtt.getStatus()!='error') return this;
|
||||
this.mqtt.connect()
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 推送消息
|
||||
* @param call {MQTT_EVENT_PUBLISH}
|
||||
*/
|
||||
publish(msg : MQTT_PUBLISH_TOPIC, call : MQTT_EVENT_PUBLISH) : xMqtt {
|
||||
// if (this.mqtt.getStatus() != 'open' && this.mqtt.getStatus() != 'message') return this;
|
||||
@escaping
|
||||
this.mqtt.publish(
|
||||
msg.topic,
|
||||
message = msg.message,
|
||||
qos = msg.qos.toInt(),
|
||||
retained=msg.retained,
|
||||
completion = (ok:boolean)=>{
|
||||
console.log('ok',ok)
|
||||
call(ok)
|
||||
}
|
||||
)
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 订阅
|
||||
* @param data {MQTT_SUBSCRIBE[]} 订阅的消息数组
|
||||
*/
|
||||
subscribe(data : MQTT_SUBSCRIBE[]) : xMqtt {
|
||||
// if (this.mqtt.getStatus() != 'open' && this.mqtt.getStatus() != 'message') return this;
|
||||
let d:NSDictionary[] = [];
|
||||
data.forEach(el=>{
|
||||
let p = JSON.parseObject(JSON.stringify(el)!)!.toMap();
|
||||
let qos = p.get('qos')! as number
|
||||
p.set('qos',qos.toInt())
|
||||
d.push(p as NSDictionary)
|
||||
})
|
||||
this.mqtt.subscribe(d)
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅
|
||||
* @param topics {string[]} 主题数组
|
||||
*/
|
||||
unsubscribe(topics : string[]) : xMqtt {
|
||||
// if (this.mqtt.getStatus() != 'open' && this.mqtt.getStatus() != 'message') return this;
|
||||
this.mqtt.unsubscribe(topics)
|
||||
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
disconnect() : xMqtt {
|
||||
this.mqtt.disconnect();
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
</plist>
|
||||
@@ -0,0 +1,122 @@
|
||||
import Foundation
|
||||
import Security
|
||||
// UTS内置对象的引用
|
||||
import DCloudUTSFoundation
|
||||
func getClientCertFromP12File(resourcePath: String, certPassword: String) -> [String: NSObject]? {
|
||||
do {
|
||||
// 读取证书文件数据
|
||||
let p12Data = try Data(contentsOf: URL(fileURLWithPath: resourcePath))
|
||||
|
||||
// 创建密钥字典用于读取p12文件
|
||||
let key = kSecImportExportPassphrase as String
|
||||
let options : NSDictionary = [key: certPassword]
|
||||
|
||||
var items : CFArray?
|
||||
let securityError = SecPKCS12Import(p12Data as NSData, options, &items)
|
||||
|
||||
guard securityError == errSecSuccess else {
|
||||
if securityError == errSecAuthFailed {
|
||||
console.log("ERROR: SecPKCS12Import returned errSecAuthFailed. Incorrect password?")
|
||||
} else {
|
||||
console.log("Failed to open the certificate file", resourcePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let theArray = items, CFArrayGetCount(theArray) > 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let dictionary = (theArray as NSArray).object(at: 0)
|
||||
guard let identity = (dictionary as AnyObject).value(forKey: kSecImportItemIdentity as String) as? NSObject else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return [
|
||||
"useCertificateChainValidation": NSNumber(value: true),
|
||||
"useSSLCertificateVerification": NSNumber(value: true),
|
||||
"clientCertificate": identity
|
||||
]
|
||||
} catch {
|
||||
console.log("读取证书文件失败", resourcePath)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 将PEM格式的证书转换为DER格式
|
||||
func convertPEMToDER(pemString: String) -> Data? {
|
||||
// 移除所有空格和换行符
|
||||
var cleanedString = pemString.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
// 检查是否包含PEM证书的头尾标记
|
||||
let beginCertPattern = "-----BEGIN CERTIFICATE-----"
|
||||
let endCertPattern = "-----END CERTIFICATE-----"
|
||||
|
||||
// 如果没有找到标准的PEM头尾标记,记录日志并尝试直接解码
|
||||
if !cleanedString.contains(beginCertPattern) || !cleanedString.contains(endCertPattern) {
|
||||
console.log("警告: PEM证书格式不标准,缺少标准头尾标记")
|
||||
// 尝试直接解码,假设整个字符串都是Base64编码
|
||||
let noWhitespace = cleanedString.replacingOccurrences(of: "\\s", with: "", options: .regularExpression)
|
||||
return Data(base64Encoded: noWhitespace)
|
||||
}
|
||||
|
||||
// 提取BEGIN和END标记之间的内容
|
||||
guard let startRange = cleanedString.range(of: beginCertPattern),
|
||||
let endRange = cleanedString.range(of: endCertPattern) else {
|
||||
console.log("无法定位PEM证书的头尾标记位置")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取BEGIN标记之后的内容
|
||||
let afterBeginIndex = cleanedString.index(after: startRange.upperBound)
|
||||
// 获取END标记之前的内容
|
||||
let beforeEndIndex = endRange.lowerBound
|
||||
|
||||
// 提取Base64编码部分
|
||||
let base64Content = String(cleanedString[afterBeginIndex..<beforeEndIndex])
|
||||
|
||||
// 移除所有空格和换行
|
||||
let noWhitespace = base64Content.replacingOccurrences(of: "\\s", with: "", options: .regularExpression)
|
||||
|
||||
// 解码Base64
|
||||
guard let data = Data(base64Encoded: noWhitespace) else {
|
||||
console.log("Base64解码失败,证书内容可能损坏")
|
||||
return nil
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func getClientCertFromCrtFile(resourcePath: String) -> [String: NSObject]? {
|
||||
|
||||
do {
|
||||
// 读取证书文件数据
|
||||
let pemData = try Data(contentsOf: URL(fileURLWithPath: resourcePath))
|
||||
// 将证书数据转换为字符串
|
||||
guard let pemString = String(data: pemData, encoding: .utf8) else {
|
||||
console.log("将证书数据转换为字符串失败")
|
||||
return nil
|
||||
}
|
||||
// 将PEM格式转换为DER格式
|
||||
guard let derData = convertPEMToDER(pemString: pemString) else {
|
||||
console.log("转换PEM到DER格式失败")
|
||||
return nil
|
||||
}
|
||||
// 创建SecCertificate对象
|
||||
guard let certificate = SecCertificateCreateWithData(nil, derData as CFData) else {
|
||||
console.log("创建SecCertificate对象失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
return [
|
||||
"useCertificateChainValidation": NSNumber(value: true),
|
||||
"useSSLCertificateVerification": NSNumber(value: true),
|
||||
"kCFStreamSSLCertificates": [certificate] as NSArray
|
||||
]
|
||||
} catch {
|
||||
console.log("读取证书文件失败",resourcePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
import Foundation
|
||||
import CocoaMQTT
|
||||
import DCloudUTSFoundation
|
||||
|
||||
// 证书加载工具预计由同目录的 xMqttCerHelp.swift 提供
|
||||
// getClientCertFromP12File(resourcePath: String, certPassword: String) -> [String: Any]?
|
||||
// getClientCertFromCrtFile(resourcePath: String) -> [String: Any]?
|
||||
|
||||
@objc public class XMqttHelp: NSObject {
|
||||
|
||||
@objc public enum ConnectStatus: Int {
|
||||
case wait
|
||||
case opening
|
||||
case open
|
||||
case error
|
||||
case dissconnect
|
||||
case message
|
||||
}
|
||||
|
||||
public typealias EventType = String // 'open' | 'error' | 'dissconnect' | 'message'
|
||||
public typealias MQTTEventCallback = @convention(block) (_ type: EventType, _ topic: String?, _ payload: String) -> Void
|
||||
public typealias VoidCallback = @convention(block) () -> Void
|
||||
|
||||
private var mqtt: CocoaMQTT?
|
||||
@objc public private(set) var connectStatus: ConnectStatus = .wait
|
||||
private var anyStateCallback: VoidCallback?
|
||||
|
||||
private struct EventItem {
|
||||
let type: EventType
|
||||
let callback: MQTTEventCallback
|
||||
}
|
||||
private var events = [String: EventItem]()
|
||||
private var pendingSubs = [(topic: String, qos: CocoaMQTTQoS)]()
|
||||
|
||||
@objc public override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
// opts keys 对齐 index.uts: protocol, server, port, path, clientId, userName, passWord, keepAliveInterval, reconnect, useSSL, certName, certPassword
|
||||
@objc public func create(_ opts: NSDictionary) -> XMqttHelp {
|
||||
let proto = ((opts["protocol"] as? String) ?? "wss").lowercased() // ws | wss
|
||||
let server = (opts["server"] as? String) ?? ""
|
||||
let port = (opts["port"] as? NSNumber)?.uint16Value ?? 0
|
||||
let path = (opts["path"] as? String) ?? "/mqtt"
|
||||
let clientId = (opts["clientId"] as? String) ?? ("iOS-" + UUID().uuidString)
|
||||
let userName = opts["userName"] as? String
|
||||
let passWord = opts["passWord"] as? String
|
||||
let keepAlive = (opts["keepAliveInterval"] as? NSNumber)?.uint16Value ?? 60
|
||||
let reconnect = (opts["reconnect"] as? NSNumber)?.boolValue ?? true
|
||||
var useSSL = (opts["useSSL"] as? NSNumber)?.boolValue ?? (proto == "wss")
|
||||
let certName = opts["certName"] as? String
|
||||
let certPassword = opts["certPassword"] as? String
|
||||
let allowUntrust = (opts["allowUntrustCACertificate"] as? NSNumber)?.boolValue ?? false
|
||||
var headers = opts["headers"] as? [String: String] ?? [:]
|
||||
let subProtocols = opts["protocols"] as? [String] ?? ["mqtt"]
|
||||
let connectTimeoutMs = (opts["connectTimeoutMs"] as? NSNumber)?.intValue ?? 30000
|
||||
|
||||
let websocket = CocoaMQTTWebSocket(uri: path)
|
||||
// if !subProtocols.isEmpty {
|
||||
// // 通过请求头携带子协议,等效于 Sec-WebSocket-Protocol
|
||||
// headers["sec-websocket-protocol"] = subProtocols.joined(separator: ", ")
|
||||
// }
|
||||
// 常见要求:补 Host 与 Origin
|
||||
headers["Host"] = server
|
||||
if headers["Origin"] == nil {
|
||||
headers["Origin"] = "https://\(server)"
|
||||
}
|
||||
// 标准版本
|
||||
headers["Sec-WebSocket-Version"] = "13"
|
||||
headers["sec-websocket-protocol"] = "mqtt"
|
||||
headers["upgrade"] = "websocket"
|
||||
headers["connection"] = "Upgrade"
|
||||
websocket.headers = headers
|
||||
// 显式打开 wss(CocoaMQTT 2.1.x 部分版本需要)
|
||||
// 存在则赋值,不存在编译器会报错;若报错我再回退此行
|
||||
#if compiler(>=5.0)
|
||||
// 尝试兼容的属性名
|
||||
// @available: 部分版本属性名为 enableSSL 或 isSecure
|
||||
// 这里使用可选链避免编译期错误不可行,因此直接尝试常见属性名
|
||||
// 如果你的 CocoaMQTT 版本不支持,将移除此行由 mqtt.enableSSL 控制
|
||||
#endif
|
||||
let mqtt = CocoaMQTT(clientID: clientId, host: server, port: port, socket: websocket)
|
||||
mqtt.username = userName
|
||||
mqtt.password = passWord
|
||||
mqtt.keepAlive = keepAlive
|
||||
mqtt.enableSSL = useSSL
|
||||
mqtt.autoReconnect = reconnect
|
||||
mqtt.cleanSession = true
|
||||
mqtt.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout")
|
||||
|
||||
console.log("[XMqttHelp] connect url => \(proto)://\(server):\(port)\(path), ssl=\(useSSL), headers=\(headers)")
|
||||
|
||||
// 证书策略(可选)
|
||||
if useSSL, let name = certName, !name.isEmpty {
|
||||
if name.lowercased().hasSuffix(".p12") {
|
||||
if let pwd = certPassword, !pwd.isEmpty {
|
||||
let base = (name as NSString).deletingPathExtension
|
||||
if let path = Bundle.main.path(forResource: base, ofType: "p12"),
|
||||
let settings = getClientCertFromP12File(resourcePath: path, certPassword: pwd) {
|
||||
mqtt.sslSettings = settings
|
||||
} else {
|
||||
console.log("[XMqttHelp] p12 证书无效或找不到: \(name)")
|
||||
}
|
||||
} else {
|
||||
console.log("[XMqttHelp] p12 证书未提供密码")
|
||||
}
|
||||
} else if name.lowercased().hasSuffix(".crt") {
|
||||
let base = (name as NSString).deletingPathExtension
|
||||
if let path = Bundle.main.path(forResource: base, ofType: "crt"),
|
||||
let settings = getClientCertFromCrtFile(resourcePath: path) {
|
||||
mqtt.sslSettings = settings
|
||||
} else {
|
||||
console.log("[XMqttHelp] crt 证书无效或找不到: \(name)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自签证书信任(调试可开,生产建议禁用或使用证书绑定)
|
||||
mqtt.allowUntrustCACertificate = allowUntrust
|
||||
|
||||
// 指定 SNI 主机名,避免非标准端口下的握手失败
|
||||
if useSSL {
|
||||
var ssl = mqtt.sslSettings ?? [:]
|
||||
ssl[kCFStreamSSLPeerName as String] = server as NSString
|
||||
mqtt.sslSettings = ssl
|
||||
}
|
||||
|
||||
// 事件回调
|
||||
weak var weakSelf = self
|
||||
mqtt.didDisconnect = { [weak weakSelf] _, error in
|
||||
guard let self = weakSelf else { return }
|
||||
self.connectStatus = .error
|
||||
self.notifyStateChange()
|
||||
self.buildCallEvents(type: "error", topic: nil, payload: (error as? CocoaMQTTError) != nil ? "连接错误" : "地址错误,或者没有网络。")
|
||||
}
|
||||
mqtt.didConnectAck = { [weak weakSelf] _, _ in
|
||||
guard let self = weakSelf else { return }
|
||||
self.connectStatus = .open
|
||||
self.notifyStateChange()
|
||||
self.buildCallEvents(type: "open", topic: nil, payload: "已连接")
|
||||
// 补发未完成的订阅
|
||||
if !self.pendingSubs.isEmpty {
|
||||
for item in self.pendingSubs {
|
||||
self.mqtt?.subscribe(item.topic, qos: item.qos)
|
||||
console.log("[XMqttHelp] re-subscribe => \(item.topic) qos=\(item.qos.rawValue)")
|
||||
}
|
||||
self.pendingSubs.removeAll()
|
||||
}
|
||||
}
|
||||
mqtt.didChangeState = { [weak weakSelf] _, state in
|
||||
guard let self = weakSelf else { return }
|
||||
switch state {
|
||||
case .connecting:
|
||||
console.log("[XMqttHelp] 连接中")
|
||||
case .connected:
|
||||
console.log("[XMqttHelp] 连接成功")
|
||||
case .disconnected:
|
||||
self.connectStatus = .dissconnect
|
||||
self.notifyStateChange()
|
||||
self.buildCallEvents(type: "dissconnect", topic: nil, payload: "已断开连接")
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
mqtt.didReceiveMessage = { [weak weakSelf] _, message, _ in
|
||||
guard let self = weakSelf else { return }
|
||||
self.connectStatus = .message
|
||||
self.notifyStateChange()
|
||||
let payload = message.string ?? (String(data: Data(message.payload), encoding: .utf8) ?? "")
|
||||
self.buildCallEvents(type: "message", topic: message.topic, payload: payload)
|
||||
}
|
||||
|
||||
self.mqtt = mqtt
|
||||
self.connectStatus = .wait
|
||||
self.notifyStateChange()
|
||||
|
||||
// 连接超时保护
|
||||
if connectTimeoutMs > 0 {
|
||||
let deadline = DispatchTime.now() + .milliseconds(connectTimeoutMs)
|
||||
DispatchQueue.main.asyncAfter(deadline: deadline) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if self.connectStatus == .opening {
|
||||
console.log("[XMqttHelp] 连接超时: \(connectTimeoutMs)ms")
|
||||
_ = self.disconnect()
|
||||
self.connectStatus = .error
|
||||
self.notifyStateChange()
|
||||
self.buildCallEvents(type: "error", topic: nil, payload: "连接超时")
|
||||
}
|
||||
}
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
@objc public func connect() -> XMqttHelp {
|
||||
guard let mqtt = self.mqtt else { return self }
|
||||
self.connectStatus = .opening
|
||||
self.notifyStateChange()
|
||||
mqtt.connect()
|
||||
return self
|
||||
}
|
||||
|
||||
@objc public func publish(_ topic: String, message: String, qos: Int, retained: Bool, completion: @escaping @convention(block) (_ ok: Bool) -> Void) -> XMqttHelp {
|
||||
guard let mqtt = self.mqtt else { completion(false); return self }
|
||||
let q: CocoaMQTTQoS = (qos == 2) ? .qos2 : ((qos == 1) ? .qos1 : .qos0)
|
||||
let msg = CocoaMQTTMessage(topic: topic, string: message, qos: q, retained: retained)
|
||||
mqtt.publish(msg)
|
||||
completion(true)
|
||||
return self
|
||||
}
|
||||
|
||||
@objc public func subscribe(_ topics: [NSDictionary]) -> XMqttHelp {
|
||||
guard let mqtt = self.mqtt else { return self }
|
||||
for item in topics {
|
||||
guard let topic = item["topic"] as? String else { continue }
|
||||
let qosVal = (item["qos"] as? NSNumber)?.intValue ?? 0
|
||||
let q: CocoaMQTTQoS = (qosVal == 2) ? .qos2 : ((qosVal == 1) ? .qos1 : .qos0)
|
||||
console.log(self.connectStatus == .open,"opeing status")
|
||||
if self.connectStatus == .open {
|
||||
mqtt.subscribe(topic, qos: q)
|
||||
console.log("[XMqttHelp] subscribe => \(topic) qos=\(q.rawValue)")
|
||||
} else {
|
||||
self.pendingSubs.append((topic, q))
|
||||
console.log("[XMqttHelp] cache subscribe (not open) => \(topic) qos=\(q.rawValue)")
|
||||
}
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
@objc public func unsubscribe(_ topics: [String]) -> XMqttHelp {
|
||||
guard let mqtt = self.mqtt else { return self }
|
||||
for t in topics { mqtt.unsubscribe(t) }
|
||||
return self
|
||||
}
|
||||
|
||||
@objc public func disconnect() -> XMqttHelp {
|
||||
guard let mqtt = self.mqtt else { return self }
|
||||
mqtt.disconnect()
|
||||
return self
|
||||
}
|
||||
|
||||
// 注册任意状态变化的回调(无参数)
|
||||
@objc public func setCallBack(_ cb: @escaping VoidCallback) {
|
||||
self.anyStateCallback = cb
|
||||
}
|
||||
|
||||
|
||||
// 规范命名,提供同等功能(可选使用)
|
||||
@objc public func getStatus() -> String {
|
||||
return self.statusString(self.connectStatus)
|
||||
}
|
||||
|
||||
// 事件系统:与 index.uts 的 addEventListener/removeEventListener 对齐
|
||||
@objc @discardableResult
|
||||
public func addEventListener(_ type: EventType, callback: @escaping MQTTEventCallback) -> String {
|
||||
let id = String(format: "%.0f-%d", Date().timeIntervalSince1970 * 1000, Int.random(in: 0...9999))
|
||||
events[id] = EventItem(type: type, callback: callback)
|
||||
return id
|
||||
}
|
||||
|
||||
@objc @discardableResult
|
||||
public func removeEventListener(_ id: String) -> XMqttHelp {
|
||||
events.removeValue(forKey: id)
|
||||
return self
|
||||
}
|
||||
|
||||
private func buildCallEvents(type: EventType, topic: String?, payload: String) {
|
||||
for (_, item) in events where item.type == type {
|
||||
item.callback(type, topic, payload)
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
anyStateCallback?()
|
||||
}
|
||||
|
||||
private func statusString(_ status: ConnectStatus) -> String {
|
||||
switch status {
|
||||
case .wait:
|
||||
return "wait"
|
||||
case .opening:
|
||||
return "opening"
|
||||
case .open:
|
||||
return "open"
|
||||
case .error:
|
||||
return "error"
|
||||
case .dissconnect:
|
||||
return "dissconnect"
|
||||
case .message:
|
||||
return "message"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user