前回投稿の、FelicaのIDmを取得するEXEをAPI的に利用するために、node.jsで動くようにしてみました。
前提
node.js とnpmはインストール済み
PowerShell管理者実行
フォルダを準備
cd
mkdir idm-server
cd idm-server
準備
無難なexpressを使います。
コード変換はiconvで・・
npm init -y
npm install express iconv-lite
node.jsプログラム作成
notepad server.js
POSTで呼んで、結果は、JSONで出力する感じにしてみました。
実は、自分は、「メモ帳」がエディタな人なんですが、みなさまはなにかもっといいエディタを使っているのかな!?!?
// server.js
const express = require('express');
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs/promises');
const iconv = require('iconv-lite');
const app = express();
app.use(express.json({ limit: '32kb' }));
// ▼ 設定(環境に合わせて)
const EXE_PATH = path.resolve('./read_idm_felicalib.exe');
const EXEC_TIMEOUT_MS = 15000; // 15秒でタイムアウト(用途で調整)
const MAX_BUFFER_BYTES = 1 * 1024 * 1024; // 出力上限 1MB
// ▼ 簡易ミューテックス(直列化)
let lock = Promise.resolve();
const withLock = (fn) => {
const start = lock.then(() => fn());
lock = start.catch(() => {}); // エラーでも次に進める
return start;
};
// ▼ IDm抽出:16桁のHEXを拾う
function extractIDm(text) {
// 16進数が8バイト(16桁)並んでいるパターン(スペースまたはコロン区切り)
const re = /((?:[0-9A-Fa-f]{2}[\s:]){7}[0-9A-Fa-f]{2})|([0-9A-Fa-f]{16})/;
const m = text.match(re);
if (!m) return null;
const hex = (m[1] || m[2]).replace(/[\s:]/g, '').toUpperCase();
return hex.length === 16 ? hex : null;
}
// ▼ 実行関数(cp932→utf8、タイムアウト、上限付き)
async function runReader(args = []) {
await fs.access(EXE_PATH); // 無ければ throw
return new Promise((resolve, reject) => {
const child = spawn(EXE_PATH, args, {
cwd: path.dirname(EXE_PATH),
windowsHide: true,
shell: false,
stdio: ['ignore', 'pipe', 'pipe']
});
let outBufs = [];
let errBufs = [];
let outSize = 0, errSize = 0;
let finished = false;
const timer = setTimeout(() => {
if (!finished) {
finished = true;
child.kill('SIGKILL');
resolve({ ok: false, timeout: true, code: null, stdout: '', stderr: '' });
}
}, EXEC_TIMEOUT_MS);
const pushChunk = (bufs, sizeRef, chunk) => {
const size = sizeRef + chunk.length;
if (size <= MAX_BUFFER_BYTES) {
bufs.push(chunk);
return size;
}
return size; // 超過分は破棄
};
child.stdout.on('data', (chunk) => { outSize = pushChunk(outBufs, outSize, chunk); });
child.stderr.on('data', (chunk) => { errSize = pushChunk(errBufs, errSize, chunk); });
child.on('error', (err) => {
if (finished) return;
finished = true;
clearTimeout(timer);
reject(err);
});
child.on('close', (code, signal) => {
if (finished) return;
finished = true;
clearTimeout(timer);
// ▼ cp932としてデコード(日本語メッセージ想定)→ UTF-8文字列
const stdout = iconv.decode(Buffer.concat(outBufs), 'cp932');
const stderr = iconv.decode(Buffer.concat(errBufs), 'cp932');
resolve({ ok: code === 0, code, signal, stdout, stderr });
});
});
}
// ▼ API:IDmを1回読む(直列化)
app.post('/read-idm', async (req, res) => {
// 必要なら body にオプション(例: ポーリング系の引数)を受けてホワイトリスト
const args = Array.isArray(req.body?.args) ? req.body.args : [];
try {
const result = await withLock(() => runReader(args));
// タイムアウト
if (result.timeout) {
return res.status(504).json({ ok: false, error: 'timeout', message: 'タッチ待ちがタイムアウトしました' });
}
// IDm抽出
const idm = extractIDm(`${result.stdout}\n${result.stderr}`) || null;
// 成功扱いの条件:
// - 終了コード0 かつ IDm が抽出できた
if (result.ok && idm) {
return res.json({ ok: true, idm, raw: { code: result.code, stdout: result.stdout, stderr: result.stderr } });
}
// ここに来たら失敗(終了コード≠0 or IDmなし)
return res.status(500).json({
ok: false,
error: 'read_failed',
idm,
code: result.code,
stdout: result.stdout,
stderr: result.stderr
});
} catch (err) {
// EXEが見つからない/起動できない等
return res.status(500).json({ ok: false, error: 'spawn_error', message: String(err) });
}
});
app.listen(3000, () => {
console.log('FeliCa IDm API running on http://localhost:3000');
});
app.use(express.static('public'));
node.jsで実行
node server.js
これだと、powershell 上げっぱなしになるので、バックグラウンドで起動させるには、
forever start server.js
にしてください。
foreverが入っていなければ、
npm install -g forever
でインストールできます。
実行テスト
curl.exe -X POST http://localhost:3000/read-idm -H "Content-Type: application/json" -d "{}"
やったー。
{
"ok": true,
"idm": "XXXXXXXXXXXXXXXX",
"raw": {
"code": 0,
"stdout": "XX XX XX XX XX XX XX XX\r\n",
"stderr": ""
}
}
webでも試したいです。
今、idm-serverフォルダにいる前提で、publicにndex.htmlを作成します。
mkdir public
notepad index.html
↓public¥ondex/html
<!doctype html>
<meta charset="utf-8">
<title>FeliCa IDm Test</title>
<style>body{font:16px/1.6 system-ui;margin:24px}button{font-size:16px;padding:8px 16px}</style>
<h1>FeliCa IDm 読み取り</h1>
<button id="btn">タッチして読み取り</button>
<pre id="out"></pre>
<script>
const $btn = document.getElementById('btn');
const $out = document.getElementById('out');
$btn.onclick = async () => {
$btn.disabled = true; $out.textContent = 'カードをタッチしてください…';
try {
const r = await fetch('/read-idm', { method:'POST', headers:{'Content-Type':'application/json'}, body:'{}' });
const j = await r.json();
if (j.ok) {
const colon = (j.idm.match(/.{2}/g)||[]).join(':');
$out.textContent = `IDm: ${j.idm} (${colon})`;
} else {
$out.textContent = `失敗: ${j.error || 'unknown'}\ncode=${j.code}\nstdout=${j.stdout}\nstderr=${j.stderr}`;
}
} catch (e) {
$out.textContent = '通信エラー: ' + e;
} finally { $btn.disabled = false; }
};
</script>
ブラウザでテスト
http://localhost:3000/
完成
めでたしめでたし。
問題は、このあと、どう使うかなんですけどね・・