20190529のNode.jsに関する記事は10件です。

Node.jsとtwilioとwindows10で あそぼ

5月31日(金)

本日の目標
課題1)韓国、台湾、香港へダイヤルできるようにする。
課題2)dial.jsの処理時間を計測する。
課題3)dial.jsを別のファイルから読み込み、?分おきに実行できるようにする。

課題1 

香港の電話番号は固定も携帯も8桁。携帯に絞ると国番号で分岐が必要になるので、めんどくさいから固定、携帯の区別はしない。

結果)香港へダイヤルできた。
注意点)twilioの設定を確認。通話⇒地理による許可⇒Low-Risk香港にチェック

// 架電先 国+頭番号リスト(アジア圏)
const CallToHeadNumList = {

    "台湾00": "+8869",    //携帯

    "韓国00": "+8210",    //携帯

    "香港00": "+852", //固定,携帯,IPなどall
}

5月30日(木)

ダイヤル処理無視して
あほみたいにループばかりするので、
同期処理するようにソースを書き直そう

一億ループさせるので
ダイヤル後にメモリ解放させましょう

本人の頭がパンクしてきた。

架電先、下8桁の生成に
乱数を使ってみる。

      // 架電先 下半身番号(8桁)を生成
      let CallToBodyNum = String(Math.random()).slice(-8);

二万件の発信処理に
少なくとも5時間30分はかかるので
頭番号21種✖️1億回ループさせても
無事に終了できるわけがない。

架電先を乱数で指定し、ループ減らして
終了後に再度実行させよう

午後0時25分

Twilioの仕様により
海外のスマホへ発信できないことが判明
固定電話でやり直します

Screenshot_20190530-120726_Chrome.jpg

午後19時20分

twilio KDDI版
中華民国へ発信できないことが判明
geo_permission.jpg

午後9時54分

韓国と台湾へはダイヤルできた
折り返し確率、1/1024

・参考サイト
メモリ解放方法
http://blog.livedoor.jp/aki_mana/archives/2587785.html

nodeで同期処理例
https://qiita.com/rawHam/items/838eefc80bc35a90e49a

乱数
http://ysklog.net/java/4-15.html

5月29日(水)

Twilioで発信してみよう

エラーが出ました。
発信先21件✖️7570ループ目で。
Ineffective heap なんちゃらエラー

Wait入れて、発信キューが捌けるのを待った方がよいのでしょうか?

対策を考えます。

対策1:下記の呪文を入力

--max-old-space-size=8192

結果1
26000ループあたりで
Windows落ちる

log_cas3vA.jpg

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

Node.jsとtwilioとwindows10で 国際電話

5月31日(金)

本日の目標
課題1)韓国、台湾、香港へダイヤルできるようにする。
課題2)dial.jsの処理時間を計測する。
課題3)dial.jsを別のファイルから読み込み、?分おきに実行できるようにする。

課題1 

香港の電話番号は固定も携帯も8桁。携帯に絞ると国番号で分岐が必要になるので、めんどくさいから固定、携帯の区別はしない。

結果)香港へダイヤルできた。
注意点)twilioの設定を確認。通話⇒地理による許可⇒Low-Risk香港にチェック

// 架電先 国+頭番号リスト(アジア圏)
const CallToHeadNumList = {

    "台湾00": "+8869",    //携帯

    "韓国00": "+8210",    //携帯

    "香港00": "+852", //固定,携帯,IPなどall
}

19時56分

Twilio運営から英語で
どんな使い方をしているのか
説明しろとメールが。

急遽、英語の勉強することになりました。
Oh!my gosh!

こちらがアカウント停止中の実行結果です。
認証エラーが出ています。
log_account stop.jpg

課題2

・処理時間が出るようにしました。(定期実行させる時、時間間隔設定に使うため)

  こちらを参考にしました。
  https://blog.roundrop.jp/66/
  https://ameblo.jp/rasenbagel/entry-11558847169.html

(躓いた点)
関数名の頭に数字を使ったらエラーになりました
追加モジュールperf_hooksをインストールしました。
ミリ秒表示されるので、1000で割りました。

(問題点)
表示される処理時間がAPIを叩くまでの時間になっている。
STATUS CALL BACK の やり方を覚えないといけない。

//処理時間の測定(前処理)
const startTime = performance.now(); // 開始時間


//メイン処理
main();

function main(){
//処理内容記入
}

//処理時間の測定(後処理)
const endTime = performance.now(); // 終了時間
console.log('処理時間:'+(endTime - startTime)/1000+'秒'); // 秒表示する

log_account stop1.jpg

課題3

外部ファイルのfunctionを呼び出す方法はわかったが
jsファイルまるごと実行させる方法がわからないのであきらめて
main関数を cronで挟みました。

(参考サイト)
https://qiita.com/n0bisuke/items/66abf6ca1c12f495aa04

//定期実行タイマーセット
cron.schedule('*/10 * * * * *', () => {     //10秒おきに実行


//処理時間の測定(前処理)
const startTime = performance.now(); // 開始時間


//メイン処理
main();

function main(){
//ダイヤル処理
}

//処理時間の測定(後処理)
const endTime = performance.now(); // 終了時間
console.log('処理時間:'+(endTime - startTime)/1000+'秒'); // 秒表示する

console.log('10秒ごとに実行')

});

実行中のビデオはこちら

5月30日(木)

ダイヤル処理無視して
あほみたいにループばかりするので、
同期処理するようにソースを書き直そう

一億ループさせるので
ダイヤル後にメモリ解放させましょう

本人の頭がパンクしてきた。

架電先、下8桁の生成に
乱数を使ってみる。

      // 架電先 下半身番号(8桁)を生成
      let CallToBodyNum = String(Math.random()).slice(-8);

二万件の発信処理に
少なくとも5時間30分はかかるので
頭番号21種✖️1億回ループさせても
無事に終了できるわけがない。

架電先を乱数で指定し、ループ減らして
終了後に再度実行させよう

午後0時25分

Twilioの仕様により
海外のスマホへ発信できないことが判明
固定電話でやり直します

Screenshot_20190530-120726_Chrome.jpg

午後19時20分

twilio KDDI版
中華民国へ発信できないことが判明
geo_permission.jpg

午後9時54分

韓国と台湾へはダイヤルできた
折り返し確率、1/1024

・参考サイト
メモリ解放方法
http://blog.livedoor.jp/aki_mana/archives/2587785.html

