Skip to content

0G Storage 上傳改走同源代理,繞開 storage node 的 CORS - #11

Merged
k66inthesky merged 2 commits into
mainfrom
claude/0g-tech-competition-deploy-lv08or
Sep 10, 2026
Merged

k66inthesky merged 2 commits into
mainfrom
claude/0g-tech-competition-deploy-lv08or

Conversation

@k66inthesky

Copy link
Copy Markdown
Contributor

問題定位

診斷收斂後確認:瀏覽器打得到 indexer,卡在後面。

SDK 上傳一個檔案要跟兩種主機講話:

  1. 問 indexer「這個檔案該傳給哪些 storage node」(indexer_getShardedNodes)→ 通
  2. 拿清單裡的 url 直接打每一台 storage node 的 zgs_uploadSegment → 不通

那些節點是各自獨立的主機,沒有為瀏覽器開 CORS,axios 只回一句沒有內容的 Network Error。

改法

新增 functions/api/og/zg/[[path]].js,把兩步都繞過自家 Worker:

前端 new Indexer('/api/og/zg/indexer')
       ├─ 轉發到真的 indexer,並把回應裡每個節點的 url 改寫成
       │  /api/og/zg/node/<編碼後的原始網址>
       └─ 節點請求再轉發到那台真的 storage node

瀏覽器全程只跟自己的網域講話,沒有跨來源請求。Flow 合約那步不經過代理(走 MetaMask 簽名,本來就沒 CORS 問題);EVM RPC 維持直連。

節點清單的形狀在 SDK 版本之間變過(有時陣列、有時 { trusted: [...] } 分片物件),所以改寫不假設結構,直接走訪整棵 JSON,看到叫 url 且是 http(s) 開頭的字串就換掉。非 0g.ai 的主機原樣留著讓它自己失敗,不靜悄悄吞掉。

安全性

  • 只轉發到 0g.ai 與其子網域的 https 主機 —— 否則就是一個任何人都能借用的開放代理。
  • 轉發時只帶 content-type,不把 cookie、authorization 之類的標頭交給第三方主機。
  • Body 上限 6MB(一個 segment 是 256KB)、30 秒逾時。

驗證

用假的 indexer / 節點驅動 Function 本體:

【1】節點網址正確改寫;非 0g.ai 主機保持原樣 ✓
【2】編碼後的網址能還原成原始目標(含 port 與 path);method 與 body 原樣帶過去 ✓
【3】白名單四種繞過嘗試全被擋:
       非 0g.ai          → 403 ✓
       http 明文          → 403 ✓
       not0g.ai(後綴冒充)→ 403 ✓
       0g.ai.evil.com    → 403 ✓
     合法 0g.ai 放行     → 200 ✓

wrangler pages dev 確認巢狀 [[path]] catch-all 路由解析正確、未知路徑回 404,且 /api/og/storage 與 /api/og/status 不受影響。

⚠️ 實際上傳仍需在瀏覽器實測 —— 這個開發環境連不到 0g.ai。失敗的話賽道一與賽道三不受影響。


🤖 Generated with Claude Code

https://claude.ai/code/session_015NBxaWtzqEX3i8NvoZEeq3


Generated by Claude Code

k66inthesky and others added 2 commits September 9, 2026 15:49
這兩點不是「線上顯示舊人物」的原因 —— 我原本以為是,實測後發現舊版程式在
同樣的 SPA fallback 條件下也能正常顯示三張照片,那個問題是部署內容裡沒有圖。
但這兩點本身仍是實際存在的脆弱處,順手修掉:

1) 探測改成四個候選同時載,不再一個一個等。
   線上有 SPA fallback(找不到檔案回 index.html 200),不存在的副檔名不會快速
   404,而是回一整份 HTML 才失敗。依序試的話總時間是四個相加;平行只花最慢的
   那一個。仍依候選順序挑第一個成功的,結果是確定的。

2) 探測是非同步的,但畫面同步檢查 heroSheetReady()。網路慢的時候簡報畫面
   可能先用 SVG 畫好,探測晚一點才回來說圖其實在,那時畫面不會自己更新,
   玩家就永遠看到備援版本。把英雄卡的繪製抽成 renderHeroCards(),
   探測完成後如果簡報畫面還開著就就地重畫。

在模擬 Cloudflare SPA fallback 的伺服器上驗過(缺檔回 200 text/html):
按下「跳過劇情」後 0.4 秒就是三張形象照,採用 /images/heroes.jpeg,無 JS 錯誤。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NBxaWtzqEX3i8NvoZEeq3
診斷收斂後確認:瀏覽器打得到 indexer,卡在後面。SDK 上傳一個檔案要跟兩種
主機講話 —— 先問 indexer「該傳給哪些 storage node」,再拿那份清單裡的 url
直接打每一台節點的 zgs_uploadSegment。第一步通,第二步不通:那些節點是各自
獨立的主機,沒有為瀏覽器開 CORS,axios 只回一句沒有內容的 "Network Error"。

