diff --git a/app/desktop/scripts/fetchMediaLibraryAssets.js b/app/desktop/scripts/fetchMediaLibraryAssets.js index d6cfe4887..b26b32910 100644 --- a/app/desktop/scripts/fetchMediaLibraryAssets.js +++ b/app/desktop/scripts/fetchMediaLibraryAssets.js @@ -8,7 +8,7 @@ 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 ASSET_HOST = 'static.codingclip.com'; const NUM_SIMULTANEOUS_DOWNLOADS = 5; const OUT_PATH = path.resolve('static', 'assets'); @@ -66,10 +66,17 @@ const fetchAsset = function (md5, callback) { return; } + // The codingclip CDN serves assets by md5ext; skip values without an extension. + if (!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: `/v1/project/asset/${md5}`, agent: myAgent }; const urlHuman = `//${getOptions.host}${getOptions.path}`; 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 ( ({ diff --git a/packages/gui/src/lib/storage.ts b/packages/gui/src/lib/storage.ts index 31b236449..21402ba2e 100644 --- a/packages/gui/src/lib/storage.ts +++ b/packages/gui/src/lib/storage.ts @@ -11,7 +11,8 @@ type Translator = ((messageId: string, defaultMessage?: string, description?: st type ConfigResponse = string | { url: string; - withCredentials: boolean; + withCredentials?: boolean; + headers?: Record; method?: 'post'; }; @@ -41,6 +42,8 @@ class Storage extends ScratchStorage { private projectHost = ''; private projectToken = ''; private assetHost = ''; + private cdnHost = ''; + private authorizationToken = ''; private translator: Translator; constructor () { @@ -79,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; } @@ -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..5c81e0250 100644 --- a/packages/gui/test/integration/costumes.test.js +++ b/packages/gui/test/integration/costumes.test.js @@ -16,6 +16,8 @@ const { // The costumes library is slow to load. Increase the timeout for these tests. jest.setTimeout(60_000); +const abbyCostumeURL = 'https://static.codingclip.com/v1/project/asset/45de34b47a2ce22f6f5d28bb35a44ff5.svg'; + const uri = path.resolve(__dirname, '../../build/index.html'); let driver; @@ -211,7 +213,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([]); }); diff --git a/packages/storage/test/integration/download-known-assets.js b/packages/storage/test/integration/download-known-assets.js index d99275a26..634084cc1 100644 --- a/packages/storage/test/integration/download-known-assets.js +++ b/packages/storage/test/integration/download-known-assets.js @@ -3,6 +3,8 @@ const test = require('tap').test; const ScratchStorage = require('../../dist/node/scratch-storage'); +const ASSET_SERVER = 'https://static.codingclip.com/v1/'; + let storage; test('constructor', t => { storage = new ScratchStorage(); @@ -79,7 +81,7 @@ 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 => `${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..053bd93c5 100644 --- a/packages/vm/test/fixtures/make-test-storage.js +++ b/packages/vm/test/fixtures/make-test-storage.js @@ -1,6 +1,6 @@ import {ScratchStorage} from 'clipcc-storage'; -const ASSET_SERVER = 'https://cdn.assets.scratch.mit.edu/'; +const ASSET_SERVER = 'https://static.codingclip.com/v1/'; const PROJECT_SERVER = 'https://cdn.projects.scratch.mit.edu/'; /** @@ -21,15 +21,7 @@ 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(''); + return `${ASSET_SERVER}project/asset/${asset.assetId}.${asset.dataFormat}`; }; /**