Site cover image

mat2uken-blog

going further, being lazy

P2Pファイル転送アプリを作った【Ponlet】

相変わらず、ブログ記事をコンスタントに書くということはできないが、なんとなく作っているのがまとまったので紹介記事を書いてみる。

ざっくりまとめ

Ponletと名付けたP2Pファイル転送ツールを作った。アカウント連携などなくとも、匿名のまま、ブラウザがあればファイルとメッセージをやりとりできる。

とりあえず、公開しているのは、Web版とiOS版。macOSは審査中、Windwosはストアで公開するのが良さそうだけでやり方まだわかってないのでそのうちという感じ。

とりあえず、手軽なWeb版は下記。

Web: https://ponlet.mat2uken.app/

iOS: https://apps.apple.com/jp/app/ponlet/id6809104715

Androidはベータテスターが12人必要というので、だれかやってくれる人がいればということでもし興味があればこちらから。

https://play.google.com/apps/testing/jp.yasagure.ponlet


特徴は、

Image in a image block
  • P2P+暗号通信が基本(Wireguard → WebRTC → リレー(DERP)という形でフォールバックする、ただし、WireguardはWeb版非対応、iOS/Androidなどのアプリのみ)即座にファイルやメッセージを送ってその場で通信終わり、保存もしない。
  • QRコードを読むだけで接続完了まで自動的に動作するので、数タップでファイルを送ることができる
  • インターネットにつながっていればOK、同じWi-FiとかLANであるとかの条件もなし(リレーだと遅いし、TailscaleのDERPが使われてるのでいつまで使えるかは謎だが)
  • Web版に関してもあくまで静的ページ、サーバ側には通信内容などもなにも見えない。(Firebase Analyticsは仕込んでる&DERPによるリレーのときはE2E暗号化されたデータは通る)
  • iOS版では、共有シートから送信できる、アプリを起動する必要がないのでさらに一手間減る

以上。

一時的に、相手にファイルやURLなどを渡したいときや自分のPCとスマホ間でさらっとファイルなどを渡したいときに使います。AirDropでええやんという感じのツールだが、アカウントが一致してないiPhoneとmacOSってAirDropダルいんですよとか、iOSからWindowsにファイルを送るのがダルすぎる、といったのが作った動機。

iOS/Android/Windows/macOSすべてで動作するようにTauri + Web版はWASMでtailcat部分を実現しているので、アプリ版も配布はしたいのだが、それぞれ手間がかかるので、とりあえずiOS版のみ配布している。iOSアプリは、とりあえず、有料設定にはしているが、とりあえず無料。そのうち、500円くらいで売るくらいで手間賃回収できると嬉しい。Web版もstatic pageとはいえ、アクセスが増えると金かかるし。

というわけで、無駄な前置きから

ここからは単なるダラダラ書いた文章なので、読みたい人だけというやつで。

世間の流れは早いもので、前回のときは、Claude Codeで書ける人ってまだまだ限られるよねということを書いた気がするが、世間にはソフトウェアエンジニアじゃなくてもソフトを世に出す人も増えていて、AI界隈、ほんとうに動きが早いなぁと思いながら、自分も最近はCodexとOpenCode Goを中心に延々といろいろなものを作っては捨て、作っては捨て、という感じで、最早ほんとうにコードをほぼ手では書かなくなった。仕事ではまだレビューもするし、多少コードも書くのだが、それ以外ではほぼ書かない、書こうという気も起きないという感じ。仕事でコード書かなくなる日は意外と遠そうな会社文化ではあるが、まあ、そもそもお前、マネージャー職だろというのもあるので、むしろ、打ち合わせと、エージェントとのやりとりを並行でできるようになって、いろいろ楽しく作っている感じ。エンジニアリングマネージャー的な職がなくなるのかと言われると、個人的にはあまり心配はしていない、今のところは。それより、経営企画とかマーケの一部業務とか経理、財務とかのほうが先じゃろと思ったりしている、そっちのほうが業界的に感度が低いだけで。

