Skip to content
Open
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
15 changes: 15 additions & 0 deletions GDJS/Runtime/ResourceLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ namespace gdjs {
);
};

const encodeLocalResourceUrl = (url: string): string => {
if (
url.startsWith('http://') ||
url.startsWith('https://') ||
url.startsWith('data:') ||
url.startsWith('blob:')
) {
return url;
}

return encodeURI(url).replace(/#/g, '%23');
};

/**
* A task of pre-loading resources used by a scene.
*
Expand Down Expand Up @@ -617,6 +630,8 @@ namespace gdjs {
* the resource (this can be for example a token needed to access the resource).
*/
getFullUrl(url: string) {
url = encodeLocalResourceUrl(url);

if (this._runtimeGame.isInGameEdition()) {
// Avoid adding cache burst to URLs which are assumed to be immutable files,
// to avoid costly useless requests each time the game is hot-reloaded.
Expand Down
11 changes: 7 additions & 4 deletions newIDE/app/src/ResourcesLoader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { addGDevelopResourceTokenIfRequired } from '../Utils/CrossOrigin';
import optionalRequire from '../Utils/OptionalRequire';
const electron = optionalRequire('electron');
const path = optionalRequire('path');
const nodeUrl = optionalRequire('url');

class UrlsCache {
projectCache: { [number]: { [string]: string } } = {};
Expand Down Expand Up @@ -132,15 +133,17 @@ export default class ResourcesLoader {
// Support local filesystem with Electron
const file = project.getProjectFile();
const projectPath = path.dirname(file);
const resourceAbsolutePath = path
.resolve(projectPath, urlOrFilename)
.replace(/\\/g, '/');
const resourceAbsolutePath = path.resolve(projectPath, urlOrFilename);
const resourceUrl = nodeUrl
? nodeUrl.pathToFileURL(resourceAbsolutePath).href
: 'file://' +
resourceAbsolutePath.replace(/\\/g, '/').replace(/#/g, '%23');

console.info('Caching resolved local filename:', resourceAbsolutePath);
return this._cache.cacheLocalFileUrl(
project,
urlOrFilename,
'file://' + resourceAbsolutePath,
resourceUrl,
!!disableCacheBurst
);
}
Expand Down
34 changes: 34 additions & 0 deletions newIDE/app/src/ResourcesLoader/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// @flow
import ResourcesLoader from './index';

jest.mock('../Utils/OptionalRequire');

describe('ResourcesLoader', () => {
let dateNowSpy;

beforeEach(() => {
ResourcesLoader.burstAllUrlsCache();
dateNowSpy = jest.spyOn(Date, 'now').mockReturnValue(1234);
});

afterEach(() => {
dateNowSpy.mockRestore();
});

test('encodes hash characters in local file URLs before adding cache parameters', () => {
const project: any = {
ptr: 1,
getProjectFile: () => '/project/Game Jam #1/game.json',
};

const resolvedUrl = ResourcesLoader.getFullUrl(
project,
'Parts/hero #1.png',
{}
);

expect(resolvedUrl).toBe(
'file:///project/Game%20Jam%20%231/Parts/hero%20%231.png?cache=1234'
);
});
});
4 changes: 4 additions & 0 deletions newIDE/app/src/Utils/__mocks__/OptionalRequire.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from 'path';
import nodeUrl from 'url';

const mockElectron = {
ipcRenderer: {
Expand Down Expand Up @@ -45,6 +46,9 @@ const mockOptionalRequire = jest.fn(
if (moduleName === 'path') {
return path;
}
if (moduleName === 'url') {
return nodeUrl;
}
if (moduleName === 'os') {
return mockOs;
}
Expand Down