Skip to content

Commit 85c4552

Browse files
committed
clean up asset & project fetching
1 parent 9bfccb4 commit 85c4552

3 files changed

Lines changed: 54 additions & 175 deletions

File tree

src/lib/project-fetcher-hoc.jsx

Lines changed: 12 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -26,38 +26,6 @@ import { MISSING_PROJECT_ID } from "./tw-missing-project";
2626
import VM from "scratch-vm";
2727
import * as progressMonitor from "../components/loader/tw-progress-monitor";
2828

29-
// TW: Temporary hack for project tokens
30-
const fetchProjectToken = (projectId) => {
31-
if (projectId === "0") {
32-
return Promise.resolve(null);
33-
}
34-
// Parse ?token=abcdef
35-
const searchParams = new URLSearchParams(location.search);
36-
if (searchParams.has("token")) {
37-
return Promise.resolve(searchParams.get("token"));
38-
}
39-
// Parse #1?token=abcdef
40-
const hashParams = new URLSearchParams(location.hash.split("?")[1]);
41-
if (hashParams.has("token")) {
42-
return Promise.resolve(hashParams.get("token"));
43-
}
44-
return fetch(
45-
`https://projects.penguinmod.com/api/v1/projects/getproject?projectID=${projectId}&requestType=metadata`,
46-
)
47-
.then((r) => {
48-
if (!r.ok) return null;
49-
return r.json();
50-
})
51-
.then((dataOrNull) => {
52-
const token = dataOrNull ? dataOrNull.id : null;
53-
return token;
54-
})
55-
.catch((err) => {
56-
log.error(err);
57-
return null;
58-
});
59-
};
60-
6129
/* Higher Order Component to provide behavior for loading projects by id. If
6230
* there's no id, the default project is loaded.
6331
* @param {React.Component} WrappedComponent component to receive projectData prop
@@ -110,6 +78,7 @@ const ProjectFetcherHOC = function (WrappedComponent) {
11078
this.props.onActivateTab(BLOCKS_TAB_INDEX);
11179
}
11280
}
81+
11382
fetchProject(projectId, loadingState) {
11483
// tw: clear and stop the VM before fetching
11584
// these will also happen later after the project is fetched, but fetching may take a while and
@@ -145,6 +114,7 @@ const ProjectFetcherHOC = function (WrappedComponent) {
145114
) {
146115
projectUrl = `https://${projectUrl}`;
147116
}
117+
148118
assetPromise = progressMonitor
149119
.fetchWithProgress(projectUrl)
150120
.then((r) => {
@@ -172,58 +142,19 @@ const ProjectFetcherHOC = function (WrappedComponent) {
172142
storage.DataFormat.JSON,
173143
);
174144
} else {
145+
assetPromise = storage.load(
146+
storage.AssetType.Project,
147+
projectId,
148+
storage.DataFormat.JSON,
149+
);
175150
storage.setProjectID(projectId);
176-
projectUrl = `https://projects.penguinmod.com/api/v1/projects/getprojectwrapper?safe=true&projectId=${projectId}&assets=false`;
177-
assetPromise = progressMonitor
178-
.fetchWithProgress(projectUrl)
179-
.then(async (r) => {
180-
if (
181-
this.props.vm.runtime.renderer
182-
?.setPrivateSkinAccess
183-
)
184-
this.props.vm.runtime.renderer.setPrivateSkinAccess(
185-
false,
186-
);
187-
if (!r.ok) {
188-
throw new Error(
189-
`Request returned status ${r.status}`,
190-
);
191-
}
192-
const project = await r.json();
193-
194-
const json = protobufToJson(
195-
new Uint8Array(project.project.data),
196-
);
197-
198-
// now get the assets
199-
let zip = new JSZip();
200-
zip.file("project.json", JSON.stringify(json));
201-
202-
/*
203-
// we will fetch assets later now
204-
for (const asset of project.assets) {
205-
zip.file(
206-
asset.id,
207-
new Uint8Array(asset.buffer.data).buffer,
208-
);
209-
}
210-
*/
211-
212-
const arrayBuffer = await zip.generateAsync({
213-
type: "arraybuffer",
214-
});
215-
216-
return arrayBuffer;
217-
})
218-
.then((buffer) => ({ data: buffer }))
219-
.catch((error) => {
220-
console.log(error);
221-
});
222151
}
223152
}
224153