nodeで同期処理例
https://qiita.com/rawHam/items/838eefc80bc35a90e49a

乱数
http://ysklog.net/java/4-15.html

5月29日(水)

Twilioで発信してみよう

エラーが出ました。
発信先21件✖️7570ループ目で。
Ineffective heap なんちゃらエラー

Wait入れて、発信キューが捌けるのを待った方がよいのでしょうか?

対策を考えます。

対策1:下記の呪文を入力

--max-old-space-size=8192

結果1
26000ループあたりで
Windows落ちる

log_cas3vA.jpg

+815035037584
トヨタ紡織株式会社 とは 環形ありません
toyota-bosyoku.com

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

Node.jsで環境変数(AppDataの場所、PATH、その他)を取得したい

結論

process.envを使います。

node.js - how to get the OS platforms user data folder - Stack Overflow

実践

ターミナルでnodeを叩くと始まるInteractive Rubyみたいなアレで実行してみましょう。

{ ALLUSERPROFILE : "C:\\ProgramData",
  APPDATA : "C:\\Users\\jotaro\\AppData\\Roaming",
  (省略
}

こんなObjectが返ってきました。これはもう要するに、process.env.APPDATAと書けば、"C:\\Users\\jotaro\\AppData\\Roaming"が返ってくるというわけですね。あとはprocess.env.PATHからPATHの編集も…は…できないようです。

制限

Process - Node.js Documentation によれば、

It is possible to modify this object, but such modifications will not be reflected outside the Node.js process. In other words, the following example would not work:

$ node -e 'process.env.foo = "bar"' && echo $foo

「値を変更することはできるけど、Node.jsのプロセス内だけでしか変わらないよ」(超適当訳)とのこと。PATHを通したり環境変数をいじることは不可能なようです。うっかりPATH破壊でもしたらと思うと、納得がいきます。
もしかしたら環境変数の変更方法というのもあるのかもしれませんが、適当にググった結果ないと判断しました。

応用

スタートメニュー

応用して、Windowsのユーザーのスタートメニューにアクセスしてみます。

test.js
console.log(`${process.env.APPDATA}\\Microsoft\\Windows\\Start Menu\\Programs\\`);

スタートメニューにショートカットを作るのも楽ちんですね。おしまい。

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

IonicとFirebaseを使って3日で作る写真共有アプリ - 画面処理の作成(Typescript)

「IonicとFirebaseを使って3日で作る写真共有アプリ」シリーズの「画面処理の作成(Typescript)」編です。
まとめ記事はコチラ↓
https://qiita.com/kazuki502/items/585aef0d79ed1235bec0

前回までで見た目の部分は出来てきたので、今回は画面内の処理を作っていこうと思います。サーバーとの通信の部分は後程やるので、今回はアップロードする写真をプレビュー表示する部分を作っていきます。

写真をアップロードする前にプレビュー表示させる

前回の画面を作った段階ではこのような画面になっていると思います。

image.png

…もっとカッコよくしたいですよね。

画面のデザインを整える

こういう時は、他のサイトを参考にして画面をデザインしていきましょう!とりあえず出てきたのはこんな感じの画面ですね。

image.png

破線の枠がある感じです。ではまずこれを作っていきましょう。ここでは"label"タグをいじっていきます。

modal-upload.component.html
<label class="photo" for="photo-upload">画像を選択</label>
<input type="file" name="photo" id="photo-upload">

<ion-button>送信</ion-button>

余計な文言を消して、"label"にcssクラスを追加しました。

そしてscssファイルを以下のように編集します(コピペでOKです)。

modal-upload.component.scss
.photo {
    width: 90%;
    height: 200px;
    border: 1px #000 dashed;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 30px;
}

そうすると、先ほどのモーダル画面がこんな風になります。

image.png

それっぽくなってきました。もうひと頑張りしたい方は、枠の中に写真のロゴなんか入れてみるとさらにそれっぽくなりますよ!Ionicにはロゴも用意されているので、目的にあるロゴを選びましょう。

Ionic logo
https://ionicons.com/

今回は写真なので"photo"と検索してみてください。
image.png

アイコンをクリックして下に出てきた"WEB COMPONENT CODE"をクリックしてコピーして自分のソースにペーストしましょう。それから少し調整を加えるとこのようになります。

image.png

modal-upload.component.html
<label class="photo" for="photo-upload">
  <ion-icon name="image"></ion-icon><br>
  画像を選択
</label>
<input type="file" name="photo" id="photo-upload">

<ion-button>送信</ion-button>
modal-upload.component.scss
.photo {
    width: 90%;
    height: 200px;
    border: 1px #000 dashed;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 30px;
    text-align: center;
    vertical-align: middle;

    & > ion-icon {
        font-size: 4em;
    }
}

それっぽくなりましたね。

"ファイルを選択ボタン"を消す

ファイルを選択ボタンはなんかいらない気がしますよね。枠線の中のエリアをクリックするとファイル選択画面が開くようにしたいですよね。はい、やりましょう。とても簡単です。ボタンの表示設定で非表示に設定すればいいんです。

modal-upload.component.scss
.photo {
    width: 90%;
    height: 200px;
    border: 1px #000 dashed;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 30px;
    text-align: center;
    vertical-align: middle;

    & > ion-icon {
        font-size: 4em;
    }
}

// 送信ボタンスタイル
.send {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    width: 90%;
}

// 画像選択input
#photo-upload {
    display: none; // ファイルをアップロードボタンを非表示に設定
}

そうすると、このような画面になります。(送信ボタンなど一部スタイルをして調整しています。

image.png

選択した写真を表示させる。

このままだと、写真を選択しても画面には何も反映されません。選択した写真を画面に反映させるにはどうすればいいでしょうか。それには、"FileReader"を使えばうまくいきます。まずは完成イメージと、完成ソースコードをご覧ください。説明はその後にします。

image.png

modal-upload.component.html
<label class="photo" for="photo-upload">
  <div [ngClass]="{'inactive': isSelected}">
    <ion-icon name="image"></ion-icon><br>
    画像を選択
  </div>
  <ion-img [ngClass]="{'inactive': !isSelected}" [src]="imageSrc"></ion-img>
</label>
<input type="file" name="photo" id="photo-upload" accept="image/*" (change)="previewPhoto($event)">

<ion-button class="send">送信</ion-button>
modal-upload.component.scss
.photo {
    width: 90%;
    height: 200px;
    border: 1px #000 dashed;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 30px;
    text-align: center;
    vertical-align: middle;

    ion-icon {
        font-size: 4em;
    }
}

.inactive {
    display: none;
}

// 送信ボタンスタイル
.send {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    width: 90%;
}

// 画像選択input
#photo-upload {
    display: none;
}
modal-upload.component.ts
import { Component, OnInit, Input } from '@angular/core'; // Inputを追加
import { NavParams } from '@ionic/angular'; // 追加