新增 functions/api/og/zg/[[path]].js 把兩步都繞過自家 Worker:

  前端 new Indexer('/api/og/zg/indexer')
         ├─ 轉發到真的 indexer,並把回應裡每個節點的 url 改寫成
         │  /api/og/zg/node/<編碼後的原始網址>
         └─ 節點請求再轉發到那台真的 storage node

這樣瀏覽器全程只跟自己的網域講話,沒有跨來源請求。Flow 合約那步不經過代理,
它走 MetaMask 簽名,本來就沒有 CORS 問題;EVM RPC 也維持直連(實測通得過)。

節點清單的形狀在 SDK 版本之間變過(有時陣列、有時 { trusted: [...] } 分片
物件),所以改寫不去假設結構,直接走訪整棵 JSON,看到叫 url 且是 http(s)
開頭的字串就換掉。非 0g.ai 的主機原樣留著讓它自己失敗,不靜悄悄吞掉。

安全性:只轉發到 0g.ai(與其子網域)的 https 主機,否則就是一個任何人都能
借用的開放代理。轉發時只帶 content-type,不把 cookie、authorization 之類的
標頭交給第三方主機。

驗證:
- 用假的 indexer/節點驅動 Function 本體:節點網址正確改寫、編碼後的網址能
  還原成原始目標(含 port 與 path)、method 與 body 原樣帶過去。
- 白名單四種繞過嘗試都被擋(非 0g.ai、http 明文、not0g.ai 這種後綴冒充、
  0g.ai.evil.com 這種子網域冒充),合法的 0g.ai 放行。
- wrangler pages dev 確認巢狀 [[path]] catch-all 路由解析正確,未知路徑回
  404,且 /api/og/storage 與 /api/og/status 不受影響。

實際上傳仍需在瀏覽器實測 —— 這個開發環境連不到 0g.ai。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NBxaWtzqEX3i8NvoZEeq3
Copilot AI lite review requested due to automatic review settings September 10, 2026 01:46
@k66inthesky
k66inthesky merged commit 5515d7e into main Sep 10, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

代理轉發目前以文字方式讀取/轉送 request body 可能破壞二進位上傳 payload,且部分 API 行為變更(忽略 indexer 參數)需要同步調整以避免誤用與誤診斷。

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

這個 PR 針對 0G Storage 在瀏覽器端上傳時會被 storage node 的 CORS 擋下的問題,新增同源代理路由,讓前端上傳流程全程只打自己網域,並順手改善簡報畫面英雄形象照的載入/更新時序。

Changes:

  • 新增 /api/og/zg/* 同源代理(indexer 轉發 + 回應節點 URL 改寫 + node 轉發),用來繞開 storage node 的 CORS。
  • 前端上傳流程改用同源 proxy indexer,避免 SDK 直連 storage node。
  • 英雄形象照探測改為並行載入,並在探測完成後可就地更新簡報畫面卡片。
File summaries
File Description
public/js/storage.js 將 0G Storage indexer 預設改成同源代理,並調整上傳時的 indexer 初始化方式。
public/js/main.js 抽出並重用英雄卡片渲染,探測完成後可在簡報畫面已顯示時就地更新。
public/js/art.js 英雄形象照探測改為並行載入候選圖片,以降低被 SPA fallback 拖慢的機率。
functions/api/og/zg/[[path]].js 新增 0G Storage 同源代理 Function:indexer 轉發與節點 URL 改寫、node 轉發與白名單限制。
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +85 to +87
const body =
request.method === 'GET' || request.method === 'HEAD' ? undefined : await request.text();
if (body && body.length > MAX_BODY) return json({ error: 'payload too large' }, 413);
Comment thread public/js/art.js
Comment on lines +387 to +391
const results = await Promise.all(HERO_SHEET_CANDIDATES.map(tryLoad));
const hit = results.findIndex(Boolean);
if (hit === -1) return (sheetState = false);
HERO_SHEET = HERO_SHEET_CANDIDATES[hit];
return (sheetState = true);
Comment thread public/js/storage.js
Comment on lines +99 to +101
// 這裡刻意忽略呼叫端傳進來的真實 indexer 網址,一律走代理 ——
// 直連的話 segment 那步會被 storage node 的 CORS 擋掉。
const client = new zg.Indexer(new URL(DEFAULT_INDEXER, location.origin).toString());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants