# Electron から MQTT.js SDK を使ってデプロイメントに接続する

本記事では、Electron プロジェクトでの [MQTT](https://www.emqx.com/en/mqtt) の利用方法を主に紹介し、シンプルな MQTT デスクトップクライアントの作成、およびクライアントと MQTT ブローカー間の接続、サブスクライブ、サブスクライブ解除、メッセージ送受信などの機能を実装する方法を解説します。

## 事前準備

### MQTT ブローカーのデプロイ

- EMQX が提供する [無料のパブリック MQTT ブローカー](https://www.emqx.com/en/mqtt/public-mqtt5-broker) を利用できます。このサービスは [EMQX プラットフォーム](https://www.emqx.com/en) をベースに構築されています。ブローカーへのアクセス情報は以下の通りです：
  - ブローカー：**broker.emqx.io**
  - TCP ポート：1883
  - SSL/TLS ポート：8883
  - WebSocket ポート：8083
  - WebSocket TLS/SSL ポート：8084
- また、[独自の MQTT ブローカーを作成](../create/overview.md)することも可能です。デプロイメントが稼働状態になったら、デプロイメント概要ページで接続情報を確認できます。後のクライアント接続段階で必要となるユーザー名とパスワードは、**アクセス制御** -> **[認証](../deployments/default_auth.md)** で設定してください。

### Electron アプリケーションの作成

[Electron](https://www.electronjs.org/) は GitHub によって開発・保守されているオープンソースのソフトウェアフレームワークです。Chromium レンダリングエンジンと Node.js ランタイムを組み合わせ、Web 技術を用いたデスクトップ GUI アプリケーションの開発を可能にします。Electron は Atom、GitHub Desktop、Light Table、Visual Studio Code、WordPress Desktop などの主要なオープンソースプロジェクトの GUI フレームワークとして使われています。[^1]

基本的な Electron プロジェクトは、`package.json`（メタデータ）、`main.js`（コード）、`index.html`（GUI）の3ファイルで構成されます。Electron の実行ファイル（Windows では electron.exe、macOS では electron.app、Linux では electron）がフレームを提供し、開発者はフラグの追加、アイコンのカスタマイズ、実行ファイルの名前変更や編集を自由に行えます。

新規プロジェクトの構築方法は多様ですが、ここではいくつかの簡単な例を挙げます：

- 手動で作成する場合、プロジェクトディレクトリで以下を実行します

  ```shell
  cd your-project

  npm init

  npm i -D electron@latest
  ```

  詳細な手順は以下のドキュメントも参照してください。

  アドレス: [https://www.electronjs.org/docs/tutorial/first-app](https://www.electronjs.org/docs/tutorial/first-app)

- 公式テンプレートプロジェクト `electron-quick-start` を使った迅速な開発

  アドレス: [https://github.com/electron/electron-quick-start](https://github.com/electron/electron-quick-start)

  ```shell
    # リポジトリをクローン
    git clone https://github.com/electron/electron-quick-start
    # リポジトリに移動
    cd electron-quick-start
    # 依存関係をインストール
    npm install
    # アプリを起動
    npm start
  ```

- `React.js` を使った開発が可能なテンプレートプロジェクト `electron-react-boilerplate` を使った迅速な開発

  アドレス: <https://github.com/electron-react-boilerplate/electron-react-boilerplate>

  ```shell
  git clone --depth 1 --single-branch https://github.com/electron-react-boilerplate/electron-react-boilerplate.git your-project-name
  cd your-project-name
  yarn
  ```

- `Vue.js` を使った開発が可能な `electron-vue` によるプロジェクトの迅速な構築は、`vue-cli` ツールを用いたプロジェクト初期化と連携します。

  アドレス: <https://github.com/SimulatedGREG/electron-vue>

  ```shell
  # vue-cli をインストールし、ボイラープレートを作成
  npm install -g vue-cli
  vue init simulatedgreg/electron-vue my-project
  
  # 依存関係をインストールし、アプリを起動
  cd my-project
  yarn # または npm install
  yarn run dev # または npm run dev
  ```

本記事では、公式の electron quick start プロジェクトテンプレートを使ってプロジェクトを初期化し、例題プロジェクトを迅速に構築します。

## 依存関係のインストール

コマンドラインから以下を実行します。

```shell
npm install mqtt --save
```

依存関係のインストール後、デバッグ用にコンソールを開きたい場合は、`main.js` のコードを修正し、`win.webContents.openDevTools()` のコメントアウトを解除してください。

```javascript
// DevTools を開く
mainWindow.webContents.openDevTools()
```

この場合、ローカルにインストールした `MQTT.js` モジュールは、フロントエンドビルダーでフロントエンドページをパッケージングしない限り、`renderer.js` に直接読み込めません。ビルドツールを使う方法以外に、以下の2つの解決策があります：

1. `webPreferences` 内で `nodeIntegration` を true に設定する方法。これにより、`webview` 内で Node 統合が有効になり、`require` や `process` などの Node API を使って低レベルのシステムリソースにアクセスできます。Node 統合はデフォルトで無効です。

   ```javascript
   const mainWindow = new BrowserWindow({
     width: 800,
     height: 600,
     webPreferences: {
       nodeIntegration: true,
       preload: path.join(__dirname, 'preload.js'),
     },
   })
   ```

2. `preload.js` 内で MQTT.js モジュールをインポートする方法。Node 統合が無効でも、このスクリプトはすべての Node API にアクセス可能です。ただし、このスクリプトの実行完了後、Node 経由で注入されたグローバルオブジェクトは削除されます。

3. [MQTT.js](https://www.emqx.com/en/blog/mqtt-js-tutorial) モジュールをメインプロセスでインポートし接続する方法。Electron では、`ipcMain` と `ipcRenderer` モジュールを使い、開発者が定義した「チャネル」を介してプロセス間通信を行います。これらのチャネルは**任意の名前**を付けられ、**双方向**に通信可能です。使用例は [IPC チュートリアル](https://www.electronjs.org/docs/latest/tutorial/ipc) を参照してください。

## 接続

より直感的に説明するため、例題の主要な接続コードは `renderer.js` ファイルに記述します。セキュリティ面を考慮し、インストールした MQTT モジュールは Node.js API の require メソッドを使い、`preload.js` ファイル内で読み込みます（上記の方法2）。また、この方法ではグローバルな window オブジェクトに注入します。

> **注意:** [Context isolation (contextIsolation)](https://www.electronjs.org/docs/latest/tutorial/context-isolation) は Electron 12 以降デフォルトで有効になっています。preload スクリプトはアタッチ先の renderer と `window` グローバルを共有しますが、`contextIsolation` のため preload スクリプトから直接 `window` に変数をアタッチできません。

そのため、`webPreferences` で `contextIsolation: false` を設定して無効化します。

```js
const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: false, // バージョン12.0.0以上はデフォルトで有効
    }
  })
```

これにより、読み込んだモジュールを `renderer.js` で直接アクセス可能になります。

- MQTT モジュールのインポート例

```javascript
// preload.js
const mqtt = require('mqtt')
window.mqtt = mqtt
```

### TCP ポートで接続

クライアント ID、ユーザー名、パスワードを以下のコードで設定します。クライアント ID は一意である必要があります。

```js
const clientId = 'emqx_vue3_' + Math.random().toString(16).substring(2, 8)
const username = 'emqx_test'
const password = 'emqx_test'
```

以下のコードでクライアントと MQTT ブローカー間の接続を確立します。

```js
const client = mqtt.connect('mqtt://broker.emqx.io:1883', {
  clientId,
  username,
  password,
  // ...その他のオプション
})
```

### TCP セキュアポートで接続

TLS/SSL 暗号化が有効な場合、接続の[パラメーターオプション](https://github.com/mqttjs/MQTT.js#mqttclientstreambuilder-options)は TCP ポート接続時と同様です。プロトコルを `mqtts` に変更し、正しいポート番号を指定するだけで構いません。

以下のコードでクライアントと MQTT ブローカー間の接続を確立します。

```js
const client = mqtt.connect('mqtts://broker.emqx.io:8883', {
  clientId,
  username,
  password,
  // ...その他のオプション
})
```

### WebSocket ポートで接続

MQTT WebSocket は接続パスとして `/path` を統一的に使用します。接続時に指定が必要で、EMQX ブローカーは `/mqtt` をパスとして使います。

そのため、WebSocket 接続時はポート番号の変更とプロトコルを `ws` に切り替えるだけでなく、`/mqtt` パスを追加する必要があります。

以下のコードでクライアントと MQTT ブローカー間の接続を確立します。

```js
const client = mqtt.connect('ws://broker.emqx.io:8083/mqtt', {
  clientId,
  username,
  password,
  // ...その他のオプション
})
```

### WebSocket セキュアポートで接続

TLS/SSL 暗号化が有効な場合、接続の[パラメーターオプション](https://github.com/mqttjs/MQTT.js#mqttclientstreambuilder-options)は WebSocket ポート接続時と同様です。プロトコルを `wss` に変更し、正しいポート番号を指定するだけで構いません。

以下のコードでクライアントと MQTT ブローカー間の接続を確立します。

```js
const client = mqtt.connect('wss://broker.emqx.io:8084/mqtt', {
  clientId,
  username,
  password,
  // ...その他のオプション
})
```

## サブスクライブとパブリッシュ

### トピックのサブスクライブ

サブスクライブするトピックと対応する [QoS レベル](https://www.emqx.com/zh/blog/introduction-to-mqtt-qos)（任意）を設定し、MQTT.js の `subscribe` メソッドを呼び出してサブスクライブ操作を行います。

```javascript
function onSub() {
  if (client.connected) {
    const { topic, qos } = subscriber
    client.subscribe(
      topic.value,
      { qos: parseInt(qos.value, 10) },
      (error, res) => {
        if (error) {
          console.error('Subscribe error: ', error)
        } else {
          console.log('Subscribed: ', res)
        }
      }
    )
  }
}
```

### トピックのサブスクライブ解除

サブスクライブ解除時は、不要になったトピックと対応する QoS（任意）を渡す必要があります。

<https://github.com/mqttjs/MQTT.js#mqttclientunsubscribetopictopic-array-options-callback>

```javascript
function onUnsub() {
  if (client.connected) {
    const { topic } = subscriber
    client.unsubscribe(topic.value, (error) => {
      if (error) {
        console.error('Unsubscribe error: ', error)
      } else {
        console.log('Unsubscribed: ', topic.value)
      }
    })
  }
}
```

### メッセージのパブリッシュ

メッセージをパブリッシュする際は、MQTT ブローカーに対して対応するトピックとメッセージ内容を通知する必要があります。

<https://github.com/mqttjs/MQTT.js#mqttclientpublishtopic-message-options-callback>

```javascript
function onSend() {
  if (client.connected) {
    const { topic, qos, payload } = publisher
    client.publish(topic.value, payload.value, {
      qos: parseInt(qos.value, 10),
      retain: false,
    })
  }
}
```

### メッセージの受信

```javascript
// onConnect 関数内
client.on('message', (topic, message) => {
  const msg = document.createElement('div')
  msg.setAttribute('class', 'message-body')
  msg.innerText = `${message.toString()}\nOn topic: ${topic}`
  document.getElementById('article').appendChild(msg)
})
```

### MQTT ブローカーからの切断

クライアントによるアクティブな切断

<https://github.com/mqttjs/MQTT.js#mqttclientendforce-options-callback>

```javascript
function onDisconnect() {
  if (client.connected) {
    client.end()
    client.on('close', () => {
      connectBtn.innerText = 'Connect'
      console.log(options.clientId + ' disconnected')
    })
  }
}
```

## 接続のテスト

ここまでで、Electron で作成した [MQTT 5.0 クライアントツール - MQTTX](https://mqttx.app)（こちらも Electron で開発）を使い、メッセージの送受信をテストします。

MQTTX からクライアントにメッセージを送信すると、メッセージが正常に受信されていることが確認できます：

![electronmessage.png](https://assets.emqx.com/images/bfb62b9f23f6836627d8e129d38b9160.png)

自作クライアントから MQTTX にメッセージを送信すると、MQTTX 側でも正常にメッセージを受信していることが確認できます：

![mqttx.png](https://assets.emqx.com/images/cc97fe533fcce20765530970d7696f58.png)

## まとめ

ここまでで、Electron を使ってシンプルな MQTT デスクトップクライアントを作成し、クライアントと MQTT ブローカー間の接続、メッセージ送受信、サブスクライブ解除、切断のシナリオをシミュレーションできました。完全なサンプルソースコードは [MQTT Client Electron ページ](https://github.com/emqx/MQTT-Client-Examples/tree/master/mqtt-client-Electron)からダウンロード可能です。また、[MQTT Client Example ページ](https://github.com/emqx/MQTT-Client-Examples)では他言語のデモ例も多数公開されていますので、ぜひご覧ください。

[^1]: https://en.wikipedia.org/wiki/Electron_(software_framework)