const RESULT = 'result';

@Component({
  selector: 'app-modal-upload',
  templateUrl: './modal-upload.component.html',
  styleUrls: ['./modal-upload.component.scss'],
})
export class ModalUploadComponent implements OnInit {

  // "value" passed in componentProps
  public imageSrc: any;
  public isSelected: boolean;
  @Input() value: number;
  private reader = new FileReader();

  constructor(
    navParams: NavParams // 追加
  ) { }

  ngOnInit() {}

  public previewPhoto(event) {
    const file = event.target.files[0];
    this.reader.onload = ((e) => {
      this.imageSrc = e.target[RESULT];
      this.isSelected = true;
    });
    this.reader.readAsDataURL(file);
  }
}

このコードをコピペすればとりあえず動くと思います。それでは、それぞれで何をしているのか順を追って説明していきましょう。

FileReaderを設定する

まずは"modal-upload.component.ts"をご覧ください。今回はFileReaderをつかうので、内部オブジェクトとして宣言しています。

  private reader = new FileReader();

そして、写真を選択したときの処理を定義します。

  public previewPhoto(event) {
    const file = event.target.files[0]; // パラメータからファイルを抽出
    this.reader.onload = ((e) => {
      this.imageSrc = e.target[RESULT]; // 予め用意しておいた変数に画像データを格納する
      this.isSelected = true; // 写真選択フラグを切り替える
    });
    this.reader.readAsDataURL(file);
  }

これで、選択した写真がthis.imageSrcに格納されます。FileReaderに関してはこれだけです。

previewを設定する

選択した写真をアプリに保持出来るようになったので、写真をどのように表示させるのかを説明していきます。先ほど"previewPhoto"という関数を定義しましたが、定義しただけでは関数を実行させることはできません。画面の要素と関連付ける必要があります。今回関連付けるべき要素は、"input"タグですね。画面要素と処理の紐づけはこのようにして書くことが出来ます。

modal-upload.component.html
<input type="file" name="photo" id="photo-upload" accept="image/*" (change)="previewPhoto($event)">

(change)="previewPhoto($event)"が重要な部分です。"(change)"はangularのイベントバインディングと呼ばれるもので、"change"というのは、要素に変更があったときなので写真が選択されたときですね。Angularのバインディングについて詳しく説明しているサイトが合ったので、もっと知りたい方はこちらをご覧ください。

これで、「写真が選択された」というイベントと、「選択された写真をアプリの中に保持する」という処理がつながりました。画像を表示するためにはこのように書けばよいです。

  <ion-img [ngClass]="{'inactive': !isSelected}" [src]="imageSrc"></ion-img>

ion-imgタグのsrc要素に画像情報が保持されている変数を割り当てるだけです。"src"ではなく、"[src]"と括弧が付いているのも、Angularのバインディングの一種です。[ngClass]="{'inactive': !isSelected}"は、条件によってCSSクラスを切り替えるための記述です。写真を設定したときに、初期表示で出ていた文字と写真アイコンを消して、画像を表示するためのものです。これが無い場合は、初期表示・写真選択時にこんな表示の仕方になります。

image.png

イメージと違うというか、すこぶるダサいですよね笑。今回は、CSSクラスで表示・非表示を切り替えましたが、"*ngIf=..."というのを使っても同じことが出来ます。

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

discord.jsのfilterを使ってみたメモ

こんにちは、ほぼ毎日discord.jsでbot作ってる人です。(?)

前置き

今回は、discord.jsでfilterを使ってみたのを記事にしてます
(説明が下手なのは許してください)
discord.js: https://discord.js.org/
Collection: https://discord.js.org/#/docs/main/stable/class/Collection

使い方

テキストチャンネルだけの数

const textChannels = message.guild.channels.filter(t => t.type == "text").size
message.channel.send(textChannels);

これでテキストチャンネルのみの数が表示されます。
説明すると
message.guild.channels.filter
(メッセージが送信されたギルドのチャンネルをフィルターする)
[以下略].filter.(t => t.type == 'text')
(tmessage.guild.channelsの省略版です(多分))
(t.type == 'text'はチャンネルのタイプです。)
'text'に入るのは、https://discord.js.org/#/docs/main/stable/class/Channel?scrollTo=type をご覧下さい。

説明はこれで終わりです

終わりに

他にもfindとかあるのでいつかだそうかなと思ってます。

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

JavaScriptのfilterをdiscord.jsで使ってみたメモ

こんにちは、ほぼ毎日discord.jsでbot作ってる人です。(?)

前置き

今回は、discord.jsでJavaScriptのfilterを使ってみたのを記事にしてます
(説明が下手なのは許してください)
(まだ無知なことがいっぱい)
discord.js: https://discord.js.org/
Collection: https://discord.js.org/#/docs/main/stable/class/Collection

使い方

テキストチャンネルだけの数

const textChannels = message.guild.channels.filter(t => t.type == "text").size
message.channel.send(textChannels);

これでテキストチャンネルのみの数が表示されます。
説明すると
message.guild.channels.filter
(メッセージが送信されたギルドのチャンネルをフィルターする)
[以下略].filter.(t => t.type == 'text')
(tmessage.guild.channelsの省略版です(多分))
(t.type == 'text'はチャンネルのタイプです。)
'text'に入るのは、https://discord.js.org/#/docs/main/stable/class/Channel?scrollTo=type をご覧下さい。

追記
.sizeは○○の数です。

説明はこれで終わりです

終わりに

他にもfindとかあるのでいつかだそうかなと思ってます。

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

【日記】技術書どおりにやって上手くいかなかったwinstonを中途半端に上手くいかせた

最近、こちらの本を読んでWebアプリ開発を勉強しています。
JavaScriptでのWeb開発 ~ Node.js + Express + MongoDB + ReactでWebアプリを開発しよう ~ その2(iOS対応版)
途中、winstonというロギングアプリを導入する方法が解説されていたところで詰まりました。

本書では次のようにwinstonの設定ファイルを定義するよう書いてありました。

lib/logger.js
var winston = require('winston')