とまあ、そういうのは置いといて、作業的には、延々と設計をChatGPT GPT-6 Proでやって、ドキュメントにして、Codexにもっていって、Astra Maxあたりでさらに煮詰めて、あとはLuna Maxでコード書かせるという世間でも多そうな流れ。その後のテストや実機での検証なんかも事前に整えておいて、ほぼなにも触らずに検証が回るようにというのも大体整えている。

といってもまあ、実際に、検証後に触るとぼろぼろ問題は残っているし、勝手にさぼって実は実装されてないとか、機能はあってもUIと接続されてないみたいなのはいくらでも起こるが、まあそれも含めて、また、延々とやりとりして直せばいいやというので、ちょっとした修正もプロンプトで、という感じになってしまった。

元々はCodexで大体完結していたのだが、Astraのトークン消費が激しすぎるのと、設計時はChatGPT Proはやはりほしいところはあるのだが、それ以降のドキュメント読ませて調整して、コード書かせて、テストして、というところはCodexのGPT系のお高いモデルじゃなくてもいいのかなぁという気持ちで、主に、DeepSeek V4.1 FlashやMuse Spark 1.3などでも、過不足なく対応できている印象で、そっちに移ってもいいかなぁというのが、2026/09時点の気持ちではある。

追記: といって、この記事を公開せずに、10月を迎え、DevDay後のCodexは、GPT 6.1 Solであればトークン消費は非常にすくなく、コーディングはSol、検証はLunaであれば、x20で充分に足りるようになった。本当に状況がコロコロ変わる。でもまあ、x5にして、DeepSeekとかと組み合わせようかなぁという気持ちは強くなってきてる、dotが便利なのでx5は維持かなと思ってるけど。

これを考えると、ローカルLLMでDeepSeekあたりが安定して動くのなら興味あるなぁと思いつつも、DGX Spark x 2か3かみたいなのを見ると、さすがに300万円クラスかぁという気持ちになって今のところまだ手を出していない。ChatGPT Pro x20+αの月3-4万円で足りないということはほぼないので、1-2年のうちにもうちょっと安くローカルLLMが実現されていることを期待して、まだクラウド&サブスクで、という気持ちで居る。ただ、最近は時間が経っても安くならないパターンも多いので、来年になったらサブスクの価格が爆上がりしてるとかありそうで怖くはある。

とまあ、そういう感じで、いろいろ細かいツールや自分専用サービスみたいなものを作ったりしていたのだが、その中で割と便利にうごいているので、公開してみるか、とおもったのがこのPonlet。

もともと、会社Macで自分としてはあるあるなのが、自分の私物のiPhoneでスクリーンショットや写真を撮ったときに会社Macに送り込みたいのだが、AirDropはアカウントが違うので割と一手間かかる上に、おま環かもしれないが、ちょいちょい検出できずにイラッとすることがあった。会社のiPhoneで写真とかも撮ればよいといえばよいのだが、デバッグ時に画面の内容を撮ったりとかでは、ついつい便利で手元にすぐある自分のiPhone Airをつかいがちとか、あとは、iOS → Windowsのファイル転送が、Windowsにはスマートフォン連携というやつがあるだろと言われるのだが、あれの操作方法いまいちピンとこない&iPhone Airだとよくつながってなくて、まずはつなぐところからはじめることになる、というあたりで、いつも大体、NASに一回ファイル保存して、それをWindowsやmacOSからエクスプローラーやFinderから開くというような手間をかけがちがった、というのが問題の出発点。

加えて、会社業務でありがちなのが、検証用端末という存在。iOS/Androidのアプリを試すときに、検証作業は集中管理されている端末を借りて、操作はワンタイムなので、できればアカウントログインは最低限にしたい&できればあまりアプリは入れたくない(特に、Andridは、いわゆるAirDrop的な使い勝手のために、LocalSendというやつを入れることが多かったが、それをダウンロードするためにGoogle Playを使えるようにするのがダルい、下手にGoogleアカウントログインするといろいろ同期されてちゃんと消さないと恥ずかしい、MDMの関係で全部リセットもできないし)というのもこれを作った動機。とりあえず、ブラウザさえあればファイルを送受信できるというのが欲しかった。

