20201012のNode.jsに関する記事は4件です。

LINEで画像にある日本語を認識しよう

モチベーション

日本で働いている私にとっては辞書は一番使っているプログラムです。
もちろん、わからない単語がたくさんあります。普通、わからない単語があったら、単語をコピーして辞書にペーストしますが、時々写真にある単語に対して大変になりました。その漢字一つ一つを手で描けなければなりません。
で、今回、写真のテキストをAIで認識してもらって、デジタルの文字コードに変換するLINE BOTを作りましょう。

 概要

illustration.png

要素

今回のシステムは要素が3つあります

  • クライアント:これユーザにつながる場所です。テキストの写真をアップロードして結果が表示されるところです。今回使いたいサービスはLine Botです。
  • サーバー:クライアントから画像をもらって、OCRサービスに送って、結果を処理してクライアントに送るところです。
  • OCRサービス:OCR(Optical Character Recognition:光学文字認識)とは、活字の文書の画像(通常イメージスキャナーで取り込まれる)を文字コードの列に変換するソフトウェアである(ソース:ウィキペディア)。

フロー

① サーバーはLineAPIでユーザーが送ったもらった画像を読んで、ファイルを保存するステップです。
② ステップ1のファイルをグーグルのOCRサービスに送ります。
③ グーグルのレスポンスから必要部分を抽出します。
④ ステップ3の結果をLineAPIでユーザーに返信します。

コード

'use strict';

const fs = require('fs');
const keyConfig = require('./config');
const express = require('express');
const bodyParser = require('body-parser');
const request = require('request');
const app = express();
app.use(bodyParser.json());
const line = require('@line/bot-sdk');
const vision = require('@google-cloud/vision');

const PORT = process.env.PORT || 3000;
const config = {
    channelSecret: keyConfig.line.channelSecret,
    channelAccessToken: keyConfig.line.channelAccessToken
};

const lineClient = new line.Client(config);

async function googleOCR(fileName, token) {
    const googleClient = new vision.ImageAnnotatorClient();

    googleClient.documentTextDetection(fileName)
    .then(response => {
        return lineClient.replyMessage(token, {
            type: 'text',
            text: response[0].fullTextAnnotation.text
        });
    })
    .catch(err => {
        console.error(err);
    });
};

const sampleFunction = async (event) => {
    const options = {
        url: `https://api.line.me/v2/bot/message/${event.message.id}/content`,
        method: 'get',
        headers: {
            'Authorization': 'Bearer ' + keyConfig.line.channelAccessToken,
        },
        encoding: null
    };
    const fileName = './image.jpg';

    request(options, function(error, response, body) {
        if (!error && response.statusCode == 200) { 
            fs.writeFileSync(fileName, new Buffer.from(body), 'binary');
            console.log('file saved');
        }
    });

    return googleOCR(fileName, event.replyToken);
};

async function handleEvent(event) {
    if(event.message.type !== 'image') {
        return;
    }
    return sampleFunction(event);
}