function Logger(){
  return winston.add(winston.transports.File, (
    filename: "log/warning.log",
    maxsize: 1048576,
    level: "warn"
  });
}

module.exports = new Logger();

この通りにファイルを作成してアプリを起動しようとすると、次のようなエラーが発生。

$ node app.js
/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/node_modules/winston-transport/legacy.js:18
    throw new Error('Invalid transport, must be an object with a log method.');
    ^

Error: Invalid transport, must be an object with a log method.
    at new LegacyTransportStream (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/node_modules/winston-transport/legacy.js:18:11)
    at DerivedLogger.add (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/node_modules/winston/lib/winston/logger.js:345:11)
    at Object.winston.<computed> [as add] (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/node_modules/winston/lib/winston.js:110:68)
    at new Logger (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/lib/logger.js:4:20)
    at Object.<anonymous> (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/lib/logger.js:12:18)
    at Module._compile (internal/modules/cjs/loader.js:759:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:770:10)
    at Module.load (internal/modules/cjs/loader.js:628:32)
    at Function.Module._load (internal/modules/cjs/loader.js:555:12)
    at Module.require (internal/modules/cjs/loader.js:666:19)
    at require (internal/modules/cjs/helpers.js:16:16)
    at Object.<anonymous> (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/app.js:12:11)
    at Module._compile (internal/modules/cjs/loader.js:759:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:770:10)
    at Module.load (internal/modules/cjs/loader.js:628:32)
    at Function.Module._load (internal/modules/cjs/loader.js:555:12)

ググったところ、次のような解決法を見つけました。
Invalid transport, must be an object with a log method winston mongodb logging

これに従って、前述のfunction Logger()を次のように修正。

lib/logger.js
function Logger(){
  return winston.add(new winston.transports.File({
    filename: "log/error.log",
    maxsize: 1048576,
    level: "error"
  }));
}

こうすることで正常にアプリが起動し、ログファイルも生成されるようになりました。

しかし問題はここからで、技術書にあるような詳細なログが出力されませんでした。
出力されたログはと言うと、次のようなちょっとしたもの。

error.log
{"level":"error"}

この一行で全文です。
明らかに正常な動作とは思えず、しかしどこがどう間違っているのかもよくわからず、現時点ではひとまずこの問題は諦めるということで切り上げました。

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

Next.js ApplicationにPassport+Auth0で認証機能を追加する

はじめに

この記事はNext.jsのサンプルアプリケーションにPassport+Auth0を利用して認証・認可機能を追加する手順で、こちらの原文を元に作成しています。Node.jsとnpmのインストール、Auth0の無料アカウントの取得とテナントの作成が完了していることが前提となっています。まだの方はこちらの記事を参照の上ご準備をお願いします。
完成版のソースコードはここで公開しています。

環境

  • OS : macOS Mojave 10.14.5
  • Node.js : 10.15.3
  • npm : 6.4.1

手順

準備と基本プロジェクトの作成

NPM Project Directoryを作成して初期化します。

$ mkdir nextjs-passport
$ cd nextjs-passport
$ npm init -y

必要なパッケージをインストールします。

$ npm i body-parser bootstrap dotenv \
  dotenv-webpack express isomorphic-fetch \
  next react react-bootstrap \
  react-dom styled-components

.babelrcを作成してコンパイルパラメータを設定します。

.babelrc
// ./.babelrc

{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true }]]
}

.envを作成して環境変数を設定します。このタイミングではPORTだけで問題ありません。

.env
# ./.env

PORT=3000

next.config.jsを作成して.envファイルのパスを指定します。

next.config.js
// ./next.config.js

require("dotenv").config();

const path = require("path");
const Dotenv = require("dotenv-webpack");

module.exports = {
  webpack: config => {
    config.plugins = config.plugins || [];

    config.plugins = [
      ...config.plugins,

      // Read the .env file
      new Dotenv({
        path: path.join(__dirname, ".env"),
        systemvars: true
      })
    ];

    return config;
  }
};

ソースコードを保管するディレクトリを作成します。

$ mkdir -p src/components
$ mkdir src/pages
$ mkdir src/state

src/pages配下にindex.jsを作成します。

index.js
// ./src/pages/index.js
import styled from "styled-components";

const Rocket = styled.div`
  text-align: center;
  img {
    width: 630px;
  }
`;

function Index() {
  return (
    <Rocket>
      <img src="https://media.giphy.com/media/QbumCX9HFFDQA/giphy.gif" />
    </Rocket>
  );
}

export default Index;

src/pages配下に_document.jsを作成します。

_document.js
// ./src/pages/_document.js

import Document, { Head, Html, Main, NextScript } from "next/document";
import { ServerStyleSheet } from "styled-components";

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        )
      };
    } finally {
      sheet.seal();
    }
  }

  render() {
    return (
      <Html>
        <Head>
          <link
            rel="stylesheet"
            href="https://bootswatch.com/4/darkly/bootstrap.min.css"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

package.jsonのscriptsプロパティを下記のように修正します。

package.json
// ./package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "next ./src"
  },

npm run devを実行してアプリケーションを起動しChromeでhttp://localhost:3000にアクセスします。画面が正常に表示されれば成功です。

$ npm run dev



Applicationの作成

src配下にthoughts-api.jsを作成してAPI Endpointを定義します。

thoughts-api.js
// ./src/thoughts-api.js

const bodyParser = require("body-parser");
const express = require("express");

const router = express.Router();

router.use(bodyParser.json());

const thoughts = [
  { _id: 123, message: "I love pepperoni pizza!", author: "unknown" },
  { _id: 456, message: "I'm watching Netflix.", author: "unknown" }
];

router.get("/api/thoughts", (req, res) => {
  const orderedThoughts = thoughts.sort((t1, t2) => t2._id - t1._id);
  res.send(orderedThoughts);
});

router.post("/api/thoughts", (req, res) => {
  const { message } = req.body;
  const newThought = {
    _id: new Date().getTime(),
    message,
    author: "unknown"
  };
  thoughts.push(newThought);
  res.send({ message: "Thanks!" });
});

src配下にserver.jsを作成してhttpリクエストをlistenします。

server.js
// ./src/server.js

require("dotenv").config();
const express = require("express");
const http = require("http");
const next = require("next");
const thoughtsAPI = require("./thoughts-api");

const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
  dir: "./src"
});
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  server.use(thoughtsAPI);

  // handling everything else with Next.js
  server.get("*", handle);

  http.createServer(server).listen(process.env.PORT, () => {
    console.log(`listening on port ${process.env.PORT}`);
  });
});

