2025年クソアプリアドベントカレンダー向けに、モールス信号でやりとりできるWebサイトを作りました。
まずは完成したものの動画です(ビープ音がでます)
技術スタックについて
- Vercel
- NextJSをデプロイします
- Supabase Realtime
- 最初は WebSocket + Railwayなども考えましたが、今回の用途ではより実装がシンプルで、コストが安いということで採用しました。ポチポチするだけで、Realtime通信のAPIを勝手に用意してくれます。
アプリは上のURLで公開されています。
なお無料枠の変更などで動かなくなる可能性があるため、動作の保証はいたしかねます。
画面設計
| 周波数入力画面 | 通話画面 |
|---|---|
![]() |
![]() |
最初はSkypeみたいなちゃんとしたものも考えましたが、簡単に使えて、工数もかからずに済むという観点から、
周波数(という名のただの数字)を合わせて入室する、メタルギアシリーズみたいなロマン仕様を採用しました。
そして、通話画面でももちろん操作が簡単であることを重視し、送信するボタンを1つ、受信はランプと音で行うようにしました。
結果2画面のシンプルな構成となっています。
Supabase周りの解説
ソースコードはこちらに公開しています。
app/call/[freq]/page.tsx のファイルのみで、信号の送受信やページの表示などをまとめて行っています。
Supabase RealTime の使い方について整理すると、以下のようになります。
# supabase-jsをnpmインストール
$ npm i @supabase/supabase-js
# .envにSupabaseのAPI Keyを設定してください
NEXT_PUBLIC_SUPABASE_URL=""
NEXT_PUBLIC_SUPABASE_ANON_KEY=""
クライアントからチャンネルを作成
// supabase.channel(...) で作った Realtime チャンネルのインスタンスを
// useRef で保持して、無駄な作り直しを防ぐ
const channel = supabase.channel(`room:${roomId}`);
channelRef.current = channel;
受信リスナーを登録
// payloadの関数が受診時の処理です。ランプを付けたり、受信音を出したり。
channel.on('broadcast', { event: 'key' }, (payload) => { ... });
購読開始
channel.subscribe();
イベント送信(broadcast)
// ボタンを押したり離したりするイベントから呼び出す。
channel.send({
type: 'broadcast',
event: 'key',
payload: { pressed, from: 'self' },
});
アンマウント時などにチャンネルを削除
supabase.removeChannel(channel);
セキュリティについて
チャンネル方式だと、チャンネル特定されたら、盗聴の危険があるよね?
と思ったそこのあなた、正解です。
クソアプリなのでアプリ側がユーザーを認証したり、暗号化することはありません。
盗聴が気になる方は、次に紹介する上杉暗号などを取り入れてみると良いと思います。
上杉暗号とは?
アニメ「Dr.STONE」にも登場する、数字と日本語を対応させた暗号文です。
「Dr.STONE」は、全人類が石化し文明を失った世界で、天才科学者である主人公が1から科学を復興させていくアニメです。
作中では、主人公の率いる日本チームと、別の天才科学者が率いるアメリカチームの戦いが繰り広げられますが、
日本チームではこの上杉暗号を使うことでアメリカチームへの情報漏洩を防いでいました。

上杉暗号は上記の表をつかって、列と行の数字2つで日本語を表します。
たとえば、6,1 は「あ」 となり、 1,1 は「い」になります。
まとめ
クソアプリを作ったおかげで、
NextJSとSupabase RealTimeの組み合わせを試すことができ、いい体験になりました。
Dr.STONEは面白いのでぜひ見てみてください。