app.post('/webhook', (req, res) => {
    Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);

結果

結果

参考:
LINE BOTからNode.jsで画像を受け取って保存する
Google Nodejs API

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure Communication Services ことはじめ (2) : 音声通話ができるまで

Azure Communication Services は、リアルタイム コミュニケーション基盤となるサービスで、テキスト | 音声 | ビデオ によるコミュニケーションのハブとなり、接続やコントロールを行うアプリやサービスを SDK などを用いて容易に開発できます。

今回は、Azure Communication Services を使ったアプリ開発の第一歩として、音声通話ができるまでを手順を追って確認し、Node.js の Web アプリを作成してみます。

開発環境

0. 事前準備

Azure Communication Services ことはじめ (1) : チャットができるまで0.事前準備 と同様に、Azure Portal で Azure Communication Services のサービスを作成し、接続文字列とエンドポイントを取得しておきます。

1. 音声通話ができるまで

今回は Node.js、Web アプリを念頭に、音声通話ができるまでの手順を確認します。

1-0. ライブラリの追加

以下のライブラリを冒頭に追加します。

ライブラリ
azure/communication-common ユーザーの作成、アクセストークン取得
azure/communication-administration  (同上) 
azure/communication-calling 音声通話のコントロール
client.js
import { AzureCommunicationUserCredential } from '@azure/communication-common';
import { CommunicationIdentityClient } from "@azure/communication-administration";
import { CallClient } from "@azure/communication-calling";

1-1. ユーザーを作成し、アクセストークンを取得する

接続文字列を用いて CommunicationIdentityClient を新規作成し、ユーザーの作成とアクセストークンの取得を行います。接続文字列には Azure Communication Services にアクセスするキーが含まれています。アクセストークンの種類は Calling を指定します。(他に Chat, SMS があります)

YOUR_CONNECTION_STRING は 事前準備で取得した 接続文字列に置き換えてください。

client.js
const identityClient = new CommunicationIdentityClient("YOUR_CONNECTION_STRING");

let userId;
let userToken;
identityClient.createUser().then(userResponse => {
    userId = userResponse.communicationUserId;
    identityClient.issueToken(userResponse, ["voip"]).then(tokenResponse => {
        userToken = tokenResponse.token;
}

1-2. 通話クライアントを作成

取得したトークンを用いて、音声通話をコントロールする CallClient を作成します。

client.js
let callClient;
const tokenCredential = new AzureCommunicationUserCredential(userToken);
callClient.createCallAgent(tokenCredential).then(agent => { callAgent = agent; }

1-3. 通話する

CallClient から、通話したい相手を CommunicationUserId で指定して Call します。

client.js
let call
call = callAgent.call([{ communicationUserId: "8:echo123" }], {});

1-4. 通話を終了する

Call を終了して音声通話を終了します。

client.js
call.hangUp({ forEveryone: true });

2. Web アプリの開発

以上の手順を踏まえて、Visual Studio Code で Node.js Web アプリを作成します。

2-1. 新規 Node.js アプリの作成

Node.js アプリを作成するフォルダーを作成し、Visual Studio Code で開き、npm init コマンドで package.json を作成します。

npm init -y

2-2. Azure Communication Services のライブラリのインストール

npm install コマンドで Azure Communication Services の必要なライブラリ (パッケージ) をインストールします

npm install @azure/communication-common --save
npm install @azure/communication-administration --save
npm install @azure/communication-calling --save

2-3. Webpack のインストール

今回は Webpack を利用して、JavaScript のモジュールバンドル & ローカルでの実行確認 を行います。
npm install コマンドで、webpack、webpack-cli、webpack-dev-server をインストールします。

npm install webpack webpack-cli webpack-dev-server --save-dev

2-4. コーディング

画面 (index.html)

ユーザーからの操作および音声入出力、各種情報を表示するため、index.html という名前でファイルを作成し、以下のような UI を作成します。

ACS_2_開発_03.png

ポイントとなるコードは以下になります。

index.html
<html>
  <body>
    <div>  <!-- UserId 出力-->
      <label>Your UserId: </label>
      <input id="caller-id-output" type="text" style="width: 600px;" disabled="true"/>
    </div>
    <div>  <!-- UserToken 出力-->
      <label>Your Token: </label>
      <textarea id="caller-token-output" type="text" style="width: 600px; height: 130px;" disabled="true"></textarea>
    </div>
    <div>  <!-- 呼び出し先 UserId 入力-->
      <input id="callee-id-input" type="text" style="width: 250px;"/>
    </div>
    <div> <!-- 通話開始、終了ボタン -->
      <button id="call-button" type="button" disabled="true">Start Call</button>
      <button id="hang-up-button" type="button" disabled="true">Hang Up</button>
    </div>
    <div>  <!-- 状態メッセージ出力 -->
      <label id="message-output" style="width: 1000px; height: 50px;"></label>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

加筆を行って整えたコードは以下になります。
ACSCallWeb202010/index.html

通話機能 (client.js)

client.js という名前でファイルを作成し、VoIP 通話をコントロールする機能を記述します。

後ほど client.js を index.js にビルドして index.html で読み込みます。

利用ライブラリー

今回は、これらのライブラリーを利用します。

client.js
import { AzureCommunicationUserCredential } from '@azure/communication-common';
import { CommunicationIdentityClient } from "@azure/communication-administration";
import { CallClient } from "@azure/communication-calling";

接続文字列

client.js に connectionString を記載しておきます。

YOUR_CONNECTION_STRING は 事前準備で取得した 接続文字列に置き換えてください。

セキュリティの観点から別の設定ファイルなどに記載して読み出すのが一般的ですが、今回は動作を確認するのみのアプリなので本体に記載しています。

client.js
let connectionString = "YOUR_CONNECTION_STRING";

UI 画面の入出力

呼び出しを行う相手先の UserId の入力を取得できるようにします。
また、通話の開始、終了のボダンのクリックを取得できるようにします。
出力は、UserId、UserToken、動作に対するメッセージを表示するようにしておきます。

client.js
// 入力
const calleeInput = document.getElementById("callee-id-input"); //通話先 UserId
const callButton = document.getElementById("call-button");      //通話開始ボタン
const hangUpButton = document.getElementById("hang-up-button"); //通話終了ボタン
// 出力
const callerIdOutput = document.getElementById("caller-id-output");       //UserId
const callerTokenOutput = document.getElementById("caller-token-output"); //UserToken
const messageOutput = document.getElementById("message-output");          //状態メッセージ

CommunicationUser の新規作成、Token の取得、CallAgent の生成

接続文字列から User を新規作成して Token を取得します。Token を利用して、通話のコントロールを行う CallAgent を生成します。CAllAgent が無事生成出来たら callButton (通話開始ボタン) をクリック可能(disabled = false)にします。

client.js
const identityClient = new CommunicationIdentityClient(connectionString);
const callClient = new CallClient();
let callAgent;
let call;

identityClient.createUser().then(userResponse => {
    callerIdOutput.value = userResponse.communicationUserId;    // UserID 画面出力
    identityClient.issueToken(userResponse, ["voip"]).then(tokenResponse => {
        const userToken = tokenResponse.token;
        callerTokenOutput.value = userToken;            // UserToken 画面出力
        messageOutput.innerText += "Got user token.";   // 状態メッセージ画面出力
        const tokenCredential = new AzureCommunicationUserCredential(userToken);
        callClient.createCallAgent(tokenCredential).then(agent => {
          callAgent = agent;
          callButton.disabled = false;                   // 通話開始ボタンをクリック可能に
          messageOutput.innerText += "\nReady to call."; // 状態メッセージ画面出力
        });
    });
});

通話の開始

callButton (通話開始ボタン) がクリックされたら、callAgent から相手の Communication Id (userToCall TextBox から取得) の呼び出しを行います。

client.js
callButton.addEventListener("click", () => {
    // start a call
    const userToCall = calleeInput.value;
    call = callAgent.call( [{ communicationUserId: userToCall }], {} );

    // change button states & adding messages
    hangUpButton.disabled = false;  // 通話停止ボタンをクリック可能に
    callButton.disabled = true;     // 通話開始ボタンをクリック不可に
    messageOutput.innerText += "\nCall started.";  // 状態メッセージ画面出力
});

通話の終了

hangupButton (通話終了ボタン) がクリックされたら、callAgent の通話を切断します。

client.js
hangUpButton.addEventListener("click", () => {
    // end the current call
    call.hangUp({ forEveryone: true });

    // change button states & adding messages
    hangUpButton.disabled = true;  // 通話停止ボタンをクリック不可に
    callButton.disabled = false;   // 通話停止ボタンをクリック可能に
    messageOutput.innerText += "\nNow hanged up.";  // 状態メッセージ画面出力
  });

最終的なコードはこちらになります。
ACSCallWeb202010/client.js

3. 音声通話を試してみる

今回は Webpack を利用しているので、client.js を index.js にビルドして起動します。

npx webpack-dev-server --entry ./client.js --output index.js --debug --devtool inline-source-map

起動したら、ブラウザーから http://localhost:8080 にアクセスします。
User Id と Token が取得できると [Start Call] のボタンがアクティブになります。
音声通話テスト用のユーザーである 8:echo123 を入力して [Start Call] をクリックします。
"Hello, welcome to Azure Communication Services audio testing system..." と音声が聞こえれば OK です。音声を録音して再生することで、こちらの音声が取得できていることも確認できます。

ACSCallWeb202010.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINE Botに羊を数えてもらったら目が覚めた

はじめに

 最近寝る時間が不規則で、すぐに寝つけないことが多いため、初めて作成するLINEBotに、羊を数えてもらうことで可能な限り早く眠りにつきたいと思いました。なお、本当に眠くなるLINEBotにするため、羊の画像をランダムで返す仕組みも考えたが、単純で、退屈で、眠気を誘うようなものをあえて目指しました。

LINE Botの詳細

 LINEmessagingAPIのCountAPI(単純な数値カウンターを作成できるAPI ※参考:https://countapi.xyz/ )を利用して、羊の数をカウントし、「眠れない」とつぶやくと、羊を数えてくれる。

コード

server2.js
'use strict'; // おまじない

// ########################################
//               初期設定など
// ########################################

// パッケージを使用します
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');

// ローカル(自分のPC)でサーバーを公開するときのポート番号です
const PORT = process.env.PORT || 3000;

// Messaging APIで利用するクレデンシャル(秘匿情報)です。
const config = {
    channelSecret: 'aaaaaaaaaaaaaaaaaaaaaa',
    channelAccessToken: 'aaaaaaaaaaaaaaaaaaaaaa'
};

const sampleFunction = async (event) => {
    // ユーザーメッセージが「眠れない」かどうか
    if (event.message.text !== '眠れない') {
        return client.replyMessage(event.replyToken, {
            type: 'text',
            text: '「眠れない」と話しかけてね'
        });
    } else {        
        let pushText = '';
        try {
            const res = await axios.get('https://api.countapi.xyz/hit/sheep/num');
            const SheepNum = res.data.value;
            pushText = `羊が${SheepNum}匹`;
        } catch (error) {
            pushText = '検索中にエラーが発生しました。ごめんね。';
            // APIからエラーが返ってきたらターミナルに表示する
            console.error(error);
        }

        // 「プッシュ」で後からユーザーに通知します
        return client.pushMessage(event.source.userId, {
            type: 'text',
            text: pushText,
        });
    }
};


// ########################################
//  LINEサーバーからのWebhookデータを処理する部分
// ########################################

// LINE SDKを初期化します
const client = new line.Client(config);

// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
    // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // サンプル関数を実行します
    return sampleFunction(event);
}



// ########################################
//          Expressによるサーバー部分
// ########################################

// expressを初期化します
const app = express();

// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
    // Webhookの中身を確認用にターミナルに表示します
    console.log(req.body.events);

    // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行
    if (req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff') {
        res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します
        console.log('検証イベントを受信しました!'); // ターミナルに表示します
        return; // これより下は実行されません
    }

    // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
    // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
    Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

// 最初に決めたポート番号でサーバーをPC内だけに公開します
// (環境によってはローカルネットワーク内にも公開されます)
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);

実行結果

IMG_9238.PNG

使ってみた結果

 全然眠れなかった。むしろ目が覚めた。
 ※242匹まで数えた。
 ※結局ディズニーオルゴールメドレーで寝ることができた。

考察

 なぜ羊を数えると眠くなるか、を調べてみたところ、理由は「人間は単調な音を聞き続けると眠たくなるため」であり、「聞く」という行為が、眠りを誘う上で重要であったことに気づいた。(参考:なぜ「羊」かというとsleep(スリープ)とsheep(シープ)が綴りや発音が似ていたため言葉遊びが理由で、寝る時に羊が数えられるようになったそうです。)
 つまり、羊の数をテキストデータとして明示するだけでは根本的に眠くならないということがわかった。※むしろ、スマホの画面が明るすぎて逆効果とさえ思う。

改善できるとしたら

 上記考察にも記載したように「単調な音を聞き続けられる」ように、眠いとつぶやいたら、羊の数を音声変換して発話してくれるBotの方が効果が高いと考えられる。
(参考となりそうなQiita記事:https://qiita.com/unokuncom/items/ffe95f6f7ffbaedcf562

最後に

最近早く寝られたときにしていることは、結局下記の3点である。
1.暖かいお風呂に20分以上つかる
2.適度な飲酒
3.オルゴールメドレーを聴く
非線形的なアプローチでLINEBotとの組み合わせからアイデアを考えてみたら、例えば、
・寝る時間を呟いたらお風呂に入るべきベストなタイミングを教えてくれるBot
・飲み仲間になってくれて、最後にそろそろ寝たら?って言ってくれるBot
・おすすめのオルゴールメドレーを検索してくれるBot
などが思いついた。時間があれば挑戦してみたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LAN上にhttps対応のwebサーバを建て、Androidスマホのchromeから警告が表示されない状態でアクセスした手順

初めに

プログレッシブwebアプリ(PWA)を展開するには、HTTPS通信でつながる環境が必要になります。
レンタルサーバを借りることなく、LAN上でPWAを展開、動作確認できる手段はないかと、調査した結果です。

環境

  • Windows10 Home
  • Node.js(v12.13.0)
  • Androidスマホ (KYOCERA TORQUE® G03 Androidバージョン 6.0.1)
  • Visual Studio Code
  • wifi環境

Windows Subsystem for Linux(WSL)のインストール

https通信を行うにあたり、DNSサーバの構築、サーバ証明書の作成が必要です。
当方が調査したところ、DNSサーバの構築にはUnbound、サーバ証明書の作成にはOpenSSLが見つかりました。Unbound、OpenSSLはともに、Windwos版もありますが、仮想Ubuntuを用いたほうが、壁にぶつかることが少なく進めることができました。のちに、下記参考記事を見つけ、仮想UbuntuではなくWSLを用いたほうがよりスムーズでした。そのWSLをインストールします。
(参考)【連載】WSL2、Visual Studio Code、DockerでグッとよくなるWindows開発環境 〜 その1:まずは概要 〜 | SIOS Tech. Lab

1. Windows PowerShellを管理者権限で起動
2. コマンドを実行する
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

再起動確認が現れるので、再起動します。

3. Ubuntu 20.04LTSをインストールする

MicroSoftStoreを起動し、Ubuntu 20.04LTSを検索し、インストールします。

Unboundを用いて、LAN上にDNSサーバを建てる

(参考)Ubuntu Serverで内向きDNSをUnboundを使ってやってみたのでメモ - Qiita
当手順は、Ubuntuでの作業になります。
当手順にて、Windows10をDNSサーバとして機能するようにします。

1. Windowsのスタートボタンより、「Ubuntu 20.04LTS」を起動する
2. Unboundをインストールする

下記コマンドを実行します。

sudo apt install unbound
3. 設定ファイルのサンプルをコピーする

下記コマンドを実行します。

sudo cp /usr/share/doc/unbound/examples/unbound.conf /etc/unbound/unbound.conf.d
4. 設定ファイルを編集する

下記コマンドを実行し設定ファイルを開きます。

sudo nano /etc/unbound/unbound.conf.d/unbound.conf

開いた設定ファイルを編集します。

unboud.conf
server: 
  verbosity: 1

  interface: 0.0.0.0

  access-control: 127.0.0.0/23 allow
  #以下は環境に応じて変更する
  #当方の環境が192.168.0.xxx系統であったので、192.168.0.0/23 allowとしています。
  #各々の環境に合わせて設定してください。
  access-control: 192.168.0.0/23 allow

  #ドメイン名とIPアドレスを紐づける
  #この例では、192.168.0.100にexample.comというドメイン名を紐づけています。
  local-data: "example.com. IN A 192.168.0.100"

python: 
remote-control:
forward-zone:
  name: "."
  forward-addr: 8.8.8.8
  forward-addr: 1.1.1.1
  forward-addr: 1.0.0.1
5. Unboudを開始する
sudo nano service unbound start

エラーが発生した場合、設定ファイルの記述に誤りがあると思われます。
以下は、設定ファイルにエラーがないか確認するコマンドです。

sudo unbound-checkconf

※サービス開始前に上記チェックコマンドを実行しても、正しく動作しませんでした。一度サービス開始コマンドを実行すれば、チェックコマンドも正しく動作しました。

他 当ステップで随時使用するコマンド

  • Unboudの停止
sudo service unbound stop
  • Unboundの再起動
sudo service unbound restart
  • Ubuntu上にインストールされているサービスの状態確認
sudo service --status-ll

OPENSSLをインストールし、自己証明書を作成する

当手順は、Ubuntuでの作業になります。
(参考)
SAN(Subject Alternative Name) のオレオレ証明書 - Qiita
Android端末にインストール可能なDER形式の自己証明書作成 - Qiita

1. Windowsのスタートボタンより、「Ubuntu 20.04LTS」を起動する
2. OpenSSLをインストールする

下記コマンドを実行します。

sudo apt install openssl
3. keyファイルを作成する
openssl genrsa -des3 -out server.key 2048

設定するパスワードを要求されるので入力します。
ここで設定するパスワードは後に必要なので控えておきます。

Enter pass phrase for server.key:
#キーボードで入力しても画面には表示されませんが、入力は受け付けられています。

#再入力を求められるので、再度パスワードを入力します。
Verifing - Enter pass phrase for server.key:
4. CSRファイルを作成する (CSR:キー発行依頼ファイル)
openssl req -new -key server.key -out server.csr

途中に国名、会社名等聞かれます。適当に入力します。
ただし、FQDN名は、設定したいドメイン名で設定する必要があります。
前述のUnboundの設定ファイル(unbound.conf)で設定したドメイン名と同じである必要があります。

5. keyファイルからパスワードを除去する
元のファイルをコピーし、バックアップserver.key.orgを作成します。
cp -p server.key server.key.org

パスワードを除去します。

openssl rsa -in server.key -out server.key
6. SANに設定するFQDNを書く

san.txtを作成します。

sudo touch san.txt

san.txtを編集します。

sudo nano san.txt
san.txt
#example.comの部分は任意に変更する。
subjectAltName = DNS:example.com

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:TRUE
keyUsage=digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
7. 証明書ファイルを生成する
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt -extfile san.txt

上記コマンドの"-days 365"の部分は、証明書の有効期間と思われます。

8. 確認

下記コマンドを実行し、エラーがないか確認します。

openssl x509 -text -in server.crt -noout
9. DER形式に変換(Android端末にインストールする際に必要)
openssl x509 -in server.crt -out server.der.crt -outform der

上記手順でWSL上に生成したserver.key、server.crt、server.der.crtは、Windowsのエクスプローラより"\\wsl$\Ubuntu-20.04\home\ユーザ名"にアクセスすることで参照可能です。

Node.jsにてhttpsサーバを建てる

当例では、httpsというプロジェクトフォルダを作成し、作業するものとします。
当手順は、Windowsでの作業になります。
プロジェクトフォルダをVisual Studio Code(以下VSCode)で起動します。

1. プロジェクトの初期化

VSCodeのターミナル画面で下記コマンドを実行します。

npm init -y
2. 必要なパッケージのインストール

VSCodeのターミナル画面で下記コマンドを順次実行します。
プロジェクトフォルダにnode_modulesフォルダが作成され、必要なファイルがダウンロードされます。

npm install --save express

npm install --save-dev @types/express
npm install --save-dev typescript
npm install --save-dev ts-loader 
npm install --save-dev webpack
npm install --save-dev webpack-cli
npm install --save-dev webpack-node-externals
3. 各種設定ファイルの作成
- tsconfig.json
  typescriptのトランスパイルの設定ファイルです。
tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true, 
    "noImplicitAny": true, 
    "module": "es2015", 
    "target": "es2017", 
    "jsx": "react", 
    "lib": ["es2018", "dom"], 
    "moduleResolution": "node", 
    "removeComments": true, 
    "strict": true, 
    "noUnusedLocals": false, 
    "noUnusedParameters": false, 
    "noImplicitReturns": true, 
    "noFallthroughCasesInSwitch": true, 
    "strictFunctionTypes": false 
  }
}
  • webpack.config.js

webpackの設定ファイルwebpack.config.jsを作成します。

webpack.config.js
const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  mode: 'development', 
  target: 'node', 
  externals: [ nodeExternals() ], 
  devtool: 'eval-source-map', 
  module: {
    rules: [
      {
        loader: 'ts-loader', 
        test: /\.ts$/, 
        exclude: [/node_modules/], 
        options: { configFile: 'tsconfig.json'}
      }
    ]
  }, 
  resolve: {
    extensions: ['.ts', '.js', '.json']
  }, 
  entry: './src/server.ts', 
  output: {
    filename: 'server.js', 
    path: path.resolve(__dirname, 'dist')
  }, 
  node: {
    __dirname: false
  }
};
  • packckage.jsonにコマンドの登録

package.jsonにコマンドを登録しておきます。

package.json
{
  ...略...
  "scripts": {
    ...
    "start": "node ./dist/server.js", 
    "build": "webpack --config webpack.config.js"
  },
  ...略...
}      
4. コーディング

プロジェクトフォルダにsrcフォルダを作成し、そこにserver.tsファイルを作成し以下のようにコーディングします。

src/server.ts
import * as Express from 'express';
import * as path from 'path';
import * as HTTPS from 'https';
import * as fs from 'fs';

const app = Express();

//スマホからexample.comにアクセスした際、文字列"Hello World"を返します。
app.get('/', (req: Express.Request, res: Express.Response) => {
  return res.send('Hello World');
});

//スマホにサーバ証明書をダウンロードできるように、ルーティングを設定します。
app.get('/download-cert', (req: Express.Request, res: Express.Response) => {
  res.download(path.join(__dirname, 'ssl/server.der.crt'));
});
const portNo = process.env.portNo || 443;

const serverOptions: HTTPS.ServerOptions = {
  key: fs.readFileSync(path.join(__dirname, 'ssl/server.key')),  
  cert: fs.readFileSync(path.join(__dirname, 'ssl/server.crt')), 
};

HTTPS.createServer(serverOptions, app).listen(portNo as number, () => {
  console.log(`Server runging on PortNo:${portNo}`);
}); 

export default app;

VSCodeのターミナル画面で、以下のコマンドを実行し、ビルドします。プロジェクトフォルダにdistフォルダが生成され、server.jsファイルが生成されます。

npm run build
5. server.key、server.crt、server.der.crtの配置

プロジェクトフォルダのdistフォルダに、sslというフォルダを作成し、server.key、server.crt、server.der.crtを配置します。

Androidスマホにサーバ証明書をインストールし、https通信でアクセスする

当手順はAndroidスマホにおける操作手順になります。
私のスマホ KYOCERA TORQUE® G03にて検証しました。
Windows10端末と、Androidスマホが同一wifi上に接続されているものとします。

1. スマホのIPアドレスを控える
  • ホーム画面より"設定"をタップ。Wi-Fiをタップ
    Wi-Fiの設定画面が開きます。

  • 画面右上の設定ボタン(右上の・が縦に3つ並んでいるボタン)をタップし、"詳細設定"をタップ
    "Wi-Fiの詳細設定"画面が起動します。
    画面下部にスクロールすると、割り当てられているIPアドレスが表示されているので控えます。

2. DNSサーバのアドレスを設定する
  • ホーム画面より"設定"をタップ。Wi-Fiをタップ
    Wi-Fiの設定画面が開きます。

  • 一覧より、接続しているWi-Fiを長押しする

    "ネットワークを削除"、"ネットワークを変更"の選択肢が表示されるので"ネットワークを変更"をタップします。

  • "詳細設定項目"を設定する

    • IP設定で"静的"を選択する
    • IPアドレスを設定する

      上記手順で控えたIPアドレスを設定します。

    • DNS1を設定する
      UnboundでDNSサーバを建てたWindows端末のIPアドレスを指定します。

    • DNS2を設定する
      元のDNSサーバのIPアドレスを指定します。

3. サーバ証明書をインストールする
  • chromeを立ち上げ"https://example.com/download-cert"にアクセスする
    この接続ではプライバシーが保護されませんというセキュリティ警告画面が表示されます。
    画面下部の"詳細設定"をタップ、"example.comにアクセスする(安全ではありません)"をタップします。
    インストールできませんでした。証明書ファイルが読み取れませんでしたというメッセージが表示されます。以下の手順に進みます。

  • ホーム画面より"設定"をタップ、設定画面で"セキュリティ"をタップ

  • セキュリティ画面で"ストレージからインストール"をタップ
    一覧の中に、"server.der.crt"があるのでタップします。
    "証明書の名前を指定する"画面が開くので、適当な証明書名を入力し、OKをタップします。

4. chormeからhttps://example.comにアクセスする

警告が表示されず、"Hello World"が表示されれば、成功です。

最後に

長い手順を踏んだ割には最終的にスマホの画面に"Hello World"が表示されるだけですが、これによりLAN内限定のPWAを展開することが可能になります。
余談ですが、当記事、私の初投稿となります。今後も少しずつ自分なりに身についた事柄をまとめて投稿していこうと思います。

参考資料

当記事は下記記事を参考にさせていただきました。
- 【図解】よく分かるデジタル証明書(SSL証明書)の仕組み 〜https通信フロー,発行手順,CSR,自己署名(オレオレ)証明書,ルート証明書,中間証明書の必要性や扱いについて〜 | SEの道標
- 【連載】WSL2、Visual Studio Code、DockerでグッとよくなるWindows開発環境 〜 その1:まずは概要 〜 | SIOS Tech. Lab
- SAN(Subject Alternative Name) のオレオレ証明書 - Qiita
- Android端末にインストール可能なDER形式の自己証明書作成 - Qiita
- Ubuntu Serverで内向きDNSをUnboundを使ってやってみたのでメモ - Qiita

ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む