package.jsonのscriptsプロパティを下記のように修正します。

package.json
// ./package.json

"scripts": {
  "dev": "node ./src/server.js",
  "build": "next build ./src",
  "start": "NODE_ENV=production node ./src/server.js"
},

npm run devを実行してアプリケーションを起動しChromeでhttp://localhost:3000/api/thoughtsにアクセスします。画面が正常に表示されれば成功です。

$ npm run dev

src/components配下にThought.js, Thoughts.jsを作成してAPIを呼び出します。

Thought.js
// ./src/components/Thought.js

import Card from "react-bootstrap/Card"

export default function Thought({ thought }) {
  const cardStyle = { marginTop: "15px" };
  return (
    <Card bg="secondary" text="white" style={cardStyle}>
      <Card.Body>
        <Card.Title>{thought.message}</Card.Title>
        <Card.Text>by {thought.author}</Card.Text>
      </Card.Body>
    </Card>
  );
}
Thoughts.js
// ./src/components/Thoughts.js

import Col from "react-bootstrap/Col";
import Row from "react-bootstrap/Row";
import Thought from "./Thought";

export default function Thoughts(props) {
  return (
    <Row>
      <Col xs={12}>
        <h2>Latest Thoughts</h2>
      </Col>
      {props.thoughts &&
        props.thoughts.map(thought => (
          <Col key={thought._id} xs={12} sm={6} md={4} lg={3}>
            <Thought thought={thought} />
          </Col>
        ))}
      {!props.thoughts && <Col xs={12}>Loading...</Col>}
    </Row>
  );
}

src/pages/index.jsを修正してAPI経由で取得するデータを表示します。

index.js
// ./src/pages/index.js

import Container from "react-bootstrap/Container";
import fetch from "isomorphic-fetch";
import Thoughts from "../components/Thoughts";

function Index(props) {
  return (
    <Container>
      <Thoughts thoughts={props.thoughts} />
    </Container>
  );
}

Index.getInitialProps = async ({ req }) => {
  const baseURL = req ? `${req.protocol}://${req.get("Host")}` : "";
  const res = await fetch(`${baseURL}/api/thoughts`);
  return {
    thoughts: await res.json()
  };
};

export default Index;

npm run devを実行してアプリケーションを起動しChromeでhttp://localhost:3000にアクセスします。画面が正常に表示されれば成功です。

src/components配下にNavbar.jsを、src/pages配下に_app.jsを作成してナビゲーションバーを追加します。

Navbar.js
// ./src/components/Navbar.js

import Link from "next/link";
import Container from "react-bootstrap/Container";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";

export default function AppNavbar() {
  const navbarStyle = { marginBottom: "25px" };
  return (
    <Navbar bg="light" expand="lg" style={navbarStyle}>
      <Container>
        <Navbar.Brand>
          <Link href="/">
            <a>Thoughts!</a>
          </Link>
        </Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="mr-auto">
            <Link href="/share-thought">
              <a className="nav-link">New Thought</a>
            </Link>
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
}
_app.js
// ./src/pages/_app.js

import React from "react";
import App, { Container as NextContainer } from "next/app";
import Head from "next/head";
import Container from "react-bootstrap/Container";
import Jumbotron from "react-bootstrap/Jumbotron";
import Navbar from "../components/Navbar";

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;

    return (
      <NextContainer>
        <Head>
          <title>Thoughts!</title>
        </Head>
        <Navbar />
        <Container>
          <Jumbotron>
            <Component {...pageProps} />
          </Jumbotron>
        </Container>
      </NextContainer>
    );
  }
}

export default MyApp;

src/pages配下にshare-thought.jsを作成して新しいルートを定義します。

share-thought.js
// ./src/pages/share-thought.js

import Form from "react-bootstrap/Form";
import Router from "next/router";
import Button from "react-bootstrap/Button";
import Container from "react-bootstrap/Container";
const { useState } = require("react");

export default function ShareThought() {
  const [message, setMessage] = useState("");

  async function submit(event) {
    event.preventDefault();
    await fetch("/api/thoughts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        message
      })
    });
    Router.push("/");
  }

  return (
    <Container>
      <Form onSubmit={submit}>
        <Form.Group>
          <Form.Label>What is in your mind?</Form.Label>
          <Form.Control
            type="text"
            placeholder="Say something"
            onChange={e => setMessage(e.target.value)}
            value={message}
          />
        </Form.Group>
        <Button variant="primary" type="submit">
          Share
        </Button>
      </Form>
    </Container>
  );
}

npm run devを実行してアプリケーションを起動しChromeでhttp://localhost:3000/api/thoughtsにアクセスします。画面が正常に表示されれば成功です。


認証機能の組み込み

Auth0にログインしてテナントを作成します。この記事では詳細な手順は割愛しています。こちらをご参照お願いします。

作成したApplicationをAuth0に登録します。左のペインからApplicationsをクリックして+CREATE APPLICATIONを押します。


Nameに任意の名前を入力、Choose an application typeでRegular Web Applicationsを選択してCREATEを押します。

Login/Logout後にリダイレクトさせるURLを指定するため、Settingsタブを選択してAllowed Callback URLs, Allowed Logout URLsに下記の値を入力してSAVE CHANGESを押します。

  • Allowed Callback URLs : http://localhost:3000/callback
  • Allowed Logout URLs : http://localhost:3000

必要なパッケージをインストールします。

$ npm install passport passport-auth0 express-session uid-safe

src配下のserver.jsを修正します。

server.js
// ./src/server.js

require("dotenv").config();
const express = require("express");
const http = require("http");
const next = require("next");
const session = require("express-session");
// 1 - importing dependencies
const passport = require("passport");
const Auth0Strategy = require("passport-auth0");
const uid = require('uid-safe');
const authRoutes = require("./auth-routes");
const thoughtsAPI = require("./thoughts-api");

