JOURNAL

Device Bridgeを作った話 — QRコードで端末間にメモを届ける、小さな橋

スマホで書いたメモを、そのままパソコンでも見たい。それだけのために、わざわざアカウント作ってクラウド同期を設定するのって、なんか大げさじゃないですか。自分はそう感じるタイプで、Device Bridgeはその小さなイライラを解消するために作りました。

仕組みは単純で、片方の端末でメモを書いて、表示されたQRコードをもう片方の端末で読み取るだけ。アカウント作成もアプリのインストールも要らず、ブラウザだけで完結します。

QRコードって最近ちょっと地味な存在になった気がしますが、こういう「端末をまたぐ最短ルート」としては未だに最強だと思っています。

保存したメモは作成から72時間で自動的に消えるようにしています。恒久的なメモ帳にする気は最初からなくて、「今すぐこれをもう1台に渡したい」という一瞬のためだけの道具に振り切ることで、機能をシンプルに保っています。

パスワードが必要な場合には「パスワード(任意)」欄に入力します。

保存してQR発行ボタンを押します。

Device Bridgeの概要
▲ Device Bridgeの画面

専用のQRコードが生成されますので、こちらをスマホのカメラで読み取ってください。

Device Bridgeの概要
▲ 生成されたQRコードを読み取ってください
Device Bridgeの概要
▲ スマホ画面にメモが表示されます

メモの内容はスマホからコピーして利用できるほか、その場で編集も可能です。もちろん編集して保存するとPC側にも編集内容が反映されます。

その他、メモ帳などで開ける、「テキスト保存」のほか、「PDFで保存」も可能です。(※PDFへの保存は、各端末のプリンターのPDF保存を選択してください)

  • 保存期間は72時間限定(自動削除)
  • パスワードは任意設定、平文保存はせずハッシュ化
  • アカウント登録・アプリインストール不要

小さくてカジュアルな道具だからこそ、ここだけは手を抜きたくなかった部分です。

QRコードに載せているのは、メモそのものではない

QRコードを読み取ると出てくるのは、メモの中身が直接埋め込まれた画面ではなく、サーバーに保存したメモを表示するためのURLです。QRコードにメモ本文をそのまま詰め込むと、長文になった瞬間にコードがびっしり複雑な模様になって読み取り精度が落ちるので、コード自体には短いURLだけを持たせて、本文はそのURL先で表示する形にしています。

72時間で消える仕組みと、毎朝届く律儀な通知

保存したメモの情報はDBに持たせていて、期限が来たものをCronで毎日早朝に削除するようにしています。削除処理が終わるたびに自分宛てに通知が届くんですが、届くのはだいたいこんな、ちょっと味気ない一文だけです。

Content-type: text/html; charset=UTF-8
2026-07-04 04:00:10 cleanup done. deleted=25

この通知だけ見ていると「今日も特に何もなかったな」ぐらいの感想しか湧かないんですが、裏を返せば毎朝Cronがちゃんと動いて、削除処理がエラーなく完走してる証拠でもあるので、安心材料になっています。

パスワードは平文で持たない

任意設定のパスワードは、PHP標準のpassword_hash()password_verify()でハッシュ化して保存しています。自前でハッシュアルゴリズムを実装する理由はどこにもないので、ここは素直に標準関数に乗りました。

$passwordHash = password_hash($password, PASSWORD_DEFAULT);
// ...
password_verify($password, $note['password_hash']);

作りながら踏んだ、小さな地雷たち

作ってる最中に踏んだトラブルを、備忘録も兼ねて残しておきます。

起きたこと原因対応
固定QR発行時にSQLSTATE[HY093]: Invalid parameter number1つのSQL文で同じ名前のプレースホルダ(:now)を2回使っていたプレースホルダ名を:created_at:updated_atに分離
ローカルDockerでQR画像が表示されないベースイメージにPNG生成用のGD拡張が入っていなかったDockerfileにlibpng-dev等とdocker-php-ext-install gdを追加
composer installがzip関連のエラーで止まるコンテナ内にunzipコマンドが無かったDockerfileにunzipパッケージを追加
composer: not foundベースイメージにComposer自体が同梱されていなかった公式composer:2イメージからバイナリだけをコピーする多段階ビルドに変更
本番Cronが動かない(php: command not found)CoreServerのcron実行環境でphpコマンドにPATHが通っていなかったcleanup.sh内でphpをフルパス指定に変更

1つ1つは地味なんですが、これ全部踏んでからようやく普通に動くようになった、というのが正直なところです。

設計として大きく変えたのは、固定QR(マイQR)まわりのUIです。当初は「使い捨てQR」と「マイQR」を同じ画面に並べて表示する作りにしていたんですが、実際に使ってもらったら「QRが2つ出てきてどっちを読み取ればいいか分からない」という声をもらいました。それを受けて、保存前にラジオボタンで用途を選んでもらい、画面には常に1つのQRしか出さない方式に作り直しました。機能自体は削らず、入り口だけ整理する方向で解決した形です。

もう1点、PDF保存機能は当初jsPDFのような外部ライブラリでの生成も検討したんですが、この手のライブラリは標準で日本語フォントに対応しておらず、文字化けを避けるには追加のフォント埋め込みが必要で余計に複雑になります。なので、ブラウザ標準の印刷機能(印刷ダイアログの「PDFに保存」)をそのまま使う方式に落ち着きました(逃げたw)。外部ライブラリへの依存も、日本語フォントの心配も両方まとめて回避できたので、結果的にはこれが一番シンプルでした。これは本音です。。

気になる方は、是非一度使ってみてください。👇