225154
return assetPromise
226155
.then((projectAsset) => {
156+
console.log(`project asset: ${projectAsset}`);
157+
227158
// tw: If the project data appears to be HTML, then the result is probably an nginx 404 page,
228159
// and the "missing project" project should be loaded instead.
229160
// See: https://projects.scratch.mit.edu/9999999999999999999999
@@ -249,57 +180,7 @@ const ProjectFetcherHOC = function (WrappedComponent) {
249180
loadingState,
250181
);
251182
} else {
252-
// pm: Failed to grab data, use the "fetch" API as a backup
253-
// we shouldnt be interrupted by the fetch replacement in tw-progress-monitor
254-
// as it uses projects.scratch.mit.edu still
255-
fetch(projectUrl)
256-
.then(async (res) => {
257-
if (!res.ok) {
258-
// Treat failure to load as an error
259-
// Throw to be caught by catch later on
260-
throw new Error(
261-
"Could not find project; " + projectUrl,
262-
);
263-
}
264-
265-
const project = await res.json();
266-
const json = protobufToJson(
267-
new Uint8Array(project.project.data),
268-
);
269-
270-
// now get the assets
271-
let zip = new JSZip();
272-
zip.file("project.json", JSON.stringify(json));
273-
274-
if (typeof project.assets !== "object") {
275-
alert(
276-
"No assets were returned. This error is temporary and should not be reported.",
277-
);
278-
throw new TypeError(
279-
"Invalid type given inside the assets list",
280-
);
281-
}
282-
for (const asset of project.assets) {
283-
zip.file(
284-
asset.id,
285-
new Uint8Array(asset.buffer.data)
286-
.buffer,
287-
);
288-
}
289-
290-
const arrayBuffer = await zip.generateAsync({
291-
type: "arraybuffer",
292-
});
293-
this.props.onFetchedProjectData(
294-
arrayBuffer,
295-
loadingState,
296-
);
297-
})
298-
.catch((err) => {
299-
throw new Error(
300-
"Could not find project; " + err,
301-
);
302-
});
183+
throw new Error("Failed to load project.");
303184
}
304185
})
305186
.catch((err) => {
@@ -360,7 +241,8 @@ const ProjectFetcherHOC = function (WrappedComponent) {
360241
ProjectFetcherComponent.defaultProps = {
361242
assetHost:
362243
"https://asset-cdn.penguinmod.com/file/penguinmod-warm-tier-s2-cf",
363-
projectHost: "https://projects.scratch.mit.edu",
244+
projectHost:
245+
"https://projects.penguinmod.com/api/v1/projects/getProject?requestType=protobuf&safe=true&projectID",
364246
};
365247

366248
const mapStateToProps = (state) => ({

src/lib/save-project-to-server.js

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import queryString from 'query-string';
2-
import xhr from 'xhr';
3-
import storage from '../lib/storage';
1+
import queryString from "query-string";
2+
import xhr from "xhr";
3+
import storage from "../lib/storage";
44

55
/**
66
* Save a project JSON to the project server.
@@ -15,31 +15,35 @@ import storage from '../lib/storage';
1515
* @return {Promise} A promise that resolves when the network request resolves.
1616
*/
1717
export default function (projectId, vmState, params) {
18+
throw new Error("we do not save in the editor");
19+
1820
const opts = {
1921
body: vmState,
2022
// If we set json:true then the body is double-stringified, so don't
2123
headers: {
22-
'Content-Type': 'application/json'
24+
"Content-Type": "application/json",
2325
},
24-
withCredentials: true
26+
withCredentials: true,
2527
};
26-
const creatingProject = projectId === null || typeof projectId === 'undefined';
28+
const creatingProject =
29+
projectId === null || typeof projectId === "undefined";
2730
const queryParams = {};
28-
if (params.hasOwnProperty('originalId')) queryParams.original_id = params.originalId;
29-
if (params.hasOwnProperty('isCopy')) queryParams.is_copy = params.isCopy;
30-
if (params.hasOwnProperty('isRemix')) queryParams.is_remix = params.isRemix;
31-
if (params.hasOwnProperty('title')) queryParams.title = params.title;
31+
if (params.hasOwnProperty("originalId"))
32+
queryParams.original_id = params.originalId;
33+
if (params.hasOwnProperty("isCopy")) queryParams.is_copy = params.isCopy;
34+
if (params.hasOwnProperty("isRemix")) queryParams.is_remix = params.isRemix;
35+
if (params.hasOwnProperty("title")) queryParams.title = params.title;
3236
let qs = queryString.stringify(queryParams);
3337
if (qs) qs = `?${qs}`;
3438
if (creatingProject) {
3539
Object.assign(opts, {
36-
method: 'post',
37-
url: `${storage.projectHost}/${qs}`
40+
method: "post",
41+
url: `${storage.projectHost}/${qs}`,
3842
});
3943
} else {
4044
Object.assign(opts, {
41-
method: 'put',
42-
url: `${storage.projectHost}/${projectId}${qs}`
45+
method: "put",
46+
url: `${storage.projectHost}/${projectId}${qs}`,
4347
});
4448
}
4549
return new Promise((resolve, reject) => {
@@ -55,7 +59,7 @@ export default function (projectId, vmState, params) {
5559
}
5660
body.id = projectId;
5761
if (creatingProject) {
58-
body.id = body['content-name'];
62+
body.id = body["content-name"];
5963
}
6064
resolve(body);
6165
});

src/lib/storage.js

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,39 @@ class Storage extends ScratchStorage {
1313
this.cacheDefaultProject();
1414
}
1515
addOfficialScratchWebStores() {
16+
const die = () => {
17+
throw new Error("Cannot use this web store like that!!!");
18+
};
19+
1620
this.addWebStore(
1721
[this.AssetType.Project],
1822
this.getProjectGetConfig.bind(this),
19-
this.getProjectCreateConfig.bind(this),
20-
this.getProjectUpdateConfig.bind(this),
23+
die,
24+
die,
2125
);
2226
this.addWebStore(
2327
[
2428
this.AssetType.ImageVector,
2529
this.AssetType.ImageBitmap,
2630
this.AssetType.Sound,
31+
this.AssetType.Font,
2732
],
2833
this.getAssetGetConfig.bind(this),
2934
// We set both the create and update configs to the same method because
3035
// storage assumes it should update if there is an assetId, but the
3136
// asset store uses the assetId as part of the create URI.
32-
this.getAssetCreateConfig.bind(this),
33-
this.getAssetCreateConfig.bind(this),
37+
die,
38+
die,
39+
);
40+
this.addWebStore(
41+
[
42+
this.AssetType.ImageVector,
43+
this.AssetType.ImageBitmap,
44+
this.AssetType.Sound,
45+
],
46+
this.getScratchAssetGetConfig.bind(this),
47+
die,
48+
die,
3449
);
3550
}
3651
setProjectHost(projectHost) {
@@ -43,22 +58,8 @@ class Storage extends ScratchStorage {
4358
this.projectId = projectId;
4459
}
4560
getProjectGetConfig(projectAsset) {
46-
const path = `${this.projectHost}/${projectAsset.assetId}`;
47-
const qs = this.projectToken ? `?token=${this.projectToken}` : "";
48-
const url = path + qs;
49-
return url;
50-
}
51-
getProjectCreateConfig() {
52-
return {
53-
url: `${this.projectHost}/`,
54-
withCredentials: true,
55-
};
56-
}
57-
getProjectUpdateConfig(projectAsset) {
58-
return {
59-
url: `${this.projectHost}/${projectAsset.assetId}`,
60-
withCredentials: true,
61-
};
61+
// projectHost ends in "projectID", so we add the equals
62+
return `${this.projectHost}=${projectAsset.assetId}`;
6263
}
6364
setAssetHost(assetHost) {
6465
this.assetHost = assetHost;
@@ -70,16 +71,8 @@ class Storage extends ScratchStorage {
7071

7172
return `${this.assetHost}/${this.projectId}_${asset.assetId}.${asset.dataFormat}`;
7273
}
73-
getAssetCreateConfig(asset) {
74-
return {
75-
// There is no such thing as updating assets, but storage assumes it
76-
// should update if there is an assetId, and the asset store uses the
77-
// assetId as part of the create URI. So, force the method to POST.
78-
// Then when storage finds this config to use for the "update", still POSTs
79-
method: "post",
80-
url: `${this.assetHost}/${asset.assetId}.${asset.dataFormat}`,
81-
withCredentials: true,
82-
};
74+
getScratchAssetGetConfig(asset) {
75+
return `https://assets.scratch.mit.edu/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/`;
8376
}
8477
setTranslatorFunction(translator) {
8578
this.translator = translator;

0 commit comments

Comments
 (0)