const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
  dir: "./src"
});
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  // 2 - add session management to Express
  const sessionConfig = {
    secret: uid.sync(18),
    cookie: {
      maxAge: 86400 * 1000 // 24 hours in milliseconds
    },
    resave: false,
    saveUninitialized: true
  };
  server.use(session(sessionConfig));

  // 3 - configuring Auth0Strategy
  const auth0Strategy = new Auth0Strategy(
    {
      domain: process.env.AUTH0_DOMAIN,
      clientID: process.env.AUTH0_CLIENT_ID,
      clientSecret: process.env.AUTH0_CLIENT_SECRET,
      callbackURL: process.env.AUTH0_CALLBACK_URL
    },
    function(accessToken, refreshToken, extraParams, profile, done) {
      return done(null, profile);
    }
  );

  // 4 - configuring Passport
  passport.use(auth0Strategy);
  passport.serializeUser((user, done) => done(null, user));
  passport.deserializeUser((user, done) => done(null, user));

  // 5 - adding Passport and authentication routes
  server.use(passport.initialize());
  server.use(passport.session());
  server.use(authRoutes);

  server.use(thoughtsAPI);

  // 6 - you are restricting access to some routes
  const restrictAccess = (req, res, next) => {
    if (!req.isAuthenticated()) return res.redirect("/login");
    next();
  };

  server.use("/profile", restrictAccess);
  server.use("/share-thought", restrictAccess);

  // handling everything else with Next.js
  server.get("*", handle);

  http.createServer(server).listen(process.env.PORT, () => {
    console.log(`listening on port ${process.env.PORT}`);
  });
});

src配下にauth-routes.jsを作成して認証ルートを定義します。

auth-routes.js
// ./src/auth-routes.js

const express = require("express");
const passport = require("passport");

const router = express.Router();

router.get("/login", passport.authenticate("auth0", {
  scope: "openid email profile"
}), (req, res) => res.redirect("/"));

router.get("/callback", (req, res, next) => {
  passport.authenticate("auth0",  (err, user) => {
    if (err) return next(err);
    if (!user) return res.redirect("/login");
    req.logIn(user, (err) => {
      if (err) return next(err);
      res.redirect("/");
    });
  })(req, res, next);
});

router.get("/logout", (req, res) => {
  req.logout();

  const {AUTH0_DOMAIN, AUTH0_CLIENT_ID, BASE_URL} = process.env;
  res.redirect(`https://${AUTH0_DOMAIN}/logout?client_id=${AUTH0_CLIENT_ID}&returnTo=${BASE_URL}`);
});

module.exports = router;

.envに必要な環境変数を追加します。

.env
PORT=3000
AUTH0_DOMAIN=mokomoko.auth0.com
AUTH0_CLIENT_ID=o4u8CRD1roZEN2t96AZZnfrO3NeegeN7
AUTH0_CLIENT_SECRET=...
AUTH0_CALLBACK_URL=http://localhost:3000/callback
BASE_URL=http://localhost:3000

AUTH0_DOMAIN, AUTH0_CLIENR_ID, AUTH0_CLIENT_SECRETはApplications->Settingsから確認できます。

src配下のthoughts-api.jsを修正します。

thoughts-api.js
const bodyParser = require("body-parser");
const express = require("express");

const router = express.Router();

router.use(bodyParser.json());

const thoughts = [
  { _id: 123, message: "I love pepperoni pizza!", author: "unknown" },
  { _id: 456, message: "I'm watching Netflix.", author: "unknown" }
];

router.get("/api/thoughts", (req, res) => {
  const orderedThoughts = thoughts.sort((t1, t2) => t2._id - t1._id);
  res.send(orderedThoughts);
});

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) return next();
  res.send(401);
}

router.post("/api/thoughts", ensureAuthenticated, (req, res) => {
  const { message } = req.body;
  const newThougth = {
    _id: new Date().getTime(),
    message,
    author: req.user.displayName
  };
  thoughts.push(newThougth);
  res.send({ message: "Thanks!" });
});

module.exports = router;

src/components配下のNavbar.jsを修正します。

Navbar.js
// ./src/components/Navbar.js

import Link from "next/link";
import Container from "react-bootstrap/Container";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";

export default function AppNavbar({ user }) {
  const navbarStyle = { marginBottom: "25px" };
  return (
    <Navbar bg="light" expand="lg" style={navbarStyle}>
      <Container>
        <Navbar.Brand>
          <Link href="/">
            <a>Thoughts!</a>
          </Link>
        </Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="mr-auto">
            {user && (
              <>
                <Link href="/share-thought">
                  <a className="nav-link">New Thought</a>
                </Link>
                <Link href="/profile">
                  <a className="nav-link">Profile</a>
                </Link>
                <Link href="/logout">
                  <a className="nav-link">Log Out</a>
                </Link>
              </>
            )}
            {!user && (
              <Link href="/login">
                <a className="nav-link">Log In</a>
              </Link>
            )}
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
}

src/pages配下にprofile.jsを作成してユーザプロファイル画面を表示します。

profile.js
// ./src/pages/profile.js

import styled from "styled-components";

const Picture = styled.img`
  border-radius: 50%;
  border: 3px solid white;
  width: 100px;
`;

function Profile({ user }) {
  return (
    <div>
      <h2>
        <Picture src={user.picture} alt={user.displayName} /> Hello, {user.displayName}
      </h2>
      <p>This is what we know about you:</p>
      <ul>
        { Object.keys(user).map(key => (
          <li key={key}>{key}: {user[key].toString()}</li>
        ))}
      </ul>
    </div>
  );
}

export default Profile;

src/pages配下の_app.jsを修正します。

_app.js
// ./src/pages/_app.js

import React from "react";
import App, { Container as NextContainer } from "next/app";
import Head from "next/head";
import Container from "react-bootstrap/Container";
import Jumbotron from "react-bootstrap/Jumbotron";
import Navbar from "../components/Navbar";

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {};
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
    }
    if (ctx.req && ctx.req.session.passport) {
      pageProps.user = ctx.req.session.passport.user;
    }
    return { pageProps };
  }

  constructor(props) {
    super(props);
    this.state = {
      user: props.pageProps.user
    };
  }

  render() {
    const { Component, pageProps } = this.props;

    const props = {
      ...pageProps,
      user: this.state.user,
    };

    return (
      <NextContainer>
        <Head>
          <title>Thoughts!</title>
        </Head>
        <Navbar user={this.state.user} />
        <Container>
          <Jumbotron>
            <Component {...props} />
          </Jumbotron>
        </Container>
      </NextContainer>
    );
  }
}

export default MyApp;

npm run devを実行してアプリケーションを起動しChromeでhttp://localhost:3000にアクセスします。Auth0の組み込みLogin画面が表示されて、ログイン後Profileタブをクリックしてユーザプロファイル正常に表示されてれば成功です。



おわりです。

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

ask-sdkでuserIdを取得する方法のワークアラウンド

