Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8960 articles
Browse latest View live

Google Sheet API(Node.js利用)で、複数シートを順番に生成できずハマったので、解決コードを晒してみる

$
0
0

Google Sheet API(Node.js利用)で、複数のシートを「1,2,3,4...」と並べて生成したいのに、
async,awaitで生成しても、シートが2,4,3,1みたいに順不同になってしまうのを、
どう回避できるかなかなかわかりませんでした。

辿り着いた解決策は、
array.reduceとPromiseを組み合わせて、thenでつないでいき、
Promise内でsetTimeoutをする、というものでした。

reduceに慣れていなかったので、
各引数の理解(特に第1とinitialValue)と、thenの理解、
また、初期値にPromice.resolve()を渡すあたりが難しかったです。

以下、実際に動くようになったコードです。
(SheetAPIのラッパークラスを自作していますので、よしなに読み替えてください)

import{Sheet}from'./Sheet.mjs'//シート関連の処理を集めた自作クラスimportdotenvfrom'dotenv'dotenv.config()constspreadsheetId=process.env.SOME_SPREADSHEET_ID/**
 * 複数のシートを順番に作成する
 */constmain=asyncfunction(){constsheet=awaitSheet.build()//auth取得などはここで処理している//複数シート作成のため連番を準備(スプレッド構文便利ね)constarr=[...(Array(10).keys())].slice(1)//0は排除//関数の中でPromiseオブジェクトを返す無名関数を準備。これをarr.reduceで逐次実行するconstcreateWithSleep=(sheetName)=>{return()=>{//ここで無名関数化してPromiseを返すのがreduce.then実行のキモ。無いと動かなかったreturnnewPromise(resolve=>{setTimeout(()=>{createSheet(sheetName,sheet)//シート生成メソッド呼び出しresolve()//resolve()でreduce内のthenが発火},2000)//thenで実行していても、一定の時間差がシート順次生成に必要!500などではだめだった})}}//ここのarr.reduceでの逐次実行がとても重要!これがないとシートの生成が順不同になる(thenでの処理実行チェーン)//シート名は's2'などでは読み取りなどのAPI実行時にカラム名と間違えられ困ったので、'組'というダブルバイトにしているarr.reduce((pre,cur)=>pre.then(createWithSleep(''+cur)),Promise.resolve())//最初にthenを発火させるために空のPromise.resolve()をinitialValueとして配置}//シートの生成部分. setTimeoutで十分時間をとっているのでasync,awaitは設定せずconstcreateSheet=(sheetName,sheet)=>{//ここのcreateSheetは普通のAPIメソッドの単なるラッパーですsheet.createSheet(sheetName,spreadsheetId).then(()=>console.log('created'))}main()

Google Sheet APIは、サクッとspreadsheetを作れたり、値を書き込んだり読み込んだり、
sheetを作成したり、超便利ですね。
nodeでローカルから楽しく操作できるのが気に入りましたが、
ちょいちょい書き方に詰まったので、同じところで詰まった方に役に立てばと、
恥を忍んでコードを晒してみます。

どなたかのお役に立てば幸いにて。
ではでは。


[WEB]ビュー(画面側)のコードに入れる、動的生成の構文 環境別まとめ

$
0
0

はじめに

webアプリケーションの実装について、ビュー(画面側)のコードには環境毎に独自の構文を埋め込むことで、webページを動的に生成する機能があります。
しかしこの構文が環境によって微妙に異なります。
いままで色々な環境での開発を経験する中で頭の中がごちゃごちゃになりそうなので、一度種類ごとに構文をまとめてみます。

1.JSP(Java系各種フレームワーク、サーブレット)

jspFile.jsp
<!-- 変数の値表示 -->
<%=(変数) %>

<!-- 処理記載 -->
<% (処理をここに書く) %>

<!-- ディレクティブ(JSPの設定) -->
<%@ (文をここに書く) %>

<!-- コメント -->
<%-- コメント文字列... --%>
<% /** コメント文字列... **/ %>

<!-- EL式 -->
${式}

2.Razor = .cshtml(ASP.net系)

razorFile.cshtml
<!--変数の値表示-->@変数<!--処理記載-->@{(処理をここに書く)}<!--処理記載の中に、そのままHTML出力したい文字を入れる-->@{<text>そのままHTML出力したい文字</text>}@:そのままHTML出力したい文字<!--名前空間のインポート-->@using名前空間<!--コメント-->@*コメント文字列...*@<%/* コメント文字列... */%><%// コメント文字列 %>

3.EJS(node.js系)

elsFile.ejs
<!-- 変数の値表示 -->
<%=(変数) %>

<!-- 変数の値表示(変数のエスケープをしない場合) -->
<%-(変数) %>

<!-- 処理記載 -->
<% (処理をここに書く) %>

<!-- コメント -->
<%# %>

上記の共通事項

  • ここであげた方法でコメントを書くと、出力されるHTMLにそのコメントは出力されません。
  • いずれも画面側のファイル単体では正しい画面表示ができません。ファイルはフレームワークのプロジェクト内に入れましょう。

DenoがTypeScriptの使用をやめる5つの理由

$
0
0

deno-will-stop-using-typescript.png

前書き

この記事は翻訳記事になります。:point_up_tone1:

近年、JSで書かれてるプロジェクトをTSに書き直すことが業界内で一種の風潮になって、
この記事で敢えてTSからJSに戻そうとする事例が目新しいと思ったので、翻訳してみました。

出処: 5 reasons why Deno will stop using TypeScript - StartFunction
原作者: eliorivero
Denoの紹介:
V8 JavaScriptエンジン及びRustプログラミング言語に基づいた、
JavaScript及びTypeScriptのランタイム環境である。Node.jsの作者であるライアン・ダールによって作成され、セキュリティと生産性に焦点を当てている。 --ウィキペディア

DenoがTypeScriptの使用をやめる5つの理由

最近、Denoが内部コードでTypeScriptの使用を停止することを指摘する文書が浮上しました。
言及されている問題には、TypeScriptのコンパイル時間、構造化、コード編成などが含まれます。
今後、Denoは内部コードに純粋なJavaScriptを使用されるそうです。

TypeScriptを使用するDenoの既存問題

現在、DenoチームがTypescript使用上、好ましくないと思う主な理由は以下になります。

  • ファイルを変更するときのTypeScriptのコンパイル時間は数分かかるため、継続的なコンパイルは非常に遅くなります。

  • 実際のDeno実行可能ファイルを作成するソースファイルで使用しているTypeScript構造と、ユーザー向けAPIがランタイムパフォーマンスの問題を引き起こしています。

  • TypeScriptは、Denoコードの整理に役立ててない。それどころか、逆効果とも言える。
    言及された問題の1つは、2つの場所で重複した独立したBodyクラスになってしまったことです。

  • TypeScriptコンパイラはd.tsファイルの生成に役立たないため、内部コードとランタイムTypeScript宣言は手動で同期を保つ必要があります。

  • 2つのTSコンパイラホストを維持しています。
    1つは内部Denoコード用で、もう1つは外部ユーザーコード用ですが、どちらも用途としては近いです。

DenoコードからTypeScriptを削除する

Denoチームの目的は、ビルド時の全ての内部DenoコードのTSの型チェックを外すこと。
彼らは、すべてのランタイムコードをJavaScriptファイルに移動する。
ただし、タイプ定義とドキュメントを保持するために、d.tsファイルを引き続き使用します。

Denoチームは内部のコードに対してのみ、TypeScriptの使用を停止することに言及するが、
Denoのユーザーは引き続きTypeScriptを使用できますし、タイプチェックももちろんされます。

TypeScriptはJavaScriptの改良版と見なされることもありますが、実際はそうではない。
他の言語と同じように欠陥があります、最も重要なものの1つは、コンパイル時間が遅いことです。
小さなプロジェクトでは、純粋なJavaScriptからTypeScriptに切り替えるときにコンパイル時間が大幅に増加することはないかもしれませんが、複雑な、例えばReactのような大規模なプロジェクトでは顕著になります。
ランタイムのサイズが大きいことを考えると、DenoがTypeScriptを止めるのも当然のことです。

開発中の型チェックは、コンパイル時にコストがかかります。
TypeScriptプロジェクトに、コンパイル時間に対処して改善する方法に関する多くのドキュメントがあるのはそれなりの理由があります。
最も興味深いアプローチの1つは、プロジェクト参照を使用することです。
これにより、開発者はTypeScriptの大きなコードを小さなパーツに分割できます。

DenoがTypeScriptの使用をやめる理由についてもっと読む

TypeScriptを内部Denoコードから削除し、代わりにJavaScriptを使用するという決定についての完全な議論は、このドキュメントにあります。
そこには、Ryan Dahlと共同研究者が、問題、その解決策、およびその実装方法について説明しています。

npmとYarnのまとめ記事、Node.jsを添えて

$
0
0

目的

Yarnやnpm(node package manager)に関する情報を要点を抑えて簡単にまとめる。
Yarnやnpmに関する記事は既にたくさんあるので参考になりそうな記事もまとめて網羅的に調べやすい記事にしたい(いろいろ動きがあれば更新していく)。

npm?

  • npmはNode.js(後述)のパッケージ管理ツール。
  • 必要なパッケージをインストールする際に、そのパッケージが動くために必要な他のパッケージも合わせてインストールすることができる。
  • モダンな開発を行いたいなら必須。

参考記事

フロントエンド開発の3ステップ(npmことはじめ)
npmの導入方法などのシンプルでわかりやすい記事。

Node.js?

  • ブラウザ上でしか動けなかったJavaScriptをパソコン上で動かせるようにする。
  • JavaScript実行環境。プラットフォーム。Webサーバの役割を担うことができる。
  • 開発に便利なパッケージを使うのに必要。

参考記事

Node.jsとはなにか?なぜみんな使っているのか?
Node.jsを利用することでフロント側でできることが増え、開発を効率的に行えるようになった。
その背景を説明しています。

Yarn?

  • npmとほぼ同じような用途に使う。
  • npmと互換性があり、npmで使用していたプロジェクト設定ファイル(package.json)がそのまま使える。
  • npmと比較するとインストールが速い、セキュリティが高いという特徴がある。
  • npmとコマンドが似ているので学習コストは低い。

Yarnをインストールするためにnpmをインストールするってなんか変な感じですよね。

参考記事

npmとは yarnとは
npmとyarnの違いを箇条書きで簡潔に紹介しています。

yarnチートシート
Yarnのコマンドがまとめられてます。npmのコマンドと似ているので導入しやすい!2017年の記事。

TypeScript(Node.js)でテキストファイルを指定行数ごとに分割する

$
0
0

テキストファイルを分割したいときってありますよね。
例えばSQLのINSERT INTO hoge VALUESに続く行が数万行ある時とか (そんなにない)

個人的に上記をやりたいタイミングがあって、npmでいい感じのモジュールを探したんですが
意外とテキストファイルを「指定した行数で」区切ってくれるやつが無かったので泣く泣く自分で作りました。

あ、Nodeです。

Streamで順次処理してるので、でっかいファイルでもヒープアウトしないはず!

Usage

hoge.ts
import{FileSplitter}from"path/to/file-splitter"// your-file.txtを100行ごとに分割するconstfileSplitter=newFileSplitter("/path/to/your-file.txt",100)// 分割開始!fileSplitter.start()

御託は良いからコードを見せろ

file-splitter.ts
importfsfrom"fs"importpathfrom"path"importreadlinefrom"readline"exportclassFileSplitter{privatemaxLines:numberprivateidentifier=0privatecurLine=0privatelineReader:readline.InterfaceprivatecurrentWriteStream:fs.WriteStreamprivateoutputPath:stringconstructor(readFrom:string,maxLines=500){// 分割したファイル群を保存する先のディレクトリを作り、そのpathを保存this.setOutputDir(readFrom)this.maxLines=maxLines// 最初のWriteStreamを作っておくthis.replaceWriteStream()// readlineに指定ファイルのReadStreamを食わせて行リーダーを作るthis.lineReader=readline.createInterface({input:fs.createReadStream(readFrom)})}start(){this.lineReader.on("line",(line)=>{// 現在の行数を保持 (1 ~ 指定行数 + 1までの値を取る)this.curLine++// ここ、もう少しうまくやりたかった人生だったconstisOn=this.curLine===this.maxLinesconstisOver=this.curLine>this.maxLinesif(isOver){// 指定行数を超えたら新しいファイル向けのWriteStreamに切り替え、行数を1にリセットするthis.replaceWriteStream()this.curLine=1}if(isOn){// そのファイル最後の行は改行無しにしている (が、例えば100行のファイルを30行とかで区切られると、最後のファイルには改行が入っちゃう。めんどくさくてこれ以上考えなかった)this.currentWriteStream.write(line)}else{this.currentWriteStream.write(`${line}\n`)}}).on("close",()=>{// 一応掃除するthis.closeWriteStreamIfExists()console.info("Done!")})}privatesetOutputDir(readFrom:string){constextension=path.extname(readFrom)constfileName=path.basename(readFrom,extension)constsplitFileBaseDir=`${path.dirname(readFrom)}/split-files`this.outputPath=`${splitFileBaseDir}/${fileName}`if(!fs.existsSync(splitFileBaseDir)){fs.mkdirSync(splitFileBaseDir)}if(!fs.existsSync(this.outputPath)){fs.mkdirSync(this.outputPath)}}privatereplaceWriteStream():void{this.identifier++this.closeWriteStreamIfExists()constwriteTo=`${this.outputPath}/file_${this.identifier}.txt`console.info(`Start writing to ${writeTo}.\n`)this.currentWriteStream=fs.createWriteStream(writeTo)}privatecloseWriteStreamIfExists(){if(this.currentWriteStream){this.currentWriteStream.close()}}}

まとめ

readline標準モジュールにたどり着くまでの人生を無駄にした。

Streamをちゃんと考えて使ったことあんまりなかったのでちょっと楽しかった。

Express.jsでファイルダウンロード

$
0
0

Express.jsで画像などのファイルをダウンロードする方法です。
本記事の内容は以下のドキュメントに書かれている内容の説明になります。
https://expressjs.com/ja/api.html

実行環境

express.js 4.17.1
MacOS 10.15.7

方法その1 簡易的な方法

importexpressfrom'express';exportconstrouter:express.Router=express.Router()router.get('/download',(req,res)=>{res.download('images/どね.jpg')})

downloadメソッドを使います。ファイルパスを引数に取ることになるので、一度ファイルをストレージのどこかに置く必要があります。すでにBuffer形式になっている場合は少し面倒ですね。

方法その2 headerに指定

importexpressfrom'express';importfsfrom'fs/promises'exportconstrouter:express.Router=express.Router()router.get('/set',async(req,res)=>{constimg=awaitfs.readFile('images/どね.jpg')constfileName=encodeURIComponent('どね.jpg')res.set({'Content-Disposition':`attachment; filename=${fileName}`})res.status(200).send(img)})

レスポンスのヘッダーを直接指定します。Content-Dispositionattachmentを指定することでブラウザ側でダウンロードファイルであることを認識してくれます。
それ以外にも以下の処理が必要になります。

  • Bufferファイルを一度読み込んでbodyに入れる
  • 日本語ファイル名の可能性がある場合はファイル名にエンコードをかける

filenameとfilename*の違いについて

引数の filename と filename* の違いは、 filename* が RFC 5987 で定義されているエンコーディングを使用するという点のみです。単一のヘッダーフィールドの値に filename と filename* の両方が存在する場合は、両方が解釈できる場合、 filename* が filename よりも優先されます。

方法1ではfilename*が自動で使われて、方法2ではどちらも指定できます。

方法1方法2
image.pngimage.png

いくつかのブラウザで試しましたがすべてfilename*を認識できていました。使える文字もこちらのほうが多いようです。あえてfilenameを使う必要はなさそうです。もし使えないブラウザがあったら教えていただきたいです。

試したブラウザを以下に置いておきます。

ブラウザバージョン対応状況
Chrome88
FireFox88
Microsoft Edge88
Internet Explorer11

まとめ

たいていはフレームワークに乗っかった方が後々困ることもないので方法1推奨です。
修正がかかったらフレームワーク側を直せば良い話ですし。

Node.jsでcsvファイルを読み書きする

$
0
0

はじめに

node.jsでcsvを読み書きしたい時があったので,備忘録としてまとめようと思い,この記事を書きました。
streamを使ったやり方など,色々と方法があったのですが,今回は fs.readFileSyncfs.writeFileSyncを用いた方法でやってみました。

大まかな流れ

  1. 必要なパッケージのダウンロード
  2. 読み込むcsvファイルの作成
  3. 実行ファイルの作成
  4. 実行

必要なパッケージのダウンロード

今回は, csvというモジュールを使用しますので,以下コマンドを使用してダウンロードします。

$npm install--save csv
  • --save : package.jsonのdependenciesに追加される

読み込むcsvファイルの作成

先頭の行に,カラム名を入れておきます。

input.csv
name,age,gender
taro,30,man
jiro,28,man
hanako,20,woman 

実行ファイルの作成

実行ファイルの完成形

input.csvというcsvファイルを読み込み,行を追加して output.csvという名前で出力するコードになっています。

sample.js
// file system モジュールを読み込むconstfs=require('fs');// csv モジュールの内,必要な機能を読み込むconstparse=require('csv-parse/lib/sync');conststringify=require('csv-stringify/lib/sync');// csvファイルを読み込むconstinputData=fs.readFileSync('./input.csv',{encoding:'utf8'});// 先頭行をcolumnとして扱い,csvデータをparseconstparsedData=parse(inputData,{columns:true});// 行を追加parsedData.push({'name':'saburo','age':'25','gender':'man'});// オブジェクトのプロパティを先頭行に記述し,csvデータに変換constoutputData=stringify(parsedData,{header:true});// csvファイルを出力fs.writeFileSync('output.csv',outputData,{encoding:'utf8'});

以下で詳細に説明します。

モジュールの読み込み

sample.js(一部)
// file system モジュールを読み込むconstfs=require('fs');// csv モジュールの内,必要な機能を読み込むconstparse=require('csv-parse/lib/sync');conststringify=require('csv-stringify/lib/sync');

実装に必要なモジュールを読み込みます。(csv-parse と csv-stringfy については,後ほど詳しく説明します。)

csvファイルを読み込む

sample.js(一部)
// csvファイルを読み込むconstinputData=fs.readFileSync('./input.csv',{encoding:'utf8'});

fsモジュールを用いて, input.csvを読み込みます。
今回は,オプションで文字コードを UTF-8と指定しています。

csvファイルをオブジェクトに変換

sample.js(一部)
// 先頭行をcolumnとして扱い,csvデータをparseconstparsedData=parse(inputData,{columns:true});

fs.readFileSyncでcsvファイルを読み込むと,以下のように単なる文字列として保存されます。

inputDataの中身
name,age,gender
taro,30,man
jiro,28,man
hanako,20,woman

これを,javascriptで操作しやすくするためにオブジェクトの形式に変更するのが, csv-parseです。
columns : trueというオプションをつけることによって,csvファイルの先頭行を,オブジェクト変換後のプロパティとして扱います。

parsedDataの中身
[
  { name: 'taro', age: '30', gender: 'man' },
  { name: 'jiro', age: '28', gender: 'man' },
  { name: 'hanako', age: '20', gender: 'woman' }
]

末尾に行を追加

sample.js(一部)
// 行を追加parsedData.push({'name':'saburo','age':'25','gender':'man'});

オブジェクトをcsvデータに変換

sample.js(一部)
// オブジェクトのプロパティを先頭行に記述し,csvデータに変換constoutputData=stringify(parsedData,{header:true});

csv-stringifyは, csv-parseとは逆に,javascriptのオブジェクトをcsvファイルとして出力するための文字列に変換するモジュールです。
変換されたデータは,以下のような中身になっています。

outputDataの中身
name,age,gender
taro,30,man
jiro,28,man
hanako,20,woman
saburo,25,man

csvファイルを出力

sample.js(一部)
// csvファイルを出力fs.writeFileSync('output.csv',outputData,{encoding:'utf8'});

fsモジュールを用いて, output.csvという名前でcsvファイルを出力します。

実行

$node sample.js
output.csv
name,age,gender
taro,30,man
jiro,28,man
hanako,20,woman
saburo,25,man

参考

【Node.js】Expressでセッション機能を使う方法

$
0
0

プログラミング勉強日記

2021年3月2日

セッション機能とは

 Webサーバーとブラウザの間で続けて通信を行うために必要な機能で、サーバー側でクライアントの状態を管理する方法である。セッションを利用することで、同じクライアントからサーバーに何回アクセスされたかなどを管理することができる。

Express sessionの使い方

 express-sessionモジュールをインストールして、--saveオプションを使用することでインストールの情報を保存できる。

$ npm install --save express-session

 そうすると、package.jsonにexpress-sessionが追加される。

package.jsoon
"dependencies":{"ejs":"^2.6.1","express":"^4.16.4","express-session":"^1.15.6"}

sessionの基本構文

 まず、インスタンス名で指定したオブジェクトに対してuseでセッションを使用すると宣言する。sessionでセッション処理を行うことを指定して、値のところで具体的にどのような処理を行うか指定する。

インスタンス名.use(session({
  設定項目: '値',
}))

参考文献

セッション機能について
セッションを扱う!express-sessionを利用する方法【初心者向け】
express-sessionでセッションを利用する[Express][node.js]


Nuxt.jsでscss導入しようとしたらエラッた

$
0
0

開発環境

macOS: 10.15.7
node: 14.15.3
npm: 6.14.9

再現方法 (2021.03.02時点)

npx create-nuxt-app projectName-xxxx

上記で諸々をインストール後に、下記でscssに必要なパッケージをインストール

npm install --save-dev node-sass sass-loader @nuxtjs/style-resources

そしてvueファイルの

<style>
  ...
</style>

を、↓に書き換える

<style lang="scss">
  ...
</style>

そして、npm run devを実行すると下記エラー

 ERROR  Failed to compile with 1 errors                                                                                                                                                                                                                                                               friendly-errors 20:33:55


 ERROR  in ./pages/index.vue?vue&type=style&index=0&lang=scss&                                                                                                                                                                                                                                        friendly-errors 20:33:55

Module build failed (from ./node_modules/sass-loader/dist/cjs.js):                                                                                                                                                                                                                                    friendly-errors 20:33:55
TypeError: this.getOptions is not a function
    at Object.loader (/Users/xxxx/node_modules/sass-loader/dist/index.js:25:24)

原因

Nodeと下記パッケージのバージョンがうまく噛み合っていないよう。
この段階でインストールされているパッケージのバージョンは↓

package.json

    "node-sass": "^5.0.0",
    "sass-loader": "^11.0.1",

解決方法

node-sasssass-loaderのバージョンを下げたらエラー解消した

解決方法の詳細

1. まずはnode-sassのバージョンを下げてみる

npm uninstall --save-dev node-sassでアンインストールして、
npm install --save-dev node-sass@4.14でバージョン指定して再インストール

npm run dev実行 → エラー解消せず(先述のエラー発生)

2. sass-loaderのバージョンも下げてみる

npm uninstall --save-dev sass-loaderでアンインストールして、
npm install --save-dev sass-loader@10.1.0でバージョン指定して再インストール

npm run dev実行 → エラー解消!!

所感

エラー解消してよかった〜

補足

今回のエラーに関しては、ググった感じでは他に起因するケースもあるようです。
本記事は原因の一つとしてご参考までにmm

Cookieを使った認証機能を実装しようとしてCORSでどハマりした時のメモ

$
0
0

前提

やりたかったこと

  • サーバーで発行されたcredential情報をcookieとして保存すること
  • cookieに保存されているcredential情報をサーバーに送付して、それを元に認証機能を実装すること

はまったこと

  • クライアントからのリクエストに対して、サーバー側からのレスポンスにSet-Cookieヘッダーを付与することは出来て、ブラウザのdeveloper toolでもSet-Cookieヘッダーがレスポンスにあることを確認出来ているのに、クライアントでcookieがセットされない。

原因

  • 原因はCORSの設定。

解決のために参考したリンク(ほんとまじでありがとうございます!)

awaitaxios.get('apiのエンドポイント',{withCredentials:true});
importcorsfrom'cors';constapp=express();app.use(cors({origin:true,credentials:true}));

M1 MacへのNode.jsインストール手順

$
0
0

結論

以下の順でインストールします

Homebrew -> anyenv -> nodenv -> Node.js

環境

  • MacBook Air (M1,2020)
  • macOS Big Sur バージョン 11.2.1
  • zsh 5.8 (x86_64-apple-darwin20.0)
    • デフォルト状態でzshが利用されている想定

Homebrewのインストール

過去のこちらの記事を参照しました

anyenvのインストール

anyenvを利用する理由としては、今回利用するnodenv(Node.js)を始め、rbenv(Ruby)pyenv(Python)など他の言語もまとめて管理できるためです

# Homebrewからanyenvをインストールする
> brew install anyenv

# シェルにパスを通す
> echo 'eval "$(anyenv init -)"' >> ~/.zshrc $ exec $SHELL -l

# 現在のシェルへ反映させる
> exec $SHELL -l

nodenvのインストール

nodenvは Node.js のバージョン管理ツールです

# anyenvからnodenをインストールする
> anyenv install nodenv

# 現在のシェルへ反映させる
> exec $SHELL -l

Node.jsのインストール

(20210303時点)現在のM1 Macのデフォルト環境では14.*以前はインストールできません
※ Rossetaなどを利用してIntelベースに置き換えることができれば可能です

# インストール可能なバージョンを確認する
> nodenv install -l
(中略)
15.10.0

# バージョン名を指定してインストールする
> nodenv install 15.10.0

# インストールしたバージョンを反映させる
> nodenv global 15.10.0

余談

まだまだすべてがM1 Macに対応できているわけではないので注意が必要になります

M1 MacへのWebAssembly環境構築手順

$
0
0

結論

良い点

  • WebAssembly(以下、Wasm)は実行環境を選ばない
  • 他言語との連携が可能
  • 5Gなど先の時代を見据えた高速な処理が可能
    • サーバーの処理をフロントで処理させることも可能
      • 動画変換など

イマイチな点

  • 資料が少ない
  • 環境が整っていない
  • 開発コストが高い(上記の理由により)

環境

  • macOS Big Sur ver11.2.1
  • zsh 5.8 (x86_64-apple-darwin20.0)

手順

Node.jsのインストール

過去のこちらの記事を参照しました

Rust のインストール

公式サイトまたは日本語サイトから手順を参照

特段理由が無い場合はrustupを用いてインストールを行うことが推奨されている

> curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

インストール方法を聞かれるがデフォルトで良い

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


   default host triple: x86_64-apple-darwin
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
> 1

下記、メッセージが表示されていればインストール完了

Rust is installed now. Great!

注意点として
パスは通してくれているが、現在のシェルに反映されてないので再読み込みさせる

To get started you need Cargo's bin directory ($HOME/.cargo/bin) in your PATH
environment variable. Next time you log in this will be done
automatically.

To configure your current shell, run:
source $HOME/.cargo/env
> source $HOME/.cargo/env

ライブラリをインストールする

Wasmへコンパイルするための標準ライブラリ

> rustup target add wasm32-unknown-unknown
info: downloading component 'rust-std' for 'wasm32-unknown-unknown'
info: installing component 'rust-std' for 'wasm32-unknown-unknown'

wasm-packのインストール

Wasmアプリケーションのbuildのために利用します

> cargo install wasm-pack

Installed package `wasm-pack v0.9.1` (executable `wasm-pack`)

以上です

余談

データ通信以外がすべてフロントエンドの技術で済む時代も来るのか!といった感じ
これからますますの発展に期待するばかりです

Node.js で Synology の WebDAV にアクセスする

$
0
0

ライブラリーのインストール

sudo npm install-g webdav

フォルダーの一覧

webdav_list.js
#! /usr/bin/node
////  webdav_list.js////                      Mar/03/2021// ---------------------------------------------------------------const{createClient}=require("webdav")constclient=createClient("https://example.synology.me:5006/",{username:"scott",password:"secret"})// ---------------------------------------------------------------asyncfunctionwebdevListfile(folder){constdirectoryItems=awaitclient.getDirectoryContents(folder)console.log(directoryItems)}// ---------------------------------------------------------------constfolder="/homes/scott/tmp"webdevListfile(folder)// ---------------------------------------------------------------

実行スクリプト

export NODE_PATH=/usr/lib/node_modules
#
./webdav_list.js

ファイルのアップロード

webdav_put.js
#! /usr/bin/node
////  webdav_put.js////                      Mar/03/2021// ---------------------------------------------------------------varfs=require("fs")// ---------------------------------------------------------------const{createClient}=require("webdav");constclient=createClient("https://example.synology.me:5006/",{username:"scott",password:"secret"});// ---------------------------------------------------------------asyncfunctionwebdevPutfile(file_in,file_out){conststr=fs.readFileSync(file_in,'utf8')awaitclient.putFileContents(file_out,str)}// ---------------------------------------------------------------constfile_in="./tmp01.txt"constfile_out="/homes/scott/tmp/tmp01.txt"webdevPutfile(file_in,file_out)// ---------------------------------------------------------------

実行スクリプト

export NODE_PATH=/usr/lib/node_modules
#
./webdav_put.js

ファイルのダウンロード

webdav_get.js
#! /usr/bin/node
////  webdav_get.js////                      Mar/03/2021// ---------------------------------------------------------------varfs=require("fs")// ---------------------------------------------------------------const{createClient}=require("webdav");constclient=createClient("https://example.synology.me:5006/",{username:"scott",password:"secret"});// ---------------------------------------------------------------asyncfunctionwebdevGetfile(file_in,file_out){conststrx=awaitclient.getFileContents(file_in,{format:"text"});fs.writeFile(file_out,strx,function(err){if(err){console.log("Error on write: "+err)}else{console.log("File written.")}})}// ---------------------------------------------------------------constfile_in="/homes/scott/tmp/tmp02.txt"constfile_out="./tmp02.txt"webdevGetfile(file_in,file_out)// ---------------------------------------------------------------

実行スクリプト

export NODE_PATH=/usr/lib/node_modules
#
./webdav_get.js

「オブジェクト名の変更」でアップロード済み画像のサムネイル生成を簡単に

$
0
0

クラウドファンクションでサムネイル生成

FirebaseのCloud Storageに画像をアップロードして、サムネイルをCloud Functionsで生成しているケースは多いと思います。
公式のサンプルでもCloud StorageのonFinalizeをトリガーにして、サムネイルを生成していますよね。
ただし、途中からサムネイルを生成するようになった、サムネイルのサイズを増やしたいなどのパターンでは、新規のクラウドファンクションを作成しても、すでにアップロードされた画像はonFinalizeのトリガーが発生せずに、サムネイルが生成されません。
そのため、他の方法を考える必要があります。

前提条件

今回この記事で参考になりそうな方はこのような条件です。

  • Cloud FunctionsのonFinalizeトリガーでサムネイルを生成している。
  • すでにアップロード済みの画像に対してサムネイル生成したい。
  • 簡単な実装でonFinalizeトリガーを発生させたい。
  • node.jsでスクリプトを書いて全ファイルに対して一括処理させたい。

ファイルをローカルにダウンロードしてアップロードしなおす(没案)

没案なので、読み飛ばしてもらって構いません。
当初は、画像をローカルの一時フォルダに格納し、それを同じ場所にアップロードし直せばonFinalizeトリガーが発生するので良いのではと考えていました。

没案となった実装内容

アップロード処理に関してはコミット履歴に残っていなかったのでTODOコメントとしました。
1行でかける処理だと思います。

// Storageから取得した画像パスに対する処理imagePaths.forEach((imagePath)=>{// 一時ディレクトリにdownloadディレクトリを作成するconstdownloadPath=os.tmpdir()+'/download/'// downloadディレクトリにStorageから取得した画像のパスを結合して、ローカル上の一時的なファイルパスとするconsttempFilePath=downloadPath+imagePath;// storageから画像をローカルに保存するawaitbucket.file(imagePath).download({destination:tempFilePath});// TODO: アップロード処理}

もちろんこれでも実現できますし、そんなに実装量も気にするほどではありませんが、デメリットも考えられます。

デメリット

  • 自分のPCのローカルストレージに一時的にファイルを置くため、ストレージ容量が足りないなどの問題が発生する可能性がある。
  • ダウンロードしてアップロードしなおす時のファイルパスが完全に一致するか検証しなければならない。

そして何よりもっとシンプルに書く方法はないかと辿り着いた結果が採用案になります。

オブジェクト名を変更する(採用案)

Cloud StorageのonFinalizeトリガーについて

Cloud StorageのonFinalizeトリガーは、バケットで新しいオブジェクト(または既存オブジェクトの新しい世代)が正常に作成された場合に送信されます。既存のオブジェクトのコピーまたは書き換えを行った場合にも送信されます。アップロードが失敗した場合、このイベントはトリガーされません。

Cloud Storageのトリガー

既存のオブジェクトの名前を変更

「既存のオブジェクトのコピーまたは書き換えを行った場合」がポイントです。
オブジェクト名の変更を行うことでonFinalizeトリガーが動くのではないかという予想が立ちました。
ただし、オブジェクト名は変更しますが、違う名前に書き換えてしまうと参照先のパスも変わってしまうので同じ名称に変更しなければなりません。
同じ名称にしたときに、オブジェクト名の変更として扱ってくれるかというのはポイントでしたが、問題ありませんでした。

オブジェクトの名前変更、コピー、移動についての公式ドキュメントはこちら

採用した実装内容

imagePaths.forEach((imagePath)=>{awaitfile.move(imagePath);}

はい、すごくシンプルな実装にすることができました。
画像パスの面倒な結合処理などもなくなりましたし、ダウンロード => アップロードといった手順を踏まず、ファイルに対してmoveとすれば良いだけです。
ローカルのストレージを使用することもないので、容量を気にする必要もありません。
こちらを実行するとオブジェクト名の変更が行われ、onFinalizeが呼び出されました。
それによってアップロード済みの画像のサムネイルも生成することができました。

最後に

これに辿り着けたのは弊社内でペアプロを実施したのが大きかったです。
もともと前任の方のソースコードを引き継いでいて、アップロードからダウンロード処理するものだと思い込んでいたんですが、すごくシンプルな案にまとめることができました。

Passport.jsのローカル認証で特定のディレクトリ以下にアクセス制限をかける

$
0
0

はじめに

HTMLなどの静的ファイルを置いたディレクトリに対し登録済みユーザーのみアクセス可能なアクセス制限をかけようと、Node.js+Express.js+Passport.jsでWEBサイトを構築しました。
ローカル認証方式で特定のディレクトリだけでなく配下のすべてのディレクトリとファイルに対しアクセス制限を掛ける方法がネットでは見つけられずハマったので解決法を共有します。

ソース

Passport.jsの設定等は他のサイトをご参照下さい。
/app/restrict というディレクトリ以下すべてに対するGETメソッドについて認証を求める方法は以下の通りです。

app.get(/app\/restrict/,(req,res,next)=>{if(!req.isAuthenticated()){req.session.originalUrlSave=req.originalUrl;res.render('login',{error:req.flash('error'),title:'ログインページ'});}else{returnnext();}});
  • ディレクトリの指定に正規表現を使います
  • 認証済みなら return next(); でGETしに来たファイルがそのまま返ります
  • restrictディレクトリ配下のURLに直接アクセスして来た場合に、ログイン画面でログイン成功したらそのURLに飛べるよう元のURLをセッションに保存しておきます。

終わりに

最初は

app.get('/app/restrict',(req,res,next)=>{

と書いていて、restrictディレクトリは意図通りアクセス制限が効くのですが、
/app/restrict/js
のようなサブディレクトリには全くアクセス制限が効いていないことに気づいて焦りました。
「指定したディレクトリの配下も掛かるハズ」と言った先入観を捨ててテストはしっかりやらないと怖いですね。


Node.jsをOpenTelemetryでトレーシングしてみる

$
0
0

はじめに

Node.js(JavaScript)でOpenTelemetryを扱う記事が少ないため、トレーシングまわりの基本的なノウハウをまとめてみました。
本記事では、Instrumentationを使用した自動トレース収集、startSpan()メソッド呼び出しによる手動トレース収集まわりを扱います。

OpenTelemetryとは?

マイクロサービスのような分散環境で、トレースやメトリクスを計測するフレームワーク。
可視化ツール(ZipkinやJaegerなど)と組み合わせることで、どの処理でどれだけ時間がかかっているのか解析しやすくなります。

おおまかな特徴
* CNCFがOpenTelemetryプロジェクトをホストしている(2015年設立)
* 従来のフレームワークに見られたベンダロックインを排除したオープンさがコンセプト
* 言語ごとにライブラリを提供
* 使用するライブラリを差し替えれば、手続きはそのままに、異なるツールとの連携が可能になる

やってみること

クライアント・サーバからトレースデータを集め、Zipkinでグラフ化してみます。
サンプルとして、クライアントが2種類のREST-APIを呼び出す構成を対象に行います。

architecture.png

実行環境

  • MacOS X
  • node: v13.8.0
  • npm: v7.5.3
  • express: v4.17.1
  • OpenTelemetry: v0.17

1. サンプル構成の作成

まずはベースとなるクライアント・サーバのサンプルを作成します。

1.1 サンプル作成

プロジェクトにExpressとAxiosをインストールします。

$ npm install express axios

下表に示す3種類のソースファイルを作成します。

ファイル名実装内容
api1server.jsAPI1のリクエストを受け付け、2秒後にレスポンスを返す
api2server.jsAPI1のリクエストを受け付け、1秒後にレスポンスを返す
client.jsAPI1、API2を順々に同期的に呼び出す
api-server1.js
'use strict';constexpress=require('express');constapp=express();constPORT=8180;constdata={name:"orange",price:200};asyncfunctionsetupRoutes(){app.use(express.json());app.get('/api/price',async(req,res)=>{// 2秒後にレスポンスを返すsetTimeout(()=>{res.json(data);},2000);});}setupRoutes().then(()=>{app.listen(PORT);console.log(`Listening on http://localhost:${PORT}`);});
api-server2.js
'use strict';constexpress=require('express');constapp=express();constPORT=8280;constdata={name:"melon",price:600};asyncfunctionsetupRoutes(){app.use(express.json());app.get('/api/price',async(req,res)=>{// 1秒後にレスポンスを返すsetTimeout(()=>{res.json(data);},1000);});}setupRoutes().then(()=>{app.listen(PORT);console.log(`Listening on http://localhost:${PORT}`);});
client.js
'use strict';constaxios=require('axios').default;asyncfunctionmakeRequest(){// 1つ目のAPIを呼び出すawaitaxios.get('http://localhost:8180/api/price').then(res=>console.log("API1: OK")).catch(err=>console.log("API1: ERROR"));// 2つ目のAPIを呼び出すawaitaxios.get('http://localhost:8280/api/price').then(res=>console.log("API2: OK")).catch(err=>console.log("API2: ERROR"));// 5秒後に終了するsetTimeout(()=>{console.log('Completed.');},5000);}makeRequest();

1.2 サンプルの動作確認

以下の手順で、2種類のサーバを起動し、Clientを起動します。
API1とAPI2を順々に呼び出し、それぞれ結果OKとなるはずです。

# Server1の起動$ node api-server1.js 
Listening on http://localhost:8180

# Server2の起動$ node api-server2.js 
Listening on http://localhost:8280

# Clientの起動$ node client.js 
API1: OK
API2: OK
Completed.

2. Zipkinの構築

トレースデータを可視化するために、DockerコンテナのZipkinを構築します。

$ docker run -d-p 9411:9411 openzipkin/zipkin

ブラウザから localhost:9411 にアクセスすると、以下の画面が表示されます。

zipkin1.png

3. OpenTelemetryの組み込み

3.1 パッケージのインストール

それでは、1章で作成したサンプルにOpenTelemetryを組み込んでいきます。
まずは必要となるパッケージをプロジェクトにインストールします。

$ npm install\
  @opentelemetry/core \
  @opentelemetry/node \
  @opentelemetry/tracing \
  @opentelemetry/instrumentation \
  @opentelemetry/exporter-zipkin
  @opentelemetry/plugin-http \
  @opentelemetry/plugin-express

3.2 tracer.jsの作成

クライアント・サーバ両方が共通で使用するExporter設定(tracer.js)を作成します。

tracer.js
constopentelemetry=require('@opentelemetry/api');const{registerInstrumentations}=require('@opentelemetry/instrumentation');const{NodeTracerProvider}=require('@opentelemetry/node');const{SimpleSpanProcessor,ConsoleSpanExporter}=require('@opentelemetry/tracing');const{ZipkinExporter}=require('@opentelemetry/exporter-zipkin');module.exports=(serviceName)=>{constprovider=newNodeTracerProvider();registerInstrumentations({tracerProvider:provider,});constexporter=newZipkinExporter({serviceName});provider.addSpanProcessor(newSimpleSpanProcessor(exporter));provider.addSpanProcessor(newSimpleSpanProcessor(newConsoleSpanExporter()));provider.register();returnopentelemetry.trace.getTracer('api-call-app');};

Exporterとはトレースデータの出力先となるオブジェクトです。
今回は以下2つのExporterを設定し、2箇所に同時に出力されるようにしています。

使用するExporter出力先
ZipkinExporterZipkin
ConsoleSpanExporterコンソール画面

また、registerInstrumentations()を呼び出すことで、先ほどnpm installコマンドでインストールしたInstrumentationパッケージ(自動トレース出力)が使用されるようになります。
今回のサンプルでは、plugin-httpとplugin-expressの2つのInstrumentationが有効化されています。

3.3 クライアント側への組み込み

続いて、クライアント側ソースにOpenTelemetryのコードを追加します。

client.js
'use strict';+consttracer=require('./tracer')('client');+constapi=require('@opentelemetry/api');constaxios=require('axios').default;- asyncfunctionsetupRoutes(){+ functionmakeRequest(){+ constspan=tracer.startSpan('client.makeRequest()',{+ kind:api.SpanKind.CLIENT,+ });+ api.context.with(api.setSpan(api.ROOT_CONTEXT,span),async()=>{// 1つ目のAPIを呼び出すawaitaxios.get('http://localhost:8180/api/price').then(res=>span.setStatus({code:api.SpanStatusCode.OK})).catch(err=>span.setStatus({code:api.SpanStatusCode.ERROR,message:err.message}));// 2つ目のAPIを呼び出すawaitaxios.get('http://localhost:8280/api/price').then(res=>span.setStatus({code:api.SpanStatusCode.OK})).catch(err=>span.setStatus({code:api.SpanStatusCode.ERROR,message:err.message}));+ });// 5秒後に終了するsetTimeout(()=>{+ span.end();console.log('Completed.');},5000);}makeRequest();

クライアント側への追加内容は以下となります。

  1. Exporterの作成(tracer.js呼び出し)
  2. トレースデータを構成するSpanの作成(startSpan()の実行)
  3. ContextにSpanを設定し、API実行のタイミングでサーバ側へSpanを伝搬させる
  4. 最後にSpan.end()でSpanを終了する

用語説明になりますが、個々の測定区間をSpanと呼び、Spanの集合がTraceとなります。
以下の図のように、Spanは親子関係を持つこともできます。

trace_span.png

3.4 サーバ側への組み込み

api-server1.js
'use strict';+consttracer=require('./tracer')('api-server1');+constapi=require('@opentelemetry/api');constexpress=require('express');constapp=express();constPORT=8180;constdata={name:"orange",price:200};asyncfunctionsetupRoutes(){app.use(express.json());app.get('/api/price',async(req,res)=>{// 2秒後にレスポンスを返すsetTimeout(()=>{res.json(data);},2000);});}setupRoutes().then(()=>{app.listen(PORT);console.log(`Listening on http://localhost:${PORT}`);});

サーバ側への追加内容はExporterの設定のみです。
api-server2.jsにも同様の追加を行います(ソースは掲載省略)。

3-5. 動作確認

1章と同様に、サーバ2つを再度起動し、クライアントを起動します。

$ node client.js 

Zipkinの画面にアクセスし、検索条件:serviceName=clientを指定して、先ほど収集したトレースデータを表示します。
以下のような5段のトレースデータが表示されれば成功です。

zipkin2.png

それぞれのトレースデータは以下を表します。

収集箇所説明収集手段
1段目クライアントstartSpan()で作成したSpanの所要時間手動
2段目クライアントサーバ1に対するhttp getのリクエスト送信〜レスポンス受信までの所要時間自動
3段目サーバ1リクエスト受信〜レスポンス送信までの所要時間自動
4段目クライアントサーバ2に対するhttp getのリクエスト送信〜レスポンス受信までの所要時間自動
5段目サーバ2リクエスト受信〜レスポンス送信までの所要時間自動

収集手段=自動は、Instrumentationが自動収集トレースデータです。

# 4. サーバ側の手動収集Spanの作成

オマケとして、サーバ側でも手動トレース収集を行ってみます。

api-server1
'use strict';consttracer=require('./tracer')('api-server1');constapi=require('@opentelemetry/api');constexpress=require('express');constapp=express();constPORT=8180;constdata={name:"orange",price:200};asyncfunctionsetupRoutes(){app.use(express.json());app.get('/api/price',async(req,res)=>{+ constspan=tracer.startSpan('processing',{+ kind:api.SpanKind.SERVER+ });setTimeout(()=>{+ span.end();res.json(data);},2000);});}setupRoutes().then(()=>{app.listen(PORT);console.log(`Listening on http://localhost:${PORT}`);});

サーバ側でstartSpan()すると、クライアントから伝搬されたトレースデータに新たなSpanが追加され、トレース測定が開始します。
測定をやめるタイミングで、span.end()します。

Zipkinで確認すると、processingという名前で手動収集したトレースデータが追加されているのがわかります。

zipkin3.png

おわりに

少ない手順で解析に有用なトレースデータを収集できるのはとても便利です。
ただ、使用するパッケージのバージョンによって手続きが異なっていて、少しでもズレた手続きをすると収集がうまくいかなくなるなど、成功パターンを編み出すのに結構時間がかかりました。
今後のバージョンアップで、もう少し使い方に自由度が出るといいなぁと願っています。

LINEBotのリッチメニューエディタがないので自作した

$
0
0

LINEBotで、以下のようなリッチメニューを表示します。

image.png

リッチメニューは、トーク画面に表示されるメニューで、自分で自由に画像を作成できます。
HTMLでいうところのクリッカブルマップに相当し、自作する画像ファイルの範囲内で、四角形で範囲を指定し、そこをタッチしたときに特定の動作を行うことができます。
特定の動作として以下が可能です。

  • ポストバックアクション
  • メッセージアクション
  • URIアクション
  • 日時選択アクション

(参考) LINEリファレンス:リッチメニュー
https://developers.line.biz/ja/reference/messaging-api/#rich-menu

ソースコード一式をGitHubに上げておきました。

poruruba/LinebotRichmenu
 https://github.com/poruruba/LinebotRichmenu

トーク画面へのリッチメニューの設定

リッチメニューは、以下の3種類の設定箇所があり、いずれにも設定した場合、上の方が優先度が高いです。

  • Messaging APIで設定するユーザー単位のリッチメニュー
  • Messaging APIで設定するデフォルトのリッチメニュー
  • LINE Official Account Managerで設定するデフォルトのリッチメニュー

3つ目は、LINE Developersのページから設定可能です。

LINE Official Account Manager
 https://manager.line.biz/

アカウントを選択し、メッセージアイテム→リッチメッセージと選択すると、管理画面が表示されます。ただし、テンプレートが少なかったり、選択範囲が固定されていたりと、使いにくかったので、今回は使いません。今回は、前者2つのMessaging APIで設定するリッチメニューを扱います。

(i) Messaging APIで設定するデフォルトのリッチメニュー
これが一番簡単で、最初に一回だけAPIを呼び出せば完了です。設定したいリッチメニューの識別子(後述)を設定します。
すべての友達に同じリッチメニューが表示されます。

api\controllers\linebot-richmenu\index.js
app.client.setDefaultRichMenu(richmenu_id);

app.clientは、LINE Bot用の自作のライブラリ「line-utils.js」です。内部で以下のnpmモジュールを使っています。

line/line-bot-sdk-nodejs
 https://github.com/line/line-bot-sdk-nodejs

(ii) Messaging APIで設定するユーザー単位のリッチメニュー
ユーザごとに異なるリッチメニューを表示します。

api\controllers\linebot-richmenu\index.js
awaitclient.linkRichMenuToUser(event.source.userId,richmenu_id);

例えば、友達になったときに呼び出せばよいでしょう。
以下のような感じです。

api\controllers\linebot-richmenu\index.js
app.follow(async(event,client)=>{awaitclient.linkRichMenuToUser(event.source.userId,richmenu_id);});

以上により、トーク画面でリッチメニューが表示されるようになります。

リッチメニューの作成

で、じゃあリッチメニューはどうやって作成して、どうやってリッチメニューのIDを取得するのか。
それには、HTTP Post等で、設定します。

(参考) LINE Messaging APIリファレンス:リッチメニュー
https://developers.line.biz/ja/reference/messaging-api/#rich-menu

1点注意がありまして、CORS対応が必要なため、ブラウザのJavascriptから直接は呼び出せません。そこで、Node.jsサーバを立ち上げて、Javascriptからの要求を受け取って登録を行うようにします。

Node.js側での実装は、linebot sdkのnpmモジュールですでに関数化されているためそれを呼び出せばよいだけです。

①createRichMenu(richMenu: RichMenu): Promise
②setRichMenuImage(richMenuId: string, data: Buffer | Readable, contentType?: string): Promise

※他のAPIは以下を参照してください。
https://github.com/line/line-bot-sdk-nodejs/blob/next/docs/api-reference/client.md

①で、クリッカブルの位置情報を含めてリッチメニューとして登録し、リッチメニューIDを取得します。
ただし、このままでは画像ファイルは登録されていないため、続けて②で画像ファイルを登録して完了です。

ちなみに、一度登録したら変更できないため、修正後の新しいリッチメニューおよび画像ファイルを登録したのち、古いリッチメニューおよび画像ファイルを削除することになります。

画像ファイルのフォーマット(JpgかPNGか)を判別するためとStream操作のために、以下のnpmモジュールも使っています。

sindresorhus /file-type
 https://github.com/sindresorhus/file-type

paulja/memory-streams-js
 https://github.com/paulja/memory-streams-js

以下の部分を、環境に合わせて設定してください。LINE Developersコンソールから確認できます。(LINE Developers コンソール: https://developers.line.biz/ )

api\controllers\linebot-richmenu\index.js
constconfig={channelAccessToken:'【チャネルアクセストークン(長期)】',channelSecret:'【チャネルシークレット】',};

リッチメニューのクリッカブルマップに相当する部分は、以下に示すJSONフォーマットである必要があります。

Messaging APIリファレンス:リッチメニューオブジェクト
 https://developers.line.biz/ja/reference/messaging-api/#rich-menu-object

もろもろのリッチメニュー作成・登録作業をWebページから行えるようにエディタを作成しておきました。

image.png

画像ファイルをアップロードして可視化し、位置情報とアクション内容を追加して、最後にセットで登録することができます。登録が完了すると、リッチメニューIDが払い出されるので、それをLINEBotサーバに設定しましょう。

クリッカブルマップのように、X座標とY座標と、幅と高さを指定し、そこにクリックされたときのアクションを割り当てます。以下のアクションを選択して設定します。
・ポストバックアクション
・メッセージアクション
・URIアクション
・日時選択サクション
入力した範囲(X座標とY座標と幅と高さ)は、画像ファイル内に四角枠で示すようにしています。

また、何のリッチメニューを登録したか忘れてしまうので、すでに登録済みのリッチメニューや画像ファイルを参照できるようにしておきました。また、すでに登録済みのリッチメニューから複製して編集登録できるようにもしておきました。

以下の部分は、立ち上げたNode.jsサーバのURLに変えてください。

public\linebot-richmenu-edit\js\start.js
constbase_url="【Node.jsサーバのURL】";

終わりに

LINEBotに関する投稿に関しては、以下も参考にしてください。
 LINEボットを立ち上げるまで。LINEビーコンも。

以上

【書きかけ】Lambdaからクリアテキスト署名メールを送ろうとして2ヶ月かかった話

$
0
0

SMIMEでメールを暗号化して送信していましたが、SMIMEに対応していないメーラーだと本文が読めないので クリアテキスト署名という形式で送ることになりました。
やり方が全然わからずプレッシャーに押しつぶされそうになりながらもなんとか解決できた経緯を書いていきます。

環境

  • Lambda(Node.js 12.x)
  • SES

nodeのバージョンを切り替える(複数管理する)

$
0
0

始める前に

※対象はMacになります
流れとしては
・nodebrewのインストール
・環境変数の設定
・nodeのインストール
・nodeのバージョンの切り替え
となります。

nodebrewのインストール

brew install nodebrew
nodebrew -v

バージョンが表示されればOK

環境変数の設定

で設定ファイルを開きます

vi ~/.bash_profile

以下の1行を追加

export PATH=$HOME/.nodebrew/current/bin:$PATH

設定を反映させる

source ~/.bash_profile

セットアップ

nodebrew setup

nodeのインストール

インストール可能なバージョンを確認

nodebrew ls-remote
nodebrew install-binary <version>

nodebrew installでもインストールできますが、上記のコマンドの方が早い

複数のバージョンをインストールできます

ちなみに<version>には以下の表記が使用できる
・v12.16.3
・12.16.3
・stable (安定版)
・latest (最新版)

以下のコマンドで現在インストールしているnodeのバージョンを確認できる

nodebrew list

nodeのバージョンの切り替え

nodebrew use <version>
node -v

バージョンが表示されればOK

【Vue.js】フロントエンド開発にDockerを使う。つらみもあるよ

$
0
0

今までローカルでフロントエンド開発やってたけど、Docker使ってみるか〜。

でもDocker難しくてわからないよ〜〜〜〜〜〜〜!!!!!!

対象読者はvue-cli開発経験のある方です。
最終的なコードはこちら(github)

なぜDockerを使うのか

バックエンドの人たちがDocker信者すぎてフロントエンド開発者の肩身が狭いから

「環境を揃えたいよね」というフワッとした動機から。
実際使ってみて今のところメリットは感じていないですが、vue-cliのバージョンを固定したりするのは追々嬉しいことになるのかも、と思います。未来への投資だと思ってやっていきます。

「Dockerでフロントエンド開発する」とは

どこまでDockerに担わせるか、というところですが、調べた感じだと「実行環境」をDockerで構築するのがベターみたいですね。
image.png
クジラの写真なかった。

vueをDockerで動かすまでの手順

vue-cliプロジェクトの作成

ローカル環境でプロジェクトを作成します。
ローカルの環境はこんな感じです。新規プロジェクトを始めるときに慌てて最新バージョンにしたりしてます。

$ node --version
v14.15.5
$ npm --version
6.14.11 
$ vue --version
@vue/cli 4.5.11

あとはいつも通り

$ vue create docker-vue
$ ls -a
.                 .gitignore        node_modules      public
..                README.md         package-lock.json src
.git              babel.config.js   package.json

Dokcerfileを作成

プロジェクトディレクトリに作ります

Dockerfile
FROM node:14.15.5-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN apk update \
    && npm install -g npm@6.14.11 @vue/cli@4.5.11 \
    && npm install

FROMでnodeイメージのバージョンを指定します。今回はローカルと同じにします。
WORKDIRはDockerコンテナ内でのプロジェクトディレクトリです。なんでもよいです。
COPYでpackage.jsonとpackage-lock.jsonをWORKDIRにコピーします。
RUNでパッケージ等インストールし、環境構築します。ここでnpmとvue-cliのバージョンを固定します。

docker-compose.ymlの作成

docker-compose.yml
version: '3'
services:
  app:
    container_name: docker-vue-test
    build: .
    ports:
      - 8080:8080
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    stdin_open: true
    tty: true
    command: "npm run serve"

コンテナ名をdocker-vue-testと敢えてつけてます。あとで使います。
最後npm run serveを命令することで、コンテナ起動と同時にvueサーバーを起動させます。

動作確認する

まずDockerイメージをビルドします。

$ docker-compose build

そしてコンテナを起動。

$ docker-compose up -d

http://localhost:8080/にアクセスすると、いつものVueの画面が表示されます。

(任意)ポートを変える

ここまででDockerで実行環境を作るという目標は達成しましたが、
8080ポートは何かと他のプロジェクトでも使ったりするので、明示的にポートを指定してみます。

vue.config.jsの作成

まず、vue-cliをどのポートを使って起動させるか指定します。
プロジェクトディレクトリにvue.config.jsを作成します。

vue.config.js
module.exports = {
  devServer: {
    port: 9000,     # 好きな数字にする
    host: '0.0.0.0',
    disableHostCheck: true,
  },
};

docker-compose.ymlの編集

docker-compose.ymlのportsを、先ほど指定した数字に合わせて修正します。

    ports:
      - 9000:9000

Docker再起動

$ docker-compose stop
$ docker-compose up -d

今度は http://localhost:9000/で画面が確認できます。

所感

良いところ

いつも通りローカル開発でき、ホットリロードで画面に反映されます。普通です。

うーん…ってなったところ

ホットリロードしなくなった時、ローカル実行ならブラウザのキャッシュをクリアすれば大体治っていたのですが、
Dockerで実行してるとキャッシュを消しても治らないことがあります。
そういう時はコンテナをstop/startするか、全然関係ない箇所をいじったりすると治りました。ここはちょっとよくわからない…

また、追加でライブラリなどを入れる時はコンテナ側にインストールする必要があります。

$ docker exec -it docker-vue-test sh
/usr/src/app # npm install hogehoge...

さいごに

開発初期段階ではまだ「Dockerにして幸せだなあ」と思うことはないです。

ただ手順もそんなに多くはないですし、git cloneしたあとに
以前はnodebrewでnodeバージョンを変えてnpm installしてrun serveして…という手数を踏んでいたところが
Dockerのイメージビルド、コンテナ起動だけになるのは少しシンプルになって小気味好いかもしれませんね。知らんけど。

Dockerに頼りきりにならず、中で何をしているか理解することもとても重要だと思っているので、
それを心に留めつつ今後もDockerと仲良くしていきたいと思います。

参考記事

ローカルを汚さずdockerを使ってvue.jsの開発環境を作る[vuecli4]

Viewing all 8960 articles
Browse latest View live