This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
@@ -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-----
+285
View File
@@ -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
}
}
// PEMDER
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)
}
// BEGINEND
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
}
// PEMDER
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
// wssCocoaMQTT 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"
}
}
}