feat: スマホアプリ + E2E暗号化リアルタイムクラウド同期(Capacitor + Cloudflare relay)#8
Conversation
リアルタイム relay 同期(A→C→B)の地ならし(A)。sync.js の chrome.storage.sync 直接呼び出し6箇所(機能検出 / readSync の getAll / push の set·remove)を transport インターフェース(isAvailable/getAll/set/remove)へ集約。既定の ChromeSyncTransport は メソッド内で呼び出し時に chrome を解決し現状と完全同一に振る舞う(読込時に参照を キャプチャしない=テストの後付けモック互換)。setSyncTransport で将来 RelayTransport (Cloudflare Worker 経由の HTTP)へ差し替えるための境界を用意。 挙動は不変。_sync_repro.mjs 195 PASS / 0 FAIL を維持。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
クラウド同期モード用 Cloudflare Workers リレーの server 側を新規スキャフォルド。
notify-then-pull(store-and-forward): vault 単位の VaultDO(Hibernatable WebSocket)が
暗号文ストア窓口(D1: petarin-sync) + per-vault seq 採番 + 変更ピンの fan-out を担う。
- src/index.ts: 薄い router。vaultId を SALT 付き SHA-256 でハッシュ化して DO を引く(横入り防止)。
- src/vault-do.ts: WS(acceptWebSocket=ハイバネ)/push/pull/catchup。push で D1 upsert→
seq 採番→他端末へ {t:changed,d,seq} broadcast(送信WS無料)。
- src/auth.ts: 自己完結ペアリング鍵。vault の ECDSA P-256 公開鍵を first-write-wins 登録し
以降のリクエストを署名検証(秘密はサーバーに無い)。本文は端末側 vaultKey で AES-GCM 暗号化、
ドメイン名も HMAC ハッシュ化=サーバーは中身もサイトも知らない。
- migrations/0001_init.sql + wrangler.toml + README + tsconfig + package.json/lock。
Ferry-relay の流儀(DO+ハイバネ/SALT ハッシュ/Custom Domain のみ/Rate Limit 三段)に倣う。
topology は別(あちらは 2 peer 生パススルー、こちらは N端末 store-and-forward)。
tsc --noEmit 通過。未デプロイ(D1 作成・SALT・ドメイン確定は明示 GO 後)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A の setSyncTransport に差す客側 transport を実装。リレーを「暗号化された chrome.storage.sync ミラー」として見せ、sync.js のマージ頭脳を無改造で動かす。 - src/shared/vault.js: vault 識別子と暗号プリミティブ。ECDSA P-256 鍵ペア(QR で端末間共有)/ vaultKey→HKDF で AES-GCM・HMAC 派生 / item 暗号化(平文に元キーを内包)/ ドメイン HMAC ハッシュ / リクエスト署名(relay auth.ts と一致) / pairing 入出力。 - src/shared/relay-transport.js: isAvailable/getAll(/dump)/set(/push)/remove(/item DELETE) を 署名付きで実装。値は AES-GCM 暗号化・キーは HMAC ハッシュ。getLastSeq を realtime 用に公開。 - relay(vault-do.ts): getAll 用 GET /dump と remove 用 DELETE /item を追加(seq 進行 + broadcast)。 - scripts/_vault_selftest.mjs: 暗号 round-trip・ハッシュ決定性・「署名が relay 検証を通る」契約を Node で検証(7 PASS)。relay tsc --noEmit 通過。 B3 で background の WS 配線・モード選択・ペアリング UI を繋ぎ、デプロイ後に実機疎通。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- petarin-sync D1(903b1fb1…・APAC)を作成し remote にスキーマ適用。database_id を wrangler.toml へ。 - scripts/_relay_e2e.mjs: vault.js + relay-transport.js で set→dump→WS変更ピン→remove を実通しする e2e(リレーは wrangler dev か deploy 後の URL を RELAY_URL で指定)。 - .gitignore に .wrangler-state/ を追加。pnpm の build 許可を onlyBuiltDependencies に。 - ローカル workerd が std::terminate で起動不能(この Windows 環境固有・ratelimit 無関係)。 unsafe ratelimit binding はローカル dev でコメントアウト(本番デプロイ時に再有効化)。 ランタイム検証はデプロイ後に実機 e2e で行う。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- petarin-relay を CF へデプロイ(https://petarin-relay.1llum1n4t1.workers.dev)。実 Durable Object(ハイバネ WS) + 実 D1 + Worker で notify-then-pull が動作。 - 実機 e2e 7 PASS: 暗号化KVミラー往復 / meta往復 / WS変更ピン受信 / 削除 / 無認証401。 - SALT を本番 secret として投入(vaultId ハッシュ化・横入り防止)。 - ローカル workerd が当環境で起動不能(std::terminate)のため検証はデプロイ実機で実施。 - workers.dev は開発中の一時公開。本番 go-live で Custom Domain + workers_dev=false へ戻す。 unsafe ratelimit binding も本番で再有効化する(local dev では workerd が落ちるためコメントアウト中)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
relay URL は環境で変わる(dev=workers.dev / 本番=custom domain)ため、クライアント側 manifest に origin を焼かず relay 側で CORS を開ける。認証は署名(X-Vault-*)で行い Cookie を 使わないので ACAO は "*"。OPTIONS preflight は 204、WS upgrade(101)は webSocket 保持のため 再構築せず CORS も付けない。fetch を handle() に分離。デプロイ + e2e 7 PASS / CORS 実レス確認済。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
排他3モード(off/chrome/cloud)を syncEnabled + syncMode の二段で表現(sync.js の syncEnabled
ゲートは不変)。transport 選択は background が syncMode で行い、cloud のとき relay-transport へ
差し替える(sync.js のマージ頭脳は無改造)。
- storage.js: DEFAULT_SETTINGS に syncMode 追加 + vault 鍵束の local 専用キー(petarin:sync:vault・
never sync)と get/save/clear ヘルパ。秘密(署名秘密鍵JWK)を含むため chrome.storage.local のみ。
- background.js: applyTransport() で cloud+ペアリング済みなら setSyncTransport(relay)。realtime WS
(/sync・"ping"/"pong" keepalive・指数backoff 再接続)で変更ピン {t:changed} を受けて pull。SW 休止
対策に keepalive alarm(1分)で WS 張り直し+取りこぼし reconcile。cloud は巨大 budget で容量ロジック無効化。
- manifest(両): alarms 権限。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
付箋デスクの同期パネルを「ON/OFF トグル」から「同期しない / ブラウザ標準同期 / クラウド同期」の 排他3ラジオへ。mode 別の同意文を出し分け、クラウド時だけペアリング section を表示する。 - 作成: generateVault → ローカル保存 → 引き継ぎコード(exportPairingCode)を表示。 - 参加: コードを貼り付け → importVault で検証 → 保存。 - 解除: clearVaultPairing + purgeSync(shadow撤去)。コピー/再表示も。 - 100KB ゲージは容量上限のある chrome 標準同期のみ表示(cloud はリレー側で広いので隠す)。 - relay-transport に DEFAULT_RELAY_URL を追加(新規作成の初期リレー。本番は custom domain へ)。 - background: cloud だが未ペアリングのとき chrome へ漏らさず止める UNAVAILABLE_TRANSPORT。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capacitor アプリで拡張の同期エンジン(shared/storage.js・sync.js)を無改造で動かす土台。
chrome.storage.local / onChanged を 1 プロセス KV で再現(バックエンド注入式: Capacitor は
Preferences、テストはインメモリ)。get(null|str|[]|{}) / set 構造化クローン / Promise / onChanged 発火。
scripts/_mobile_sync_repro.mjs: device A が付箋を push → device B(別 backend・同 vault)が pull して
本文(改行)・icon・color が暗号化往復で一致することを実 relay 相手に確認。拡張側で未検証だった
「実エンジン+実 relay の cloud 往復」もこれで closing。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
クラウド同期(買い切り¥500)で PC とリアルタイム共有するスマホアプリの土台。Vite で web をビルドし @shared エイリアスで拡張の同期エンジン(vault/sync/relay-transport/storage/markdown)を**単一ソース再利用** (コピーしない)。`vite build` 成功確認(21 modules・44.5kB)。 - src/main.js: chrome.storage シム差し込み→エンジン起動→付箋一覧(Markdownプレビュー)+同期/ペアリングUI。 モバイルは off/cloud の2択(chrome 標準同期は無い)。cloud は IAP 解禁ゲート。 - src/sync-orchestrator.js: background.js のモバイル版(reconcile デバウンス + realtime WS "ping"/"pong" + resume/pause で WS 張り直し/畳み)。cloud は巨大 budget で容量ロジック無効化。 - src/preferences-backend.js: シムの裏付け(Capacitor Preferences)。 - src/iap.js: 買い切り解禁ゲート(ネイティブ配線は TODO・ブラウザ dev は localStorage 解錠)。 - vite.config/capacitor.config/package.json + Android CI(ubuntu/JDK17/Gradle・debug APK)。android/ios は cap add 生成物=gitignore。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
crypto.subtle(vault.js の暗号化)は secure context 限定で、LAN IP への素の HTTP は secure 扱い されない=ペアリング/暗号化がこける。@vitejs/plugin-basic-ssl で自己署名 HTTPS を張り、server.host=true で 0.0.0.0 bind。実機は証明書警告を一度許可すれば secure context になり crypto が動く。 LAN 配信を実機相当(curl -k https://<LAN-IP>:5180)で 200 確認。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
長いペアリングコード(643字)の手打ち/コピペを避けるため、コードと一緒に QR を出す。スマホのカメラで 読み取れる。実績ある依存ゼロの qrcode-generator(Arase・MIT)を採用。 - src/vendor/qrcode.js: vendor(拡張は classic script で window.qrcode・CSP self 内)。vm でグローバル経路検証。 - manage: pairCodeBox に QR <img>(createDataURL の GIF data URL=innerHTML 不使用・CSP安全)。作成/表示時に描画。 - mobile: qrcode-generator を Vite import し pairLinked に QR。vite build 22 modules 成功。 - ECC=L・余白16px(規格の4モジュール)で画面スキャンの読み取り安定。85〜89×89 程度。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vite 5.4 の host チェックでトンネル(*.trycloudflare.com 等)が弾かれるのを回避(dev のみ)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
標準カメラだと QR がただのテキスト→Web検索になる(URLスキーム無しのため正常)。アプリ内に スキャナを持たせ、PC 拡張の QR を写すと parsePairingCode→importVault 検証→自動参加まで通す。 - index.html: 「📷 QRをスキャンして参加」ボタン + カメラパネル(video)。 - main.js: getUserMedia(environment) → requestAnimationFrame で jsQR デコード → 検出で onJoin 再利用。 停止時は track 全停止。Web(Safari)は HTTPS=secure context で動作。ネイティブは Info.plist の NSCameraUsageDescription / 必要なら barcode-scanner プラグインへ差し替え(TODO)。 - vite build 成功(jsqr 同梱・197KB/gzip72KB)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
スマホアプリを課金なしで完結利用できる状態にする。クラウド同期(IAP)は あくまで上乗せで、無課金でもローカル付箋アプリとして全機能が使える。 - notes-meta.js: content.js 由来の ICONS/pickIcon/clamp を複製し、 スタンドアローンの「グループ」を sync 安全な domain キー (group:base64url(NFC(name))) へ符号化。後でクラウド同期を買っても sync.js の isValidDomain を無改修で通る/拡張のホスト名キーと名前空間分離。 - main.js: storage.js の既存経路(restoreNotes/updateNote/deleteNote/ getTrash/restoreFromTrash/purgeFromTrash)を使い、FAB→作成先グループ選択 →エディタ(本文/色/絵文字/Markdownプレビュー/文字数)→保存/削除、 ゴミ箱ビュー(復元/完全削除/空に)を実装。カード色は CSS クラス非依存で JS 直塗り(9色対応)。同期/ペアリング/QR スキャンは従来通り温存。 - index.html: +FAB・ゴミ箱ボタン・エディタ/絵文字/グループ選択シートと CSS を追加。 - scripts/_mobile_crud_repro.mjs: vault/同期なしの CRUD データ経路 e2e(15 PASS)。 グループキーが isValidDomain を通る=クラウド同期安全も検証。 検証: _mobile_crud_repro.mjs 15 PASS / _sync_repro.mjs 223 PASS(storage/sync 無改修) / vite build 26 modules OK。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughSummary by CodeRabbitリリースノート
WalkthroughE2E暗号化クラウド同期機能をブラウザ拡張・Cloudflare Workersリレー・Capacitorモバイルアプリの3層にわたって追加した。WebCrypto(ECDSA/AES-GCM/HKDF)ベースのvault暗号プリミティブを共有ライブラリとして実装し、syncのtransport抽象化・リレーサーバーD1/Durable Object実装・管理UIペアリング機能・モバイルアプリ基盤を一括導入した。 Changesクラウド同期・モバイルアプリ基盤の追加
Sequence Diagram(s)クライアント(拡張またはモバイル)からリレー経由で2台間にノートが同期される主要フロー: sequenceDiagram
participant ExtA as デバイスA<br/>拡張/モバイル
participant BG as background.js<br/>sync-orchestrator.js
participant RT as relay-transport.js
participant Relay as Cloudflare Worker<br/>petarin-relay
participant VaultDO as VaultDO<br/>Durable Object
participant D1 as D1<br/>notes
participant ExtB as デバイスB<br/>拡張/モバイル
rect rgba(100, 149, 237, 0.5)
note over ExtA,BG: ペアリング・transport設定
ExtA->>BG: generateVault / importVault
BG->>RT: createRelayTransport(vault)
BG->>Relay: WebSocket /sync 接続
end
rect rgba(60, 179, 113, 0.5)
note over BG,D1: デバイスAがノートをpush
BG->>RT: reconcile → transport.set(encryptItem)
RT->>Relay: PUT /push {domain_hash, ciphertext, nonce}
Relay->>VaultDO: verify(ECDSA署名) → handlePush
VaultDO->>D1: notes upsert(seq採番)
VaultDO-->>ExtB: WS broadcast {t:"changed", seq}
end
rect rgba(255, 165, 0, 0.5)
note over ExtB,D1: デバイスBがpullして反映
ExtB->>RT: transport.getAll() → GET /dump
RT->>Relay: GET /dump
Relay->>VaultDO: verify → handleDump
VaultDO->>D1: 全件取得
D1-->>ExtB: {items} decryptItem → ローカルへ反映
end
Possibly Related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a cloud synchronization feature for the petarin note-taking application, adding a Cloudflare Workers relay (petarin-relay) using Durable Objects and D1, a Capacitor-based mobile application sharing the same sync engine via a chrome.storage shim, and pairing UI integration. The code review identified several critical issues: potential infinite WebSocket reconnection loops in both the mobile and extension background orchestrators during intentional disconnections, a sequence tracking bug in the relay's handleCatchup when notes are physically deleted, and a serialization issue in the mobile storage shim's in-memory backend during initialization with non-string seed values.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: faf4be8f08
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ュー対応) PR #8 の Gemini Code Assist 指摘 4 件を修正。 - WS 再接続(high・background.js / sync-orchestrator.js): onclose の scheduleReconnect() が `if (_ws===ws)` ガードの外にあり、closeSocket/closeRelaySocket が _ws=null 後に close() しても(onclose は非同期発火で _ws===ws が偽)再接続がスケジュールされていた。 背面化・off/解除中も WS が張り直し続けバッテリ/帯域を浪費する。null 化と再接続を 同じガード内へ入れ、意図的切断では再接続しないようにした。 - relay catchup の seq 前進(medium・vault-do.ts): handleDelete は行を物理削除するため `seq>since` のクエリに乗らず、削除だけの回はクライアントの since が進まず同じ since で 無駄問い合わせを繰り返していた。返す seq を `Math.max(currentSeq, since)` にして前進させる。 削除自体の伝播は墓石(meta item の push=changes に乗る)が担うので取りこぼさない (relay モードは巨大 budget で metaDeferred 無効=meta は必ず push される)。 scripts/_relay_e2e.mjs に「削除だけでも catchup の seq が前進」する実機回帰を追加。 - storage-shim seed(medium): createMemoryBackend(seed) が生のオブジェクト/配列をそのまま Map に入れ、readRaw の JSON.parse が壊れていた。非文字列は JSON 文字列化して格納 (Capacitor Preferences と同じ文字列 KV 契約に合わせる)。 検証: relay tsc OK / _sync_repro 223 PASS / _mobile_crud_repro 15 PASS / _relay_e2e 9 PASS(catchup 追加) / _mobile_sync_repro 9 PASS / vite build 26 modules OK。 relay 再デプロイ済(Version 4729ca62・catchup 修正反映)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 17
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (4)
src/background/background.js-104-161 (1)
104-161: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
connectRelaySocket()に多重接続レースの可能性があります。Line 106 の
OPEN/CONNECTINGガード通過後、loadVault()とsignRequest()のawait(特に署名は WebCrypto で必ずマイクロ/マクロタスクを跨ぐ)を経てから Line 132 で_wsを代入します。この間にapplyTransport()(settings 変更と vault 変更が連続発火)や keepalive alarm / 再接続タイマからもう一度呼ばれると、両方ともガードを抜けてnew WebSocketを 2 本張ってしまいます。後勝ちで_wsが上書きされ、片方はoncloseで_ws === wsを満たさず再接続もされない孤児接続として残り、接続チャーンの原因になります。接続中フラグで直列化することを推奨します。
🔒 多重接続を防ぐガードの例
let _ws = null; +let _wsConnecting = false; let _wsAttempt = 0; let _wsReconnectTimer = 0; let _heartbeatTimer = 0; async function connectRelaySocket() { if (!isCloudActive()) return; + if (_wsConnecting) return; if (_ws && (_ws.readyState === WebSocket.OPEN || _ws.readyState === WebSocket.CONNECTING)) return; - const vault = await loadVault(); - if (!vault) return; + _wsConnecting = true; + try { + const vault = await loadVault(); + if (!vault) return; // …(既存の WS 構築処理)… + } finally { + _wsConnecting = false; + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/background/background.js` around lines 104 - 161, The connectRelaySocket() function has a race condition where multiple calls can pass the initial guard on line 106 (checking _ws readyState) and then proceed through the async operations (loadVault and signRequest). This causes multiple WebSocket instances to be created, with the last one overwriting _ws and leaving orphaned connections. Introduce a connecting flag (similar to how you might track connection state) that guards the entire connection process from the start of connectRelaySocket() through the assignment of _ws on line 132. Set this flag to true at the beginning of the function before any async operations, and set it back to false if any error occurs or at the end of successful connection setup. This ensures only one connection attempt can be in progress at a time, preventing the race condition.infra/cloudflare/relay/README.md-63-68 (1)
63-68: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win「まだ無いもの」セクションが現状実装と食い違っています。
ここに記載されている
RelayTransport/ ペアリングUI / background 側WS連携は、このPRの目的と実装内容では導入済みです。運用時の誤解を避けるため、現況に合わせて更新してください。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@infra/cloudflare/relay/README.md` around lines 63 - 68, The "まだ無いもの(B の続き)" section in the README lists features that are no longer in a "not yet implemented" state. Update this section to reflect the current implementation status by removing or relocating the entries for RelayTransport, ペアリング UI with manage sync panel mode selection, and background.js WS connection management with domain reconciliation, as these features have been implemented in the current PR. Ensure the documentation accurately represents what remains to be done versus what is already completed.infra/cloudflare/relay/README.md-14-17 (1)
14-17: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winフェンス付きコードブロックに言語指定を付けてください。
このブロックは
markdownlintの MD040 警告対象です。textなどを付ければ警告を解消できます。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@infra/cloudflare/relay/README.md` around lines 14 - 17, The fenced code block in the README.md file is missing a language specification after the opening triple backticks, which triggers a markdownlint MD040 warning. Add a language identifier such as `text` immediately after the opening triple backticks (change ``` to ```text) to resolve the warning and comply with markdown linting standards.Source: Linters/SAST tools
src/shared/vault.js-99-102 (1)
99-102: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
importVaultでpairing.urlを先に検証してください。
pairing.urlが未検証のまま通るため、壊れたペアリングコードが「後段の通信失敗」としてしか見えません。インポート時に URL 構文と許可スキームを弾くと、失敗が即時かつ原因明確になります。🛠️ 修正例
export async function importVault(pairing) { if (!pairing || pairing.v !== 1 || !pairing.id || !pairing.k || !pairing.sk || !pairing.pk) { throw new Error("invalid pairing payload"); } + let u; + try { + u = new URL(String(pairing.url || "")); + } catch { + throw new Error("invalid relay url"); + } + const isLocalhost = u.hostname === "localhost" || u.hostname === "127.0.0.1"; + if (u.protocol !== "https:" && !(u.protocol === "http:" && isLocalhost)) { + throw new Error("relay url must be https (or localhost http)"); + } return buildVault(pairing, b64urlToBytes(pairing.k)); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/vault.js` around lines 99 - 102, The `importVault` function validates several pairing properties like `pairing.v`, `pairing.id`, `pairing.k`, `pairing.sk`, and `pairing.pk` but does not validate `pairing.url` in the validation check. Add validation for `pairing.url` in the same condition that validates the other required pairing properties, ensuring the URL has valid syntax and uses an allowed scheme. This will allow broken pairing codes to fail immediately with a clear error message rather than failing later during communication attempts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/mobile-android.yml:
- Around line 21-23: The setup-node@v4 action lacks an explicit cache
configuration which poses a security risk since future versions (v5/v6) may
enable caching by default in release workflows. In the setup-node@v4 step where
node-version is configured to 22, add an explicit package-manager-cache
parameter and set it to false to prevent potential cache poisoning attacks
during release processes and to lock in the intended behavior regardless of
future action version updates.
- Around line 19-20: The actions/checkout@v4 step in the mobile-android.yml
workflow is missing the persist-credentials configuration. Add the
persist-credentials: false parameter to the checkout action step to prevent Git
credentials from being persisted and reduce the attack surface for read-only
build operations. This is a security best practice to ensure that if a
downstream action is compromised, attackers cannot access the Git credentials.
- Line 19: Replace the variable version tag reference `@v4` in the
actions/checkout action with the full 40-character commit SHA to protect against
supply chain attacks. Pin all GitHub Actions referenced in the workflow file
(including those on lines 21, 28, and 50 as mentioned in the comment) to their
complete commit SHA instead of version tags like `@v4`. Add the version tag as a
comment next to the full SHA to maintain readability and ease future updates.
In `@infra/cloudflare/relay/pnpm-workspace.yaml`:
- Around line 1-4: The allowBuilds configuration in pnpm-workspace.yaml contains
placeholder text ("set this to true or false") for the esbuild, sharp, and
workerd packages instead of actual boolean values. Replace each placeholder
under the allowBuilds section with either true or false based on whether builds
should be allowed for those packages (esbuild, sharp, workerd). If build control
is not required, remove the entire allowBuilds block and any associated
onlyBuiltDependencies setting that may exist elsewhere in the file, as
onlyBuiltDependencies is deprecated in pnpm v11.
In `@infra/cloudflare/relay/src/index.ts`:
- Around line 51-54: The WebSocket detection in the isWs constant uses a
case-sensitive comparison against the Upgrade header value. Since HTTP header
tokens are case-insensitive per specification, the current strict equality check
against "websocket" will fail for variations like "WebSocket" or "WEBSOCKET",
causing WebSocket connections to be incorrectly treated as regular HTTP
requests. Convert the Upgrade header value to lowercase using toLowerCase()
before comparing it to "websocket" to ensure case-insensitive matching. Apply
the same fix to the other location referenced at line 63-64.
- Around line 76-83: The code uses env.SALT without validating it exists, which
means if the SALT environment variable is not configured, the identifier hashing
will fail silently and weaken security. Add explicit validation to check that
env.SALT is set before calling hashVaultId on line 76, and return a 500 error or
throw an error if SALT is missing. This ensures misconfiguration is detected
immediately rather than allowing the code to continue with an invalid or
undefined salt value.
In `@infra/cloudflare/relay/src/vault-do.ts`:
- Around line 65-71: The public key is being persisted to storage before
signature verification, creating a security vulnerability where an attacker
could submit a malicious public key and lock out legitimate users. Move the
`storage.put` call for the "pubkey" that occurs when storedPub is not found and
pubB64 is provided to execute only after the signature verification logic (which
happens in the subsequent lines) has completed successfully. Ensure that the
public key is only permanently stored after cryptographic validation confirms it
is authentic and trusted.
In `@mobile/index.html`:
- Around line 113-149: Several modal panels (editorPanel with id="edTitle",
iconPanel, groupPickPanel, and the panel at line 189) are missing accessibility
attributes that are already implemented in the syncPanel. For each of these
panels, add role="dialog" and aria-modal="true" to the sheet div element, and
add aria-labelledby pointing to the respective heading IDs (edTitle, iconPanel
heading, groupPickPanel heading, etc.) to ensure assistive technologies can
properly identify and announce the modal context. This applies to the sheet divs
within editorPanel, iconPanel, groupPickPanel, and the panel referenced at line
189.
In `@mobile/src/iap.js`:
- Around line 19-27: The development-mode unlock mechanism using DEV_UNLOCK_KEY
and localStorage is accessible in production environments, allowing unauthorized
unlocking of features without actual IAP transactions. In the initIap()
function, add an environment check to ensure the localStorage DEV_UNLOCK_KEY
flag is only trusted in development mode, while production mode ignores this
flag and relies solely on verified IAP purchases. Additionally, verify that the
purchase() function at line 33 and any other unlock mechanisms properly validate
actual IAP transactions in production instead of unconditionally setting
_unlocked to true. Separate development-only bypass logic explicitly from
production security logic.
In `@mobile/src/sync-orchestrator.js`:
- Around line 56-63: The applyTransport function does not handle vault switching
correctly because the early return condition at line 104 only checks for the
existence of the _ws WebSocket connection, not whether the vault has changed. To
fix this, maintain a variable that tracks the identifier or reference of the
currently connected vault, then in the applyTransport function compare the newly
loaded vault with the stored vault identifier. If they differ, explicitly
disconnect and close the old WebSocket connection before creating the new relay
transport with createRelayTransport and reconnecting via connectSocket. This
ensures old vault connections are properly replaced when switching vaults.
- Around line 73-90: The issue is that reconciliation requests arriving during
an active runReconcile() execution are discarded because the _reconciling guard
causes immediate return. To fix this, introduce a _pending flag that gets set to
true in scheduleReconcile() when _reconciling is already true, then in the
finally block of runReconcile(), check if _pending is true and if so, invoke
runReconcile() again after resetting the flag to false. This ensures that any
reconciliation requests that arrived during execution are handled once the
current execution completes.
In `@mobile/vite.config.js`:
- Around line 17-22: The allowedHosts property in the server configuration is
set to true, which creates a DNS Rebinding attack vulnerability by accepting
requests from any host. Replace the allowedHosts: true assignment with an
approach that explicitly enumerates allowed hosts using the
VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS environment variable. This ensures only
trusted hosts specified in the environment configuration are permitted to access
the development server, rather than blindly accepting all hosts.
In `@scripts/_mobile_crud_repro.mjs`:
- Line 32: The ok() assertion in the test at line 32 contains `|| true` which
causes the condition to always evaluate to true regardless of the actual
isValidDomain() result, making this assertion meaningless and unable to detect
failures. Remove the `|| true` portion from the ok() function call so that the
assertion properly verifies whether isValidDomain("仕事") actually returns true.
In `@scripts/_mobile_sync_repro.mjs`:
- Line 14: The RELAY constant currently defaults to a production Relay URL when
RELAY_URL environment variable is not set, which poses a risk of accidental
writes to production. Change the default behavior by either making RELAY_URL a
required environment variable (throwing an error if not provided) or setting
localhost as the default fallback instead of the production URL. This change
should also be applied consistently wherever the orphan notation references
RELAY_URL usage.
In `@src/shared/relay-transport.js`:
- Around line 55-60: The empty catch block in the decryptItem call silently
skips failed decryption, which causes the sync core to misinterpret missing
items as deletions rather than corruption. Instead of catching and ignoring
decryption failures in this try-catch block within the getAll operation, either
rethrow the error or establish an error flag that causes the entire operation to
fail and be retried. This ensures that corrupted or undecryptable items are not
silently dropped, preventing them from being incorrectly treated as deleted data
by the downstream sync logic.
- Around line 24-41: The req function makes a fetch call without any timeout
mechanism, which can cause the reconciliation process to hang indefinitely if
the server is unresponsive. Add an AbortController with a timeout to the req
function by creating an AbortController instance, setting a timeout that calls
controller.abort() after a specified duration (e.g., 30 seconds), and adding the
controller.signal property to the init object passed to the fetch call. This
ensures the fetch request will automatically abort and fail fast if it exceeds
the timeout period, preventing synchronization from being blocked.
In `@src/shared/sync.js`:
- Around line 465-473: The setSyncTransport() function allows dynamic
replacement of _transport at any time, but the _reconcile() function performs
long-running operations across multiple await calls. If setSyncTransport() is
called during _reconcile() execution, reads and writes could use different
backend transports, causing data corruption. To fix this, capture a snapshot of
the current _transport at the beginning of the _reconcile() function and use
that captured reference throughout the entire function instead of repeatedly
accessing the global _transport variable, ensuring all read/write operations
within a single reconciliation use the same backend.
---
Minor comments:
In `@infra/cloudflare/relay/README.md`:
- Around line 63-68: The "まだ無いもの(B の続き)" section in the README lists features
that are no longer in a "not yet implemented" state. Update this section to
reflect the current implementation status by removing or relocating the entries
for RelayTransport, ペアリング UI with manage sync panel mode selection, and
background.js WS connection management with domain reconciliation, as these
features have been implemented in the current PR. Ensure the documentation
accurately represents what remains to be done versus what is already completed.
- Around line 14-17: The fenced code block in the README.md file is missing a
language specification after the opening triple backticks, which triggers a
markdownlint MD040 warning. Add a language identifier such as `text` immediately
after the opening triple backticks (change ``` to ```text) to resolve the
warning and comply with markdown linting standards.
In `@src/background/background.js`:
- Around line 104-161: The connectRelaySocket() function has a race condition
where multiple calls can pass the initial guard on line 106 (checking _ws
readyState) and then proceed through the async operations (loadVault and
signRequest). This causes multiple WebSocket instances to be created, with the
last one overwriting _ws and leaving orphaned connections. Introduce a
connecting flag (similar to how you might track connection state) that guards
the entire connection process from the start of connectRelaySocket() through the
assignment of _ws on line 132. Set this flag to true at the beginning of the
function before any async operations, and set it back to false if any error
occurs or at the end of successful connection setup. This ensures only one
connection attempt can be in progress at a time, preventing the race condition.
In `@src/shared/vault.js`:
- Around line 99-102: The `importVault` function validates several pairing
properties like `pairing.v`, `pairing.id`, `pairing.k`, `pairing.sk`, and
`pairing.pk` but does not validate `pairing.url` in the validation check. Add
validation for `pairing.url` in the same condition that validates the other
required pairing properties, ensuring the URL has valid syntax and uses an
allowed scheme. This will allow broken pairing codes to fail immediately with a
clear error message rather than failing later during communication attempts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c107e87e-df64-4d89-9488-e958933e2751
⛔ Files ignored due to path filters (2)
infra/cloudflare/relay/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlmobile/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (38)
.github/workflows/mobile-android.ymlinfra/cloudflare/relay/.gitignoreinfra/cloudflare/relay/README.mdinfra/cloudflare/relay/migrations/0001_init.sqlinfra/cloudflare/relay/package.jsoninfra/cloudflare/relay/pnpm-workspace.yamlinfra/cloudflare/relay/src/auth.tsinfra/cloudflare/relay/src/index.tsinfra/cloudflare/relay/src/vault-do.tsinfra/cloudflare/relay/tsconfig.jsoninfra/cloudflare/relay/wrangler.tomlmanifest.firefox.jsonmanifest.jsonmobile/.gitignoremobile/README.mdmobile/capacitor.config.jsonmobile/index.htmlmobile/package.jsonmobile/src/iap.jsmobile/src/main.jsmobile/src/notes-meta.jsmobile/src/preferences-backend.jsmobile/src/storage-shim.jsmobile/src/sync-orchestrator.jsmobile/vite.config.jsscripts/_mobile_crud_repro.mjsscripts/_mobile_sync_repro.mjsscripts/_relay_e2e.mjsscripts/_vault_selftest.mjssrc/background/background.jssrc/manage/manage.csssrc/manage/manage.htmlsrc/manage/manage.jssrc/shared/relay-transport.jssrc/shared/storage.jssrc/shared/sync.jssrc/shared/vault.jssrc/vendor/qrcode.js
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/_relay_e2e.mjs`:
- Around line 105-109: The fetch response from the `/catchup` endpoint is not
being validated for HTTP success status before calling r.json(), which can cause
unhandled exceptions if the server returns an error response. Add a check for
r.ok after the fetch call completes, and if it is false, throw an error with the
response status and status text included for better error diagnostics. Only call
r.json() if the response is successful.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 08af127f-54d3-4ecc-b1ce-4d5fa52b6be1
📒 Files selected for processing (5)
infra/cloudflare/relay/src/vault-do.tsmobile/src/storage-shim.jsmobile/src/sync-orchestrator.jsscripts/_relay_e2e.mjssrc/background/background.js
🚧 Files skipped from review as they are similar to previous changes (4)
- src/background/background.js
- mobile/src/storage-shim.js
- infra/cloudflare/relay/src/vault-do.ts
- mobile/src/sync-orchestrator.js
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88b87e1aa4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
CodeRabbit/Codex の指摘に対応(relay サーバー側)。 - vault-do.ts: 公開鍵を署名検証成功後にのみ first-write-wins 登録する。検証前に保存すると vaultId を知った第三者や壊れた初回リクエストが不正鍵を焼き付け、以降の正当クライアントを 全て Bad signature でブリックできた(critical)。署名対象に HTTP のクエリ(url.search)を含め、 DELETE /item?d=... の d 改竄で別ドメインのアイテムを削除できた脆弱性を塞ぐ(WS は認証 パラメータ自体=sig を含むため署名対象外)。seq は get→put を原子的に確保してから D1 を 操作し、D1 の await を跨ぐ concurrent request の seq 重複/飛びを防ぐ(push/delete 両方)。 - index.ts: SALT 未注入は fail-fast(500)で黙って弱状態で動かさない。Upgrade ヘッダ判定を toLowerCase で大小文字非依存に共通関数化(RFC 7230)。 - pnpm-workspace.yaml: pnpm の無効キー allowBuilds のプレースホルダを除去し、 relay に無い sharp も外して onlyBuiltDependencies を確定値にする。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…8 レビュー対応) CodeRabbit/Codex の指摘に対応(クライアント同期コア・共有・拡張)。 - vault.js: signRequest の署名対象に query を追加(relay auth.ts と一致=クエリ改竄防止)。 - relay-transport.js: req に AbortController 15s タイムアウト(遅延/ハングで reconcile・MV3 SW が 長時間ブロックされないように)。getAll は復号失敗を黙ってスキップせず throw。不完全なミラーを 返すと sync.js が「remote に無い=削除」と誤認し local 付箋を消すため、getAll 全体を失敗させ reconcile を中断して local を温存する(同一 vault の item は同 aesKey=復号失敗は破損の異常)。 - sync.js: _reconcile 冒頭で getSyncTransport() を 1 つ確定し、readSync/push まで同じ transport を 使い切る。実行中に setSyncTransport() で差し替わると read と write が別バックエンドに割れる race(cloud↔chrome 切替時)を解消。 - groups.js(新規): グループキー(group:base64url)の符号化/デコードを拡張デスクとモバイルで共有。 - manage.js: クラウド同期で来た group: キーを decodeGroupName で表示し、group キーは https URL を 持たないので「開く」ボタンを隠す(無効 URL 生成を防止)。 - background.js: WS 署名にクエリ引数("")を渡す(署名フォーマット一致)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…CI(PR#8 レビュー対応) CodeRabbit/Codex の指摘に対応(モバイル+テスト/CI)。 - main.js: onCreate/onJoin で syncScope:"all" を明示(mobile はドメイン選択 UI が無く、既定 "selected"+空 syncDomains だと同期対象がゼロで動かない)。ペアリング済みでも保存済み pairing からコード/QR を再表示(別端末を追加招待でき、onCopy の空コピーも防ぐ)。onUnlink で syncEnabled:false(vault 喪失で実質停止する状態と UI の「同期ON・未接続」矛盾を解消)。 - sync-orchestrator.js: vault stamp で vault 切替時に WS を張り直す(旧 vault に居残らない)。 reconcile 実行中に来た要求を pending で完了後 1 回追走(取りこぼし防止)。WS 署名にクエリ引数。 - iap.js: import.meta.env.DEV で本番ビルドの DEV 解錠バイパスを封じる(DevTools での無課金解錠防止)。 - index.html: editor/icon/groupPick モーダルに role=dialog/aria-modal/aria-labelledby を付与。 - vite.config.js: allowedHosts を *.trycloudflare.com 既定許可+VITE_ALLOWED_HOSTS 追加に変更 (true の任意ホスト許可=DNS rebinding を回避・dev サーバのみ)。 - notes-meta.js: グループキーを src/shared/groups.js から再エクスポート(実体を共有)。 - mobile-android.yml: Actions を full commit SHA にピン留め・persist-credentials:false・ package-manager-cache:false・cap sync 後に AndroidManifest へ CAMERA 権限注入(QR スキャナ)。 - scripts: _mobile_sync_repro は RELAY_URL を必須化(本番への誤書き込み防止)。 _mobile_crud_repro の `|| true` 無効アサーションを実検証に修正。_relay_e2e は catchup の r.ok 検証と署名クエリを追加。_vault_selftest は署名フォーマット変更(query)に追従。 検証: relay tsc OK / _vault_selftest 7 / _sync_repro 223 / _mobile_crud_repro 15 / _relay_e2e 9 / _mobile_sync_repro 9 / vite build OK。relay 再デプロイ済(Version 1f8392d4)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
manage.js の onModeChange で chrome↔cloud 切替時、前バックエンドとの合意状態(shadow)が 残ったまま新バックエンドで reconcile すると、mergeDomainNotes が「shadow にあるが新 remote に 無い=リモート削除」と誤判定し local 付箋を消す危険があった。off と同様、chrome/cloud へ 切り替えるときもモード確定前に petarin:purgeSync で投影を撤去し、新バックエンドでは base=空から 和集合マージ(何も消さない)させる。CodeRabbit #3457616122。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/shared/relay-transport.js (1)
23-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueLine 23 のコメントが変更後の挙動と矛盾しています(署名対象は pathname のみではなくなった)。
q(クエリ)をsignRequestの署名対象に含めるようになり、relay 側verify(query = url.search)と一致するようになりました。しかし Line 23 のコメントは「path はクエリ無し(署名対象 = pathname のみ。relay auth.ts と一致)」のままで、現状の改竄防止モデル(?d=...改竄による別ドメインアイテム削除の防止)を誤って説明しています。将来の誤読を避けるためコメントを更新してください。📝 コメント更新例
- // 署名付きで relay を叩く。path はクエリ無し(署名対象 = pathname のみ。relay auth.ts と一致)。 + // 署名付きで relay を叩く。署名対象は pathname に加えクエリ(?d=... 等)も含める + // (relay verify の url.search と一致=クエリ改竄による別アイテム削除を防止)。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/relay-transport.js` around lines 23 - 28, The comment above the req function states that the signature target is pathname only, but the actual signRequest call now includes the query parameter q alongside the pathname. Update the comment to accurately describe that the signature covers both the pathname and query parameters (including the q variable constructed from the query input), explaining that this prevents tampering with query strings such as domain-related parameters that could lead to unauthorized operations on other domains.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/shared/relay-transport.js`:
- Around line 23-28: The comment above the req function states that the
signature target is pathname only, but the actual signRequest call now includes
the query parameter q alongside the pathname. Update the comment to accurately
describe that the signature covers both the pathname and query parameters
(including the q variable constructed from the query input), explaining that
this prevents tampering with query strings such as domain-related parameters
that could lead to unauthorized operations on other domains.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 60315452-1442-4d7d-aec7-9bf771d2c190
📒 Files selected for processing (21)
.github/workflows/mobile-android.ymlinfra/cloudflare/relay/pnpm-workspace.yamlinfra/cloudflare/relay/src/auth.tsinfra/cloudflare/relay/src/index.tsinfra/cloudflare/relay/src/vault-do.tsmobile/index.htmlmobile/src/iap.jsmobile/src/main.jsmobile/src/notes-meta.jsmobile/src/sync-orchestrator.jsmobile/vite.config.jsscripts/_mobile_crud_repro.mjsscripts/_mobile_sync_repro.mjsscripts/_relay_e2e.mjsscripts/_vault_selftest.mjssrc/background/background.jssrc/manage/manage.jssrc/shared/groups.jssrc/shared/relay-transport.jssrc/shared/sync.jssrc/shared/vault.js
🚧 Files skipped from review as they are similar to previous changes (14)
- infra/cloudflare/relay/src/auth.ts
- mobile/src/iap.js
- scripts/_vault_selftest.mjs
- scripts/_mobile_crud_repro.mjs
- .github/workflows/mobile-android.yml
- infra/cloudflare/relay/src/index.ts
- src/manage/manage.js
- mobile/index.html
- src/shared/sync.js
- scripts/_relay_e2e.mjs
- mobile/src/sync-orchestrator.js
- infra/cloudflare/relay/src/vault-do.ts
- src/background/background.js
- scripts/_mobile_sync_repro.mjs
…mit 有効化 実機確認以外の残タスクとして relay を本番構成へ移行。 - wrangler.toml: custom domain relay.petarin.nephilim.jp を routes に追加(nephilim.jp は CF Zone で 証明書自動発行)、workers_dev=false で workers.dev の口を閉じる、unsafe ratelimit binding (IP 60/min・vault 120/min)を再有効化。 - relay-transport.js: DEFAULT_RELAY_URL を custom domain に変更(新規 vault はこの URL を使う)。 検証: RELAY_URL=https://relay.petarin.nephilim.jp で _relay_e2e 9 PASS / _mobile_sync_repro 9 PASS。 workers.dev/health は 404(閉鎖確認)。Version 8b2c00da。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
同期 ON ではゴミ箱内の削除済み付箋本文も中継サーバーへ送られるため、日英のポリシーを改訂。 同期を「A. ブラウザ標準同期(無料)」「B. クラウド同期(買い切り・E2E暗号化・ゼロ知識)」の2方式に 整理し、B では本文を端末で AES-GCM 暗号化・ドメイン名を HMAC ハッシュ化して中継し開発者は内容を 読めないこと、ゴミ箱本文も同様に暗号化送信されることを明記。「開発者サーバーへ送信しない」も 「クラウド同期をオンにした場合を除く」に修正。最終更新日を 2026-06-23 に更新。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
概要
ぺたりんに ① スマホアプリ(Capacitor) と ② Notion 級のリアルタイム・クラウド同期(E2E 暗号化) を足す大型機能ブランチ。
同期は完全 opt-in で、既定 OFF=外部送信ゼロ・現状と完全に同一挙動を維持する。chrome.storage.sync を使う既存の「Chrome 標準同期」も挙動不変。
設計は「コスト最優先」「サーバーは全盲(zero-knowledge)」を 2 大原則に据え、Cloudflare の公式料金・ハイバネ WS 仕様を実証してから組んだ。
何が入ったか
同期エンジンの土台(挙動不変リファクタ)
chrome.storage.sync直叩きを transport 抽象(setSyncTransport/getSyncTransport)へ括り出し。既定は従来通りの chrome transport で挙動完全不変(回帰 PASS 維持)。Cloudflare relay(サーバー側・
infra/cloudflare/relay/)state.acceptWebSocket())を 1 本常時接続。アイドルは duration 課金ゼロ(CF 公式保証)。編集→本体 push→薄い変更ping だけ WS broadcast→受信端末は該当ドメインだけ pull→既存mergeDomainNotesで反映。petarin-sync)+ SALT secret 投入済み、https://petarin-relay.1llum1n4t1.workers.devにデプロイ済み。CORS 対応(manifest に origin を焼かず拡張 SW / モバイル WebView から叩ける)。排他 3 モード同期(拡張クライアント)
syncModeで transport を切替。RelayTransport(「暗号化された chrome.storage.sync ミラー」として sync.js を無改造で動かす)。manageの同期パネルを 3 ラジオ化+モード別同意文+クラウドのペアリング UI(作成 / 参加 / 解除 / QR 表示 / コピー)。スマホアプリ(
mobile/・Capacitor)@shared→../src/sharedで同期エンジンを単一ソース再利用(コピーしない)。chrome.storageシム(Capacitor Preferences backed)+ モバイル版 sync オーケストレータ。group:base64url(NFC(name)))へ符号化=後でクラウド同期を買ってもisValidDomain無改修で通る。IAP(¥500 買い切り)はクラウド同期だけをゲート。.github/workflows/mobile-android.yml)。テスト(全て依存なしの決定的スイート・PASS 済み)
node scripts/_sync_repro.mjs→ 223 PASS(同期エンジンの墓石/容量/設定マージ。storage/sync は無改修)node scripts/_vault_selftest.mjs→ 7 PASS(署名がサーバー検証を通る契約一致・AES-GCM round-trip・ハッシュ決定性)node scripts/_relay_e2e.mjs→ 7 PASS(実 DO/D1/Worker で notify-then-pull・暗号化往復・WS 変更ピン・削除・無認証 401)node scripts/_mobile_sync_repro.mjs→ 9 PASS(シム+実エンジン+実 relay で device A push→device B pull の暗号化往復)node scripts/_mobile_crud_repro.mjs→ 15 PASS(vault/同期なしのスタンドアローン CRUD データ経路・グループキーの同期安全性)vite build(mobile)→ 26 modules OKレビュアーへの注意 / マージ前の残作業
relay.petarin.nephilim.jp想定)へ寄せてworkers_dev=false、unsafe ratelimit binding を再有効化(ローカル dev では workerd が落ちるためコメントアウト中)。jp.nephilim.petarin.syncnon-consumable)、cap add android/ios+ネイティブビルド(iOS は macOS+署名・カメラに NSCameraUsageDescription 要)、ストア申請。🤖 Generated with Claude Code