-
-
Notifications
You must be signed in to change notification settings - Fork 310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ScriptLoader
#2417
base: dev/1.4
Are you sure you want to change the base?
Add ScriptLoader
#2417
Changes from 3 commits
ed78593
2a1e40d
0a214c7
9081f74
2ef9c8e
e7bf294
12357bb
f67960e
44025d7
89b957b
fde6eca
e68253f
80e4eed
76d32a5
fe5e35c
ac334dc
cd9d4bb
a443c04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { AssetPromise, AssetType, Loader, LoadItem, resourceLoader, Script } from "@galacean/engine-core"; | ||
|
||
interface ESModuleStructure { | ||
default?: Script; | ||
[key: string]: any; | ||
} | ||
|
||
@resourceLoader(AssetType.Script, ["js", "mjs"], false) | ||
export class ScriptLoader extends Loader<ESModuleStructure> { | ||
load(item: LoadItem): AssetPromise<ESModuleStructure> { | ||
return new AssetPromise((resolve, reject) => { | ||
const { url } = item; | ||
(import(url) as Promise<ESModuleStructure>) | ||
.then((esModule) => { | ||
resolve(esModule); | ||
}) | ||
.catch(reject); | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,52 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { expect } from "chai"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { WebGLEngine, Script } from '@galacean/engine' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { AssetType } from "@galacean/engine-core"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let engine: WebGLEngine; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
interface ESModuleStructure { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default?: Script; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[key: string]: any; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
before(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const canvasDOM = document.createElement("canvas"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canvasDOM.width = 1024; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canvasDOM.height = 1024; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
engine = await WebGLEngine.create({ canvas: canvasDOM }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add test cleanup to prevent memory leaks. The test setup creates a WebGLEngine instance but doesn't clean it up after tests complete. Consider adding an Add this cleanup code: +after(() => {
+ engine.destroy();
+}); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
describe("ScriptLoader test", function () { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
it("loader from string url", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
engine.resourceManager.load<ESModuleStructure>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
url: "https://cdn.jsdelivr.net/npm/[email protected]/+esm", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type: AssetType.Script | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.then((script) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(script).not.to.be.null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(script.default).not.to.be.null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(script.colord).not.to.be.null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(script.getFormat).not.to.be.null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(script.random).not.to.be.null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improve test reliability and security. Several concerns with the current implementation:
Consider these improvements: it("loader from string url", async () => {
+ const timeout = 5000; // 5 seconds
+ try {
engine.resourceManager.load<ESModuleStructure>({
url: "https://cdn.jsdelivr.net/npm/[email protected]/+esm",
type: AssetType.Script
})
- .then((script) => {
+ .then((script) => {
expect(script).not.to.be.null;
expect(script.default).not.to.be.null;
expect(script.colord).not.to.be.null;
expect(script.getFormat).not.to.be.null;
expect(script.random).not.to.be.null;
- });
+ }, { timeout });
+ } catch (error) {
+ throw new Error(`Script loading failed: ${error.message}`);
+ }
}); Consider mocking the external dependency for more reliable tests.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
it("loader from blob raw script text", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const esModuleString = ` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const colord = "colord"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const getFormat = () => "getFormat"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default colord; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
engine.resourceManager.load<ESModuleStructure>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
url: URL.createObjectURL(new Blob([esModuleString], { type: "application/javascript" })), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type: AssetType.Script | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.then((script) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(script).not.to.be.null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(script.colord).not.to.be.null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(script.getFormat).not.to.be.null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(script.default).not.to.be.null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(script.default).equal(script.colord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add resource cleanup and improve test assertions. The test creates a Blob URL but doesn't clean it up, which could lead to memory leaks. Also, the assertions could be more specific. Apply these improvements: it("loader from blob raw script text", async () => {
const esModuleString = `
export const colord = "colord";
export const getFormat = () => "getFormat";
export default colord;
`
+ const blobUrl = URL.createObjectURL(new Blob([esModuleString], { type: "application/javascript" }));
+ try {
engine.resourceManager.load<ESModuleStructure>({
- url: URL.createObjectURL(new Blob([esModuleString], { type: "application/javascript" })),
+ url: blobUrl,
type: AssetType.Script
})
.then((script) => {
expect(script).not.to.be.null;
- expect(script.colord).not.to.be.null;
- expect(script.getFormat).not.to.be.null;
- expect(script.default).not.to.be.null;
+ expect(script.colord).to.equal("colord");
+ expect(script.getFormat).to.be.a("function");
+ expect(script.getFormat()).to.equal("getFormat");
expect(script.default).equal(script.colord)
});
+ } finally {
+ URL.revokeObjectURL(blobUrl);
+ }
}); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Based on the implementation review, I can now provide a final response: Dynamic script loading requires additional security measures The ScriptLoader uses the native ES module
However, the loader lacks:
Recommendations:
🔗 Analysis chainConsider security implications of dynamic script loading. The ScriptLoader allows loading and executing scripts from external sources, which could pose security risks if used with untrusted content. Consider:
Let's check if there are any security measures in the main implementation: Let me search for the ScriptLoader implementation to better understand the security context: Let me check the ScriptLoader implementation to understand how scripts are loaded: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Search for security-related code in ScriptLoader implementation
rg -i "integrity|security|policy|trust" --type ts
Length of output: 96 Script: #!/bin/bash
# Find and examine ScriptLoader implementation
fd ScriptLoader.ts
Length of output: 56 Script: #!/bin/bash
# Look for script loading related code
rg -A 10 "AssetType.Script" --type ts
Length of output: 2275 Script: #!/bin/bash
# Search for any validation or security checks in resource loading
rg -A 5 "load.*\(" --type ts
Length of output: 157332 Script: #!/bin/bash
# Get the ScriptLoader implementation
cat packages/loader/src/ScriptLoader.ts
Length of output: 637 🧰 Tools🪛 eslint[error] 52-52: Insert (prettier/prettier) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance type safety and documentation for ESModuleStructure interface
The interface could benefit from:
any
📝 Committable suggestion