Files
PaperPhone/README_JA.md
2026-04-01 17:07:11 +08:00

15 KiB
Raw Blame History

PaperPhone IM

🌐 他の言語 / Other Languages: 中文 · English · 한국어 · Français · Deutsch · Русский · Español

WeChatスタイルのエンドツーエンド暗号化インスタントメッセージアプリ。ステートレス ECDH + XSalsa20-Poly1305 のメッセージ単位暗号化、リアルタイムビデオ通話、Cloudflare R2 ファイルストレージ、多言語対応、iOS PWA デプロイをサポート。

Node.js MySQL Redis WebRTC

Deploy on Zeabur


ui

機能一覧

機能 説明
🔐 エンドツーエンド暗号化 ステートレス ECDH + XSalsa20-Poly1305 — メッセージごとの一時鍵、前方秘匿性
🗝️ ゼロ知識サーバー サーバーは暗号文のみ保存。秘密鍵はデバイスから離れません
📹 ビデオ/音声通話 WebRTC P2P1:1+ Meshグループ、Cloudflare TURN による NAT トラバーサル
👥 グループチャット 最大2000人、プレーンテキストメッセージ暗号化なし、通知オフモード、メンバー管理
⏱️ メッセージ自動削除 5段階なし/1日/3日/1週間/1ヶ月、DM は双方設定可、グループはオーナーのみ
🔔 プッシュ通知 Web Push (VAPID) + OneSignal デュアルチャネル — オフラインでも通知
🌐 多言語対応 中国語・英語・日本語・韓国語・フランス語・ドイツ語・ロシア語・スペイン語(自動検出+手動切替)
📱 iOS 永久署名不要 PWA — Safari「ホーム画面に追加」でエンタープライズ証明書なしで利用可能
💬 リッチメッセージ テキスト・画像・動画・ドキュメントファイルPDF/DOCX/XLSX等、タイプ別アイコン付き・音声・絵文字パネル200+種、8カテゴリ・Telegram ステッカーパック・既読確認
🌐 モーメンツ テキスト最大9枚写真または1本の動画≤10分、いいね友達アバター表示、コメント、タグベースの公開範囲制御
👤 ユーザープロフィール 連絡先プロフィール(アバター/ニックネーム/モーメンツフィード)、「この人のモーメンツを非表示」と「自分のモーメンツを非公開」の双方向プライバシー制御
📰 タイムライン 小紅書スタイルの公開フィード——2列マソンリーレイアウト、画像/動画+テキスト最大50メディア、2000文字、匿名投稿、いいね・コメント
🏷️ フレンドタグ 友達に複数タグを設定12色プリセット、タグ別に連絡先をフィルタリング
🗂️ R2 オブジェクトストレージ Cloudflare R2 で画像/音声ファイルを保存 — オプションの CDN 直リンク
🔑 二段階認証 (2FA) Google Authenticator 対応 TOTP、8つのリカバリーコード、ログイン時に強制検証
🏗️ セルフホスト対応 Docker Compose ワンコマンドデプロイ、Node.js + Redis マルチノード対応

技術スタック

バックエンド (server/)
  Node.js 20 + Express + ws
  MySQL 8.0  — ユーザー・メッセージの永続化
  Redis      — オンラインプレゼンス+クロスノードルーティング
  Cloudflare R2 — 画像/音声ファイルストレージS3互換API
  JWT + bcrypt 認証

フロントエンド (client/)
  ネイティブ HTML + Vanilla JSESM、バンドラー不要
  libsodium-wrappersWebAssembly — Curve25519 / XSalsa20-Poly1305
  WebRTC API — ビデオ/音声通話
  PWA: manifest.json + Service Worker

暗号化レイヤー
  ステートレス ECDH + XSalsa20-Poly1305 — メッセージごとの一時 ECDH 鍵ペア
  秘密鍵4層永続化: メモリ → localStorage → sessionStorage → IndexedDB
  秘密鍵はデバイスにのみ保存、サーバーに送信されることはありません

クイックスタート

方法0Zeabur ワンクリッククラウドデプロイ

Deploy on Zeabur

Note