というあたりで、どっかでツール整備したいなぁと思っていて、WebRTCは慣れているので、それでファイル転送ツールでもつくって、リレーはCloudflareあたりのTURNサーバに対応するかなぁというようなことを考えていた。ただ、シグナリングサーバも用意しないといけないし、メンテも考えると自分専用がせいぜいだし、それでもシグナリングまわりつくってworkersかなにかでwebsocketとかかなぁ、ちょっとめんどくさいな、というので、実際につくるところまでは踏み込んでいなかった、という感じだったのだが、先日、Tailscaleがtailcatというツールを公開しているのを知って、この使い勝手いいなー、これをそのままGUIでラップして使えるようにしようかなーと思ったところで、上記の解決もついでにできればよいのでは、となって実際につくりはじめた感じ

Tailscaleは知ってる人も多そうだけど、WireguardをベースにしたP2Pのメッシュネットワーク?VPN?のサービスで、私の日常生活ではもはやなくてはならない存在であって、自分では本当にそこら中で使っているんだけど、基本的に自分のアカウントにデバイスを登録するという流れで、且つ、そこではログインを要求されるので、上記のような検証端末での要求には合わない、というサービスではあった。

それに対して、tailcatはコマンドラインでの動作ではあるが、アカウントは必要とせず、DERPをつかったやりとりをベースにして、ユニークな識別子を発行、それをアドレス的に指定すれば対向からはネットワークの状況を意識せずにつながりますという、tailscaleのWireguard P2P + DERPリレーという部分だけを取り出したようなコマンドになっている。実際使ってみると、これはいいなーお手軽で便利、となったので、GUIを被せればスマホでも使えるかなとおもって試しはじめたところで、上記のような検証端末での接続やAirDropの代替としていけるのでは、となってちょっとずつ作って使い勝手をいろいろと試行錯誤しているのが現在、というところ。

なので、ベースはtailcatであり、通信部分はほぼ自分では作っていない。tailcatのリポジトリからコードをもらってきて、それをgoのdaemonにするか、ブラウザではwasm化してそれを叩くというのを基本にしている。(ただし、WebRTCのDatachannelによる通信とそれにともなうDERPをつかったシグナリングの対応は現在まだマージされていないので、PRを参照して勝手にチェリーピックしてマージした版を使っている)

tailcatからDERPを使うのは説明を読む限り、無保証ではあるが、特に使ってはいけないとも書いてないので、とりあえずはそのまま組み込んでいる。アプリでは近いDERPを選んだり、Web版ではAPIが近いものを並べ替えてくれるというのがtailcatにも組み込まれているようだったので、たぶん適度に自分に近いDERPが勝手に選ばれるはず。

という相変わらず長い長い前置きで、tailcatのdaemon化+WASM化をして、GUIを被せたもの、というのがPonletの正体である、という話。

技術スタックの話

大体、これで書きたいものは書いた気がしたが、最後に、せっかくなのでということでいろいろ試した技術スタックの話を書いておこうとおもう。

今回のアプリは、iOS/Android/Windows/macOS/Webと5プラットフォームすべてに対応している(公開するのはいまのところ、iOSとWebだけかもだけど)

というわけで、このすべてに対応できるようにということで、tailcat部分は、Goで書かれているので、daemonということで別プロセス化できるwin/macなどはそうして、iOS/AndroidはGoの部分をライブラリ的にして、RustからFFIで叩けるようにCでラップしている。そして、ブラウザはWASM化はtailscaleではすでにある程度実績があるのか、いろんな参考資料があって割とさくっと対応できた。

問題はUIの部分で、全プラットフォーム対応で、なにを使うか迷ったが、別件でslintが結構いい感じなのでは、という話があったので、最初はslintで書いてみることにした。

slintはRustで書けるDeclativeなUIフレームワークで、iOS/Android/Windows/macOS/Web(WASM)という範囲であればライセンス的にも緩く、今回は、ソース公開してもいいので、OSSであればなんの不都合もなさそうということではじめた。

