Setsuna(刹那)は、デバイス間でテキストをリアルタイム共有するWebアプリケーションです。
スマートフォンでコピーしたテキストを、PCでそのまま取得するといった用途に使用できます。
| 項目 |
内容 |
| アプリケーション名 |
Setsuna |
| バージョン |
1.3.4 |
| ライセンス |
MIT |
| カテゴリ |
技術 |
| フロントエンド |
Next.js 15 (App Router), React 19 |
| スタイリング |
Tailwind CSS |
| データベース |
Turso (SQLite互換) |
| ORM |
Prisma |
| リアルタイム通信 |
Server-Sent Events (SSE) |
| テスト |
Vitest, Testing Library, Playwright |
| Linter/Formatter |
oxlint, Prettier |
| Gitフック |
husky, lint-staged |
| デプロイ先 |
Vercel |
| 言語 |
TypeScript |
| 国際化 |
next-intl |
本プロジェクトではt-wada氏のTDDを採用。詳細は TEST.md#テスト戦略 を参照。
概要:
src/lib/, src/app/api/: TDD(テスト先行)
src/components/, E2E: 後付けテスト
- ユーザーは新しいルームを作成できる
- ルーム作成時に6文字の英数字コードが自動生成される
- ルームは作成から24時間後に自動削除される
- ユーザーはルームコードを入力してルームに参加できる
- 存在しないまたは期限切れのルームにはアクセスできない
- URLでの直接アクセスも可能(例:
/room/A1B2C3)
- ユーザーはテキストを入力して送信できる
- 最大10,000文字まで送信可能
- 送信されたテキストは全参加者にリアルタイムで配信される
- 新しいテキストはリアルタイムで画面に表示される
- 過去のテキストは時系列で一覧表示される
- 各テキストにはコピーボタンが付属
- ワンクリックでクリップボードにコピー可能
- 英語(デフォルト)と日本語に対応
- URL構造:
/en(英語), /ja(日本語)
- UIから言語切替が可能(LanguageSwitcherコンポーネント)
- 翻訳ファイルは
messages/ディレクトリに配置
- ルームは作成から24時間後に自動削除
- 削除時にルーム内のすべてのメッセージも同時に削除
- Vercel Cron Jobsにより1時間ごとに削除処理を実行
- 管理者は環境変数(
ADMIN_PASSWORD)で設定したパスワードでログイン
- JWTトークン(
ADMIN_JWT_SECRETで署名)をHttpOnly Cookieで管理(24時間有効)
- 未認証の
/adminアクセスはログインページへリダイレクト
- 重要:
ADMIN_PASSWORD と ADMIN_JWT_SECRET の両方を設定しないと認証が動作しない
- アクティブルーム数
- 総メッセージ数
- 本日のルーム/メッセージ作成数
- 過去7日間の日別統計グラフ
- ルーム一覧表示(ページネーション、10件/ページ)
- 検索機能(ルームコードで検索)
- フィルター(アクティブ/期限切れ)
- ルーム詳細・メッセージ閲覧
- ルーム強制削除
| 項目 |
対策 |
| ルームコード |
暗号学的に安全な乱数で生成(crypto.randomBytes) |
| 文字種 |
紛らわしい文字(0,O,1,I,L)を除外した31文字(A-HJ-NP-Z, 2-9) |
| 組み合わせ数 |
31^6 ≒ 約8.9億通り |
| XSS対策 |
Reactの自動エスケープ機能を活用 |
| HTTPS |
本番環境ではHTTPS必須 |
| 項目 |
目標値 |
| 初期ロード |
3秒以内 |
| メッセージ配信遅延 |
500ms以内 |
| 同時接続数 |
ルームあたり100接続 |
- Vercelの自動スケーリングを活用
- TursoのエッジDBによる低レイテンシー
| 項目 |
内容 |
| SSEブロードキャスト |
インメモリ管理のため、単一インスタンス前提での運用 |
| スケールアウト |
複数インスタンス間でのSSE通知は非対応(将来的にRedis Pub/Sub導入検討) |
| 変数名 |
説明 |
必須 |
例 |
DATABASE_URL |
ローカルSQLiteデータベースのパス |
開発時 |
file:./dev.db |
TURSO_DATABASE_URL |
TursoデータベースのURL |
本番 |
libsql://your-db.turso.io |
TURSO_AUTH_TOKEN |
Turso認証トークン |
本番 |
(Tursoから取得) |
CRON_SECRET |
Cronジョブ認証用シークレット |
本番 |
(ランダム文字列) |
ADMIN_PASSWORD |
管理ダッシュボードのログインパスワード |
任意 |
(任意のパスワード) |
ADMIN_JWT_SECRET |
JWTトークン署名用のシークレットキー |
管理機能使用時 |
(ランダム文字列、32バイト以上推奨) |
# データベース
DATABASE_URL="file:./dev.db"
# 管理ダッシュボード(任意)
ADMIN_PASSWORD="your-password"
ADMIN_JWT_SECRET="your-jwt-secret"
# データベース(Turso)
TURSO_DATABASE_URL="libsql://your-db.turso.io"
TURSO_AUTH_TOKEN="your-token"
# Cronジョブ認証
CRON_SECRET="your-cron-secret"
# 管理ダッシュボード
ADMIN_PASSWORD="your-secure-password"
ADMIN_JWT_SECRET="your-secure-jwt-secret"
安全なシークレットキーを生成するには、以下のコマンドを使用:
# ADMIN_JWT_SECRET用
openssl rand -base64 32
# CRON_SECRET用
openssl rand -hex 32
- 管理ダッシュボード:
ADMIN_PASSWORD と ADMIN_JWT_SECRET の両方を設定しないと、管理画面へのログインが失敗します(500エラー)
- 本番環境: シークレットキーは十分な長さ(32バイト以上)のランダム文字列を使用してください
- Vercel: Vercelの環境変数設定画面から設定してください
[開始]
│
▼
┌─────────────────┐
│ ホームページ │
│ (ルーム作成/参加) │
└─────────────────┘
│
├─── 「ルームを作成」────┐
│ ▼
│ ┌─────────────────┐
│ │ ルームコード生成 │
│ │ (例: ABCD23) │
│ └─────────────────┘
│ │
│ ▼
│ ┌─────────────────┐
│ │ ルームページへ │
│ │ 自動遷移 │
│ └─────────────────┘
│ │
├─── 「ルームに参加」───┤
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │コード入力画面 │ │
│ └───────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ ルームページ │◀──┘
│ └───────────────┘
│ │
│ ▼
│ ┌───────────────┐
│ │テキスト入力・ │
│ │ 送信・コピー │
│ └───────────────┘
│ │
│ ▼
│ ┌───────────────┐
│ │ リアルタイム │
│ │ 同期 │
│ └───────────────┘
│
▼
[24時間後自動削除]
Setsuna/
├── docs/ # 仕様書
│ ├── SPEC.md # 全体仕様(本ファイル)
│ ├── API.md # API仕様
│ ├── DB.md # データベース仕様
│ ├── UI.md # UI/UX仕様
│ └── TEST.md # テスト仕様
├── messages/ # i18n翻訳ファイル
│ ├── en.json # 英語
│ └── ja.json # 日本語
├── .husky/ # Gitフック
│ ├── pre-commit # コミット前にlint-staged実行
│ └── pre-push # プッシュ前にテスト実行
├── prisma/
│ └── schema.prisma # DBスキーマ
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── layout.tsx
│ │ ├── page.tsx # ルートリダイレクト
│ │ ├── globals.css
│ │ ├── [locale]/ # ロケール対応ルート
│ │ │ ├── layout.tsx # ロケールレイアウト
│ │ │ ├── page.tsx # ホームページ
│ │ │ └── room/[code]/ # ルームページ
│ │ ├── admin/ # 管理ダッシュボード
│ │ │ ├── layout.tsx # 管理画面レイアウト
│ │ │ ├── page.tsx # ダッシュボード
│ │ │ ├── login/ # ログインページ
│ │ │ └── rooms/ # ルーム管理
│ │ └── api/ # APIルート
│ ├── components/ # Reactコンポーネント
│ │ └── admin/ # 管理用コンポーネント
│ │ ├── AdminHeader.tsx # ナビゲーションヘッダー
│ │ └── StatsCard.tsx # 統計カード
│ ├── i18n/ # i18n設定
│ │ ├── routing.ts # ルーティング設定
│ │ ├── request.ts # リクエスト設定
│ │ └── navigation.ts # ナビゲーションAPI
│ ├── lib/ # ユーティリティ + テスト(*.test.ts)
│ ├── hooks/ # カスタムフック
│ └── types/ # 型定義
├── e2e/ # E2Eテスト(Playwright)
├── middleware.ts # next-intlミドルウェア
├── .oxlintrc.json # Oxlint設定
├── prettier.config.mjs # Prettier設定
├── lint-staged.config.mjs # lint-staged設定
├── vitest.config.ts # Vitest設定
├── playwright.config.ts # Playwright設定
├── vercel.json # Vercel設定
├── package.json
└── README.md