テンプレートのデプロイ後、1つの手動手順が必要です。これを行わないとログイン/登録が機能しません:

  1. Zeabur コンソール → server サービス → 環境変数 → ZEABUR_WEB_URL の値をコピー(例:http://10.43.x.x:3000
  2. client サービス → 環境変数 → 変数 SERVER_URL を追加 = 上記でコピーした値
  3. client サービスを再起動

既知の注意事項:

  • 初回起動時、サーバーは自動的にすべてのデータベーステーブルを作成します(CREATE TABLE IF NOT EXISTS)— SQL の手動インポートは不要
  • Redis はクラスター内でパスワードなしで動作します
  • MySQL アクセスが拒否された場合、server サービスの DB_PASS を MySQL サービスの MYSQL_ROOT_PASSWORD に手動設定してください
  • サービスコンテナの内部IPを取得するには、Zeabur コンソールで該当サービスのターミナルを開き、以下を実行:
    hostname -i
    

方法1Docker Compose推奨 — ローカルビルド不要)

# リポジトリをクローン
git clone <repo-url> && cd paperphone

# 環境変数をコピーして編集
cp server/.env.example server/.env
# DB_PASS / JWT_SECRET / R2_* などを編集

# イメージを取得して起動
docker compose up -d

# サービスステータスを確認
docker compose ps

# ブラウザで開く
open http://localhost

Docker Hub のビルド済みイメージ:

  • facilisvelox/paperphone-client:latest
  • facilisvelox/paperphone-server:latest

注意:サーバーは初回起動時に自動的にデータベーススキーマを初期化します — SQL の手動インポートは不要です。

方法2ローカル手動起動

1. 環境を準備

# 環境変数をコピーして編集
cp server/.env.example server/.env
# DB_HOST / DB_PASS / REDIS_HOST / R2_* などを入力

# 注:サーバーは初回起動時に自動で schema.sql を実行します

2. バックエンドを起動

cd server
npm install
npm run dev   # → http://localhost:3000

3. フロントエンドを起動

npx serve client -p 8080
# → http://localhost:8080

ビデオ通話の設定

ビデオ通話と音声通話は WebRTC P2P を使用し、同一 LAN 内ではすぐに使えます。異なるネットワーク間の通話には、NAT トラバーサル用の TURN サーバーが必要です。

Cloudflare TURN の使用(推奨)

  1. Cloudflare DashboardWorkers & PagesCalls → App を作成
  2. App IDApp SecretToken Keyをコピー
  3. server/.env に追加:
CF_CALLS_APP_ID=your_app_id_here
CF_CALLS_APP_SECRET=your_app_secret_here
  1. バックエンドを再起動 — TURN クレデンシャルは通話セッションごとに自動取得されますTTL: 86,400秒

未設定時STUN のみにフォールバックしますGoogle + Cloudflare パブリック STUN。LAN 内の通話は追加設定なしで動作します。

通話タイプ

タイプ 通信方式 推奨用途
1:1 ビデオ通話 WebRTC P2P + TURN すべてのシナリオ
1:1 音声通話 WebRTC P2P + TURN すべてのシナリオ
グループ通話 WebRTC Meshフルメッシュ 最大6人

プッシュ通知の設定

オフラインメッセージ通知は2つのチャネルで配信され、配信率を最大化します:

チャネル 対応プラットフォーム 設定
Web Push (VAPID) ブラウザ (Chrome/Edge/Firefox) + iOS PWA (Safari 16.4+) VAPID キー
OneSignal Median.co 経由のネイティブ Android/iOS アプリ OneSignal App ID + REST Key

Web Push の設定

  1. VAPID キーを生成1回のみ
cd server
npx web-push generate-vapid-keys
  1. server/.env に追加:
VAPID_PUBLIC_KEY=your_public_key_here
VAPID_PRIVATE_KEY=your_private_key_here
VAPID_SUBJECT=mailto:admin@your-domain.com
  1. サーバーを再起動 — ユーザーは設定ページから通知を有効にできます

iOS ユーザーは先に「ホーム画面に追加」PWAを行う必要があり、iOS 16.4以上のみサポートされます。

OneSignal の設定Median.co ネイティブアプリ)

  1. OneSignal Dashboard でアプリを作成し、Firebase を設定
  2. Median.co で OneSignal を有効にし、App ID を入力
  3. OneSignal の App IDREST API Keyserver/.env に追加:
ONESIGNAL_APP_ID=your_onesignal_app_id
ONESIGNAL_REST_KEY=your_onesignal_rest_api_key

未設定時:プッシュ通知は自動的に無効化されます — 他の機能には影響しません。