コアにパッチを送ってマージしてもらったのですが、リリースまでもうしばらく時間がかかりそうなのでワークアラウンドとしてメモ。

2019/05/30追記

ask-sdk / ask-sdk-coreがリリースされました。
https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/blob/2.0.x/CHANGELOG.md

getUserIdはversion2.6.0以降で利用できます。

要点

  • ask-sdk-coreのutilityにgetUserIdがないため、自力で取得する必要がある。
  • リリースブランチにはマージされているが、リリースされていないので、まだ使えない。
  • マージしてもらったコードを貼るので、待てない人はこれを使ってね

コード

/**
 * Retrieves the user ID from the request.
 *
 * The method retrieves the userId property from the input request. This value uniquely identifies the user
 * and is generally used as input for some Alexa-specific API calls. More information about this can be found here:
 * https://developer.amazon.com/docs/custom-skills/request-and-response-json-reference.html#system-object
 *
 * @param {RequestEnvelope} requestEnvelope
 * @return {string}
 */
export function getUserId(requestEnvelope : RequestEnvelope) : string {
  return requestEnvelope.context.System.user ? requestEnvelope.context.System.user.userId : null;
}

via: https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/commit/f8984233380d901ac11508bee652f1c249d7e54c

余談

getDeviceId()はすでにあるので、device idを取りたい場合は、import { getDeviceId } from 'ask-sdk-core'で良いと思います。

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

AWS LambdaとHyperledger Fabric SDK for Node.jsを利用してAmazon Managed Blockchainのブロックチェーンネットワークにアクセスする

Amazon Managed BlockchainのブロックチェーンネットワークはVPC内に構築されるため、VPC外からブロックチェーンネットワークへアクセスするにはクライアントとなるアプリなりサービスを開発して経由する必要があります。
AWS LambdaでVPC内に関数を配置するとアクセス可能になるはずなので試してみました。

Amazon VPC 内のリソースにアクセスできるように Lambda 関数を構成する - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/vpc.html

前提

Dockerを利用して開発環境を構築します。

AWS Lambdaへのデプロイにはserverlessを利用します。

Serverless - The Serverless Application Framework powered by AWS Lambda, API Gateway, and more
https://serverless.com/

AWS Lambdaを利用するのでAWSアカウントや権限も必要となります。

> docker --version
Docker version 18.09.2, build 6247962

> docker-compose --version
docker-compose version 1.23.2, build 1110ad01

> sls --version
1.43.0

Amazon Managed Blockchainでブロックチェーンネットワークが構築済み

下記の2記事の手順でブロックチェーンネットワークが構築済みでfabcarのサンプルが動作する環境がある前提です。

Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークを構築してみた - Qiita
https://qiita.com/kai_kou/items/e02e34dd9abb26219a7e

Amazon Managed Blockchainで作成したブロックチェーンネットワークにHyperledger Fabric SDK for Node.jsでアクセスしてみる - Qiita
https://qiita.com/kai_kou/items/5a6b0fc148f04857a878

開発環境を構築する

Hyperledger FabricのSDKをAWS Lambda上で利用するにはLinuxでnpm install する必要があったのでDockerを利用して開発環境を構築します。

Dockerコンテナの立ち上げ

> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> touch Dockerfile
> touch docker-compose.yml

AWS Lambdaで利用できるNode.jsのバージョンは8.1010.x となります。
Hyperledger Fabric SDK for Node.jsは8.x系で動作するのでDockerにも8.x をインストールします。

AWS Lambda ランタイム - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html

Dockerfile
FROM amazonlinux

RUN yum update && \
    curl -sL https://rpm.nodesource.com/setup_8.x | bash - && \
    yum install -y gcc-c++ make nodejs && \
    npm i -g serverless
docker-compose.yml
version: '3'
services:
  app:
    build: .
    volumes:
      - ./:/src
    working_dir: /src
    tty: true
> docker-compose build

> docker-compose run app bash

Node.jsのプロジェクト作成

コンテナが立ち上がったらserverlessでNode.jsのテンプレートでプロジェクトを作成します。

コンテナ内
$ sls create \
  --template aws-nodejs \
  --path fablic-app

$ cd fablic-app
$ npm init

Hyperledger Fabric SDK for Node.jsが利用できるようにpackage.json を編集してnpm install を実行します。

package.json
{
    "name": "fabcar",
    "version": "1.0.0",
    "description": "Hyperledger Fabric Car Sample Application",
    "main": "fabcar.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "dependencies": {
        "fabric-ca-client": "~1.2.0",
        "fabric-client": "~1.2.0",
        "fs-extra": "^8.0.1",
        "grpc": "^1.6.0"
    },
    "author": "",
    "license": "Apache-2.0",
    "keywords": [
    ]
}
コンテナ内
$ npm install

証明書の用意

Hyperledger Fabric SDK for Node.jsでブロックチェーンネットワークへアクセスするのに各種証明書が必要となるためプロジェクトに含めます。こちらはDockerコンテナ外で行います。

コンテナ外
> cd fablic-app
> aws s3 cp s3://us-east-1.managedblockchain-preview/etc/managedblockchain-tls-chain.pem ./managedblockchain-tls-chain.pem

# EC2インスタンスからhfc-key-storeフォルダを取得
> scp -r -i [EC2インスタンス用のpemファイルパス] ec2-user@xxx.xxx.xxx.xxx:/home/ec2-user/fabric-samples/fabcar/hfc-key-store ./hfc-key-store

実装

今回はブロックチェーンネットワークのステートDBから情報を取得する実装を行います。
下記記事でも利用しているquery.js をAWS Lambdaで実行できるように編集しました。

Amazon Managed Blockchainで作成したブロックチェーンネットワークにHyperledger Fabric SDK for Node.jsでアクセスしてみる - Qiita
https://qiita.com/kai_kou/items/5a6b0fc148f04857a878

handler.jsquery.js のメソッドを呼び出し結果を返す実装にしました。

> cd fabric-app
> tree -F -L 1 .
.
├── handler.js
├── hfc-key-store/
├── managedblockchain-tls-chain.pem
├── node_modules/
├── package-lock.json
├── package.json
├── query.js
└── serverless.yml
handler.js
var query = require('./query');

module.exports.hello = async (event) => {
  var result = await query.run();
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: JSON.parse(result),
      input: event,
    }, null, 2),
  };
};

実装のポイントは下記となります。

  • 証明書フォルダhfc-key-store/tmp にコピーして利用
  • module.exports.run = async () => {}handler.js から呼び出し可能にする
  • async/await で同期的に実行する
