Conversation
WalkthroughDM機能を新設し、MLS/E2EE・チャットルーム・鍵管理関連コードとドキュメントを全面的に削除/置換。RESTとActivityPub Create/Note単一宛先でのDM保存/通知を実装。DBインターフェースとモデルをDM向けに再構成。WebSocketのバイナリ/MLS経路を整理。クライアント側MLS依存UI/ストレージ/テストを撤去。 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant API as API (/api/dm)
participant DB
participant WS as WebSocket Hub
participant UserA as User A
participant UserB as User B
Client->>API: POST /api/dm { from, to, content }
API->>DB: saveDMMessage(from, to, content)
DB-->>API: { id, from, to, content, createdAt }
API->>WS: sendToUser(from, { type:"dm", payload })
API->>WS: sendToUser(to, { type:"dm", payload })
API-->>Client: 200 { id, from, to, content }
note over WS,UserA: 双方へDM通知
note over WS,UserB: 双方へDM通知
sequenceDiagram
autonumber
participant Inbox as ActivityPub Inbox
participant Handler as activity_handlers(Create)
participant DB
participant Deliver as deliverActivityPubObject
Inbox->>Handler: Create{ object: Note, to:[target] }
alt toが単一Actorで非公開コレクション
Handler->>DB: saveMessage(..., {to, cc})
Handler-->>Inbox: 202 (dm handled)
opt リモート宛先
Handler->>Deliver: POST Note to remote inbox
Deliver-->>Handler: 2xx/4xx
end
else それ以外
Handler->>DB: saveObject(...)
Handler-->>Inbox: 202 (post handled)
end
note over Handler: 単一宛先NoteをDMとして分岐
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Possibly related PRs
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/api/routes/ws.ts (1)
60-70: 任意ユーザーへのなりすましが可能(重大)— register の payload.user を無検証で採用
registerメッセージ受信時にpayload.userをそのままstate.userとして登録しています。セッション検証も署名検証もなく、攻撃者が任意のユーザー名で登録し、そのユーザー宛の DM 通知を受け取れる恐れがあります。是正案(推奨順):
- A) サーバー側で WS 接続時に認証済みユーザーを確定し、自動登録する(クライアントからの register を廃止/任意化)。
c.get("user")(認証ミドルウェアで設定)や署名付きクッキー/JWT から復元してください。- B) 互換性のために
registerを残す場合でも、payload.userがサーバーで認証済みのユーザーと一致することを必須チェックにしてください。一致しない場合はエラーを返し、ソケットをクローズ。参考差分(案 A:onOpen で登録、register 削除):
-import type { WSContext } from "hono/ws"; +import type { WSContext } from "hono/ws"; +import type { Context } from "hono"; -export type WsState = Record<string, unknown>; +export interface WsState { + context: Context; + user?: string; +} @@ -registerMessageHandler("register", (payload, ws, state) => { - const user = (payload as { user?: string }).user; - if (!user) return; - state.user = user; - let set = userSockets.get(user); - if (!set) { - set = new Set(); - userSockets.set(user, set); - } - set.add(ws); -}); +// register に頼らず、onOpen でサーバーが判定したユーザーを登録する方針に変更 @@ upgradeWebSocket((c) => { - const state: WsState = { context: c }; + const state: WsState = { context: c }; return { - onOpen(_evt, ws) { - for (const h of openHandlers) h(ws, state); - }, + onOpen(_evt, ws) { + // 例:認証ミドルウェアでセット済みのユーザーを取得(実装に合わせて調整) + const authedUser = (c.get("user") as string | undefined); + if (!authedUser) { + // 未認証なら接続を終了(適切なコード/理由に置き換え) + try { ws.close(4401, "unauthorized"); } catch {} + return; + } + state.user = authedUser; + let set = userSockets.get(authedUser); + if (!set) { + set = new Set(); + userSockets.set(authedUser, set); + } + set.add(ws); + for (const h of openHandlers) h(ws, state); + },互換性案(B:
registerを残すが一致検証を追加)も提示可能です。必要なら提示します。app/api/activity_handlers.ts (1)
114-117: 環境変数の取得は getEnv(c) に統一(ガイドライン準拠)
(c as { get("env") })はガイドライン違反です。getEnv(c)に寄せてください。提案差分:
-import { - createAcceptActivity, - deliverActivityPubObject, - extractAttachments, - getDomain, -} from "./utils/activitypub.ts"; +import { + createAcceptActivity, + deliverActivityPubObject, + extractAttachments, + getDomain, + getEnv, +} from "./utils/activitypub.ts"; @@ - const env = (c as { get: (k: string) => unknown }).get("env") as Record< - string, - string - >; + const env = getEnv(c as Context);
🧹 Nitpick comments (27)
app/client/src/components/chat/FriendList.tsx (1)
232-239: アクセシビリティ強化(div+onClick はキーボード操作不可)div に onClick だけだとキーボード操作で選択できません。
role="button"とtabIndex、Enter/Space での起動を追加すると操作性が上がります。適用例(対象行に追加):
- <div + <div class={`flex items-center p-3 rounded-lg cursor-pointer transition-colors ${ props.selectedFriend === friend.id ? "bg-[#4a4a4a]" : "hover:bg-[#3c3c3c]" }`} - onClick={() => props.onSelectFriend(friend.id)} + role="button" + tabIndex={0} + onClick={() => props.onSelectFriend(friend.id)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + props.onSelectFriend(friend.id); + } + }} >補足: 本ファイルの normalizeHandle と Profile.tsx の normalizeActor は目的が近いので utils へ集約すると DRY になります。
app/client/src/components/Profile.tsx (1)
242-249: ハンドル正規化の重複を共通化本ファイルの
normalizeActorと FriendList.tsx のnormalizeHandleが重複しています。utils/handle.tsのような共通ユーティリティへ切り出すと保守性が上がります(片側の修正漏れ防止)。適用例(外部ファイル追加のため参考コード):
// utils/handle.ts export function normalizeHandleOrActor(id: string): string | undefined { if (!id) return; if (id.startsWith("http")) { try { const u = new URL(id); const name = u.pathname.split("/").pop() || ""; return name ? `${name}@${u.hostname}` : undefined; } catch { return; } } return id.includes("@") ? id : undefined; }app/takos_host/client/src/pages/WelcomePage.tsx (1)
270-285: 初期 DM 同期フェッチの堅牢化提案以下の改善を任意でご検討ください。
- 空値ガード
userName()またはrootDomain()が空文字の場合は早期リターンし、不正な URL を生成しないようにします。- 認証情報の送信
- Cookie ベースの認証を利用している場合、
fetchのオプションに{ credentials: "include" }を追加すると確実です。- レスポンスチェック&ロギング
res.okを確認し、エラー時にはステータスや例外情報をログ出力するとトラブルシュートが容易になります。- サーバ側挙動の確認済み
app/api/routes/dm.tsではクエリパラメータに対し文字列型制約のみがあり、user1 === user2のケースもそのまま受け入れ、DB のlistDMsBetweenでは同一ユーザー間のメッセージを検索する設計になっています。仕様変更は不要です。適用例(抜粋):
onMount(async () => { - if (!loggedIn()) return; - const actor = `${userName()}@${rootDomain()}`; + if (!loggedIn()) return; + const name = userName(); + const domain = rootDomain(); + if (!name || !domain) return; // 空値ガード + const actor = `${name}@${domain}`; try { - await fetch( - `/api/dm?user1=${encodeURIComponent(actor)}&user2=${encodeURIComponent(actor)}`, - ); + const url = `/api/dm?user1=${encodeURIComponent(actor)}&user2=${encodeURIComponent(actor)}`; + const res = await fetch(url, { credentials: "include" }); + if (!res.ok) { + console.debug("Initial DM sync failed:", res.status); + } } catch (e) { - // 失敗しても無視 + console.debug("Initial DM sync error:", e); } });app/api/models/takos/dm_message.ts (1)
4-15: 会話キーの導入で OR クエリを簡素化(設計オプション)A↔B の双方向検索を
$or: [{from:A,to:B},{from:B,to:A}]で行うより、conversationKey = sort([from,to]).join('#')のような正規化キーを持たせ、{tenant_id, conversationKey, createdAt}で単一等価検索+ソートにするとインデックス効率と実装がシンプルになります。参考(概念実装):
dmMessageSchema.add({ conversationKey: { type: String, index: true }, }); dmMessageSchema.pre("validate", function (next) { const a = String(this.from || "").trim().toLowerCase(); const b = String(this.to || "").trim().toLowerCase(); this.conversationKey = [a, b].sort().join("#"); next(); }); // インデックス dmMessageSchema.index({ tenant_id: 1, conversationKey: 1, createdAt: -1 });app/client/src/components/Application.tsx (1)
22-33: モバイル判定は matchMedia の利用が簡潔で効率的
resizeハンドラで幅判定していますが、matchMedia("(max-width: 768px)")のchangeを使うと負荷低減・判定の単純化ができます。現状でも問題は軽微ですが、将来的な保守性向上に有効です。参考差分(概念):
- onMount(() => { - const checkMobile = () => { - setIsMobile(globalThis.innerWidth <= 768); - }; - checkMobile(); - globalThis.addEventListener("resize", checkMobile); - connectWebSocket(); - return () => globalThis.removeEventListener("resize", checkMobile); - }); + onMount(() => { + const mq = globalThis.matchMedia?.("(max-width: 768px)"); + const update = () => setIsMobile(mq ? mq.matches : globalThis.innerWidth <= 768); + update(); + mq?.addEventListener?.("change", update); + connectWebSocket(); + return () => mq?.removeEventListener?.("change", update); + });app/client/src/components/chat/ChatSettingsOverlay.tsx (1)
13-23: モーダルのアクセシビリティ強化(role/aria/Escape)最小 UI で良いのですが、
role="dialog"とaria-modal="true"、タイトル参照、Escape で閉じる対応があると支援技術・キーボード操作に優しくなります。適用差分案:
- <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> - <div class="bg-white rounded p-4 w-72 text-black"> - <h2 class="text-lg font-bold mb-2">チャット設定</h2> + <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> + <div + class="bg-white rounded p-4 w-72 text-black outline-none" + role="dialog" + aria-modal="true" + aria-labelledby="chat-settings-title" + tabIndex={-1} + onKeyDown={(e) => { if (e.key === "Escape") props.onClose(); }} + > + <h2 id="chat-settings-title" class="text-lg font-bold mb-2">チャット設定</h2> <p class="mb-4">現在、設定項目はありません。</p> <button type="button" class="px-4 py-2 rounded bg-gray-700 text-white" onClick={props.onClose} > 閉じる </button> </div> </div>app/client/src/components/Setting/index.tsx (1)
16-24: ログアウトの多重実行を防止し、ボタンを無効化する現在は連打で複数回リクエストされ得ます。UX とサーバー負荷の観点から、処理中はボタンを無効化してください。
実装例(コンポーネント内追記):
import { createSignal } from "solid-js"; export function Setting() { const [isLoggingOut, setIsLoggingOut] = createSignal(false); const handleLogout = async () => { if (isLoggingOut()) return; setIsLoggingOut(true); try { await apiFetch("/api/logout", { method: "POST" }); } catch (err) { console.error("logout failed", err); } finally { setIsLoggedIn(false); setIsLoggingOut(false); } }; // ... <button type="button" class="bg-red-700 text-white px-4 py-2 rounded hover:bg-red-800 transition disabled:opacity-60" disabled={isLoggingOut()} onClick={handleLogout} > ログアウト </button> }app/api/routes/ws.ts (2)
91-104: onMessage の入力検証と未知 type のエラーレスポンスを追加
handler?.(msg.payload, ...)で未定義 type を黙殺しています。クライアント不具合の早期検知のため、typeの存在チェックと未知 type のエラーレスポンスを返すのが安全です。また、ハンドラ実行時の例外を捕捉してonErrorに委譲すると運用性が上がります。適用差分案:
if (typeof evt.data === "string") { try { const msg = JSON.parse(evt.data); - const handler = messageHandlers.get(msg.type); - handler?.(msg.payload, ws, state); + if (!msg || typeof msg.type !== "string") { + ws.send(JSON.stringify({ error: "missing_type" })); + return; + } + const handler = messageHandlers.get(msg.type); + if (!handler) { + ws.send(JSON.stringify({ error: "unknown_type", type: msg.type })); + return; + } + Promise.resolve(handler(msg.payload, ws, state)).catch((_e) => { + ws.send(JSON.stringify({ error: "handler_error", type: msg.type })); + }); } catch { ws.send(JSON.stringify({ error: "invalid message" })); } } else { ws.send(JSON.stringify({ error: "binary_payload_not_allowed" })); }
23-39: send/broadcast 時の CLOSE 状態送信を防止(微最適化)閉じたソケットに対する
sendは例外やノイズログの原因になります。readyState === WebSocket.OPENを確認してください。適用差分案:
export function sendToUser(user: string, data: unknown) { const sockets = userSockets.get(user); if (!sockets) return; const message = typeof data === "string" ? data : JSON.stringify(data); for (const s of sockets) { - s.send(message); + // OPEN のみ送信 + if ((s.raw as WebSocket).readyState === WebSocket.OPEN) s.send(message); } } @@ const message = typeof data === "string" ? data : JSON.stringify(data); for (const set of userSockets.values()) { for (const s of set) { - s.send(message); + if ((s.raw as WebSocket).readyState === WebSocket.OPEN) s.send(message); } } }app/client/src/components/chat/ChatRoomList.tsx (2)
147-162: normalizeHandle の重複を共通化してバグ予防本ファイルと
FriendList.tsxにほぼ同等のnormalizeHandle実装が重複しています。将来の仕様変更時に片方の修正漏れが起きやすいです。utils/handle.ts等に切り出して単一実装へ寄せてください。共通化イメージ:
// app/client/src/utils/handle.ts export function normalizeHandle(id?: string): string | undefined { if (!id) return undefined; if (id.startsWith("http")) { try { const u = new URL(id); const name = u.pathname.split("/").pop() || ""; if (!name) return undefined; return `${name}@${u.hostname}`; } catch { return undefined; } } return id.includes("@") ? id : undefined; }使用側:
-import { getDomain } from "../../utils/config.ts"; +import { getDomain } from "../../utils/config.ts"; +import { normalizeHandle } from "../../utils/handle.ts";
399-405: selectedRoomState は一貫して Room.id を保持しています下記のとおり、
selectedRoomStateからChatRoomList→FriendListまで渡される値はすべてRoom.id(DM では友だちハンドル、グループではチャンネルID)として統一されており、型・値の不整合はありません。
selectedRoomState(atom<string | null>)は常にRoom.idを保持していますChat.tsxで取得したselectedRoom()の値をそのままChatRoomList→FriendList(selectedFriend/onSelectFriend)に渡しています- DM API 呼び出し時の
/api/dmのtoフィールドにも同じselectedRoom()の値が使われており、正しく送信されていますよって、旧実装との互換性や選択値の意味に問題はありません。
オプション:更なる可読性向上のため、命名を明確化するリファクタを検討してもよいでしょう
ChatRoomListProps.onSelect→onSelectFriendFriendListProps.selectedFriend→selectedRoomId/selectedFriendIdapp/client/src/components/microblog/api.ts (3)
355-358: 同期API化に伴うawaitの取り残しを削除してください
getCachedUserInfo/setCachedUserInfoは同期関数ですが、呼び出し元にawaitが残っています。意味上は問題ないものの、読みやすさと意図の明確化のために除去を推奨します。Promise.allに同期処理を渡している箇所も合わせて整理してください。適用例:
- const cached = await getCachedUserInfo(identifier); + const cached = getCachedUserInfo(identifier); ... - await setCachedUserInfo(identifier, userInfo); + setCachedUserInfo(identifier, userInfo); ... - const cachedInfo = await getCachedUserInfo(identifier); + const cachedInfo = getCachedUserInfo(identifier);Also applies to: 370-372, 388-394
312-319:UserInfo型定義の重複を解消し、単一起点へ集約を
app/api/services/user-info.tsに同等のUserInfoが存在します。型の二重管理は将来的な乖離や不整合の原因になります。shared層など参照可能な共通モジュールへ移動し、このファイルからはそれをimportする形へ寄せてください。
321-328: メモリキャッシュに上限とエビクション戦略を追加検討時間判定は読み出し時のみで、Mapからの掃除が行われません。長期稼働で識別子が増え続けるとメモリリーク様の増加が起こり得ます。サイズ上限(例: LRU 1000件)と定期的なエビクション/弱参照の導入を検討してください。
docs/key-sharing.md (1)
3-5: 平文DMへの移行に伴うリスク表示と仕様例(JSON-LD)の明記を追加してください
- 「サーバ管理者/侵害者からも内容が読める」「転送はTLSだがE2EEではない」等のリスクを注意書きとして明示してください。
- ActivityPub互換実装として、
Create/Noteの最小完全例(@context/to/cc/可視性)を載せると実装者/運用者に親切です。例(参考・最小例):
{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Create", "actor": "https://example.com/users/alice", "to": ["https://remote.example/users/bob"], "cc": [], "object": { "type": "Note", "attributedTo": "https://example.com/users/alice", "to": ["https://remote.example/users/bob"], "cc": [], "content": "hi bob (plaintext DM)", "sensitive": false } }加えて、配送ポリシー(例: フォロワーコレクションや Public への拡散と無関係であること、サーバ間の保存/ログ方針)にも一言触れておくと誤解が減ります。
docs/chat_ux.md (1)
15-18: 「toが1件=DM扱い」の前提条件と相互運用注意点を補足してくださいAP実装間の相互運用では、可視性(
to/cc/audience/to: Publicでないこと)や配信スコープの解釈が実装依存な側面があります。本仕様では「アプリ層でDMとして扱う」という前提を明言し、以下を補足すると実装者に親切です。
toは受信者1件、ccは空、Publicへは送らない(非公開)。- 受信側がルーム自動作成する条件(例: 自身宛の非公開NoteをDM扱いにする)。
- 既存ルームとの突合ルール(同一相手との1:1は同じルームにスレッド化等)。
- 失敗時(ブロック、配信拒否)のUI出し分け。
- JSON-LD最小例を「仕様」節か付録にリンク。
app/api/routes/dm.ts (2)
31-33:as { _id: string }のキャスト依存を解消し、DBインターフェイスへ型を上げてください
saveDMMessageの戻り値を共有インターフェイス(app/shared/db.ts)で型定義し、ここではその型を受ける形にしてください。キャストは将来の変更で静的検査が効かず破損しやすいです。
21-23: 入力サイズの上限・内容検証を追加検討DoSや肥大化を避けるため
contentの最大長(例: 2000)や禁止内容(空文字のみ、巨大な連続空白など)をZodで制限しておくと事故を減らせます。README.md (2)
177-182: RESTエンドポイントの認証・応答スキーマ・ページング仕様を明文化実装者/クライアント双方の合意形成のため、要ログイン/レート制限、レスポンス形式、ソート順、ページング/カーソル等を記述してください。
提案差分:
### RESTエンドポイント -- `POST /api/dm` – `{ "from": "alice", "to": "bob", "content": "こんにちは" }` +- `POST /api/dm` (要ログイン, RateLimit適用) – `{ "from": "alice", "to": "bob", "content": "こんにちは" }` を送信してDMを作成 -- `GET /api/dm?user1=alice&user2=bob` – 2人間のメッセージ一覧を取得 +- `GET /api/dm?user1=alice&user2=bob&limit=50&before=ISO8601` (要ログイン, RateLimit適用) – 2人間のメッセージ一覧を取得(既定は新しい順、`before` でページング) + +レスポンス例: +```json +[ + { "id": "dm_...", "from": "alice@example.com", "to": "bob@example.com", "content": "こんにちは", "createdAt": "2025-08-22T12:34:56.000Z" } +] +``` + +WebSocket通知: +```json +{ "type": "dm", "payload": { "id": "dm_...", "from": "alice@example.com", "to": "bob@example.com", "content": "こんにちは", "createdAt": "2025-08-22T12:34:56.000Z" } } +```
183-199: ActivityPubでDM判定される条件を厳密化して明記
toが単一Actorでもcc/bto/bccに Public や followers が混在するとDMになりません。判定条件と落とし穴の注意を追記してください。提案差分:
### ActivityPub経由 -`Create` アクティビティで `Note` を送信する際、`to` 配列に受信者の -アクターURIを1件だけ指定するとDMとして扱われます。 +`Create` アクティビティで `Note` を送信する際、`to` 配列に受信者の +アクターURIを1件だけ指定し、かつ `cc`/`bto`/`bcc` に Public や followers 等のコレクションを含めない場合にDMとして扱われます。 +(Public, followers, outbox, collections, liked/likes などが含まれると公開投稿として処理されます)docs/multi-device-sync.md (1)
3-9: 同期仕様の詳細(順序、重複排除、再取得、通知ペイロード)を補足実装/クライアント間の取り決めとして、メッセージの並び順、欠落時の再同期方法、WS通知の形式と重複排除戦略を追記してください。
追記案:
- 並び順:
createdAt降順(または昇順)で返す。API/WSとも同一フィールド名。- 再同期:
GET /api/dm?user1&user2&before=ISO8601&limit=Nでページング。クライアントは最後に取得した最古のcreatedAtをbeforeに指定。- 重複排除:
idをキーに冪等にマージする(WSとAPI両経路で同一IDが来る可能性)。- WS通知ペイロード例:
{ "type": "dm", "payload": { "id": "...", "from": "alice@example.com", "to": "bob@example.com", "content": "...", "createdAt": "..." } }app/api/activity_handlers.ts (3)
92-110: DM判定をより厳密に(cc/bto/bcc を除外し、コレクション宛先を排除)
toが1件という条件に加えて、cc/bto/bccに Public や followers 等が混在するとDMにすべきでないため、明示的に空 or 非コレクションであることを確認してください。提案差分:
- // to が1件の Note は DM とみなす - if (objTypes.includes("Note") && toList.length === 1) { + // to が1件 かつ cc/bto/bcc が空 or 非コレクション の Note は DM とみなす + if (objTypes.includes("Note") && toList.length === 1) { const target = toList[0]; const isCollection = (url: string): boolean => { if (url === "https://www.w3.org/ns/activitystreams#Public") return true; try { const path = new URL(url).pathname; return path.endsWith("/followers") || path.endsWith("/following") || path.endsWith("/outbox") || path.endsWith("/collections") || path.endsWith("/liked") || path.endsWith("/likes"); } catch { return false; } }; if (isCollection(target)) return; + const ccList = Array.isArray(obj.cc) ? obj.cc : []; + const btoList = Array.isArray((obj as any).bto) ? (obj as any).bto : []; + const bccList = Array.isArray((obj as any).bcc) ? (obj as any).bcc : []; + if ([...ccList, ...btoList, ...bccList].some((x) => isCollection(String(x)))) { + return; + }
130-140: WS通知ペイロードに createdAt を含めてクライアントの整列/重複排除を容易にレスポンス/通知のスキーマを揃えるとクライアント実装が単純になります。
提案差分:
- const payload = { + const payload = { id: String(msg._id), from: fromHandle, to: toHandle, content: typeof obj.content === "string" ? obj.content : "", + createdAt: + (msg as any).published?.toISOString?.() ?? + (msg as any).createdAt?.toISOString?.() ?? + new Date().toISOString(), };
119-129: DM保存経路の統一と添付情報抽出の追加を検討してくださいActivityPub経由のDMでは
saveMessage、REST API経由のDMではsaveDMMessageを使っており、別々の永続化先に保存されるため、UIや履歴が分断されてしまいます。いずれか一方に統一するか、ビュー層で両者を統合する仕組みを導入してください。また、Note オブジェクトに含まれるattachment(s)もDM保存時に抽出・保存できるように実装を追加することをおすすめします。• 主な関連箇所
- app/api/activity_handlers.ts(
saveMessageを利用)- app/shared/db.ts(
saveDMMessage/listDMsBetween宣言)- app/api/DB/mongo.ts(
saveDMMessage/listDMsBetween実装)- app/api/routes/dm.ts(REST DM API ルート)
• 添付情報抽出を追加する例(保存先統一は方針に応じて調整)
const db = createDB(env); + const attachments = extractAttachments(obj); + const extra: Record<string, unknown> = + typeof obj.extra === "object" && obj.extra !== null + ? { ...(obj.extra as Record<string, unknown>) } + : {}; + if (attachments.length > 0) extra.attachments = attachments; const msg = await db.saveMessage( domain, actor, typeof obj.content === "string" ? obj.content : "", - typeof obj.extra === "object" && obj.extra !== null - ? obj.extra as Record<string, unknown> - : {}, + extra, { to: toList, cc: Array.isArray(obj.cc) ? obj.cc : [] }, ) as { _id: unknown };app/shared/db.ts (1)
69-71: DBインターフェースの定義確認以下の出力で
app/shared/db.tsのDBインターフェースが確認されました。saveDMMessageとlistDMsBetweenが共にPromise<unknown>を返す仕様になっており、型安全性が担保されていません。export interface DB { // ... saveDMMessage(from: string, to: string, content: string): Promise<unknown>; listDMsBetween(user1: string, user2: string): Promise<unknown[]>; // ... }指摘内容
- 戻り値が
unknownのため、呼び出し元での型チェックが困難DMMessage型({ id: string; from: string; to: string; content: string; createdAt: Date })を導入し、メソッドシグネチャをPromise<DMMessage>/Promise<DMMessage[]>に変更- ユーザー識別子は
username@domain形式に正規化する旨をコード上に明示対応案(例)
+export interface DMMessage { + id: string; + from: string; // username@domain + to: string; // username@domain + content: string; + createdAt: Date; +} export interface DB { @@ - saveDMMessage(from: string, to: string, content: string): Promise<unknown>; + saveDMMessage( + from: string /* username@domain */, + to: string /* username@domain */, + content: string + ): Promise<DMMessage>; @@ - listDMsBetween(user1: string, user2: string): Promise<unknown[]>; + listDMsBetween( + user1: string /* username@domain */, + user2: string /* username@domain */, + opts?: { limit?: number; before?: Date } + ): Promise<DMMessage[]>;実装上のポイント
app/api/models/takos/dm_message.tsですでに Mongoose モデルDMMessageが定義されているので、shared 側に同名の型を定義し、メソッド実装部では Mongoose ドキュメントをこの型にマッピングfrom/toの引数前処理で、たとえばnormalizeUsername(raw: string): stringを呼んでusername@domain形式を保証app/api/DB/mongo.ts (2)
360-368: $or を避けるためにペア正規化キー(pairKey)を保存し、クエリ効率を改善from/to の順序差分を吸収した
pairKey(例:min(from,to)|max(from,to))を保存しておくと、クエリ側で$orを使わず単一条件でヒットさせられます。併せて(tenant_id, pairKey, createdAt)の複合インデックスを張ることで大量会話でも安定した性能が出ます。以下の差分で pairKey を保存できます(最小変更):
- async saveDMMessage(from: string, to: string, content: string) { - const doc = new DMMessage({ from, to, content }); + async saveDMMessage(from: string, to: string, content: string) { + const pairKey = from < to ? `${from}|${to}` : `${to}|${from}`; + const doc = new DMMessage({ from, to, content, pairKey }); if (this.env["DB_MODE"] === "host") { (doc as unknown as { $locals?: { env?: Record<string, string> } }) .$locals = { env: this.env }; } await doc.save(); return doc.toObject(); }補助: モデル側の推奨インデックス(変更は別ファイル)
// app/api/models/takos/dm_message.ts(例) schema.index({ tenant_id: 1, pairKey: 1, createdAt: 1 });必要なら
dm_message.ts用の完全なパッチを作ります。
370-380: listDMsBetween のクエリ最適化とページング上限の追加を検討現状
$orで全件を昇順取得するため、会話履歴が多いとI/O/メモリ負荷が高くなります。上記のpairKeyを用いた単一条件化に加え、API/UXの要件に応じてページング(cursor/before/since/limit)を導入するのが安全です。少なくともハード上限(例: 500件)を付けると事故を防げます。また、並び替えキーcreatedAtはスキーマ側のtimestamps: true前提です(前コメントの確認参照)。
- ルート実装(
app/api/routes/dm.ts)が昇順前提か、クライアントがページングするかを確認してください。DMMessageにcreatedAtが存在すること(timestamps: true)を確認してください。最小修正(ハード上限のみ)の例:
- async listDMsBetween(user1: string, user2: string) { - const query = this.withTenant( - DMMessage.find({ - $or: [ - { from: user1, to: user2 }, - { from: user2, to: user1 }, - ], - }), - ); - return await query.sort({ createdAt: 1 }).lean(); + async listDMsBetween(user1: string, user2: string) { + const query = this.withTenant( + DMMessage.find({ + $or: [ + { from: user1, to: user2 }, + { from: user2, to: user1 }, + ], + }), + ); + return await query.sort({ createdAt: 1 }).limit(500).lean(); }推奨修正(pairKey に移行し、さらに効率化):
- async listDMsBetween(user1: string, user2: string) { - const query = this.withTenant( - DMMessage.find({ - $or: [ - { from: user1, to: user2 }, - { from: user2, to: user1 }, - ], - }), - ); - return await query.sort({ createdAt: 1 }).lean(); + async listDMsBetween(user1: string, user2: string) { + const pairKey = user1 < user2 ? `${user1}|${user2}` : `${user2}|${user1}`; + const query = this.withTenant(DMMessage.find({ pairKey })); + return await query.sort({ createdAt: 1 }).lean(); }さらに型活用の観点では、
Promise<DMMessageDoc[]>のように戻り値型を固定しておくと呼び出し側の安全性が上がります。必要ならページング対応(
opts: { before/after/limit })へインターフェースとルートを含めた一括パッチを提案します。
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (2)
app/client/deno.lockis excluded by!**/*.lockapp/takos_host/deno.lockis excluded by!**/*.lock
📒 Files selected for processing (42)
README.md(1 hunks)app/api/DB/mod.ts(1 hunks)app/api/DB/mongo.ts(2 hunks)app/api/activity_handlers.ts(2 hunks)app/api/models/takos/dm_message.ts(1 hunks)app/api/models/takos/encrypted_message.ts(0 hunks)app/api/models/takos/handshake_message.ts(0 hunks)app/api/models/takos/key_package.ts(0 hunks)app/api/models/takos_host/encrypted_message.ts(0 hunks)app/api/models/takos_host/handshake_message.ts(0 hunks)app/api/models/takos_host/key_package.ts(0 hunks)app/api/routes/activitypub.ts(0 hunks)app/api/routes/dm.ts(1 hunks)app/api/routes/e2ee.ts(0 hunks)app/api/routes/e2ee_test.ts(0 hunks)app/api/routes/ws.ts(1 hunks)app/api/server.ts(3 hunks)app/api/utils/activitypub.ts(0 hunks)app/client/deno.json(1 hunks)app/client/src/components/Application.tsx(1 hunks)app/client/src/components/Profile.tsx(1 hunks)app/client/src/components/Setting/index.tsx(1 hunks)app/client/src/components/chat/ChatRoomList.tsx(1 hunks)app/client/src/components/chat/ChatSettingsOverlay.tsx(1 hunks)app/client/src/components/chat/ChatTitleBar.tsx(0 hunks)app/client/src/components/chat/FriendList.tsx(1 hunks)app/client/src/components/chat/FriendRoomList.tsx(0 hunks)app/client/src/components/e2ee/api.ts(0 hunks)app/client/src/components/e2ee/binding.ts(0 hunks)app/client/src/components/e2ee/mls_message.ts(0 hunks)app/client/src/components/e2ee/mls_test.ts(0 hunks)app/client/src/components/e2ee/mls_wrapper.ts(0 hunks)app/client/src/components/e2ee/storage.ts(0 hunks)app/client/src/components/e2ee/useMLS.ts(0 hunks)app/client/src/components/microblog/api.ts(4 hunks)app/client/src/utils/config.ts(0 hunks)app/shared/db.ts(1 hunks)app/takos_host/client/src/api.ts(0 hunks)app/takos_host/client/src/pages/WelcomePage.tsx(3 hunks)docs/chat_ux.md(1 hunks)docs/key-sharing.md(1 hunks)docs/multi-device-sync.md(1 hunks)
💤 Files with no reviewable changes (21)
- app/client/src/utils/config.ts
- app/api/models/takos_host/handshake_message.ts
- app/api/models/takos/key_package.ts
- app/client/src/components/e2ee/binding.ts
- app/api/models/takos_host/key_package.ts
- app/api/routes/activitypub.ts
- app/client/src/components/e2ee/mls_test.ts
- app/client/src/components/e2ee/useMLS.ts
- app/api/routes/e2ee_test.ts
- app/client/src/components/chat/ChatTitleBar.tsx
- app/api/models/takos/handshake_message.ts
- app/client/src/components/chat/FriendRoomList.tsx
- app/api/utils/activitypub.ts
- app/takos_host/client/src/api.ts
- app/api/models/takos_host/encrypted_message.ts
- app/api/models/takos/encrypted_message.ts
- app/client/src/components/e2ee/mls_message.ts
- app/api/routes/e2ee.ts
- app/client/src/components/e2ee/storage.ts
- app/client/src/components/e2ee/api.ts
- app/client/src/components/e2ee/mls_wrapper.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: TypeScriptの型定義を活用する
エラーハンドリングを適切に実装する
Files:
app/client/src/components/chat/FriendList.tsxapp/client/src/components/Setting/index.tsxapp/client/src/components/Profile.tsxapp/api/models/takos/dm_message.tsapp/client/src/components/chat/ChatSettingsOverlay.tsxapp/api/routes/dm.tsapp/api/DB/mod.tsapp/api/DB/mongo.tsapp/api/routes/ws.tsapp/shared/db.tsapp/takos_host/client/src/pages/WelcomePage.tsxapp/api/server.tsapp/client/src/components/chat/ChatRoomList.tsxapp/client/src/components/microblog/api.tsapp/client/src/components/Application.tsxapp/api/activity_handlers.ts
app/api/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
環境変数は常に getEnv(c) を用いてコンテキストから取得する
Files:
app/api/models/takos/dm_message.tsapp/api/routes/dm.tsapp/api/DB/mod.tsapp/api/DB/mongo.tsapp/api/routes/ws.tsapp/api/server.tsapp/api/activity_handlers.ts
app/api/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Honoフレームワークのミドルウェアパターンに従う
Files:
app/api/models/takos/dm_message.tsapp/api/routes/dm.tsapp/api/DB/mod.tsapp/api/DB/mongo.tsapp/api/routes/ws.tsapp/api/server.tsapp/api/activity_handlers.ts
app/api/models/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
MongooseのスキーマでDBモデルを定義する
Files:
app/api/models/takos/dm_message.ts
app/api/{activity_handlers.ts,utils/activitypub*.ts}
📄 CodeRabbit inference engine (CLAUDE.md)
ActivityPubの仕様に準拠したJSON-LD形式を使用する
Files:
app/api/activity_handlers.ts
🧠 Learnings (3)
📚 Learning: 2025-08-13T10:14:15.274Z
Learnt from: CR
PR: takoserver/takos#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-13T10:14:15.274Z
Learning: Applies to app/api/models/**/*.ts : MongooseのスキーマでDBモデルを定義する
Applied to files:
app/api/models/takos/dm_message.tsapp/api/DB/mongo.ts
📚 Learning: 2025-08-13T10:14:15.274Z
Learnt from: CR
PR: takoserver/takos#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-13T10:14:15.274Z
Learning: Applies to app/api/**/*.{ts,tsx} : Honoフレームワークのミドルウェアパターンに従う
Applied to files:
app/api/routes/dm.ts
📚 Learning: 2025-08-13T10:14:15.274Z
Learnt from: CR
PR: takoserver/takos#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-13T10:14:15.274Z
Learning: Applies to app/api/{activity_handlers.ts,utils/activitypub*.ts} : ActivityPubの仕様に準拠したJSON-LD形式を使用する
Applied to files:
app/api/DB/mongo.tsapp/client/src/components/microblog/api.tsapp/api/activity_handlers.ts
🧬 Code graph analysis (7)
app/api/models/takos/dm_message.ts (1)
app/api/models/plugins/tenant_scope.ts (1)
tenantScope(25-75)
app/client/src/components/chat/ChatSettingsOverlay.tsx (1)
app/client/src/components/chat/types.ts (1)
Room(21-39)
app/api/routes/dm.ts (3)
app/api/DB/mod.ts (1)
createDB(11-13)app/shared/config.ts (1)
getEnv(49-56)app/api/routes/ws.ts (1)
sendToUser(23-30)
app/takos_host/client/src/pages/WelcomePage.tsx (1)
app/takos_host/client/src/state.ts (2)
userNameState(6-6)rootDomainState(11-11)
app/client/src/components/chat/ChatRoomList.tsx (1)
app/client/src/components/chat/FriendList.tsx (1)
FriendList(25-279)
app/client/src/components/microblog/api.ts (1)
app/api/services/user-info.ts (1)
UserInfo(6-12)
app/api/activity_handlers.ts (5)
app/api/DB/mod.ts (1)
createDB(11-13)app/api/routes/ws.ts (2)
sendToUser(23-30)broadcast(32-39)app/api/utils/activitypub.ts (2)
deliverActivityPubObject(194-261)getDomain(684-687)app/api/DB/mongo.ts (1)
saveObject(51-117)app/api/services/user-info.ts (2)
getUserInfo(79-184)formatUserInfoForPost(315-345)
🔇 Additional comments (15)
app/client/src/components/chat/FriendList.tsx (1)
238-238: ワンライナー化は問題ありません(挙動不変)クリックハンドラを簡潔にした変更は挙動を変えず可読性も向上しています。
app/client/src/components/Profile.tsx (1)
242-249: openChat の簡素化は新しい DM モデルと整合的です部屋生成 API 呼び出しを排し、ハンドルへ正規化してチャット画面へ遷移するだけにしたのは、1:1 スレッドへ収束した今回の方針と一致しています。
app/takos_host/client/src/pages/WelcomePage.tsx (2)
1-3: onMount の導入と状態利用の差し替えは妥当です
onMountの追加とrootDomainState/userNameStateの導入は今回の初期 DM 同期フローと整合的です。
197-201: Features の「プライベートDM」置換は方向性に合致E2EE 強調からシンプル DM へコピーを刷新した点は、今回の PR 目的と一致しています。
app/api/models/takos/dm_message.ts (2)
11-11: テナントスコープの適用は適切
tenantScopeプラグインでtenant_idを自動付与・クエリ強制している点は要件に合致しています。
13-17: モデル再利用パターンは妥当(Hot reload 耐性)
mongoose.models.DMMessage ?? mongoose.model(...)のパターンで重複登録を回避できており問題ありません。app/client/src/components/Application.tsx (1)
35-40: registerUser の送信タイミングは安全に実装されています
registerUserは以下の仕組みで、WebSocket 接続前後いずれの場合も必ずユーザー登録メッセージを送信することが確認できました。
registerUser(user)実行時にsocketが未生成であれば、currentUserを更新したうえでconnectWebSocket()を呼び出し、即座に返却connectWebSocket()内のsocket.onopenハンドラで、接続完了時に必ずcurrentUser(先に設定されたユーザー名)を送信socketが存在し接続中(readyState !== OPEN)の場合は onopen ハンドラ、それ以外にすでにOPENなら即時送信以上により、接続確立前に呼び出された登録リクエストがロストすることはありません。追加の修正は不要と判断します。
app/client/src/components/microblog/api.ts (1)
409-417: クライアント側キャッシュマッピングのリファクタリングを推奨
setCachedUserInfoは同期関数のため、Promise.allで並列実行しても意味がありません。- 現状のように
uncached[index] → fetchedInfos[index]の順序依存で割り当てると、サーバーの応答順がずれたり欠損した場合に別のユーザー情報を誤ってキャッシュするリスクがあります。以下のように、レスポンス内の各
UserInfoから一意な識別子を合成してマッピングする実装をおすすめします。これにより順序に依存せず安全にキャッシュを更新できます。- await Promise.all( - fetchedInfos.map((info, index) => - setCachedUserInfo(uncached[index], info) - ), - ); - - fetchedInfos.forEach((info, index) => { - fetchedMap[uncached[index]] = info; - }); + for (const info of fetchedInfos) { + // isLocal フラグなどを使って一意のキーを合成 + const id = info.isLocal + ? info.userName + : `${info.userName}@${info.domain}`; + setCachedUserInfo(id, info); + fetchedMap[id] = info; + }あわせて、バックエンドの
/api/users/batchエンドポイントが「要求順を固定した配列」を返しているのか、「キー付き(オブジェクト)で返しているのか」を確認してください。キー付きレスポンスに変更できる場合は、クライアント/サーバーともに識別子付きの仕様に揃えるとさらに堅牢になります。
- バックエンド実装や API ドキュメントを参照し、返却データに ID フィールドが含まれているか確認してください。
- 必要に応じてサーバー側もレスポンス仕様を見直し、クライアントと同じ識別子設計に合わせましょう。
app/api/DB/mod.ts (1)
8-8: 公開Surfaceの縮小は妥当です
startKeyPackageCleanupJobを公開から外し、必要最小限の export に整理されている点、OKです。app/api/server.ts (3)
27-27: dm ルートの追加は妥当ですAPI 面の置換(e2ee → dm)に沿った適切なエクスポート/マウントです。
83-83: 公開側ルートの整理もOKroot側にdmを出していない点は意図通り(DMは /api 配下)。副作用なし。
55-76: /api/dm はミドルウェア配下でレート制限が適用されています
app/api/routes/dm.ts内の環境変数取得は、30行目と50行目の2箇所でgetEnv(c)が使用されていることを確認しました。- 直接
c.get('env')の呼び出しは存在しませんでした。以上より、レート制限構成および環境変数取得はガイドラインに準拠しているため問題ありません。
app/api/activity_handlers.ts (2)
81-83: object の型チェックと早期 return は適切Create/Noteの前提条件として妥当です。
158-188: 非DMフローの処理は概ねOK(保存→整形→タイムライン通知)
getUserInfo→formatUserInfoForPost→broadcast/sendToUserの流れは既存設計と整合しています。app/api/DB/mongo.ts (1)
21-21: DBインターフェースとMongoDB実装の整合性について
app/shared/db.tsのDBインターフェースにはsaveDMMessage/listDMsBetweenが定義されており、app/api/DB/mongo.tsのMongoDBクラスで両メソッドが実装されています。他にimplements DBしているクラスは存在しないため、現状型エラーや実行時未定義のリスクはありません。戻り値に
Promise<DMMessageDoc>のような型エイリアスを導入すると、呼び出し側の補完および型安全性がさらに向上します。
| const targetHost = (() => { | ||
| try { | ||
| return new URL(target).hostname; | ||
| } catch { | ||
| return ""; | ||
| } | ||
| })(); | ||
| if (targetHost && targetHost !== domain) { | ||
| deliverActivityPubObject([target], activity, actor, domain, env).catch( | ||
| (err) => { | ||
| console.error("Delivery failed:", err); | ||
| }, | ||
| ); | ||
| } | ||
| return; |
There was a problem hiding this comment.
リモート配送の判定が acct:/user@domain 形式で機能しない(配送漏れ)
new URL(target) でホスト抽出しているため、acct:alice@example.com や alice@example.com を受け取るケースで targetHost が空になり配送がスキップされます。ローカル/リモート判定を包括的にしてください。
修正差分:
- const targetHost = (() => {
- try {
- return new URL(target).hostname;
- } catch {
- return "";
- }
- })();
- if (targetHost && targetHost !== domain) {
+ const targetDomain = (() => {
+ try {
+ if (target.startsWith("http")) return new URL(target).hostname;
+ const acct = target.startsWith("acct:") ? target.slice(5) : target;
+ if (acct.includes("@")) return acct.split("@").pop() || "";
+ } catch (_) {}
+ return "";
+ })();
+ // ローカル以外は配送対象(acct:/user@domain も対象)
+ if (targetDomain && targetDomain !== domain) {
deliverActivityPubObject([target], activity, actor, domain, env).catch(
(err) => {
console.error("Delivery failed:", err);
},
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const targetHost = (() => { | |
| try { | |
| return new URL(target).hostname; | |
| } catch { | |
| return ""; | |
| } | |
| })(); | |
| if (targetHost && targetHost !== domain) { | |
| deliverActivityPubObject([target], activity, actor, domain, env).catch( | |
| (err) => { | |
| console.error("Delivery failed:", err); | |
| }, | |
| ); | |
| } | |
| return; | |
| const targetDomain = (() => { | |
| try { | |
| if (target.startsWith("http")) return new URL(target).hostname; | |
| const acct = target.startsWith("acct:") ? target.slice(5) : target; | |
| if (acct.includes("@")) return acct.split("@").pop() || ""; | |
| } catch (_) {} | |
| return ""; | |
| })(); | |
| // ローカル以外は配送対象(acct:/user@domain も対象) | |
| if (targetDomain && targetDomain !== domain) { | |
| deliverActivityPubObject([target], activity, actor, domain, env).catch( | |
| (err) => { | |
| console.error("Delivery failed:", err); | |
| }, | |
| ); | |
| } | |
| return; |
🤖 Prompt for AI Agents
In app/api/activity_handlers.ts around lines 141 to 155, the check that
determines remote delivery uses new URL(target) which returns empty for
acct:alice@example.com and plain alice@example.com so delivery is skipped;
update the host-extraction to handle three cases: 1) if target starts with
"acct:" parse the part after "acct:" and extract the domain after the last "@";
2) if target matches an email-like pattern (user@host) extract the host after
"@"; 3) otherwise attempt new URL(target). Normalize the extracted host (trim,
toLowerCase) and then compare to domain; if host exists and differs from domain,
call deliverActivityPubObject as before. Ensure the implementation handles
malformed inputs safely and falls back to "" on failure.
| import HandshakeMessage from "../models/takos/handshake_message.ts"; | ||
| import HostHandshakeMessage from "../models/takos_host/handshake_message.ts"; | ||
| import PendingInvite from "../models/takos/pending_invite.ts"; | ||
| import DMMessage from "../models/takos/dm_message.ts"; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
DMMessage の導入は妥当。timestamps/インデックス/テナント対応の前提確認をお願いします
DMMessage の利用でDM保存がシンプルになっていて良いです。一方で本ファイルでは createdAt を前提に並び替えています(後述)。dm_message.ts 側で timestamps: true(createdAt/updatedAt)と、ホストモードでのテナント分離用プラグイン($locals.env 依存)が有効になっているかを確認してください。合わせて検索性能のために (tenant_id, from, to, createdAt) もしくは後述の pairKey を使った複合インデックスの有無も要チェックです。
以下でスキーマ前提を確認できます:
必要なら dm_message.ts に最適なインデックス定義のパッチも用意します。
🏁 Script executed:
#!/bin/bash
# dm_message.ts のスキーマ定義・timestamps・index・テナント対応を確認
fd -a 'dm_message.ts' app | xargs -I{} sh -c 'echo "--- {} ---"; nl -ba "{}" | sed -n "1,220p"'
rg -nC2 -e 'timestamps\s*:\s*true' app/**/dm_message.ts
rg -nC2 -e '\.index\(' app/**/dm_message.ts
rg -nC3 -e '\$locals' app/**/dm_message.tsLength of output: 518
先程のスクリプトでは nl コマンドが存在せず、また rg のファイルパス指定も誤っていたため出力が得られませんでした。以下のスクリプトで再度 dm_message.ts のスキーマ定義、timestamps、インデックス、テナント対応を確認してください。
🏁 Script executed:
#!/bin/bash
# dm_message.ts のスキーマ定義・timestamps・index・テナント対応を再確認
FILE=app/api/models/takos/dm_message.ts
if [ ! -f "$FILE" ]; then
echo "Error: $FILE が存在しません"
exit 1
fi
echo "--- $FILE ---"
# ファイル先頭200行を表示
cat -n "$FILE" | sed -n '1,200p'
echo "\n-- timestamps: true の設定を検索 --"
rg -nC2 -e 'timestamps\s*:\s*true' "$FILE"
echo "\n-- インデックス定義 (.index) を検索 --"
rg -nC2 -e '\.index\(' "$FILE"
echo "\n-- テナント分離 ($locals) を検索 --"
rg -nC3 -e '\$locals' "$FILE"Length of output: 1330
timestamps オプション未設定かつ複合インデックス未定義のため、dm_message.ts 側は以下のポイントで要対応です。
次に、テナント分離プラグインの実装を確認し、$locals.env 依存やスキーマへのテナントフィールド追加をチェックしてください。
🏁 Script executed:
#!/bin/bash
# tenant_scope プラグイン実装を確認
FILE=app/api/models/plugins/tenant_scope.ts
if [ ! -f "$FILE" ]; then
echo "Error: プラグインファイルが見つかりません: $FILE"
exit 1
fi
echo "--- $FILE ---"
# ファイル先頭200行を表示
cat -n "$FILE" | sed -n '1,200p'
echo "\n-- \$locals(ドキュメント・クエリローカル)使用箇所を検索 --"
rg -nC2 -e '\$locals' "$FILE"
echo "\n-- envKey パラメータ使用箇所を検索 --"
rg -nC2 -e 'envKey' "$FILE"
echo "\n-- schema.add や index 定義を追加しているか検索 --"
rg -nC2 -e 'schema\.add' "$FILE"
rg -nC2 -e 'index\(' "$FILE"Length of output: 3762
DMMessage スキーマに timestamps と複合インデックスを追加してください
DMMessage の導入自体は正しく、テナント分離プラグインも tenant_id を追加していますが、現状では以下の課題があります。
- timestamps 未設定
現在のスキーマではcreatedAtを手動定義しているのみで、Mongoose の自動管理されるtimestampsオプション(createdAt/updatedAt)が使われていません。 - 複合インデックス未定義
テナント+送信者/受信者/作成日時での検索性能向上のため、(tenant_id, from, to, createdAt)の複合インデックスが必要です。
対応箇所は以下のとおりです。
-
app/api/models/takos/dm_message.ts
- Schema 定義に
{ timestamps: true }を追加し、手動定義のcreatedAtを削除 - 複合インデックスを追加
import mongoose from "mongoose"; import tenantScope from "../plugins/tenant_scope.ts";
- Schema 定義に
-
const dmMessageSchema = new mongoose.Schema({
-
from: { type: String, required: true, index: true },
-
to: { type: String, required: true, index: true },
-
content: { type: String, required: true },
-
createdAt: { type: Date, default: Date.now },
-
});
-
const dmMessageSchema = new mongoose.Schema({
-
from: { type: String, required: true, index: true },
-
to: { type: String, required: true, index: true },
-
content: { type: String, required: true },
-
}, { timestamps: true });
-
// 複合インデックス(テナント・送信元・送信先・作成日時)
-
dmMessageSchema.index({
-
tenant_id: 1,
-
from: 1,
-
to: 1,
-
createdAt: 1,
-
});
dmMessageSchema.plugin(tenantScope, { envKey: "ACTIVITYPUB_DOMAIN" });
const DMMessage = mongoose.models.DMMessage ??
mongoose.model("DMMessage", dmMessageSchema, "dm_messages");
export default DMMessage;
export { dmMessageSchema };
- app/api/models/plugins/tenant_scope.ts
- プラグインは `tenant_id` フィールドと単一インデックスを正しく追加しています。
- `options.envKey`(デフォルトでは `"ACTIVITYPUB_DOMAIN"`)が稼働環境で正しく設定されているかご確認ください。
以上を反映いただくことで、作成日時によるソート・検索性能の担保および自動更新されるタイムスタンプが有効化されます。
必要に応じてパッチの作成をお手伝いしますのでご相談ください。
> Committable suggestion skipped: line range outside the PR's diff.
<details>
<summary>🤖 Prompt for AI Agents</summary>
In app/api/models/takos/dm_message.ts (update schema definition) and review
app/api/models/plugins/tenant_scope.ts (envKey check): remove the manual
createdAt field from the DMMessage schema, enable Mongoose automatic timestamps
by passing { timestamps: true } to the Schema constructor, and add a composite
index on (tenant_id, from, to, createdAt) to improve tenant-scoped queries;
leave tenant_scope plugin behavior as-is (it already adds tenant_id and a single
index) but verify options.envKey is correctly set in your environment.
</details>
<!-- fingerprinting:phantom:poseidon:chinchilla -->
<!-- This is an auto-generated comment by CodeRabbit -->
| const dmMessageSchema = new mongoose.Schema({ | ||
| from: { type: String, required: true, index: true }, | ||
| to: { type: String, required: true, index: true }, | ||
| content: { type: String, required: true }, | ||
| createdAt: { type: Date, default: Date.now }, | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
スキーマのバリデーション強化とクエリ最適化(インデックス追加・timestamps化)
DM の主ユースケース(2ユーザー間の時系列取得)に対し、現状は単項インデックスのみでクエリ効率とスキーマ堅牢性が不足します。以下を推奨します。
- content に
trimとmaxlength(例: 8KB 程度)を付与 - from/to に
trimと長さ制約(例: <= 2048) timestampsでcreatedAtを自動付与(updatedAtは不要なら無効化)- 代表的クエリに効く複合インデックス(tenant_id, from, to, createdAt desc)
適用例:
-const dmMessageSchema = new mongoose.Schema({
- from: { type: String, required: true, index: true },
- to: { type: String, required: true, index: true },
- content: { type: String, required: true },
- createdAt: { type: Date, default: Date.now },
-});
+const dmMessageSchema = new mongoose.Schema(
+ {
+ from: { type: String, required: true, index: true, trim: true, minlength: 1, maxlength: 2048 },
+ to: { type: String, required: true, index: true, trim: true, minlength: 1, maxlength: 2048 },
+ content: { type: String, required: true, trim: true, maxlength: 8192 },
+ },
+ {
+ timestamps: { createdAt: true, updatedAt: false },
+ versionKey: false,
+ },
+);
+
+// 2者会話の時系列取得で効く複合インデックス
+dmMessageSchema.index({ tenant_id: 1, from: 1, to: 1, createdAt: -1 });
+// 逆方向の条件にもヒットしやすく(OR クエリが多い場合)
+dmMessageSchema.index({ tenant_id: 1, to: 1, from: 1, createdAt: -1 });補足: 将来的にスパム対策で rate-limit/サイズ制限を API レイヤでも行うと万全です。
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/api/models/takos/dm_message.ts around lines 4–9, strengthen schema
validation and query performance: add trim and maxlength to content (e.g., 8192)
and trim/maxlength to from and to (e.g., 2048); add a tenant_id field (String,
required/indexed) if not present; enable mongoose timestamps (use timestamps: {
createdAt: true, updatedAt: false }) and remove manual createdAt field; and
create a compound index on { tenant_id: 1, from: 1, to: 1, createdAt: -1 } to
speed common two-user time-ordered queries. Ensure indexes are declared on the
schema and validation limits are applied as described.
| // DM 用のシンプルな REST エンドポイント | ||
|
|
||
| const app = new Hono(); | ||
| app.use("/dm/*", authRequired); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
/dm が未保護になるパス指定のバグ(認証バイパス)
app.use("/dm/*", authRequired) だと /dm 直下(POST/GET)はマッチせず未保護になります。高リスクです。/dm と /dm/* の両方にミドルウェアを適用してください。
- app.use("/dm/*", authRequired);
+ app.use("/dm", authRequired);
+ app.use("/dm/*", authRequired);🏁 Script executed:
#!/bin/bash
# Honoの他ルートでも同様の指定漏れが無いか確認
rg -nP "app\.use\(['\"]/[^'\"]+\*/['\"],\s*authRequired\)" app/api | sed -n '1,200p'
rg -nP "app\.(get|post|put|delete)\(['\"]/dm['\"]" app/api/routes/dm.ts -n -C2Length of output: 175
dm.ts のルート定義を確認するため、ファイル先頭 200 行を出力します。
🏁 Script executed:
sed -n '1,200p' app/api/routes/dm.tsLength of output: 1369
「/dm」エンドポイントが認証対象から漏れています
app.use("/dm/*", authRequired) だと、/dm(POST/GET)のパスにはマッチせず認証バイパスが発生します。高リスクな状態なので、以下のように /dm と /dm/* の両方にミドルウェアを適用してください。
対象ファイル
- app/api/routes/dm.ts
修正例
- app.use("/dm/*", authRequired);
+ // /dm 直下のエンドポイントにも認証を適用
+ app.use("/dm", authRequired);
+ // /dm/以下のサブパスにも認証を適用
+ app.use("/dm/*", authRequired);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| app.use("/dm/*", authRequired); | |
| // /dm 直下のエンドポイントにも認証を適用 | |
| app.use("/dm", authRequired); | |
| // /dm/以下のサブパスにも認証を適用 | |
| app.use("/dm/*", authRequired); |
🤖 Prompt for AI Agents
In app/api/routes/dm.ts around line 12, the middleware registration
app.use("/dm/*", authRequired) misses the exact "/dm" path so requests to "/dm"
bypass authentication; update the routing to apply authRequired to both "/dm"
and "/dm/*" (or equivalent pattern that covers both the base path and subpaths)
so that POST/GET to "/dm" are also protected.
| "json", | ||
| z.object({ | ||
| from: z.string(), | ||
| to: z.string(), | ||
| content: z.string(), | ||
| }), | ||
| ), |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
送信者をリクエストBodyから受け取るのはなりすまし可能—認証主体から厳密に導出を
from をクライアント入力に依存すると、任意ユーザーを騙ったDM送信が可能です。認証ミドルウェアのコンテキストから送信者IDを取得し、Bodyからは外してください。加えてバリデーションを強化してください(長さ/空文字/上限など)。
例(概念 diff。authRequired が c.get("user") に { id: string } を格納する想定):
- zValidator(
- "json",
- z.object({
- from: z.string(),
- to: z.string(),
- content: z.string(),
- }),
- ),
+ zValidator(
+ "json",
+ z.object({
+ to: z.string().min(1).max(320),
+ content: z.string().min(1).max(2000),
+ }),
+ ),
async (c) => {
- const { from, to, content } = c.req.valid("json") as {
- from: string;
- to: string;
- content: string;
- };
+ const { to, content } = c.req.valid("json") as { to: string; content: string };
+ const auth = c.get("user") as { id: string } | undefined;
+ if (!auth?.id) return c.json({ error: "unauthorized" }, 401);
+ const from = auth.id;
const db = createDB(getEnv(c));
- const doc = await db.saveDMMessage(from, to, content) as { _id: string };
- const payload = { id: doc._id, from, to, content };
+ try {
+ const doc = await db.saveDMMessage(from, to, content) as { _id: string };
+ const payload = { id: doc._id, from, to, content };
+ // オフラインでもsendToUserは黙ってreturnする想定でOK
+ sendToUser(to, { type: "dm", payload });
+ sendToUser(from, { type: "dm", payload });
+ return c.json(payload, 201);
+ } catch (e) {
+ return c.json({ error: "failed to persist DM" }, 500);
+ }
},Also applies to: 25-36
🏁 Script executed:
#!/bin/bash
# authRequired が user情報をどこへ格納するか確認(c.get('user') or c.var.user 等)
rg -n "authRequired" app/api | sed -n '1,200p'
rg -nP "c\.(get|var)\(['\"]user['\"]" app/api -n -C2Length of output: 2266
🏁 Script executed:
#!/bin/bash
# Extract the auth middleware implementation to see where it stores authenticated user info
sed -n '1,200p' app/api/utils/auth.tsLength of output: 901
🏁 Script executed:
#!/bin/bash
# Locate and show the implementation of createAuthMiddleware to see how it stores session/user info
rg -n 'createAuthMiddleware' -C50 shared/auth.tsLength of output: 103
🏁 Script executed:
#!/bin/bash
# Locate shared/auth.ts and print its contents to inspect how the middleware attaches the user to context
if [ -f shared/auth.ts ]; then
sed -n '1,200p' shared/auth.ts
else
echo "shared/auth.ts not found in root directory"
fiLength of output: 122
🏁 Script executed:
#!/bin/bash
# Locate implementation of createAuthMiddleware
rg -n "createAuthMiddleware" -C5 .
# Dump auth middleware implementation if found
if [ -f app/shared/auth.ts ]; then
echo "--- app/shared/auth.ts ---"
sed -n '1,200p' app/shared/auth.ts
fi
# Dump SessionDoc type definition
if [ -f app/shared/types.ts ]; then
echo "--- app/shared/types.ts ---"
sed -n '1,200p' app/shared/types.ts
fiLength of output: 4897
DM送信エンドポイントの送信者をリクエストボディから取得しない
DM 送信時に from をクライアント入力に依存すると、他ユーザーになりすまして送信できる攻撃経路が残ります。必ず認証コンテキスト(セッション)からログインユーザーID を取得し、リクエストボディからは除外してください。また、to と content のバリデーション(長さ・空文字防止など)も強化してください。
対象ファイル:
- app/api/routes/dm.ts (行17–23付近)
変更案(概念 diff):
--- a/app/api/routes/dm.ts
@@
- zValidator(
- "json",
- z.object({
- from: z.string(),
- to: z.string(),
- content: z.string(),
- }),
- ),
+ zValidator(
+ "json",
+ z.object({
+ to: z.string().min(1).max(320),
+ content: z.string().min(1).max(2000),
+ }),
+ ),
@@
- const { from, to, content } = c.req.valid("json") as {
- from: string;
- to: string;
- content: string;
- };
+ const { to, content } = c.req.valid("json") as {
+ to: string;
+ content: string;
+ };
+ // 認証ミドルウェアでセットされたユーザー情報を取得
+ const user = c.get("user") as { id: string } | undefined;
+ if (!user?.id) return c.json({ error: "unauthorized" }, 401);
+ const from = user.id;
const db = createDB(getEnv(c));
- const doc = await db.saveDMMessage(from, to, content) as { _id: string };
+ const doc = await db.saveDMMessage(from, to, content) as { _id: string };加えて、現在の認証ミドルウェア実装ではセッション情報をコンテキストにセットしていないため、以下のように attach オプションを追加し、ユーザーID を c.set("user", { id: ... }) で登録できるようにしてください。
--- a/app/api/utils/auth.ts
@@ const authRequired: MiddlewareHandler = createAuthMiddleware<SessionDoc>({
updateSession: async (session, expires, c) => {
// セッション更新処理
},
+ attach: (c, session) => {
+ // session に userId が含まれる前提
+ c.set("user", { id: (session as any).userId });
+ },
});以上の対応で、なりすましリスクを排除しつつバリデーションを強化できます。
🤖 Prompt for AI Agents
In app/api/routes/dm.ts around lines 17–23, the handler currently accepts a
`from` field from the request body which allows impersonation; remove `from`
from the request schema and instead read the authenticated user id from the
request context/session (e.g. c.get("user").id or similar) populated by the auth
middleware; tighten the schema to validate `to` (non-empty string, max length)
and `content` (non-empty, trimmed, max length) and enforce those constraints
before sending; also update the authentication middleware to use the attach
option so it sets the session user into the context (e.g. c.set("user",{id:
...})) so the route can reliably obtain the sender id from the server-side
session rather than client input.
| app.get( | ||
| "/dm", | ||
| zValidator( | ||
| "query", | ||
| z.object({ user1: z.string(), user2: z.string() }), | ||
| ), | ||
| async (c) => { | ||
| const { user1, user2 } = c.req.valid("query") as { | ||
| user1: string; | ||
| user2: string; | ||
| }; | ||
| const db = createDB(getEnv(c)); | ||
| const messages = await db.listDMsBetween(user1, user2); | ||
| return c.json(messages); | ||
| }, | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
DM取得APIにアクセス制御がありません—当事者以外が他人のDMを閲覧可能
GET /dm でも認証主体が user1/user2 のいずれかであることを確認してください。確認が無いと第三者が任意の2者間DMを取得できます。
例:
async (c) => {
const { user1, user2 } = c.req.valid("query") as {
user1: string;
user2: string;
};
+ const auth = c.get("user") as { id: string } | undefined;
+ if (!auth?.id) return c.json({ error: "unauthorized" }, 401);
+ if (auth.id !== user1 && auth.id !== user2) {
+ return c.json({ error: "forbidden" }, 403);
+ }
const db = createDB(getEnv(c));
- const messages = await db.listDMsBetween(user1, user2);
- return c.json(messages);
+ try {
+ const messages = await db.listDMsBetween(user1, user2);
+ return c.json(messages);
+ } catch (_e) {
+ return c.json({ error: "failed to load DMs" }, 500);
+ }
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| app.get( | |
| "/dm", | |
| zValidator( | |
| "query", | |
| z.object({ user1: z.string(), user2: z.string() }), | |
| ), | |
| async (c) => { | |
| const { user1, user2 } = c.req.valid("query") as { | |
| user1: string; | |
| user2: string; | |
| }; | |
| const db = createDB(getEnv(c)); | |
| const messages = await db.listDMsBetween(user1, user2); | |
| return c.json(messages); | |
| }, | |
| ); | |
| app.get( | |
| "/dm", | |
| zValidator( | |
| "query", | |
| z.object({ user1: z.string(), user2: z.string() }), | |
| ), | |
| async (c) => { | |
| const { user1, user2 } = c.req.valid("query") as { | |
| user1: string; | |
| user2: string; | |
| }; | |
| // Ensure the caller is authenticated | |
| const auth = c.get("user") as { id: string } | undefined; | |
| if (!auth?.id) { | |
| return c.json({ error: "unauthorized" }, 401); | |
| } | |
| // Only allow if the caller is one of the two participants | |
| if (auth.id !== user1 && auth.id !== user2) { | |
| return c.json({ error: "forbidden" }, 403); | |
| } | |
| const db = createDB(getEnv(c)); | |
| try { | |
| const messages = await db.listDMsBetween(user1, user2); | |
| return c.json(messages); | |
| } catch (_e) { | |
| return c.json({ error: "failed to load DMs" }, 500); | |
| } | |
| }, | |
| ); |
🤖 Prompt for AI Agents
In app/api/routes/dm.ts around lines 39–54, the GET /dm handler lacks access
control allowing any caller to fetch any two users' DMs; retrieve the
authenticated user id from the request/context (e.g., from c.req.user,
c.getAuthUser(), or the framework auth helper used elsewhere), verify that the
authenticated id equals either user1 or user2 from the validated query, and if
not return a 403 Forbidden response before calling db.listDMsBetween; keep
validation and existing DB call intact and add the early auth check and error
response.
| "zod": "npm:zod@^3.24.4", | ||
| "bcrypt": "https://deno.land/x/bcrypt@v0.4.1/mod.ts", | ||
| "ts-mls": "npm:ts-mls@^1.1.0" | ||
| "bcrypt": "https://deno.land/x/bcrypt@v0.4.1/mod.ts" |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
bcrypt の重複・混在(bcryptjs と deno.land/x/bcrypt)を解消してください
クライアント側の imports に bcryptjs と bcrypt(deno.land/x)が同時に存在しています。バンドル環境(Vite)とランタイム(ブラウザ/Tauri)での互換性や重複サイズ増を招く懸念があります。現状どちらが実際に参照されているかを確認のうえ、片方に統一をお願いします。フロントエンドでの用途なら純 JS 実装の bcryptjs で十分なケースが多いです。
以下のスクリプトでクライアント配下での使用箇所を洗い出せます(使用されていなければ imports から削除可能です)。
🏁 Script executed:
#!/bin/bash
# クライアント配下での bcrypt 利用状況確認
rg -n --type=ts --type=tsx -C2 -g 'app/client/**' -P '(from\s+[\'"]bcryptjs[\'"]|from\s+[\'"]bcrypt[\'"]|import\([\'"]bcrypt(js)?[\'"]\))'Length of output: 321
以下の簡易スクリプトで再度クライアント配下の bcrypt/bcryptjs 利用箇所を洗い出してみてください:
🏁 Script executed:
#!/bin/bash
# クライアント配下での bcrypt 利用状況確認 (シンプル版)
rg -n --type=ts --type=tsx -C2 -g 'app/client/**' \
-e 'import.*bcrypt' \
-e 'import\(.*bcrypt'Length of output: 121
以下のスクリプトで、app/client 配下のあらゆる拡張子ファイルを対象に bcryptjs および deno.land/x/bcrypt の使用箇所を洗い出してみてください。
🏁 Script executed:
#!/bin/bash
# クライアント配下での bcryptjs / deno bcrypt 利用状況確認
echo "🔍 bcryptjs の検索"
rg -n -g 'app/client/**/*.{ts,tsx,js,jsx}' 'bcryptjs' || true
echo
echo "🔍 deno.land/x/bcrypt の検索"
rg -n -g 'app/client/**/*.{ts,tsx,js,jsx}' 'deno\.land\/x\/bcrypt' || trueLength of output: 407
以下のスクリプトで、deno.land/x/bcrypt の固定文字列検索を行い、クライアント配下での使用有無を再確認してください。
🏁 Script executed:
#!/bin/bash
# クライアント配下での deno.land/x/bcrypt 利用状況確認(固定文字列検索)
echo "🔍 deno.land/x/bcrypt の検索(固定文字列)"
rg -n -F 'deno.land/x/bcrypt' -g 'app/client/**/*.{ts,tsx,js,jsx}' || trueLength of output: 148
app/client/deno.json の不要な bcrypt エントリを削除してください
app/client/src/utils/crypto.ts では純粋に bcryptjs(import { hashSync } from "bcryptjs";)のみが利用されており、deno.land/x/bcrypt("https://deno.land/x/bcrypt@v0.4.1/mod.ts")は一切参照されていません。クライアント側に未使用の依存を残さないよう、以下の 1 行を削除してください。
• 削除対象
- ファイル:
app/client/deno.json - 行番号:およそ 49 行目
- 現在の内容
"bcrypt": "https://deno.land/x/bcrypt@v0.4.1/mod.ts"
— これにより、重複依存の解消とバンドルサイズ最適化が図れます。
🤖 Prompt for AI Agents
In app/client/deno.json around line 49, remove the unused bcrypt dependency
entry (the line: "bcrypt": "https://deno.land/x/bcrypt@v0.4.1/mod.ts") because
client code uses bcryptjs only; edit the JSON to delete that single key/value
pair and ensure the file remains valid JSON (no trailing commas), then run your
dependency/bundle check (e.g., deno cache or your build) to confirm no
references remain.
| @@ -1,8 +1,8 @@ | |||
| import { createEffect, createSignal, onCleanup, onMount, Show } from "solid-js"; | |||
| import { createEffect, createSignal, onMount } from "solid-js"; | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion
Show の未インポートによりビルドエラー(JSX 未解決コンポーネント)
このファイル内で <Show> を多数使用していますが、solid-js からの Show インポートが削除されています。ビルド/実行時に未定義となります。
適用差分案:
-import { createEffect, createSignal, onMount } from "solid-js";
+import { createEffect, createSignal, onMount, Show } from "solid-js";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { createEffect, createSignal, onMount } from "solid-js"; | |
| // app/client/src/components/Application.tsx | |
| import { createEffect, createSignal, onMount, Show } from "solid-js"; |
🤖 Prompt for AI Agents
In app/client/src/components/Application.tsx around lines 1 to 1, the component
uses <Show> in many places but the Show import from "solid-js" is missing; add
Show to the named imports from "solid-js" (e.g. import { createEffect,
createSignal, onMount, Show } from "solid-js") so the JSX component is defined
and the build error is resolved.
| icon: Lock, | ||
| title: "ac", | ||
| desc: | ||
| "activitypub-e2eeを利用したエンドツーエンド暗号化。プライバシーを最優先に考えた設計。", | ||
| highlight: "セキュア", | ||
| title: "プライベートDM", | ||
| desc: "シンプルなダイレクトメッセージで気軽にやり取りできます。", | ||
| highlight: "プライベート", | ||
| }, |
There was a problem hiding this comment.
比較表の E2EE 表記が仕様と不一致(ユーザー向け誤情報)
本 PR で MLS/E2EE を撤去したにもかかわらず、下段の比較表(COMPARISON)で「E2EE: takos=○」のままです。実装と矛盾するため更新してください(例: takos: "×" あるいは将来計画なら注記)。
更新イメージ(参考・比較表側の定数定義を修正):
{
label: "E2EE (エンドツーエンド暗号化)",
takos: "×", // 現状はプレーンテキスト DM
others: { Mastodon: "×", Misskey: "×", Twitter: "×", Facebook: "×" },
}🤖 Prompt for AI Agents
In app/takos_host/client/src/pages/WelcomePage.tsx around lines 197 to 201, the
comparison table still labels takos as E2EE ("○") which contradicts the current
implementation that no longer provides MLS/E2EE; update the comparison table's
constant for the E2EE row to reflect the correct state (set takos to "×" or
replace with a string that notes it's planned/not implemented) and ensure other
vendors remain correct, keeping the label "E2EE (エンドツーエンド暗号化)" and adjusting any
UI text or tests that assert the old value.
| ## DM API | ||
|
|
||
| 平文で1対1のDMを送受信するためのシンプルなAPIです。メッセージはサーバーに | ||
| 保存され、当事者へWebSocketで通知されます。 | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
DMは平文であることとプライバシー上の注意点を明記してください
E2EEを外した変更の要点なので、利用者が誤解しないよう「保存時・配送時とも暗号化されない」「サーバ管理者・侵入者・バックアップ媒体から閲覧され得る」等の注意喚起を追記してください。
提案差分:
## DM API
-平文で1対1のDMを送受信するためのシンプルなAPIです。メッセージはサーバーに
-保存され、当事者へWebSocketで通知されます。
+平文で1対1のDMを送受信するためのシンプルなAPIです。メッセージはサーバーに
+保存され、当事者へWebSocketで通知されます。
+
+注意:
+- 本機能はエンドツーエンド暗号化(E2EE)ではありません。サーバー保存時・配送時ともに平文です。
+- サーバー管理者や侵害時の第三者が内容を閲覧可能である点を理解した上でご利用ください。
+- 機微情報の送信は推奨しません。📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ## DM API | |
| 平文で1対1のDMを送受信するためのシンプルなAPIです。メッセージはサーバーに | |
| 保存され、当事者へWebSocketで通知されます。 | |
| ## DM API | |
| 平文で1対1のDMを送受信するためのシンプルなAPIです。メッセージはサーバーに | |
| 保存され、当事者へWebSocketで通知されます。 | |
| 注意: | |
| - 本機能はエンドツーエンド暗号化(E2EE)ではありません。サーバー保存時・配送時ともに平文です。 | |
| - サーバー管理者や侵害時の第三者が内容を閲覧可能である点を理解した上でご利用ください。 | |
| - 機微情報の送信は推奨しません。 |
🤖 Prompt for AI Agents
In README.md around lines 172 to 176, the DM API section currently states
messages are plain-text but lacks explicit privacy warnings; update this section
to clearly state that messages are not encrypted at rest or in transit, that
server operators, attackers who gain access, and backup media can read stored
messages, and advise users that sensitive information should not be sent via
this DM system; add a short, prominent caution paragraph or bullet points
immediately after the existing description with those exact points and a
recommendation to use E2EE for sensitive content.
概要
テスト
deno fmt app/takos_host/client/src/pages/WelcomePage.tsxdeno lint app/takos_host/client/src/pages/WelcomePage.tsxhttps://chatgpt.com/codex/tasks/task_e_68a8ed9b4f2c8328ae8427aebd9f891d
Summary by CodeRabbit