Skip to content

Feature/app 93 mqtt reconnect i os #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 70 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,29 @@ MQTT client for React Native application.
- Secure MQTT connection over TLS 1.2.
- Authentication of both of server and client by X.509 certificates.
- Certificates and a private key stored in a device specific key store.
- [Android KeyStore](https://developer.android.com/training/articles/keystore#UsingAndroidKeyStore) on Android
- [Default keychain](https://developer.apple.com/documentation/security/keychain_services/keychains) on iOS
- [Android KeyStore](https://developer.android.com/training/articles/keystore#UsingAndroidKeyStore) on Android
- [Default keychain](https://developer.apple.com/documentation/security/keychain_services/keychains) on iOS

## Dependencies

This library wraps the following libraries,

- [Paho MQTT Client for Android variant maintained by hannesa2](https://github.com/hannesa2/paho.mqtt.android) (Android)

This library is forked as [emoto-kc-ak/paho.mqtt.android](https://github.com/emoto-kc-ak/paho.mqtt.android) to make Maven artifacts.

- [CocoaMQTT](https://github.com/emqx/CocoaMQTT) (iOS)

## Installation

```sh
npm install --save git+https://github.com/emoto-kc-ak/react-native-mqtt-client.git#v0.1.1
npm install --save git+https://github.com/emoto-kc-ak/react-native-mqtt-client.git#v0.1.4
```

## Usage

```js
import MqttClient from "react-native-mqtt-client";
import MqttClient from 'react-native-mqtt-client';
```

### Configuring an identity
Expand All @@ -43,13 +45,18 @@ MqttClient.setIdentity({
keyPem: IOT_KEY, // PEM representation string of a private key
keyStoreOptions, // options for a device specific key store. may be omitted
})
.then(() => { /* handle success */ })
.catch(({code, message}) => { /* handle error */ });
.then(() => {
/* handle success */
})
.catch(({ code, message }) => {
/* handle error */
});
```

`keyStoreOptions` is an optional object that may have the following fields,
- `caCertAlias`: (string) Alias associated with a root certificate (Android only). See [`KeyStore.setCertificateEntry`](https://developer.android.com/reference/java/security/KeyStore#setCertificateEntry(java.lang.String,%20java.security.cert.Certificate))
- `keyAlias`: (string) Alias associated with a private key (Android only). See [`KeyStore.setKeyEntry`](https://developer.android.com/reference/java/security/KeyStore#setKeyEntry(java.lang.String,%20java.security.Key,%20char[],%20java.security.cert.Certificate[]))

- `caCertAlias`: (string) Alias associated with a root certificate (Android only). See [`KeyStore.setCertificateEntry`](<https://developer.android.com/reference/java/security/KeyStore#setCertificateEntry(java.lang.String,%20java.security.cert.Certificate)>)
- `keyAlias`: (string) Alias associated with a private key (Android only). See [`KeyStore.setKeyEntry`](<https://developer.android.com/reference/java/security/KeyStore#setKeyEntry(java.lang.String,%20java.security.Key,%20char[],%20java.security.cert.Certificate[])>)
- `caCertLabel`: (string) Label associated with a root certificate (iOS only). See [`kSecAttrLabel`](https://developer.apple.com/documentation/security/ksecattrlabel)
- `certLabel`: (string) Label associated with a client certificate (iOS only). See [`kSecAttrLabel`](https://developer.apple.com/documentation/security/ksecattrlabel)
- `keyApplicationTag`: (string) Tag associated with a private key (iOS only). See [`kSecAttrApplicationTag`](https://developer.apple.com/documentation/security/ksecattrapplicationtag)
Expand All @@ -64,8 +71,12 @@ MqttClient.connect({
port: IOT_PORT, // (number) Port to connect.
clientId: IOT_DEVICE_ID, // (string) Client ID of a device connecting.
})
.then(() => { /* handle success */ })
.catch(({code, message}) => { /* handle error */ });
.then(() => {
/* handle success */
})
.catch(({ code, message }) => {
/* handle error */
});
```

It attempts to connect to `ssl://$IOT_ENDPOINT:$IOT_PORT`.
Expand All @@ -76,11 +87,16 @@ It attempts to connect to `ssl://$IOT_ENDPOINT:$IOT_PORT`.

```js
MqttClient.publish(topic, payload)
.then(() => { /* handle success */ })
.catch(({code, message}) => { /* handle error */ });
.then(() => {
/* handle success */
})
.catch(({ code, message }) => {
/* handle error */
});
```

Where,

- `topic`: (string) Topic where `payload` is to be published.
- `payload`: (string) Payload to be published. Usually a stringified JSON object.

Expand All @@ -90,11 +106,16 @@ Where,

```js
MqttClient.subscribe(topic)
.then(() => { /* handle success */ })
.catch(({code, message}) => { /* handle error */ });
.then(() => {
/* handle success */
})
.catch(({ code, message }) => {
/* handle error */
});
```

Where,

- `topic`: (string) Topic to subscribe.

To handle messages in the subscribed topic, you have to handle a [`receive-message` event](#received-message).
Expand All @@ -104,7 +125,7 @@ To handle messages in the subscribed topic, you have to handle a [`receive-messa
`MqttClient.disconnect` disconnects from an MQTT broker.

```js
MqttClient.disconnect()
MqttClient.disconnect();
```

### Loading an identity
Expand All @@ -113,8 +134,12 @@ An identity stored in a device specific key store by `MqttClient.setIdentity` ma

```js
MqttClient.loadIdentity(keyStoreOptions)
.then(() => { /* handle success */ })
.catch(({code, message}) => { /* handle error */ });
.then(() => {
/* handle success */
})
.catch(({ code, message }) => {
/* handle error */
});
```

Please refer to [Configuring an identity](#configuring-an-identity) for details of `keyStoreOptions`.
Expand All @@ -125,8 +150,12 @@ An identity stored in a device specific key store by `MqttClient.setIdentity` ma

```js
MqttClient.resetIdentity(keyStoreOptions)
.then(() => { /* handle success */ })
.catch(({code, message}) => { /* handle error */ });
.then(() => {
/* handle success */
})
.catch(({ code, message }) => {
/* handle error */
});
```

Please refer to [Configuring an identity](#configuring-an-identity) for details of `keyStoreOptions`.
Expand All @@ -137,11 +166,16 @@ Please refer to [Configuring an identity](#configuring-an-identity) for details

```js
MqttClient.isIdentityStored(keyStoreOptions)
.then((isStored) => { /* handle success */ })
.catch(({code, message}) => { /* handle error */ });
.then((isStored) => {
/* handle success */
})
.catch(({ code, message }) => {
/* handle error */
});
```

Where,

- `isStored`: (boolean) Whether an identity is stored in a device-specific key store.

Please refer to [Configuring an identity](#configuring-an-identity) for details of `keyStoreOptions`.
Expand All @@ -155,26 +189,33 @@ Please refer to [Configuring an identity](#configuring-an-identity) for details
A `connected` event is notified when connection to an MQTT broker is established.

```js
MqttClient.addListener('connected', () => { /* handle connection */ })
MqttClient.addListener('connected', () => {
/* handle connection */
});
```

#### disconnected

A `disconnected` event is notified when connection to an MQTT broker is disconnected.

```js
MqttClient.addListener('disconnected', () => { /* handle disconnection */ })
MqttClient.addListener('disconnected', () => {
/* handle disconnection */
});
```

#### received-message

A `received-message` event is notified when a message is arrived from an MQTT broker.

```js
MqttClient.addListener('received-message', ({topic, payload}) => { /* handle message */ });
MqttClient.addListener('received-message', ({ topic, payload }) => {
/* handle message */
});
```

Where,

- `topic`: (string) Topic where a message has been published.
- `payload`: (string) Payload of a message. Usually a stringified JSON object.

Expand All @@ -183,7 +224,9 @@ Where,
A `got-error` event is notified when an error has occurred.

```js
MqttClient.addListener('got-error', (err) => { /* handle error */ })
MqttClient.addListener('got-error', (err) => {
/* handle error */
});
```

## iOS Tips
Expand Down Expand Up @@ -215,6 +258,7 @@ npm run prepare
```

Artifacts will be updated in the following directories,

- `lib/commonjs`
- `lib/module`
- `lib/typescript`
Expand All @@ -229,4 +273,4 @@ MIT

## Acknowlegement

This project is bootstrapped with [callstack/react-native-builder-bob](https://github.com/callstack/react-native-builder-bob).
This project is bootstrapped with [callstack/react-native-builder-bob](https://github.com/callstack/react-native-builder-bob).
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ class RNMqttClient(reactContext: ReactApplicationContext)
* - `clientId`: {`String`} Client ID of the device.
* - `host`: {`String`} Host name of the MQTT broker.
* - `port`: {`int`} Port of the MQTT broker.
* - `reconnect`: {`Boolean`} Reconnect if connection is lost.
*
* @param params
*
Expand Down Expand Up @@ -304,7 +305,9 @@ class RNMqttClient(reactContext: ReactApplicationContext)
override fun connectionLost(cause: Throwable?) {
Log.d(NAME, "connectionLost", cause)
this@RNMqttClient.notifyError("ERROR_CONNECTION", cause)
this@RNMqttClient.notifyEvent("disconnected", null)
if(!parsedParams.reconnect) {
this@RNMqttClient.notifyEvent("disconnected", null)
}
}

override fun deliveryComplete(token: IMqttDeliveryToken) {
Expand Down Expand Up @@ -340,8 +343,9 @@ class RNMqttClient(reactContext: ReactApplicationContext)
}
})
val connectOptions = MqttConnectOptions()
connectOptions.setSocketFactory(socketFactory)
connectOptions.setCleanSession(true)
connectOptions.socketFactory = socketFactory
connectOptions.isCleanSession = true
connectOptions.isAutomaticReconnect = parsedParams.reconnect
Log.d(NAME, "connecting to the broker")
val token = client.connect(connectOptions)
token.setActionCallback(object: IMqttActionListener {
Expand Down Expand Up @@ -549,15 +553,17 @@ class RNMqttClient(reactContext: ReactApplicationContext)
private class ConnectionParameters(
val host: String,
val port: Int,
val clientId: String
val clientId: String,
val reconnect: Boolean
) {
companion object {
// Parses a given object from JavaScript.
fun parseReadableMap(params: ReadableMap): ConnectionParameters {
return ConnectionParameters(
host=params.getRequiredString("host"),
port=params.getRequiredInt("port"),
clientId=params.getRequiredString("clientId")
clientId=params.getRequiredString("clientId"),
reconnect=params.getRequiredBoolean("reconnect")
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,32 @@ fun ReadableMap.getOptionalMap(key: String): ReadableMap? {
}
return this.getMap(key)
}

/**
* Obtains an optional Boolean value from a given `ReadableMap`.
*
* @param key
*
* Key associated with the value to be obtained.
*
* @return
*
* Boolean value associated with `key`.
* `false` if no value is associated with `key`.
*
* @throws IllegalArgumentException
*
* If the value associated with `key` is not a Boolean.
*/
fun ReadableMap.getRequiredBoolean(key: String): Boolean {
if (!this.hasKey(key)) {
return false
}
if (this.getType(key) != ReadableType.Boolean) {
throw IllegalArgumentException(
"$key must be associated with a Boolean" +
" but ${this.getType(key)} was given"
)
}
return this.getBoolean(key)
}
4 changes: 3 additions & 1 deletion ios/MqttClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ class MqttClient : RCTEventEmitter {
let host: String = RCTConvert.nsString(params["host"])
let port: Int = RCTConvert.nsInteger(params["port"])
let clientId: String = RCTConvert.nsString(params["clientId"])
let reconnect: Bool = RCTConvert.bool(params["reconnect"])
self.client = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port))
self.client!.username = ""
self.client!.password = ""
Expand All @@ -311,6 +312,7 @@ class MqttClient : RCTEventEmitter {
self.client!.keepAlive = 60
self.client!.delegate = self
self.client!.logLevel = .debug
self.client!.autoReconnect = reconnect
_ = self.client!.connect()
resolve(nil)
}
Expand Down Expand Up @@ -441,7 +443,7 @@ extension MqttClient : CocoaMQTTDelegate {
completionHandler(false)
return
}

switch result {
case .proceed:
completionHandler(true)
Expand Down
2 changes: 1 addition & 1 deletion ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ platform :ios, '11.0'
target 'MqttClient' do
use_frameworks!

pod 'CocoaMQTT'
pod 'CocoaMQTT', '2.1.1'
end
20 changes: 11 additions & 9 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
PODS:
- CocoaAsyncSocket (7.6.4)
- CocoaMQTT (1.2.0):
- CocoaAsyncSocket (~> 7.6.3)
- CocoaMQTT (2.1.1):
- CocoaMQTT/Core (= 2.1.1)
- CocoaMQTT/Core (2.1.1):
- MqttCocoaAsyncSocket (~> 1.0.8)
- MqttCocoaAsyncSocket (1.0.8)

DEPENDENCIES:
- CocoaMQTT
- CocoaMQTT (= 2.1.1)

SPEC REPOS:
trunk:
- CocoaAsyncSocket
- CocoaMQTT
- MqttCocoaAsyncSocket

SPEC CHECKSUMS:
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
CocoaMQTT: 0b43b6ec5196c1ea30f4636d9ad5afa474fd616b
CocoaMQTT: 204713d369b9a3ed8d90af2e0666f11121d06e74
MqttCocoaAsyncSocket: 77d3b74f76228dd5a05d1f9526eab101d415b30c

PODFILE CHECKSUM: c5d39cc2472e2b2a31cd4a3611619e1f90f00a77
PODFILE CHECKSUM: d869e597c5c87ca654a9bd0e46189acac14b8d2c

COCOAPODS: 1.9.3
COCOAPODS: 1.11.3
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-mqtt-client",
"version": "0.1.1",
"version": "0.1.4",
"description": "MQTT client for React Native",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down
2 changes: 1 addition & 1 deletion react-native-mqtt-client.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ Pod::Spec.new do |s|


s.dependency "React"
s.dependency "CocoaMQTT"
s.dependency "CocoaMQTT", "~> 2.1.1"
end
Loading