query.js
'use strict';
/*
* Copyright IBM Corp All Rights Reserved
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
 * Chaincode query
 */

var Fabric_Client = require('fabric-client');
var path = require('path');
var util = require('util');
var os = require('os');
var fs = require('fs-extra');

//
var fabric_client = new Fabric_Client();

// setup the fabric network
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpcs://nd-xxxxxxxxxxxxxxxxxxxxxxxxxx.m-xxxxxxxxxxxxxxxxxxxxxxxxxx.n-xxxxxxxxxxxxxxxxxxxxxxxxxx.managedblockchain.us-east-1.amazonaws.com:30003',
    { pem: fs.readFileSync('./managedblockchain-tls-chain.pem').toString(), 'ssl-target-name-override': null});
channel.addPeer(peer);

//
var member_user = null;
var store_base_path = path.join(__dirname, 'hfc-key-store');
var store_path = path.join('/tmp', 'hfc-key-store');
console.log('Store path:'+store_path);
var tx_id = null;

// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
module.exports.run = async () => {
    // 証明書ファイルを/tmp ディレクトリにコピーして利用する
    fs.copySync(store_base_path, store_path);
    console.log('Store copied!');

    return await Fabric_Client.newDefaultKeyValueStore({ path: store_path
    }).then((state_store) => {
        // assign the store to the fabric client
        fabric_client.setStateStore(state_store);
        var crypto_suite = Fabric_Client.newCryptoSuite();
        // use the same location for the state store (where the users' certificate are kept)
        // and the crypto store (where the users' keys are kept)
        var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
        crypto_suite.setCryptoKeyStore(crypto_store);
        fabric_client.setCryptoSuite(crypto_suite);

        // get the enrolled user from persistence, this user will sign all requests
        return fabric_client.getUserContext('user1', true);
    }).then((user_from_store) => {
        if (user_from_store && user_from_store.isEnrolled()) {
            console.log('Successfully loaded user1 from persistence');
            member_user = user_from_store;
        } else {
            throw new Error('Failed to get user1.... run registerUser.js');
        }

        // queryCar chaincode function - requires 1 argument, ex: args: ['CAR4'],
        // queryAllCars chaincode function - requires no arguments , ex: args: [''],
        const request = {
            //targets : --- letting this default to the peers assigned to the channel
            chaincodeId: 'fabcar',
            fcn: 'queryAllCars',
            args: ['']
        };

        // send the query proposal to the peer
        return channel.queryByChaincode(request);
    }).then((query_responses) => {
        console.log("Query has completed, checking results");
        // query_responses could have more than one  results if there multiple peers were used as targets
        if (query_responses && query_responses.length == 1) {
            if (query_responses[0] instanceof Error) {
                console.error("error from query = ", query_responses[0]);
            } else {
                console.log("Response is ", query_responses[0].toString());
                return query_responses[0].toString();
            }
        } else {
            console.log("No payloads were returned from query");
        }
    }).catch((err) => {
        console.error('Failed to query successfully :: ' + err);
    });
};

serverlessの設定

AWS LambdaでVPC内配置されるようにserverless.yml を編集します。
vpciamRoleStatements の定義については下記が参考になりました。
セキュリティグループとサブネットはAmazon Managed Blockchainで構築したブロックチェーンネットワークと同じものを指定します。

ServerlessでLambdaをVPC内にデプロイする - Qiita
https://qiita.com/70_10/items/ae22a7a9bca62c273495

serverless.yml
service: fabric-app

provider:
  name: aws
  runtime: nodejs8.10

  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "ec2:CreateNetworkInterface"
        - "ec2:DescribeNetworkInterfaces"
        - "ec2:DeleteNetworkInterface"
      Resource:
        - "*"

  vpc:
    securityGroupIds:
      - sg-xxxxxxxxxxxxxxxxx
    subnetIds:
      - subnet-xxxxxxxx
      - subnet-yyyyyyyy

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get

AWS Lambdaにデプロイする

> sls deploy

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service fabric-app.zip file to S3 (40.12 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............
Serverless: Stack update finished...
Service Information
service: fabric-app
stage: dev
region: us-east-1
stack: fabric-app-dev
resources: 10
api keys:
  None
endpoints:
  GET - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/hello
functions:
  hello: fabric-app-dev-hello
layers:
  None
Serverless: Removing old service artifacts from S3...

デプロイができたらエンドポイントにアクセスしてみます。

> curl https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/hello

{
  "message": [
    {
      "Key": "CAR0",
      "Record": {
        "make": "Toyota",
        "model": "Prius",
        "colour": "blue",
        "owner": "Tomoko"
      }
    },
    {
      "Key": "CAR1",
      "Record": {
        "make": "Ford",
        "model": "Mustang",
        "colour": "red",
        "owner": "Brad"
      }
    },
    (略)
  ],
  "input": {
    "resource": "/hello",
    "path": "/hello",
    "httpMethod": "GET",
    (略)
  }
}

はい。
無事にAWS Lambda関数からHyperledger Fabric SDK for Node.jsを利用してブロックチェーンネットワークにアクセスすることができました。

VPC内にLamnbda関数を配置する必要があるため、ENI(仮想ネットワークインターフェース)の利用に伴う制限や起動速度に課題が発生するかもしれませんので、実際に利用する際には負荷検証などしっかりと行う必要がありそうです。

AWS LambdaをVPC内に配置する際の注意点 | そるでぶろぐ
https://devlog.arksystems.co.jp/2018/04/04/4807/

参考

Amazon VPC 内のリソースにアクセスできるように Lambda 関数を構成する - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/vpc.html

Serverless - The Serverless Application Framework powered by AWS Lambda, API Gateway, and more
https://serverless.com/

Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークを構築してみた - Qiita
https://qiita.com/kai_kou/items/e02e34dd9abb26219a7e

Amazon Managed Blockchainで作成したブロックチェーンネットワークにHyperledger Fabric SDK for Node.jsでアクセスしてみる - Qiita
https://qiita.com/kai_kou/items/5a6b0fc148f04857a878

AWS Lambda ランタイム - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html

ServerlessでLambdaをVPC内にデプロイする - Qiita
https://qiita.com/70_10/items/ae22a7a9bca62c273495

AWS LambdaをVPC内に配置する際の注意点 | そるでぶろぐ
https://devlog.arksystems.co.jp/2018/04/04/4807/

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