base on MQTT 5.0 client library for iOS and macOS written in Swift # CocoaMQTT ![PodVersion](https://img.shields.io/cocoapods/v/CocoaMQTT5.svg) ![Platforms](https://img.shields.io/cocoapods/p/CocoaMQTT5.svg) ![License](https://img.shields.io/cocoapods/l/BadgeSwift.svg?style=flat) ![Swift version](https://img.shields.io/badge/swift-5-orange.svg) MQTT v3.1.1 and v5.0 client library for iOS/macOS/tvOS written with Swift 5 ## Build Build with Xcode 11.1 / Swift 5.1 IOS Target: 12.0 or above OSX Target: 10.13 or above TVOS Target: 10.0 or above ## xcode 14.3 issue: ```ruby File not found: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphonesimulator.a ``` If you encounter the issue, Please update your project minimum depolyments to 11.0 ## Installation ### Swift Package Manager To integrate CocoaMQTT into your Xcode project using [Swift Package Manager](https://swift.org/package-manager/), follow these steps: 1. Open your project in Xcode. 2. Go to `File` > `Swift Packages` > `Add Package Dependency`. 3. Enter the repository URL: `https://github.com/emqx/CocoaMQTT.git`. 4. Choose the latest version or specify a version range. 5. Add the package to your target. At last, import "CocoaMQTT" to your project: ```swift import CocoaMQTT ``` ### CocoaPods To integrate CocoaMQTT into your Xcode project using [CocoaPods](http://cocoapods.org), you need to modify you `Podfile` like the followings: ```ruby use_frameworks! target 'Example' do pod 'CocoaMQTT' end ``` Then, run the following command: ```bash $ pod install ``` At last, import "CocoaMQTT" to your project: ```swift import CocoaMQTT ``` ### Carthage Install using [Carthage](https://github.com/Carthage/Carthage) by adding the following lines to your Cartfile: ``` github "emqx/CocoaMQTT" "master" ``` Then, run the following command: ```bash $ carthage update --platform iOS,macOS,tvOS --use-xcframeworks ``` At last: On your application targets “General” settings tab, in the "Frameworks, Libraries, and Embedded content" section, drag and drop CocoaMQTT.xcframework, CocoaAsyncSocket.xcframework and Starscream.xcframework from the Carthage/Build folder on disk. Then select "Embed & Sign". ## Usage Create a client to connect [MQTT broker](https://www.emqx.com/en/mqtt/public-mqtt5-broker): ```swift ///MQTT 5.0 let clientID = "CocoaMQTT-" + String(ProcessInfo().processIdentifier) let mqtt5 = CocoaMQTT5(clientID: clientID, host: "broker.emqx.io", port: 1883) let connectProperties = MqttConnectProperties() connectProperties.topicAliasMaximum = 0 connectProperties.sessionExpiryInterval = 0 connectProperties.receiveMaximum = 100 connectProperties.maximumPacketSize = 500 mqtt5.connectProperties = connectProperties mqtt5.username = "test" mqtt5.password = "public" mqtt5.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout") mqtt5.keepAlive = 60 mqtt5.delegate = self mqtt5.connect() ///MQTT 3.1.1 let clientID = "CocoaMQTT-" + String(ProcessInfo().processIdentifier) let mqtt = CocoaMQTT(clientID: clientID, host: "broker.emqx.io", port: 1883) mqtt.username = "test" mqtt.password = "public" mqtt.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout") mqtt.keepAlive = 60 mqtt.delegate = self mqtt.connect() ``` Now you can use closures instead of `CocoaMQTTDelegate`: ```swift mqtt.didReceiveMessage = { mqtt, message, id in print("Message received in topic \(message.topic) with payload \(message.string!)") } ``` ## SSL Secure #### One-way certification No certificate is required locally. If you want to trust all untrust CA certificates, you can do this: ```swift mqtt.allowUntrustCACertificate = true ``` #### Two-way certification Need a .p12 file which is generated by a public key file and a private key file. You can generate the p12 file in the terminal: ``` openssl pkcs12 -export -clcerts -in client-cert.pem -inkey client-key.pem -out client.p12 ``` Note: Please use openssl version 1.1 (e.g. `brew install [email protected]`), otherwise you may not be able to import the generated .p12 file to the system correctly. ## MQTT over Websocket In the 1.3.0, The CocoaMQTT has supported to connect to MQTT Broker by Websocket. If you integrated by **Swift Package Manager**, follow these steps: 1. Open your project in Xcode. 2. Go to `File` > `Swift Packages` > `Add Package Dependency`. 3. Enter the repository URL: `https://github.com/emqx/CocoaMQTT.git`. 4. Choose the latest version or specify a version range. 5. Add the package to your target. At last, import "CocoaMQTT" and "Starscream" to your project: ```swift import CocoaMQTT import CocoaMQTTWebSocket import Starscream ``` If you integrated by **CocoaPods**, you need to modify you `Podfile` like the followings and execute `pod install` again: ```ruby use_frameworks! target 'Example' do pod 'CocoaMQTT/WebSockets' end ``` If you're using CocoaMQTT in a project with only a `.podspec` and no `Podfile`, e.g. in a module for React Native, add this line to your `.podspec`: ```ruby Pod::Spec.new do |s| ... s.dependency "Starscream" end ``` Then, Create a MQTT instance over Websocket: ```swift ///MQTT 5.0 let websocket = CocoaMQTTWebSocket(uri: "/mqtt") let mqtt5 = CocoaMQTT5(clientID: clientID, host: host, port: 8083, socket: websocket) let connectProperties = MqttConnectProperties() connectProperties.topicAliasMaximum = 0 // ... mqtt5.connectProperties = connectProperties // ... _ = mqtt5.connect() ///MQTT 3.1.1 let websocket = CocoaMQTTWebSocket(uri: "/mqtt") let mqtt = CocoaMQTT(clientID: clientID, host: host, port: 8083, socket: websocket) // ... _ = mqtt.connect() ``` If you want to add additional custom header to the connection, you can use the following: ```swift let websocket = CocoaMQTTWebSocket(uri: "/mqtt") websocket.headers = [ "x-api-key": "value" ] websocket.enableSSL = true let mqtt = CocoaMQTT(clientID: clientID, host: host, port: 8083, socket: websocket) // ... _ = mqtt.connect() ``` If you want to connect using WebSocket Secure (wss), you can use the following example: ```swift import CocoaMQTT import CocoaMQTTWebSocket import Starscream class WebSocketManager { private var mqttClient: CocoaMQTT? var message: String = "" var token: String = "" func setupMQTTClient(with token: String) { let socket = CocoaMQTTWebSocket(uri: "/mqtt") socket.enableSSL = true mqttClient = CocoaMQTT(clientID: token, host: "host", port: 443, socket: socket) mqttClient?.delegate = self } func connect() { guard let mqttClient = mqttClient else { return } mqttClient.connect() } } extension WebSocketManager: CocoaMQTTDelegate { func mqtt(_ mqtt: CocoaMQTT, didReceive trust: SecTrust, completionHandler: @escaping (Bool) -> Void) { // Implement your custom SSL validation logic here. // For example, you might want to always trust the certificate for testing purposes: completionHandler(true) } func mqtt(_ mqtt: CocoaMQTT, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { if let serverTrust = challenge.protectionSpace.serverTrust { completionHandler(.useCredential, URLCredential(trust: serverTrust)) return } } completionHandler(.performDefaultHandling, nil) } func mqttUrlSession(_ mqtt: CocoaMQTT, didReceiveTrust trust: SecTrust, didReceiveChallenge challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { print("\(#function), \n result:- \(challenge.debugDescription)") } func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) { print("Published message with ID: \(id)") } func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopics topics: [String]) { print("Unsubscribed from topics: \(topics)") } func mqttDidPing(_ mqtt: CocoaMQTT) { print("MQTT did ping") } func mqttDidReceivePong(_ mqtt: CocoaMQTT) { print("MQTT did receive pong") } func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: (any Error)?) { print("Disconnected from MQTT broker with error: \(String(describing: err))") } func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) { print("Connected to MQTT broker with acknowledgment: \(ack)") } func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) { if let messageString = message.string { DispatchQueue.main.async { self.message = messageString } print("Received message: \(messageString) on topic: \(message.topic)") } } func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) { print("Published message: \(message.string ?? "") with ID: \(id)") } func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) { print("Subscribed to topics: \(success), failed to subscribe to: \(failed)") } func mqtt(_ mqtt: CocoaMQTT, didDisconnectWithError err: Error?) { print("Disconnected from MQTT broker with error: \(String(describing: err))") } } ``` ## Example App You can follow the Example App to learn how to use it. But we need to make the Example App works first: ```bash $ cd Examples Then, open the `Example.xcodeproj` by Xcode and start it! ## Dependencies These third-party functions are used: ~~[GCDAsyncSocket](https://github.com/robbiehanson/CocoaAsyncSocket)~~ * [MqttCocoaAsyncSocket](https://github.com/leeway1208/MqttCocoaAsyncSocket) * [Starscream](https://github.com/daltoniam/Starscream) ## LICENSE MIT License (see `LICENSE`) ## Contributors * [@andypiper](https://github.com/andypiper) * [@turtleDeng](https://github.com/turtleDeng) * [@jan-bednar](https://github.com/jan-bednar) * [@jmiltner](https://github.com/jmiltner) * [@manucheri](https://github.com/manucheri) * [@Cyrus Ingraham](https://github.com/cyrusingraham) ## Author - Feng Lee <[email protected]> - CrazyWisdom <[email protected]> - Alex Yu <[email protected]> - Leeway <[email protected]> ## Twitter https://twitter.com/EMQTech ", Assign "at most 3 tags" to the expected json: {"id":"13424","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"