1+ // Extension Loader for 02Engine CLI
2+ // 支持Web、Node.js和P4三种环境
3+ // 迁移自scratch-vm的extension loader,完全独立不依赖scratch-vm
4+
5+ // 尝试导入Node.js环境的依赖
6+ let JSDOM , nodeFetch ;
7+ try {
8+ // 动态导入,避免在浏览器环境中出错
9+ JSDOM = require ( 'jsdom' ) . JSDOM ;
10+ nodeFetch = require ( 'node-fetch' ) ;
11+ } catch ( error ) {
12+ // 在浏览器环境中这些模块可能不可用
13+ console . debug ( 'Node.js modules not available, running in browser environment' ) ;
14+ }
15+
16+ /**
17+ * 检测当前运行环境
18+ * @returns {string } 'browser' | 'node' | 'standalone'
19+ */
20+ const detectEnvironment = ( ) => {
21+ if ( typeof process !== 'undefined' && process . versions && process . versions . node ) {
22+ // 检测是否为standalone环境
23+ if ( process . env . STANDALONE ) {
24+ return 'standalone' ;
25+ }
26+ return 'node' ;
27+ }
28+ return 'browser' ;
29+ } ;
30+
31+ /**
32+ * 浏览器环境的扩展加载器
33+ * @param {string } extensionURL - 扩展URL
34+ * @returns {Promise<string> } 扩展源代码
35+ */
36+ const loadExtensionBrowser = ( extensionURL ) => new Promise ( ( resolve , reject ) => {
37+ const script = document . createElement ( 'script' ) ;
38+ script . onload = ( ) => {
39+ script . remove ( ) ;
40+ resolve ( extensionURL ) ;
41+ } ;
42+ script . onerror = ( ) => {
43+ script . remove ( ) ;
44+ reject ( new Error ( `Failed to load extension: ${ extensionURL } ` ) ) ;
45+ } ;
46+ script . src = extensionURL ;
47+ document . head . appendChild ( script ) ;
48+ } ) ;
49+
50+ /**
51+ * Node.js环境的扩展加载器
52+ * @param {string } extensionURL - 扩展URL
53+ * @returns {Promise<string> } 扩展源代码
54+ */
55+ const loadExtensionNode = async ( extensionURL ) => {
56+ // 检查是否已设置全局document对象
57+ if ( ! global . document && JSDOM ) {
58+ // 模拟浏览器环境核心对象
59+ const dom = new JSDOM ( '<!DOCTYPE html><body></body>' ) ;
60+ global . document = dom . window . document ;
61+ global . window = dom . window ;
62+ global . location = dom . window . location ;
63+ global . fetch = nodeFetch ; // 使用node-fetch替换浏览器fetch
64+ } else if ( ! global . document ) {
65+ throw new Error ( 'Document object not available. Running in Node.js environment requires jsdom package.' ) ;
66+ }
67+
68+ try {
69+ // 用node-fetch下载扩展脚本
70+ const response = await fetch ( extensionURL ) ;
71+ if ( ! response . ok ) {
72+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
73+ }
74+ const scriptCode = await response . text ( ) ;
75+
76+ // 在模拟的window环境中执行脚本
77+ // 使用eval或vm.runInContext取决于安全性要求
78+ // 这里使用window.eval确保在正确的上下文中执行
79+ if ( global . window && global . window . eval ) {
80+ global . window . eval ( scriptCode ) ;
81+ } else {
82+ // 降级方案
83+ eval ( scriptCode ) ; // eslint-disable-line no-eval
84+ }
85+
86+ return extensionURL ;
87+ } catch ( err ) {
88+ throw new Error ( `Error loading extension ${ extensionURL } : ${ err . message } ` ) ;
89+ }
90+ } ;
91+
92+ /**
93+ * Standalone环境(Electron等)的扩展加载器
94+ * 继承Node环境的逻辑但可能有特殊的处理
95+ * @param {string } extensionURL - 扩展URL
96+ * @returns {Promise<string> } 扩展源代码
97+ */
98+ const loadExtensionStandalone = async ( extensionURL ) => {
99+ // Standalone环境中,如果可以访问浏览器的fetch,则使用浏览器逻辑
100+ if ( typeof fetch !== 'undefined' && typeof document !== 'undefined' ) {
101+ return loadExtensionBrowser ( extensionURL ) ;
102+ }
103+ // 否则降级到Node.js逻辑
104+ return loadExtensionNode ( extensionURL ) ;
105+ } ;
106+
107+ /**
108+ * 自动检测环境并选择合适的加载方法
109+ * Load an extension from an arbitrary URL.
110+ * @param {string } extensionURL
111+ * @returns {Promise<string> } Resolves with extension URL if loaded successfully.
112+ */
113+ const loadExtension = ( extensionURL ) => {
114+ const environment = detectEnvironment ( ) ;
115+
116+ switch ( environment ) {
117+ case 'browser' :
118+ return loadExtensionBrowser ( extensionURL ) ;
119+ case 'node' :
120+ return loadExtensionNode ( extensionURL ) ;
121+ case 'standalone' :
122+ return loadExtensionStandalone ( extensionURL ) ;
123+ default :
124+ throw new Error ( `Unsupported environment: ${ environment } ` ) ;
125+ }
126+ } ;
127+
128+ /**
129+ * 获取扩展源代码(用于打包到项目中)
130+ * @param {string } extensionURL
131+ * @returns {Promise<string> } 扩展源代码文本
132+ */
133+ const fetchExtensionSource = async ( extensionURL ) => {
134+ const environment = detectEnvironment ( ) ;
135+
136+ if ( environment === 'browser' || environment === 'standalone' ) {
137+ // 浏览器和standalone环境优先使用原生fetch
138+ if ( typeof fetch !== 'undefined' ) {
139+ const response = await fetch ( extensionURL ) ;
140+ if ( ! response . ok ) {
141+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
142+ }
143+ return response . text ( ) ;
144+ }
145+ }
146+
147+ // Node.js环境或standalone环境的降级方案
148+ const fetchImpl = nodeFetch || ( global && global . fetch ) ;
149+ if ( ! fetchImpl ) {
150+ throw new Error ( 'Fetch not available in this environment' ) ;
151+ }
152+ const response = await fetchImpl ( extensionURL ) ;
153+ if ( ! response . ok ) {
154+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
155+ }
156+ return response . text ( ) ;
157+ } ;
158+
159+ /**
160+ * 包装扩展源代码,使其在非沙盒环境中更安全
161+ * @param {string } source - 扩展源代码
162+ * @returns {string } 包装后的源代码
163+ */
164+ const wrapExtensionSource = ( source ) => {
165+ // Wrap the extension in an IIFE so that extensions written for the sandbox are less
166+ // likely to cause issues in an unsandboxed environment due to global pollution or
167+ // overriding Scratch.*
168+ return `(function(Scratch) { ${ source } })(Scratch);` ;
169+ } ;
170+
171+ module . exports = {
172+ loadExtension,
173+ fetchExtensionSource,
174+ wrapExtensionSource,
175+ detectEnvironment,
176+ loadExtensionBrowser,
177+ loadExtensionNode,
178+ loadExtensionStandalone
179+ } ;
0 commit comments