実際、Rustはまあ慣れてはいるので、生成されたコードを眺めたりレビューしながら順調にGUIはできあがり、それなりに動くものになった、が、特にブラウザでの動作で、当然だが、HTML/JSは最小限にして、Canvas描画+WASMでのtailcatの連携を作っていくのだが、メインループを暴走させてまったく操作できなくなったり、日本語表示のためにはフォント組み込みが必要(そりゃそうだ)とか、テキストボックスで日本語入力しようとIME有効にして入力すると、なんかあるなるな不具合の(「a」と打つと、「aあ」となる)みたいな多バイト環境苦手な子みたいな挙動があったりで、正直、UI調整するのがダルい気持ちでいっぱいになってしまい、1週間くらい触るのをやめてしまった。

それでちょっとモチベーションがさがってしまっていたが、一念発起、つまらない打ち合わせの最中に、思い切ってUIはブラウザで一番不都合がないように、WebViewにしてしまうかぁというので、全面的にTauriに移行しつつ、ブラウザでは同様のI/FをWASM側で実現してUIを共通化する方向で、ガッと書き直すことにして、ChatGPT Proと延々とどういう構成がいいかをやりとりして、まるっと書き直しさせて、HTML/CSS/JSでのUIへ刷新したのが今の形。

といっても、Web UI部分のスタックも、仕事ではReact+Reduxみたいなのをベースに割と重厚なというか、一般的でそれなりにキャッチアップしやすく、みたいなところを意識した構成にするのだが、個人的且つどれだけ遊んでもAIがなんとかしてくれる、ということで、今UIはいかにバンドルサイズを小さくするか、という方向に振った。

そのために、いろいろChatGPTやりとりした結果、VanJSが比較的良さそうということになり、VanJS+その他の部分もできるかぎり依存を減らして、CSSなどはなにかフレームワークは導入せず、デザインも最低限にというコンセプトで構築した。数KBくらいでロードできるので満足。まあ、結局、WASMがでかいんであんまり意味ないんだけど。

Tauriは、WASMとの連携I/Fまでを提供してくれるわけではないので、そこは別々のレイヤー構造としつつ、adapterとして抽象化した。なので、Web側もロジックや状態管理といったところはJSでは実装しておらず、TauriでつくったRustのコードをWASMにして共用している。

また、WebViewとnativeのところは、レイテンシが大きいなぁとおもっていろいろ試したところ、カスタムschemeにしてPOSTするほうが速かったとかあったので、あんまりなにかが改善するわけでもないけど、Tauri invokeにfallbackするまえに自作の通信機構を使うようにした。

 Web
------
 VanJS + TypeScript
         ↓
 browser adapter
         ↓ MessageChannel / Binary RPC
 Dedicated Worker
   ├ Rust WASM: state、handshake、transfer、OPFS
   └ Go Tailcat bridge → WebRTC / DERP


Tauri
------
VanJS + TypeScript
        ↓
tauri adapter
        ↓
Android binary port
   → custom scheme
   → JSON + Tauri invoke fallback
        ↓
Rust/Tauri native service

上記のような構成で、割と純粋なUI部分はHTML+CSS+JSで書く、ロジックに切り離せるところはRustでWASM化、tailcatはWASM化 or daemon化で疎結合、という流れができあがった。マルチプラットフォームで開発といえば、FlutterやReactNativeなどが思い浮かぶところだが、WebView(+WASM)+Rustという組み合わせは意外と悪くないんじゃないかなというのが作ってみた感想。RustはC/C++のABIも割と簡単に叩けるので、仕事だとオーディオやビデオの処理など、そもそもC++以外で書ける気がしないとか、過去の資産の流用がというのとも相性がよいのがいい。

https://github.com/mat2uken/tailcatsend
ソースはまるっと公開している、READMEとかはめんどくさいのでAI丸出しだが、まあ別にいいかなということで。

最後に

というわけで、いろいろ細かいものを作り捨てているのが現状ではあるが、めずらしくある程度公開できそうな目処が立ったのでこちらは公開してみることにした。

ほかにもハード絡むものとかもいくつか作ってみたりしているのだがこちらが世に出るのはもうちょっと先かなぁという感じ。AIのおかげで、自分のモチベーションが最大の障壁であり、どうやってやる気を出すかに、仕事もプライベートも課題を感じている今日この頃。