From e5c479f0b11c62e099387e06fca5c454255f04e2 Mon Sep 17 00:00:00 2001 From: SimonShiki Date: Fri, 14 Aug 2026 17:16:52 +0800 Subject: [PATCH 1/4] :wrench: chore(gui): switch to codingclip api for library and tests Signed-off-by: SimonShiki --- packages/gui/src/containers/gui.tsx | 2 ++ packages/gui/src/containers/library-item.jsx | 3 ++- packages/gui/src/lib/project-fetcher-hoc.jsx | 9 ++++++- packages/gui/src/lib/storage.ts | 24 +++++++++++++++---- .../gui/test/integration/costumes.test.js | 8 ++++++- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/gui/src/containers/gui.tsx b/packages/gui/src/containers/gui.tsx index 782685e93..b20fb3256 100644 --- a/packages/gui/src/containers/gui.tsx +++ b/packages/gui/src/containers/gui.tsx @@ -49,6 +49,7 @@ interface StorageWithOfficialStores { interface OwnProps extends ComponentProps { assetHost?: string; + cdnHost?: string; cloudHost?: string; fetchingProject?: boolean; isLoading?: boolean; @@ -137,6 +138,7 @@ class GUI extends React.Component { const { /* eslint-disable @typescript-eslint/no-unused-vars */ assetHost, + cdnHost, cloudHost, error, isError, diff --git a/packages/gui/src/containers/library-item.jsx b/packages/gui/src/containers/library-item.jsx index 51e105053..55e62e4a3 100644 --- a/packages/gui/src/containers/library-item.jsx +++ b/packages/gui/src/containers/library-item.jsx @@ -4,6 +4,7 @@ import React from 'react'; import {injectIntl} from 'react-intl'; import LibraryItemComponent from '../components/library-item/library-item.jsx'; +import storage from '../lib/storage'; class LibraryItem extends React.PureComponent { constructor (props) { @@ -106,7 +107,7 @@ class LibraryItem extends React.PureComponent { render () { const iconMd5 = this.curIconMd5(); const iconURL = iconMd5 ? - `https://cdn.assets.scratch.mit.edu/internalapi/asset/${iconMd5}/get/` : + `${storage.cdnHost}project/asset/${iconMd5}` : this.props.iconRawURL; return ( ; method?: 'post'; }; @@ -41,6 +42,8 @@ class Storage extends ScratchStorage { private projectHost = ''; private projectToken = ''; private assetHost = ''; + private cdnHost = ''; + private authorizationToken = ''; private translator: Translator; constructor () { @@ -102,19 +105,32 @@ class Storage extends ScratchStorage { this.assetHost = assetHost; } + setCdnHost (cdnHost: string): void { + this.cdnHost = cdnHost; + } + + setAuthorizationToken (token: string): void { + this.authorizationToken = `Bearer ${token}`; + } + getAssetGetConfig (asset: Asset): ConfigResponse { - return `${this.assetHost}/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/`; + return { + url: `${this.cdnHost}project/asset/${asset.assetId}.${asset.dataFormat}`, + headers: {referer: location.host} + }; } getAssetCreateConfig (asset: Asset): ConfigResponse { + const headers = this.authorizationToken ? {authorization: this.authorizationToken} : undefined; return { // There is no such thing as updating assets, but storage assumes it // should update if there is an assetId, and the asset store uses the // assetId as part of the create URI. So, force the method to POST. // Then when storage finds this config to use for the "update", still POSTs method: 'post', - url: `${this.assetHost}/${asset.assetId}.${asset.dataFormat}`, - withCredentials: true + url: `${this.assetHost}project/uploadAsset/${asset.assetId}.${asset.dataFormat}`, + withCredentials: true, + headers }; } diff --git a/packages/gui/test/integration/costumes.test.js b/packages/gui/test/integration/costumes.test.js index f711987e7..7ddef1c52 100644 --- a/packages/gui/test/integration/costumes.test.js +++ b/packages/gui/test/integration/costumes.test.js @@ -16,6 +16,12 @@ const { // The costumes library is slow to load. Increase the timeout for these tests. jest.setTimeout(60_000); +// The built GUI uses the codingclip asset CDN by default; build with a matching +// USE_SCRATCH_ASSET_API env var to run these tests against the Scratch CDN. +const abbyCostumeURL = process.env.USE_SCRATCH_ASSET_API ? + 'https://cdn.assets.scratch.mit.edu/internalapi/asset/45de34b47a2ce22f6f5d28bb35a44ff5.svg/get/' : + 'https://static.codingclip.com/v1/project/asset/45de34b47a2ce22f6f5d28bb35a44ff5.svg'; + const uri = path.resolve(__dirname, '../../build/index.html'); let driver; @@ -211,7 +217,7 @@ describe('Working with costumes', () => { .mouseMove(abbyElement) .perform(); // wait for one of Abby's alternate costumes to appear - await findByXpath('//img[@src="https://cdn.assets.scratch.mit.edu/internalapi/asset/45de34b47a2ce22f6f5d28bb35a44ff5.svg/get/"]'); + await findByXpath(`//img[@src="${abbyCostumeURL}"]`); const logs = await getLogs(); await expect(logs).toEqual([]); }); From 7a013957909fc1b2a4142f6fe2200141f9d5367c Mon Sep 17 00:00:00 2001 From: SimonShiki Date: Fri, 14 Aug 2026 18:08:48 +0800 Subject: [PATCH 2/4] :wrench: chore: make tests use codingclip service by default Signed-off-by: SimonShiki --- .github/workflows/validate.yml | 2 ++ .../test/integration/download-known-assets.js | 9 ++++++++- .../vm/test/fixtures/make-test-storage.js | 19 +++++++++---------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 441a6e91a..1c0a241c4 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -100,6 +100,8 @@ jobs: run-attempt: ${{ github.run_attempt }} - name: Test + env: + USE_SCRATCH_ASSET_API: 1 run: pnpm vm test test-block: diff --git a/packages/storage/test/integration/download-known-assets.js b/packages/storage/test/integration/download-known-assets.js index d99275a26..c6ebb351f 100644 --- a/packages/storage/test/integration/download-known-assets.js +++ b/packages/storage/test/integration/download-known-assets.js @@ -3,6 +3,11 @@ const test = require('tap').test; const ScratchStorage = require('../../dist/node/scratch-storage'); +const useScratchAssetApi = !!process.env.USE_SCRATCH_ASSET_API; +const ASSET_SERVER = useScratchAssetApi ? + 'https://cdn.assets.scratch.mit.edu/' : + 'https://static.codingclip.com/v1/'; + let storage; test('constructor', t => { storage = new ScratchStorage(); @@ -79,7 +84,9 @@ test('addWebSource', t => { t.doesNotThrow(() => { storage.addWebSource( [storage.AssetType.ImageVector, storage.AssetType.ImageBitmap, storage.AssetType.Sound], - asset => `https://cdn.assets.scratch.mit.edu/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/` + asset => useScratchAssetApi ? + `https://cdn.assets.scratch.mit.edu/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/` : + `${ASSET_SERVER}project/asset/${asset.assetId}.${asset.dataFormat}` ); }); t.end(); diff --git a/packages/vm/test/fixtures/make-test-storage.js b/packages/vm/test/fixtures/make-test-storage.js index ecc099a99..64fdc780b 100644 --- a/packages/vm/test/fixtures/make-test-storage.js +++ b/packages/vm/test/fixtures/make-test-storage.js @@ -1,6 +1,10 @@ import {ScratchStorage} from 'clipcc-storage'; -const ASSET_SERVER = 'https://cdn.assets.scratch.mit.edu/'; +const useScratchAssetApi = !!process.env.USE_SCRATCH_ASSET_API; + +const ASSET_SERVER = useScratchAssetApi ? + 'https://cdn.assets.scratch.mit.edu/' : + 'https://static.codingclip.com/v1/'; const PROJECT_SERVER = 'https://cdn.projects.scratch.mit.edu/'; /** @@ -21,15 +25,10 @@ const getProjectUrl = function (asset) { * @returns {string} a URL to download a project asset (PNG, WAV, etc.) */ const getAssetUrl = function (asset) { - const assetUrlParts = [ - ASSET_SERVER, - 'internalapi/asset/', - asset.assetId, - '.', - asset.dataFormat, - '/get/' - ]; - return assetUrlParts.join(''); + if (useScratchAssetApi) { + return `${ASSET_SERVER}internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/`; + } + return `${ASSET_SERVER}project/asset/${asset.assetId}.${asset.dataFormat}`; }; /** From f9c143c10872d8ea6be2c2a65b4ed8fec521e764 Mon Sep 17 00:00:00 2001 From: SimonShiki Date: Fri, 14 Aug 2026 18:11:27 +0800 Subject: [PATCH 3/4] :wrench: chore(desktop): download media library assets from codingclip by default Signed-off-by: SimonShiki --- app/desktop/scripts/fetchMediaLibraryAssets.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/desktop/scripts/fetchMediaLibraryAssets.js b/app/desktop/scripts/fetchMediaLibraryAssets.js index d6cfe4887..3347b11c7 100644 --- a/app/desktop/scripts/fetchMediaLibraryAssets.js +++ b/app/desktop/scripts/fetchMediaLibraryAssets.js @@ -8,7 +8,11 @@ const async = require('async'); const libraries = require('./lib/libraries'); const NO_CACHE = process.argv.includes('--no-cache'); -const ASSET_HOST = 'cdn.assets.scratch.mit.edu'; +const useScratchAssetApi = !!process.env.USE_SCRATCH_ASSET_API; +const ASSET_HOST = useScratchAssetApi ? 'cdn.assets.scratch.mit.edu' : 'static.codingclip.com'; +const getAssetPath = useScratchAssetApi ? + md5 => `/internalapi/asset/${md5}/get/` : + md5 => `/v1/project/asset/${md5}`; const NUM_SIMULTANEOUS_DOWNLOADS = 5; const OUT_PATH = path.resolve('static', 'assets'); @@ -66,10 +70,17 @@ const fetchAsset = function (md5, callback) { return; } + // The codingclip CDN serves assets by md5ext; skip values without an extension. + if (!useScratchAssetApi && !md5.includes('.')) { + console.warn(`Skipped (no file extension): ${md5}`); + callback(); + return; + } + const myAgent = connectionPool.pop() || new https.Agent({keepAlive: true}); const getOptions = { host: ASSET_HOST, - path: `/internalapi/asset/${md5}/get/`, + path: getAssetPath(md5), agent: myAgent }; const urlHuman = `//${getOptions.host}${getOptions.path}`; From 0cab71e83a80b3991256902df0e88a7937d2d650 Mon Sep 17 00:00:00 2001 From: SimonShiki Date: Sun, 16 Aug 2026 16:07:53 +0800 Subject: [PATCH 4/4] :fire: chore: remove scratch api fallback Signed-off-by: SimonShiki --- .github/workflows/validate.yml | 2 -- app/desktop/scripts/fetchMediaLibraryAssets.js | 10 +++------- packages/gui/src/lib/project-fetcher-hoc.jsx | 2 +- packages/gui/src/lib/storage.ts | 2 +- packages/gui/test/integration/costumes.test.js | 6 +----- .../storage/test/integration/download-known-assets.js | 9 ++------- packages/vm/test/fixtures/make-test-storage.js | 9 +-------- 7 files changed, 9 insertions(+), 31 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 1c0a241c4..441a6e91a 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -100,8 +100,6 @@ jobs: run-attempt: ${{ github.run_attempt }} - name: Test - env: - USE_SCRATCH_ASSET_API: 1 run: pnpm vm test test-block: diff --git a/app/desktop/scripts/fetchMediaLibraryAssets.js b/app/desktop/scripts/fetchMediaLibraryAssets.js index 3347b11c7..b26b32910 100644 --- a/app/desktop/scripts/fetchMediaLibraryAssets.js +++ b/app/desktop/scripts/fetchMediaLibraryAssets.js @@ -8,11 +8,7 @@ const async = require('async'); const libraries = require('./lib/libraries'); const NO_CACHE = process.argv.includes('--no-cache'); -const useScratchAssetApi = !!process.env.USE_SCRATCH_ASSET_API; -const ASSET_HOST = useScratchAssetApi ? 'cdn.assets.scratch.mit.edu' : 'static.codingclip.com'; -const getAssetPath = useScratchAssetApi ? - md5 => `/internalapi/asset/${md5}/get/` : - md5 => `/v1/project/asset/${md5}`; +const ASSET_HOST = 'static.codingclip.com'; const NUM_SIMULTANEOUS_DOWNLOADS = 5; const OUT_PATH = path.resolve('static', 'assets'); @@ -71,7 +67,7 @@ const fetchAsset = function (md5, callback) { } // The codingclip CDN serves assets by md5ext; skip values without an extension. - if (!useScratchAssetApi && !md5.includes('.')) { + if (!md5.includes('.')) { console.warn(`Skipped (no file extension): ${md5}`); callback(); return; @@ -80,7 +76,7 @@ const fetchAsset = function (md5, callback) { const myAgent = connectionPool.pop() || new https.Agent({keepAlive: true}); const getOptions = { host: ASSET_HOST, - path: getAssetPath(md5), + path: `/v1/project/asset/${md5}`, agent: myAgent }; const urlHuman = `//${getOptions.host}${getOptions.path}`; diff --git a/packages/gui/src/lib/project-fetcher-hoc.jsx b/packages/gui/src/lib/project-fetcher-hoc.jsx index 9d878f791..8e7f7a740 100644 --- a/packages/gui/src/lib/project-fetcher-hoc.jsx +++ b/packages/gui/src/lib/project-fetcher-hoc.jsx @@ -144,7 +144,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { ProjectFetcherComponent.defaultProps = { assetHost: 'https://api.codingclip.com/v1/', cdnHost: 'https://static.codingclip.com/v1/', - projectHost: 'https://projects.scratch.mit.edu' + projectHost: 'https://api.codingclip.com/v1' }; const mapStateToProps = state => ({ diff --git a/packages/gui/src/lib/storage.ts b/packages/gui/src/lib/storage.ts index d3e72419b..21402ba2e 100644 --- a/packages/gui/src/lib/storage.ts +++ b/packages/gui/src/lib/storage.ts @@ -82,7 +82,7 @@ class Storage extends ScratchStorage { } getProjectGetConfig (projectAsset: Asset): ConfigResponse { - const path = `${this.projectHost}/${projectAsset.assetId}`; + const path = `${this.projectHost}project/json/${projectAsset.assetId}`; const qs = this.projectToken ? `?token=${this.projectToken}` : ''; return path + qs; } diff --git a/packages/gui/test/integration/costumes.test.js b/packages/gui/test/integration/costumes.test.js index 7ddef1c52..5c81e0250 100644 --- a/packages/gui/test/integration/costumes.test.js +++ b/packages/gui/test/integration/costumes.test.js @@ -16,11 +16,7 @@ const { // The costumes library is slow to load. Increase the timeout for these tests. jest.setTimeout(60_000); -// The built GUI uses the codingclip asset CDN by default; build with a matching -// USE_SCRATCH_ASSET_API env var to run these tests against the Scratch CDN. -const abbyCostumeURL = process.env.USE_SCRATCH_ASSET_API ? - 'https://cdn.assets.scratch.mit.edu/internalapi/asset/45de34b47a2ce22f6f5d28bb35a44ff5.svg/get/' : - 'https://static.codingclip.com/v1/project/asset/45de34b47a2ce22f6f5d28bb35a44ff5.svg'; +const abbyCostumeURL = 'https://static.codingclip.com/v1/project/asset/45de34b47a2ce22f6f5d28bb35a44ff5.svg'; const uri = path.resolve(__dirname, '../../build/index.html'); diff --git a/packages/storage/test/integration/download-known-assets.js b/packages/storage/test/integration/download-known-assets.js index c6ebb351f..634084cc1 100644 --- a/packages/storage/test/integration/download-known-assets.js +++ b/packages/storage/test/integration/download-known-assets.js @@ -3,10 +3,7 @@ const test = require('tap').test; const ScratchStorage = require('../../dist/node/scratch-storage'); -const useScratchAssetApi = !!process.env.USE_SCRATCH_ASSET_API; -const ASSET_SERVER = useScratchAssetApi ? - 'https://cdn.assets.scratch.mit.edu/' : - 'https://static.codingclip.com/v1/'; +const ASSET_SERVER = 'https://static.codingclip.com/v1/'; let storage; test('constructor', t => { @@ -84,9 +81,7 @@ test('addWebSource', t => { t.doesNotThrow(() => { storage.addWebSource( [storage.AssetType.ImageVector, storage.AssetType.ImageBitmap, storage.AssetType.Sound], - asset => useScratchAssetApi ? - `https://cdn.assets.scratch.mit.edu/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/` : - `${ASSET_SERVER}project/asset/${asset.assetId}.${asset.dataFormat}` + asset => `${ASSET_SERVER}project/asset/${asset.assetId}.${asset.dataFormat}` ); }); t.end(); diff --git a/packages/vm/test/fixtures/make-test-storage.js b/packages/vm/test/fixtures/make-test-storage.js index 64fdc780b..053bd93c5 100644 --- a/packages/vm/test/fixtures/make-test-storage.js +++ b/packages/vm/test/fixtures/make-test-storage.js @@ -1,10 +1,6 @@ import {ScratchStorage} from 'clipcc-storage'; -const useScratchAssetApi = !!process.env.USE_SCRATCH_ASSET_API; - -const ASSET_SERVER = useScratchAssetApi ? - 'https://cdn.assets.scratch.mit.edu/' : - 'https://static.codingclip.com/v1/'; +const ASSET_SERVER = 'https://static.codingclip.com/v1/'; const PROJECT_SERVER = 'https://cdn.projects.scratch.mit.edu/'; /** @@ -25,9 +21,6 @@ const getProjectUrl = function (asset) { * @returns {string} a URL to download a project asset (PNG, WAV, etc.) */ const getAssetUrl = function (asset) { - if (useScratchAssetApi) { - return `${ASSET_SERVER}internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/`; - } return `${ASSET_SERVER}project/asset/${asset.assetId}.${asset.dataFormat}`; };