Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/infra/smalruby-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

| エンドポイント | Lambda | 用途 |
|---|---|---|
| `GET /cors-proxy` | `smalruby-api-cors-proxy{stageSuffix}` | 任意 URL の CORS フリーフェッチ + Google Drive URL 変換 + バイナリ Base64 化。音声合成 (text2speech) もこの汎用プロキシ経由で `synthesis-service.scratch.mit.edu` を叩く |
| `GET /cors-proxy` | `smalruby-api-cors-proxy{stageSuffix}` | 任意 URL の CORS フリーフェッチ + Google Drive URL 変換 + バイナリ Base64 化。音声合成 (text2speech) と翻訳 (translate) もこの汎用プロキシ経由で `synthesis-service.scratch.mit.edu` / `translate-service.scratch.mit.edu` を叩く |
| `GET /mesh-domain` | `smalruby-api-mesh-zone{stageSuffix}` | クライアント IP から Mesh ドメイン (CRC32 ハッシュ) を生成 |
| `GET /scratch-api-proxy/projects/{projectId}` | `smalruby-api-scratch-projects{stageSuffix}` | Scratch 公式 API (project info) のステータス透過プロキシ |
| `GET /scratch-api-proxy/translate` | `smalruby-api-scratch-translate{stageSuffix}` | Scratch 公式翻訳サービスのプロキシ |
| `GET /scratch-api-proxy/translate` | `smalruby-api-scratch-translate{stageSuffix}` | **obsolete**。translate は共通 `cors-proxy` 経由に統一した (#862、text2speech #861 と同方式)。新規開発で使わない。Lambda/ルートは deploy を伴うため今は残置 |

`OPTIONS` (preflight) は HTTP API v2 の **built-in CORS** が自動処理。旧 SAM の `cors-for-smalruby` Lambda は不要。

Expand Down Expand Up @@ -60,10 +60,12 @@ Scratch Foundation 公式 API (`https://api.scratch.mit.edu/projects/{projectId}

実装: `infra/smalruby-api/lambda/scratch-api-projects.ts`

### `GET /scratch-api-proxy/translate`
### `GET /scratch-api-proxy/translate` (obsolete)

Scratch translate サービス (`https://translate-service.scratch.mit.edu/translate`) のプロキシ。

> ⚠️ **obsolete — 新規開発で使わない**: 翻訳 (translate) は text2speech (#861) と同じく**汎用 `GET /cors-proxy?url=<encoded 翻訳URL>`** 経由に統一した (#862)。`cors-proxy` は翻訳のテキスト応答をそのまま返すため、この専用 Lambda は不要になった。フロント側の実装は `packages/scratch-vm/src/extensions/scratch3_translate/index.js` を参照。Lambda コード (`lambda/scratch-api-translate.ts`) と CDK ルート定義は **deploy を伴うため今は削除せず残置**している (別作業で撤去予定)。

実装: `infra/smalruby-api/lambda/scratch-api-translate.ts`

> **音声合成 (text2speech) について**: 音声合成サービス (`https://synthesis-service.scratch.mit.edu/synth`) も同じ CORS 制約があるが、専用 Lambda は作らず**汎用 `GET /cors-proxy?url=<encoded 合成URL>`** を再利用する。`cors-proxy` は `audio/*` をバイナリと判定して Base64 返却 (`isBase64Encoded: true`) するため、API Gateway 側でバイト列にデコードされ、拡張の `arrayBuffer()` がそのまま音声を得られる。フロント側の実装は `packages/scratch-vm/src/extensions/scratch3_text2speech/index.js` を参照 (#859)。
Expand Down
2 changes: 1 addition & 1 deletion docs/maintenance/smalruby-markers-vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ scratch-vm の **upstream ファイルに埋め込んだ Smalruby マーカー**
| `src/engine/runtime.js` | BEFORE_STEP event | upstream v13.7.2 が削除した `BEFORE_STEP` イベント(getter + `_step` での emit)を維持。Mesh v2 (broadcast-receiver.js / mesh-service.js) が毎フレーム queued remote events を流すために購読している |
| `src/engine/blocks.js` | XML coords guard | `blockToXML` で x/y が finite number のときだけ XML 属性を出力。Ruby → blocks 変換の x/y 未指定 (undefined) を scratch-blocks v2 に正しく伝え、`fromRuby` 再レイアウト経路を維持する |
| `src/engine/blocks.js` | orphaned-parent guard | `getTopLevelScript` で `block.parent` が this._blocks に存在しない場合に停止。Ruby → blocks 変換中の孤立 parent id で `undefined.parent` 参照クラッシュを防ぐ |
| `src/extensions/scratch3_translate/index.js` | translate CORS proxy | `serverURL` を Smalruby プロキシ (`https://api.smalruby.app/scratch-api-proxy/`) に上書き。Scratch の翻訳サービスは CORS を scratch.mit.edu 限定にしたため smalruby.app からの直叩きが失敗する。マーカー無しで上書きしていた過去の版が v13.7.2 upstream マージで静かに revert された (#857) ため、次回以降のマージで検知できるようマーカーで囲む |
| `src/extensions/scratch3_translate/index.js` | translate CORS proxy | 翻訳 URL を Smalruby の**汎用** CORS プロキシ (`https://api.smalruby.app/cors-proxy?url=<encoded 翻訳URL>`) で包む。Scratch の翻訳サービスは CORS を scratch.mit.edu 限定にしたため smalruby.app からの直叩きが失敗する。汎用 cors-proxy はテキスト応答をそのまま返すので専用 Lambda (`/scratch-api-proxy/translate`) は不要 (obsolete)。`serverURL` は upstream の値 (`https://translate-service.scratch.mit.edu/`) のまま維持し、URL 組み立て箇所だけラップするので upstream 差分が最小。text2speech (#859) と同じ方式に統一 (#862)。過去にマーカー無しで上書きした版が v13.7.2 upstream マージで静かに revert された (#857) ため、マーカーで囲んで次回以降のマージで検知できるようにする |
| `src/extensions/scratch3_text2speech/index.js` | synthesis CORS proxy | 音声合成 URL を Smalruby の**汎用** CORS プロキシ (`https://api.smalruby.app/cors-proxy?url=<encoded 合成URL>`) で包む。Scratch の音声合成サービスは CORS を scratch.mit.edu 限定にしたため smalruby.app からの直叩きが失敗する。汎用 cors-proxy はバイナリ音声を Base64 で返却する (API Gateway がバイト列にデコード) ので専用 Lambda は不要。`SERVER_HOST` は upstream の値のまま維持し、URL 組み立て箇所だけラップするので upstream 差分が最小。translate (#857) と同じ根本原因 (#859) |

## 関連ファイル
Expand Down
22 changes: 17 additions & 5 deletions packages/scratch-vm/src/extensions/scratch3_translate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ const blockIconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYA
* The url of the translate server.
* @type {string}
*/
const serverURL = 'https://translate-service.scratch.mit.edu/';

// === Smalruby: Start of translate CORS proxy ===
// Scratch's translate service is CORS-locked to scratch.mit.edu, so calling it
// directly from smalruby.app fails ('Access-Control-Allow-Origin' mismatch).
// Route through the Smalruby proxy (infra/smalruby-api scratch-api-proxy/translate)
// which forwards to translate-service.scratch.mit.edu server-side and returns the
// response with permissive CORS headers. Keep this override across upstream merges
// (it was silently reverted during the v13.7.2 merge — see issue #857).
const serverURL = 'https://api.smalruby.app/scratch-api-proxy/';
// Instead of a dedicated per-service Lambda, we reuse Smalruby's *generic* CORS
// proxy (infra/smalruby-api `GET /cors-proxy?url=<encoded target URL>`). It fetches
// the target server-side and returns the (text) response with permissive CORS
// headers. See where the translate request URL is built (search for CORS_PROXY_HOST).
// serverURL stays at the upstream value so upstream merges only touch the URL-build
// site (guarded by test/unit/extension_translate_proxy.js). Same root cause as
// text2speech (#857/#859), which the dedicated translate Lambda now replaces (#862).
const CORS_PROXY_HOST = 'https://api.smalruby.app/cors-proxy';
// === Smalruby: End of translate CORS proxy ===

/**
Expand Down Expand Up @@ -272,6 +277,13 @@ class Scratch3TranslateBlocks {
urlBase += '&text=';
urlBase += encodeURIComponent(args.WORDS);

// === Smalruby: Start of translate CORS proxy ===
// Wrap the translate URL in the generic Smalruby CORS proxy so smalruby.app
// is not blocked by the CORS-locked Scratch service. The whole translate URL
// (including its query string) becomes the encoded `url` param.
urlBase = `${CORS_PROXY_HOST}?url=${encodeURIComponent(urlBase)}`;
// === Smalruby: End of translate CORS proxy ===

const tempThis = this;
const translatePromise = fetchWithTimeout(urlBase, {}, serverTimeoutMs)
.then(response => response.text())
Expand Down
23 changes: 15 additions & 8 deletions packages/scratch-vm/test/unit/extension_translate_proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ const extPath = require.resolve('../../src/extensions/scratch3_translate');

// The Translate extension is an upstream Scratch file whose translate service is
// CORS-locked to scratch.mit.edu. Smalruby must route requests through its own
// proxy (api.smalruby.app/scratch-api-proxy/) so smalruby.app is not blocked by
// CORS. This test guards that the override is not silently reverted by an
// upstream merge (as happened during the v13.7.2 merge; see issue #857).
test('translate extension routes fetch through the Smalruby CORS proxy', (t) => {
// generic CORS proxy (api.smalruby.app/cors-proxy?url=<encoded translate URL>) so
// smalruby.app is not blocked by CORS. This test guards that the proxy wrapping is
// not silently reverted by an upstream merge (same root cause as text2speech; see
// issue #862 / #859 / #857).
test('translate extension routes fetch through the generic Smalruby CORS proxy', (t) => {
// Stub fetchWithTimeout before the extension captures it via destructuring at
// module load time, then fresh-require the extension so it picks up the stub.
const fetchModule = require(fetchModulePath);
Expand All @@ -31,13 +32,19 @@ test('translate extension routes fetch through the Smalruby CORS proxy', (t) =>
t.ok(capturedUrl, 'fetchWithTimeout was called');
t.match(
capturedUrl,
/^https:\/\/api\.smalruby\.app\/scratch-api-proxy\/translate\?/,
'serverURL points to the Smalruby CORS proxy',
/^https:\/\/api\.smalruby\.app\/cors-proxy\?url=/,
'request goes to the generic Smalruby CORS proxy',
);
// The translate URL (with its query) is carried as the encoded `url` param.
t.match(
capturedUrl,
/url=https%3A%2F%2Ftranslate-service\.scratch\.mit\.edu%2Ftranslate/,
'the CORS-locked translate URL is wrapped as the proxy `url` param',
);
t.notMatch(
capturedUrl,
/translate-service\.scratch\.mit\.edu/,
'does not call the CORS-locked Scratch translate service directly',
/^https:\/\/translate-service\.scratch\.mit\.edu/,
'the browser does not call the CORS-locked Scratch translate service directly',
);
t.end();
});
Expand Down
Loading