OmniP2P とは
OmniP2P は、ブラウザだけで完結する P2P 通信・永続化ライブラリです。 Nostr リレーをシグナリング/フォールバック搬送に、WebRTC を主搬送に、 Blossom / NIP-96 / IPFS ゲートウェイをコンテンツアドレス永続化ストレージに使い、 さらに E2EE (X25519 + AES-GCM) と CRDT 共同編集 (MiniCRDT, Yjs 互換) までを 単一 JS ファイルに詰め込んでいます。
サーバー不要・ビルド不要・依存パッケージ不要。
<script src="v2.js"> を書くだけで、
チャット・音声/動画通話・画面共有・録画保存・共同編集・KV データベースを備えたアプリを作れます。
主要機能
Nostr → WebRTC 自動移行
決定的イニシエータ選出 + ICE restart で確実にネゴシエート。P2P 確立後は超低遅延。
自動フォールバック
WebRTC が張れない相手には、Nostr ブロードキャストへ自動で切替え。必ずメッセージが届きます。
Blossom / NIP-96 永続化
コンテンツアドレスな KV + Blob DB。書き込みは 1 本高速、裏で非同期に多重冗長化。
音声/動画通話 & 録画
startCall() / shareScreen()、MediaRecorder で暗号化保存 + Nostr 告知。
E2EE 標準搭載
X25519 ECDH → HKDF → AES-GCM 256 (AAD)。保存前にも暗号化してからアップロード可能。
CRDT 共同編集
MiniCRDT (Yjs 互換シェイプ)。WebRTC で即時同期、Nostr で永続化。
プラガブル拡張
Yjs / Automerge / Matrix / GunDB / OrbitDB / Iris / Waku を 必要な時だけ 遅延ロード。
ゼロ依存 / ゼロビルド
純 JS 実装の secp256k1 (BIP-340 schnorr) + X25519。<script> だけで動く。
放置切断バグ対策
EOSE 応答実確認 heartbeat + 実イベント watchdog + スタガー再接続で瞬断ゼロ。
v2 での変更点 (WHAT'S NEW)
v2 ではコアを「軽量な通信・保存エンジン」に絞り、重い外部依存 (Gun / Matrix / Yjs / Waku 等) は 必要な時だけ CDN から遅延ロードする外部アダプタに分離しました。
🎯 レビュー全指摘 (#1〜#12) への対応
| # | 修正内容 |
|---|---|
| #1 | ハートビートは EOSE 応答を実確認してから「生存」とみなす (half-open 検知) |
| #2 | watchdog は アプリイベント受信のみでリセット (ハートビート応答では ×) |
| #3 | リレー再接続をスタガー + 「最低 1 本維持」フェイルセーフで瞬断ゼロ化 |
| #4 | Room.send のフォールバック判定を pc.open 実数ベースに統一 |
| #5 | SDP/ICE 等の重要イベントは minAck 複数 + ack 不足時の自動再送 |
| #6 | 保留 ICE 候補にリングバッファ上限 (重複配送の氾濫を抑制) |
| #7 | polite 側も restart タイムアウト後に re-offer 要求へフォールバック |
| #8 | RelayConnection と PeerConnection の再接続を状態機械で協調 (glare 低減) |
| #9 | DataChannel bufferedAmount バックプレッシャー流量制御 |
| #10 | 再接続時に since を「最後に受信した時刻」に更新し取りこぼし防止 |
| #11 | RTC が connected なら presence timeout だけでは切断しない (RTC 優先) |
| #12 | ハートビート専用サブ ID を再接続毎に再生成 |
インストール
1. CDN から (推奨)
<script src="https://cdn.jsdelivr.net/gh/2chkakinie-arch/OmniP2P@main/v2.js"></script>
ブラウザでは window.OmniP2P が使えるようになります。
2. Node / CommonJS
const OmniP2P = require('./v2.js');
3. AMD
UMD ラッパーが同梱されているため AMD ローダーからも define(['OmniP2P'], fn) で使えます。
4. ネイティブ ESM
ファイル末尾のコメントに従い、ラッパー経由で import OmniP2P from './v2.js' できます。
クイックスタート
これだけで、E2EE チャット + 音声/動画通話 + 永続保存 + 共同編集がすべて動きます。
<script src="https://cdn.jsdelivr.net/gh/2chkakinie-arch/OmniP2P@main/v2.js"></script>
<script>
(async () => {
// 1) インスタンス生成 (鍵は自動生成 or 既存の nsec/hex を渡す)
const omni = new OmniP2P({
relays: [
'wss://relay.damus.io',
'wss://nos.lol',
'wss://relay.nostr.band',
'wss://nostr.wine'
],
// secretKey: '<64-hex or nsec...>' // 省略で新規生成
});
await omni.start(); // リレー接続 + heartbeat 開始
// 2) ルームに参加 (合言葉ベース、E2EE 自動)
const room = await omni.join('my-secret-room', { password: 'p@ss' });
// 3) メッセージ (WebRTC 優先、ダメなら Nostr で必ず届く)
room.on('message', (msg, peer) => console.log('recv', msg, 'from', peer));
room.send({ hello: 'world' });
// 4) 音声/動画通話
const call = await room.startCall({ audio: true, video: true });
call.on('track', (stream, peer) => {
const v = document.createElement('video');
v.srcObject = stream; v.autoplay = true; document.body.appendChild(v);
});
// 画面共有に差し替え:
// await call.shareScreen();
// 録画して暗号化保存:
// const rec = call.record(); ... const url = await rec.stopAndStore();
// 5) 永続保存 (暗号化 KV / Blob DB)
const ref = await omni.storage.put('note:1', { text: 'hi' }); // -> 参照
const val = await omni.storage.get('note:1'); // -> {text:'hi'}
const url = await omni.storage.putBlob(fileBlob); // -> 公開URL
// 6) CRDT 共同編集
const doc = room.doc('shared'); // MiniCRDT (Yjs 互換 API)
const ymap = doc.getMap('state');
ymap.set('count', 1);
doc.on('update', () => console.log(ymap.toJSON()));
})();
</script>
OmniP2P を選ぶ理由
| 比較軸 | 従来スタック | OmniP2P v2 |
|---|---|---|
| 導入 | npm install × 数十、webpack 設定、TURN サーバー構築… | <script> 1 行、ビルド不要、サーバー不要 |
| シグナリング | 自前のシグナリングサーバーを運用 | 公開 Nostr リレーを利用、サーバーゼロ |
| 永続化 | DB + API + 認証 + オブジェクトストレージ… | Blossom / NIP-96 で storage.put/get/putBlob だけ |
| E2EE | ライブラリ選定 → 鍵交換設計 → 実装 → 監査… | デフォルト有効。X25519 + AES-GCM 256 + AAD |
| 放置切断 | WebSocket の half-open で気づかず沈黙 | EOSE 実確認 heartbeat + watchdog + スタガー再接続 |
| 共同編集 | Yjs + y-webrtc + y-indexeddb + プロバイダ… | room.doc() だけで CRDT。Yjs へも差し替え可能 |
| 依存管理 | 数百 MB の node_modules と脆弱性対応 | 単一ファイル、外部依存ゼロ |
Time-to-First-Message: 秒
ページを開いた瞬間から、他ピアと暗号化メッセージを送受信できます。
NAT 越えの徹底
WebRTC 失敗時は Nostr リレー経由の暗号化搬送に自動フォールバック。
拡張は自由
コアはミニマル、必要な機能だけ OmniP2P.loadAdapter() で足す。
API リファレンス
new OmniP2P(opts)
| オプション | 型 | 説明 |
|---|---|---|
opts.relays | string[] | Nostr リレー URL 群 |
opts.secretKey | string? | 秘密鍵 (hex / nsec)。省略で自動生成 |
opts.iceServers | RTCIceServer[]? | STUN / TURN |
opts.storage | object? | { blossom?:string[], nip96?:string[], ipfs?:string[] } |
opts.heartbeatMs | number? | default 20000 |
opts.watchdogMs | number? | default 55000 |
opts.debug | boolean? | 詳細ログ出力 |
omni.*
omni.start() // リレー接続 + heartbeat 開始
omni.stop() // 全接続を優雅にクローズ
omni.join(roomId, { password?, e2ee? }) // -> Room
omni.pubkey // string (hex) 自分の公開鍵
omni.storage // Storage (put/get/del/putBlob/getBlob/list)
omni.on(evt, cb) // 'ready' | 'relay:open' | 'relay:close' | 'error'
omni.off(evt, cb)
omni.emit(evt, ...args)
Room
room.send(data, { reliable?, to? }) // to 省略で全員へ
room.on('message' | 'peer:join' | 'peer:leave' | 'state', cb)
room.peers() // -> string[] (pubkey hex)
room.startCall(constraints) // -> Call
room.doc(name) // -> CRDT Doc
room.leave()
Call
call.localStream // 自分の MediaStream
call.on('track', (stream, peer) => {}) // 相手のトラック受信
call.shareScreen() // 画面共有へ差し替え
call.stopScreen()
call.replaceTrack(kind, track) // 'audio' | 'video'
call.mute(kind, bool)
call.record() // -> Recorder
// recorder.stopAndStore() // -> url (暗号化アップロード済み URL)
call.hangup()
Storage
storage.put(key, jsonable, { encrypt? }) // -> ref
storage.get(key) // -> value
storage.del(key)
storage.putBlob(blob|file, { encrypt?, name? }) // -> url
storage.getBlob(url, { key? }) // -> Blob
storage.list(prefix?) // -> [{key, ref, ts}]
CRDT Doc (MiniCRDT / Yjs 互換)
const doc = room.doc('shared');
const map = doc.getMap('state');
const arr = doc.getArray('list');
const txt = doc.getText('body');
map.set('count', 1);
arr.push(['hello']);
txt.insert(0, 'こんにちは');
doc.on('update', (update, origin) => { /* 増分パッチ */ });
doc.transact(() => { /* まとめて 1 update に */ });
本物の Yjs を使いたい場合は、先に yjs を読み込んで OmniP2P.use(OmniP2P.adapters.Yjs) するだけで差し替わります。
プラガブルアダプタ (差し替え / 増強)
コアはミニマルに保ちつつ、必要な機能だけをアダプタとして CDN から遅延ロードできます。 すべてのアダプタは、対応する SDK が読み込まれていればそれを使い、なければ内蔵実装にフォールバックします。 依存を強制しません。
// 1) 明示的な遅延ロード (推奨)
await OmniP2P.loadAdapter('gundb'); // GunDB.js リアルタイムグラフ同期
await OmniP2P.loadAdapter('matrix'); // Matrix.js Megolm E2EE 永続ルーム
await OmniP2P.loadAdapter('yjs'); // Yjs.js 本物の Y.Doc プロバイダ
await OmniP2P.loadAdapter('waku'); // Waku.js 並列匿名搬送 + Store 永続
// 2) 既にロード済み SDK と差し替える場合
OmniP2P.use(OmniP2P.adapters.Yjs);
OmniP2P.use(OmniP2P.adapters.Waku, { pubsubTopic: '/omnip2p/1/msg/proto' });
OmniP2P.use(OmniP2P.adapters.Matrix, { homeserver: 'https://matrix.org' });
| アダプタ名 | 役割 | ロード元 |
|---|---|---|
MiniCRDT | コア同梱の軽量 CRDT (Yjs 互換シェイプ) | 内蔵 |
Iris | ソーシャルグラフ (フォロー/DM 等) | 内蔵 |
Yjs | 本物の Y.Doc プロバイダ | 外部遅延 |
Automerge | Automerge 用プロバイダ | 外部遅延 |
Matrix | Matrix Megolm E2EE 永続ルーム | 外部遅延 |
GunDB | リアルタイムグラフ同期 DB | 外部遅延 |
OrbitDB | IPFS 上の P2P DB | 外部遅延 |
Waku | 並列匿名搬送 + Store 永続 | 外部遅延 |
外部アダプタはすべて https://cdn.jsdelivr.net/gh/2chkakinie-arch/OmniP2P@main/<Name>.js から自動取得されます。
設計メモ
- Nostr の署名/検証は WebCrypto では secp256k1 が無いため、純 JS secp256k1 (schnorr, BIP-340) を内蔵実装。SHA-256 は WebCrypto。
- E2EE の X25519 は純 JS 実装 (RFC 7748) を内蔵。HKDF / AES-GCM は WebCrypto。
- すべて tree-shake 不要の単一クロージャ。グローバル汚染は
OmniP2Pのみ。 - 大規模対応: リレーは多重接続 + レース、WebRTC はmesh + relay-mesh ハイブリッド、保存はコンテンツアドレス指定 + マルチプロバイダ冗長化。
ライブデモ
実際にライブラリを CDN から読み込み、あなたのブラウザ上で OmniP2P を起動します。 別タブで同じ URL を開けば、同じ部屋に自動で参加してメッセージを送受信できます。
FAQ
Q. サーバーは何も要らないのですか?
A. はい。シグナリングは公開 Nostr リレーで、永続化は Blossom / NIP-96 / IPFS ゲートウェイで行います。自前サーバーは不要です。TURN が必要な超厳しい NAT 環境のみ、任意で iceServers に指定してください。
Q. ブラウザ以外でも動きますか?
A. UMD 対応なので Node / Deno / Bun でも require / import で動きます (WebCrypto / WebRTC 相当の polyfill が必要な場合があります)。
Q. E2EE の鍵管理はどうなっていますか?
A. ルームの合言葉から HKDF でルーム鍵を導出し、ピア間は X25519 ECDH で共有鍵を作ります。すべての通信 (WebRTC / Nostr) と保存 (Blossom) の暗号化には AES-GCM 256 に AAD を付けて使用します。
Q. Nostr リレーが全部落ちたらどうなりますか?
A. 一度 WebRTC が確立していれば、そのまま P2P 通信は継続します。加えて「最低 1 本維持」フェイルセーフとスタガー再接続により、瞬断は極小化されます。
Q. コアだけ使いたいのですが、Yjs / Matrix / Gun は読み込まれますか?
A. いいえ。それらは OmniP2P.loadAdapter() を呼んだ時にはじめて CDN から取得される遅延ロードです。コアの初期バイトサイズには含まれません。
LICENSE
MIT License
Copyright (c) 2026 OmniP2P Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
利用規約
1. 目的
本規約は、OmniP2P (以下「本ソフトウェア」) の利用に関する条件を定めるものです。
2. 分散ネットワークの性質
本ソフトウェアは公開 Nostr リレー、Blossom / NIP-96 サーバー、IPFS ゲートウェイなどの分散ネットワークを利用します。これらのネットワークの可用性、コンテンツ保持期間、検閲耐性については保証されません。
3. 暗号化と鍵管理
E2EE 機能は提供されますが、秘密鍵 / パスワードの管理は利用者の責任です。喪失した場合、暗号化されたデータは復号できません。
4. 禁止事項
- 法令または公序良俗に違反する行為
- 第三者の権利 (著作権 / プライバシー等) を侵害する行為
- マルウェア配布、フィッシング、スパム等のネットワーク濫用
- 本ソフトウェアの脆弱性を悪用する行為
5. 免責
本ソフトウェアは MIT ライセンスに従い「現状有姿」で提供されます。開発者およびコントリビュータは、本ソフトウェアの利用または利用不能から生じるいかなる損害についても責任を負いません。
6. 準拠法
本規約は日本法に準拠し、これに関する紛争は東京地方裁判所を第一審の専属的合意管轄とします。
最終更新: 2026-07-21