iOS — 証明書不要の永続デプロイ

  1. HTTPS ドメインのサーバーにデプロイWebRTC と Web Crypto API には HTTPS が必要)
  2. Safarihttps://your.domain.com を開く
  3. 画面下部の共有ボタン ⬆️ をタップ
  4. ホーム画面に追加追加 を選択

ネイティブアプリと同様に動作します — Apple エンタープライズ証明書不要、期限なし。


本番デプロイNginx

server {
    listen 443 ssl http2;
    server_name your.domain.com;

    ssl_certificate     /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # フロントエンド静的ファイル
    location / {
        root /path/to/paperphone/client;
        try_files $uri /index.html;
    }

    # REST API
    location /api/ {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
    }

    # WebSocketメッセージング通話シグナリング
    location /ws {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
    }
}

データベーススキーマ

18テーブル、サーバー初回起動時に自動作成CREATE TABLE IF NOT EXISTS

テーブル 用途
users ユーザー情報 + ECDH/OPK 公開鍵
prekeys X3DH ワンタイムプリキープール
friends 友達関係pending / accepted / blocked
groups / group_members グループチャット+メンバー(通知オフ状態含む)
messages 暗号化メッセージ(オフラインバッファ、配信後削除可能)
moments ソーシャル投稿(テキスト ≤ 1024文字
moment_images 投稿画像1投稿最大9枚
moment_videos 投稿動画サムネイル再生時間、1投稿1本、≤10分
moment_likes いいね(ユーザーごと投稿ごとにユニーク)
moment_comments コメント(≤ 512文字/件)
push_subscriptions Web Push サブスクリプションVAPID
onesignal_players OneSignal デバイス登録Median.co
user_totp TOTP 二段階認証のシークレットとリカバリーコード
moment_privacy モーメンツのユーザーレベルプライバシー設定(非表示/非公開)
timeline_posts タイムライン投稿(テキスト ≤2000文字、匿名対応
timeline_media タイムラインメディア(画像/動画、最大50件/投稿)
timeline_likes タイムラインいいね
timeline_comments タイムラインコメント(匿名対応)

セキュリティモデル

登録時:
  デバイスが IKアイデンティティキー+ SPK署名付きプリキー+ 10× OPKワンタイムプリキーを生成
  公開鍵はアップロード、秘密鍵はデバイスに保存4層永続化

メッセージ送信時:
  送信者が受信者の IK 公開鍵を取得
  一時 ECDH 鍵ペアを生成(メッセージごとに新しいペア)
  X25519 ECDH → 共有秘密 → XSalsa20-Poly1305 暗号化
  一時公開鍵はメッセージヘッダーで送信、使用後に破棄

サーバーが見るもの:
  ✅ 暗号文ブロブルーティングメタデータ送受信者UUID、タイムスタンプ
  ❌ 平文 / 秘密鍵 / 一時鍵 / 通話内容

環境変数リファレンス

変数 説明 デフォルト
PORT サーバーポート 3000
JWT_SECRET JWT 署名キー(本番環境では必ず変更 dev_secret
DB_HOST / DB_PASS / DB_NAME MySQL 接続設定
REDIS_HOST / REDIS_PASS Redis 接続設定
R2_ACCOUNT_ID Cloudflare アカウント ID
R2_ACCESS_KEY_ID R2 API トークンのアクセスキー
R2_SECRET_ACCESS_KEY R2 API トークンのシークレットキー
R2_BUCKET R2 バケット名
R2_PUBLIC_URL R2 公開 URL任意— CDN 直リンクを有効化
CF_CALLS_APP_ID Cloudflare Calls App ID任意
CF_CALLS_APP_SECRET Cloudflare Calls App Secret任意
VAPID_PUBLIC_KEY Web Push VAPID 公開鍵(任意)
VAPID_PRIVATE_KEY Web Push VAPID 秘密鍵(任意)
VAPID_SUBJECT VAPID 連絡先メール(任意) mailto:admin@paperphone.app
ONESIGNAL_APP_ID OneSignal App ID任意、Median.co用
ONESIGNAL_REST_KEY OneSignal REST API Key任意
TELEGRAM_BOT_TOKEN Telegram Bot Token任意、ステッカープロキシ
STICKER_PACKS カスタムステッカーパック(任意、カンマ区切り 名前:ラベル、無制限) デフォルト8パック

ライセンス

MIT © PaperPhone Contributors