@open-wxa/wxa-runner-core 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web.js ADDED
@@ -0,0 +1,700 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/web.ts
21
+ var web_exports = {};
22
+ __export(web_exports, {
23
+ DEFAULT_DEVICE: () => DEFAULT_DEVICE,
24
+ FileStore: () => FileStore,
25
+ MOCK_ID_HEADER: () => MOCK_ID_HEADER,
26
+ MOCK_PREFIX: () => MOCK_PREFIX,
27
+ PROXY_PREFIX: () => PROXY_PREFIX,
28
+ SETUP_MODULE: () => SETUP_MODULE,
29
+ SETUP_MODULE_SOURCE: () => SETUP_MODULE_SOURCE,
30
+ extname: () => extname,
31
+ hostHtml: () => hostHtml,
32
+ mimeType: () => mimeType,
33
+ mockedResponse: () => mockedResponse,
34
+ proxyFetch: () => proxyFetch,
35
+ routeApp: () => routeApp
36
+ });
37
+ module.exports = __toCommonJS(web_exports);
38
+
39
+ // src/project/toJs.ts
40
+ function toJs(value) {
41
+ return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
42
+ }
43
+
44
+ // src/server/pluginCode.ts
45
+ function findPluginPage(build, pagePath) {
46
+ for (const plugin of build.plugins) {
47
+ const prefix = `__plugin__/${plugin.provider}/`;
48
+ if (plugin.pages.includes(pagePath)) return { plugin, innerPath: pagePath.slice(prefix.length) };
49
+ }
50
+ return null;
51
+ }
52
+ function pluginScriptUrl(plugin, file) {
53
+ return `/__wxa__/plugin/${encodeURIComponent(plugin.provider)}/${encodeURIComponent(plugin.version)}/${file}`;
54
+ }
55
+
56
+ // src/server/html.ts
57
+ function hostHtml() {
58
+ return `<!DOCTYPE html>
59
+ <html lang="zh-CN">
60
+ <head>
61
+ <meta charset="utf-8">
62
+ <meta name="viewport" content="width=device-width, initial-scale=1">
63
+ <title>wxa-runner</title>
64
+ </head>
65
+ <body>
66
+ <div id="wxa-root"></div>
67
+ <script src="/__wxa__/runtime/host.js"><\/script>
68
+ </body>
69
+ </html>`;
70
+ }
71
+ function enableHotUpdate(hot) {
72
+ return hot ? `<script>if (typeof __virtualDOM__ !== 'undefined' && __virtualDOM__.enableUpdateWxAppCode) __virtualDOM__.enableUpdateWxAppCode();<\/script>
73
+ ` : "";
74
+ }
75
+ function appserviceHtml(build, launch, options) {
76
+ const wxConfig = {
77
+ ...build.wxConfig,
78
+ appLaunchInfo: { scene: 1001, path: launch.path, query: launch.query },
79
+ ...options.isolated ? { isIsolateContext: true } : {}
80
+ };
81
+ const v = build.version;
82
+ const pluginUrls = build.plugins.map((plugin) => pluginScriptUrl(plugin, "appservice.js"));
83
+ const moduleUrls = build.modules.map((id) => `/__wxa__/module/${encodeURI(id)}?v=${v}`);
84
+ const bootstrapUrl = `/__wxa__/service-bootstrap.js?v=${v}`;
85
+ if (options.isolated) {
86
+ const scripts = [...pluginUrls, ...moduleUrls, bootstrapUrl];
87
+ return `<!DOCTYPE html>
88
+ <html>
89
+ <head>
90
+ <meta charset="utf-8">
91
+ <title>appservice</title>
92
+ <script>var __wxConfig = ${toJs(wxConfig)};<\/script>
93
+ <script src="/__wxa__/runtime/frame.js" data-kind="service" data-app-service="${escapeHtml(JSON.stringify(scripts))}"${options.hot ? ' data-hot="1"' : ""}><\/script>
94
+ <script src="/__baselib__/WAServiceMainContext.js"><\/script>
95
+ </head>
96
+ <body></body>
97
+ </html>`;
98
+ }
99
+ const modules = [...pluginUrls, ...moduleUrls].map((src) => `<script src="${src}"><\/script>`).join("\n");
100
+ return `<!DOCTYPE html>
101
+ <html>
102
+ <head>
103
+ <meta charset="utf-8">
104
+ <title>appservice</title>
105
+ <script>var __wxConfig = ${toJs(wxConfig)};<\/script>
106
+ <script src="/__wxa__/runtime/frame.js" data-kind="service"><\/script>
107
+ <script src="/__baselib__/WAService.js"><\/script>
108
+ ${enableHotUpdate(options.hot)}<script>
109
+ if (typeof define !== 'function') {
110
+ console.error('[wxa-runner] \u57FA\u7840\u5E93\u6CA1\u6709\u5B8C\u6210\u521D\u59CB\u5316\uFF08define \u4E0D\u5B58\u5728\uFF09\uFF0C\u5F53\u524D\u57FA\u7840\u5E93\u7248\u672C\u53EF\u80FD\u4E0D\u53D7\u652F\u6301');
111
+ }
112
+ <\/script>
113
+ ${modules}
114
+ <script src="${bootstrapUrl}"><\/script>
115
+ <script>parent.__wxaHost.frameLoaded('service', 0);<\/script>
116
+ </head>
117
+ <body></body>
118
+ </html>`;
119
+ }
120
+ function subcontextHtml(id, empty) {
121
+ return `<!DOCTYPE html>
122
+ <html>
123
+ <head>
124
+ <meta charset="utf-8">
125
+ <title>subcontext ${id}</title>
126
+ <script src="/__wxa__/runtime/frame.js" data-kind="service" data-context-id="${id}"><\/script>
127
+ ${empty ? "" : '<script src="/__baselib__/WASubContext.js"><\/script>\n'}<script>parent.__wxaContexts.loaded(${id});<\/script>
128
+ </head>
129
+ <body></body>
130
+ </html>`;
131
+ }
132
+ function webviewHtml(build, pagePath, webviewId, options) {
133
+ const id = Number(webviewId) || 0;
134
+ return `<!DOCTYPE html>
135
+ <html>
136
+ <head>
137
+ ${webviewHead(build, pagePath, id, options.scripts)}
138
+ ${enableHotUpdate(options.hot)}<script src="/__wxa__/page-frame.js?v=${build.version}"><\/script>
139
+ ${pageWindow(build, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
140
+ </head>
141
+ <body>
142
+ ${scriptTags(options.scripts, "body")}<script>
143
+ (function () {
144
+ var path = ${toJs(pagePath)};
145
+ if (typeof __wxAppCode__[path + '.wxss'] === 'function') __wxAppCode__[path + '.wxss']();
146
+ var generateFunc = $gwx('./' + path + '.wxml');
147
+ if (generateFunc) {
148
+ document.dispatchEvent(new CustomEvent('generateFuncReady', { detail: { generateFunc: generateFunc } }));
149
+ } else {
150
+ document.body.innerText = './' + path + '.wxml not found';
151
+ console.error('[wxa-runner] ./' + path + '.wxml not found');
152
+ }
153
+ parent.__wxaHost.frameLoaded('webview', ${id});
154
+ })();
155
+ <\/script>
156
+ </body>
157
+ </html>`;
158
+ }
159
+ function workerScript(build, baseLib) {
160
+ const modules = build.workerModules.map((id) => `/__wxa__/module/${encodeURI(id)}?v=${build.version}`);
161
+ const prelude = `var __wxConfig = ${toJs(build.wxConfig)}; __wxConfig.env.USER_DATA_PATH = location.origin + "/__wxa__/usr/" + new URL(location.href).searchParams.get("session");
162
+ importScripts('/__wxa__/runtime/worker.js');
163
+ `;
164
+ if (!baseLib.hasWorker) {
165
+ const message = `[wxa-runner] \u57FA\u7840\u5E93 ${baseLib.version} \u91CC\u6CA1\u6709 WAWorker.js\uFF0CWorker \u4E0D\u80FD\u8FD0\u884C\u3002\u6362\u7528\u5F00\u53D1\u8005\u5DE5\u5177\u4E0B\u8F7D\u7684\u57FA\u7840\u5E93\uFF0C\u6216\u5347\u7EA7 wxa-runner \u5E26\u4E0A\u65B0\u7684\u5185\u7F6E\u57FA\u7840\u5E93`;
166
+ return `${prelude}throw new Error(${toJs(message)});
167
+ `;
168
+ }
169
+ return `${prelude}var __wxaConsole = self.console;
170
+ importScripts('/__baselib__/WAWorker.js');
171
+ self.console = __wxaConsole;
172
+ importScripts(${modules.map((url) => toJs(url)).join(", ")});
173
+ `;
174
+ }
175
+ function pluginWebviewHtml(build, plugin, innerPath, pagePath, webviewId, options = {}) {
176
+ const id = Number(webviewId) || 0;
177
+ const html2 = plugin.files.get(`/${innerPath}.html`);
178
+ const page = html2 ? new TextDecoder().decode(html2) : `<script>console.error(${JSON.stringify(`[wxa-runner] \u63D2\u4EF6 ${plugin.alias} \u91CC\u6CA1\u6709\u9875\u9762 ${innerPath}`)})<\/script>`;
179
+ return `<!DOCTYPE html>
180
+ <html>
181
+ <head>
182
+ ${webviewHead(build, pagePath, id, options.scripts)}
183
+ <script src="${pluginScriptUrl(plugin, "pageframe.js")}"><\/script>
184
+ ${pageWindow(build, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
185
+ </head>
186
+ <body>
187
+ ${scriptTags(options.scripts, "body")}${page}
188
+ <script>parent.__wxaHost.frameLoaded('webview', ${id});<\/script>
189
+ </body>
190
+ </html>`;
191
+ }
192
+ function pageWindow(build, pagePath) {
193
+ return { ...build.wxConfig.global?.window, ...build.wxConfig.page?.[pagePath + ".html"]?.window };
194
+ }
195
+ function webviewHead(build, pagePath, id, scripts) {
196
+ const wxConfig = { ...build.wxConfig, window: pageWindow(build, pagePath) };
197
+ return `<meta charset="utf-8">
198
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
199
+ <title>${escapeHtml(pagePath)}</title>
200
+ <!-- \u5C0F\u7A0B\u5E8F\u6E32\u67D3\u5C42\u7684\u9ED8\u8BA4\u5B57\u53F7\u662F 16px\uFF1B\u4E0D\u58F0\u660E\u7684\u8BDD\u4F1A\u7EE7\u627F\u6D4F\u89C8\u5668\u8BBE\u7F6E\u91CC\u7684\u9ED8\u8BA4\u5B57\u53F7\u3002\u653E\u5728\u6700\u524D\u9762\uFF0C\u57FA\u7840\u5E93\u548C\u9875\u9762\u6837\u5F0F\u90FD\u80FD\u8986\u76D6\u5B83 -->
201
+ <style>html{font-size:16px}</style>
202
+ <!-- \u771F\u673A\u7684\u6EDA\u52A8\u6761\u662F\u4E0D\u5360\u5E03\u5C40\u7684\u6D6E\u5C42\uFF1B\u684C\u9762\u6D4F\u89C8\u5668\u7684\u5E38\u89C4\u6EDA\u52A8\u6761\u548C\u81EA\u5B9A\u4E49 ::-webkit-scrollbar \u4F1A\u6324\u7A84\u9875\u9762\uFF0C\u4E00\u5F8B\u9690\u85CF\u3002
203
+ scrollbar-width \u4F18\u5148\u4E8E ::-webkit-scrollbar\uFF0C\u9875\u9762\u6837\u5F0F\u4E0D\u4F1A\u518D\u628A\u6EDA\u52A8\u6761\u52A0\u56DE\u6765 -->
204
+ <style>*{scrollbar-width:none}::-webkit-scrollbar{display:none}</style>
205
+ <!-- \u903B\u8F91\u5C42\u5EFA\u7684 2d\u3001webgl \u753B\u5E03\u642C\u8FDB wx-canvas \u540E\u94FA\u6EE1\u5B83\uFF08\u89C1 frame.ts \u7684 moveCanvasesIntoPages\uFF09\u3002
206
+ \u57FA\u7840\u5E93\u8FD8\u4F1A\u6309\u903B\u8F91\u5C42\u91CC\u7684\u5E03\u5C40\u6539\u5B83\u7684\u4F4D\u7F6E\u548C\u663E\u9690\uFF0C\u8FD9\u91CC\u4E00\u5F8B\u538B\u4F4F\uFF1B\u89E6\u6478\u7167\u5E38\u4EA4\u7ED9 wx-canvas \u81EA\u5DF1\u7684 canvas -->
207
+ <style>.wxa-service-canvas{position:absolute!important;left:0!important;top:0!important;width:100%!important;height:100%!important;visibility:inherit!important;pointer-events:none}</style>
208
+ <script>var __wxConfig = ${toJs(wxConfig)};<\/script>
209
+ <script src="/__wxa__/runtime/frame.js" data-kind="webview" data-webview-id="${id}"><\/script>
210
+ ${scriptTags(scripts, "head")}<script src="/__baselib__/WAWebview.js"><\/script>`;
211
+ }
212
+ function scriptTags(scripts, position) {
213
+ return (scripts ?? []).filter((script) => (script.position ?? "body") === position).map((script) => script.src !== void 0 ? `<script src="${escapeHtml(script.src)}"><\/script>
214
+ ` : `<script>${script.content.replace(/<\/script/gi, "<\\/script")}<\/script>
215
+ `).join("");
216
+ }
217
+ function escapeHtml(text) {
218
+ return text.replace(/[&<>"]/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[ch]);
219
+ }
220
+
221
+ // src/server/appRoutes.ts
222
+ var DEFAULT_DEVICE = {
223
+ model: "iPhone X",
224
+ screenWidth: 375,
225
+ screenHeight: 812,
226
+ pixelRatio: 3,
227
+ statusBarHeight: 44,
228
+ safeAreaBottom: 34,
229
+ system: "iOS 16.0",
230
+ platform: "devtools"
231
+ };
232
+ var PLUGIN_SCRIPT = /^\/__wxa__\/plugin\/([^/]+)\/([^/]+)\/(appservice\.js|pageframe\.js)$/;
233
+ function routeApp(source, url) {
234
+ const { build } = source;
235
+ const pathname = safeDecode(url.pathname);
236
+ if (pathname === null) return null;
237
+ const query = url.searchParams;
238
+ switch (pathname) {
239
+ case "/__wxa__/appservice.html": {
240
+ const launchPath = (query.get("path") || "").replace(/^\//, "");
241
+ const launch = build.pages.includes(launchPath) ? { path: launchPath, query: Object.fromEntries(new URLSearchParams(query.get("query") || "")) } : { path: build.pages[0], query: {} };
242
+ return html(appserviceHtml(build, launch, { hot: source.hot, isolated: source.isolated }));
243
+ }
244
+ case "/__wxa__/subcontext.html": {
245
+ const id = Number(query.get("id"));
246
+ if (!Number.isInteger(id) || id <= 0) return { status: 400, type: "text", body: "\u5B50\u4E0A\u4E0B\u6587 id \u65E0\u6548" };
247
+ return html(subcontextHtml(id, query.get("empty") === "1"));
248
+ }
249
+ case "/__wxa__/worker.js":
250
+ return js(workerScript(build, source.baseLib));
251
+ case "/__wxa__/service-bootstrap.js":
252
+ return js(build.serviceBootstrap);
253
+ case "/__wxa__/service-templates.js":
254
+ return js(build.serviceTemplates);
255
+ case "/__wxa__/page-templates.js":
256
+ return js(build.pageTemplates);
257
+ case "/__wxa__/page-styles.js":
258
+ return js(build.pageStyles);
259
+ case "/__wxa__/page-frame.js":
260
+ return js(build.pageFrame);
261
+ }
262
+ const webview = /^\/__wxa__\/webview\/(.+)\.html$/.exec(pathname);
263
+ if (webview) {
264
+ const pagePath = webview[1];
265
+ if (!build.pages.includes(pagePath)) return null;
266
+ const webviewId = Number(query.get("id"));
267
+ const pluginPage = findPluginPage(build, pagePath);
268
+ return html(pluginPage ? pluginWebviewHtml(build, pluginPage.plugin, pluginPage.innerPath, pagePath, webviewId, { scripts: source.webviewScripts }) : webviewHtml(build, pagePath, webviewId, { hot: source.hot, scripts: source.webviewScripts }));
269
+ }
270
+ const pluginScript = PLUGIN_SCRIPT.exec(pathname);
271
+ if (pluginScript) {
272
+ const [, provider, version, file] = pluginScript;
273
+ const code = build.plugins.find((plugin) => plugin.provider === provider && plugin.version === version)?.files.get(`/${file}`);
274
+ return code ? { status: 200, type: "js", body: code } : null;
275
+ }
276
+ const pluginFile = /^\/__plugin__\/([^/]+)(\/.+)$/.exec(pathname);
277
+ if (pluginFile) {
278
+ const file = build.plugins.find((plugin) => plugin.provider === pluginFile[1])?.files.get(pluginFile[2]);
279
+ return file ? { status: 200, type: extname(pluginFile[2]) || "application/octet-stream", body: file } : null;
280
+ }
281
+ const module2 = /^\/__wxa__\/module\/(.+\.js)$/.exec(pathname);
282
+ if (module2) {
283
+ const id = module2[1];
284
+ if (!build.modules.includes(id) && !build.workerModules.includes(id)) {
285
+ return { status: 404, type: "js", body: `console.error(${JSON.stringify(`[wxa-runner] \u6A21\u5757 ${id} \u4E0D\u5B58\u5728`)})` };
286
+ }
287
+ return js(source.moduleScript(id));
288
+ }
289
+ return null;
290
+ }
291
+ var MIME_TYPES = {
292
+ html: "text/html; charset=utf-8",
293
+ js: "application/javascript; charset=utf-8",
294
+ text: "text/plain; charset=utf-8",
295
+ json: "application/json; charset=utf-8",
296
+ css: "text/css; charset=utf-8",
297
+ wxss: "text/plain; charset=utf-8",
298
+ txt: "text/plain; charset=utf-8",
299
+ svg: "image/svg+xml",
300
+ png: "image/png",
301
+ jpg: "image/jpeg",
302
+ jpeg: "image/jpeg",
303
+ gif: "image/gif",
304
+ webp: "image/webp",
305
+ bmp: "image/bmp",
306
+ ico: "image/x-icon",
307
+ mp3: "audio/mpeg",
308
+ wav: "audio/wav",
309
+ aac: "audio/aac",
310
+ m4a: "audio/mp4",
311
+ ogg: "audio/ogg",
312
+ mp4: "video/mp4",
313
+ webm: "video/webm",
314
+ mov: "video/quicktime",
315
+ woff: "font/woff",
316
+ woff2: "font/woff2",
317
+ ttf: "font/ttf",
318
+ otf: "font/otf",
319
+ wasm: "application/wasm"
320
+ };
321
+ function mimeType(type) {
322
+ if (type.includes("/")) return type;
323
+ return MIME_TYPES[type.replace(/^\./, "").toLowerCase()] ?? "application/octet-stream";
324
+ }
325
+ function extname(filePath) {
326
+ const match = /\.[^./]+$/.exec(filePath);
327
+ return match ? match[0] : "";
328
+ }
329
+ function html(body) {
330
+ return { status: 200, type: "html", body };
331
+ }
332
+ function js(body) {
333
+ return { status: 200, type: "js", body };
334
+ }
335
+ function safeDecode(pathname) {
336
+ try {
337
+ return decodeURIComponent(pathname);
338
+ } catch {
339
+ return null;
340
+ }
341
+ }
342
+
343
+ // src/server/FileStore.ts
344
+ var storeSequence = 0;
345
+ var textEncoder = new TextEncoder();
346
+ var textDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
347
+ function normalizeEncoding(encoding) {
348
+ switch (String(encoding).toLowerCase()) {
349
+ case "utf8":
350
+ case "utf-8":
351
+ return "utf8";
352
+ case "utf16le":
353
+ case "utf-16le":
354
+ case "ucs2":
355
+ case "ucs-2":
356
+ return "utf16le";
357
+ case "latin1":
358
+ case "binary":
359
+ return "latin1";
360
+ case "ascii":
361
+ return "ascii";
362
+ case "base64":
363
+ case "base64url":
364
+ return String(encoding).toLowerCase();
365
+ case "hex":
366
+ return "hex";
367
+ default:
368
+ throw new Error(`Unknown encoding: ${encoding}`);
369
+ }
370
+ }
371
+ function encodeText(text, encoding) {
372
+ const format = normalizeEncoding(encoding);
373
+ if (format === "utf8") return textEncoder.encode(text);
374
+ if (format === "base64" || format === "base64url") {
375
+ let base64 = text.split("=", 1)[0].replace(/-/g, "+").replace(/_/g, "/").replace(/[^A-Za-z0-9+/]/g, "");
376
+ if (base64.length % 4 === 1) base64 = base64.slice(0, -1);
377
+ base64 = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
378
+ return Uint8Array.from(atob(base64), (char) => char.charCodeAt(0));
379
+ }
380
+ if (format === "hex") {
381
+ const bytes2 = [];
382
+ for (let index = 0; index + 1 < text.length; index += 2) {
383
+ const pair = text.slice(index, index + 2);
384
+ if (!/^[\da-f]{2}$/i.test(pair)) break;
385
+ bytes2.push(parseInt(pair, 16));
386
+ }
387
+ return new Uint8Array(bytes2);
388
+ }
389
+ const bytes = new Uint8Array(text.length * (format === "utf16le" ? 2 : 1));
390
+ for (let index = 0; index < text.length; index++) {
391
+ const code = text.charCodeAt(index);
392
+ if (format === "utf16le") {
393
+ bytes[index * 2] = code & 255;
394
+ bytes[index * 2 + 1] = code >>> 8;
395
+ } else bytes[index] = code & 255;
396
+ }
397
+ return bytes;
398
+ }
399
+ function decodeText(bytes, encoding) {
400
+ const format = normalizeEncoding(encoding);
401
+ if (format === "utf8") return textDecoder.decode(bytes);
402
+ if (format === "hex") return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
403
+ const chunks = [];
404
+ for (let offset = 0; offset < bytes.length; offset += 32768) {
405
+ const chunk = bytes.subarray(offset, offset + 32768);
406
+ const codes = [];
407
+ for (let index = 0; index < chunk.length; index++) {
408
+ if (format === "utf16le") {
409
+ if (index + 1 < chunk.length) codes.push(chunk[index] | chunk[++index] << 8);
410
+ } else codes.push(format === "ascii" ? chunk[index] & 127 : chunk[index]);
411
+ }
412
+ chunks.push(String.fromCharCode(...codes));
413
+ }
414
+ const text = chunks.join("");
415
+ if (format === "base64") return btoa(text);
416
+ if (format === "base64url") return btoa(text).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
417
+ return text;
418
+ }
419
+ var FileStore = class {
420
+ /** @param roots 预先创建的目录(如用户目录)。rmdir 不能删除它们和它们的上级目录:删掉之后再写 USER_DATA_PATH 都会失败 */
421
+ constructor(roots = []) {
422
+ this.etagPrefix = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${++storeSequence}-${Math.random().toString(36).slice(2)}`;
423
+ this.version = 0;
424
+ this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: new Uint8Array(0), mtime: Date.now(), etag: "" }]]);
425
+ this.operations = {
426
+ readFile: ({ filePath, encoding }) => {
427
+ const data = this.read(filePath);
428
+ return encoding === void 0 ? { data: decodeText(data, "base64"), binary: true } : { data: decodeText(data, encoding) };
429
+ },
430
+ writeFile: (args) => this.writeEncoded(args, false),
431
+ appendFile: (args) => this.writeEncoded(args, true),
432
+ copyFile: ({ srcPath, destPath }) => {
433
+ this.write(destPath, this.read(srcPath));
434
+ return {};
435
+ },
436
+ access: ({ path }) => {
437
+ this.existing(path);
438
+ return {};
439
+ },
440
+ stat: ({ path }) => {
441
+ const node = this.existing(path);
442
+ const stats = {
443
+ mode: node.dir ? 16877 : 33188,
444
+ size: node.data.length,
445
+ lastAccessedTime: Math.floor(node.mtime / 1e3),
446
+ lastModifiedTime: Math.floor(node.mtime / 1e3)
447
+ };
448
+ return { stats, ...stats };
449
+ },
450
+ getFileInfo: ({ filePath }) => ({ size: this.read(filePath).length }),
451
+ mkdir: ({ dirPath, recursive }) => {
452
+ this.mkdir(dirPath, !!recursive);
453
+ return {};
454
+ },
455
+ readdir: ({ dirPath }) => ({ files: this.readdir(dirPath) }),
456
+ unlink: ({ filePath }) => {
457
+ this.remove(filePath, { dir: false });
458
+ return {};
459
+ },
460
+ rmdir: ({ dirPath, recursive }) => {
461
+ this.assertNotRoot(dirPath);
462
+ this.remove(dirPath, { dir: true, recursive: !!recursive });
463
+ return {};
464
+ }
465
+ };
466
+ this.roots = roots.map((root) => this.normalize(root));
467
+ for (const root of this.roots) this.mkdir(root, true);
468
+ }
469
+ normalize(filePath) {
470
+ const stripped = String(filePath || "").replace(/^[a-z]+:\/\/[^/]*/i, "");
471
+ const parts = [];
472
+ for (const part of stripped.split("/")) {
473
+ if (!part || part === ".") continue;
474
+ if (part === "..") parts.pop();
475
+ else parts.push(part);
476
+ }
477
+ return "/" + parts.join("/");
478
+ }
479
+ stat(filePath) {
480
+ return this.nodes.get(this.normalize(filePath));
481
+ }
482
+ mkdir(dirPath, recursive = false) {
483
+ const target = this.normalize(dirPath);
484
+ if (this.nodes.has(target)) throw new Error(`file already exists ${dirPath}`);
485
+ const parent = target.slice(0, target.lastIndexOf("/")) || "/";
486
+ if (!this.nodes.get(parent)?.dir) {
487
+ if (!recursive) throw new Error(`no such file or directory ${dirPath}`);
488
+ this.mkdir(parent, true);
489
+ }
490
+ this.nodes.set(target, { dir: true, data: new Uint8Array(0), mtime: Date.now(), etag: "" });
491
+ }
492
+ write(filePath, input, append = false) {
493
+ const data = typeof input === "string" ? textEncoder.encode(input) : new Uint8Array(input);
494
+ const target = this.normalize(filePath);
495
+ const parent = target.slice(0, target.lastIndexOf("/")) || "/";
496
+ if (!this.nodes.get(parent)?.dir) throw new Error(`no such file or directory ${filePath}`);
497
+ const existing = this.nodes.get(target);
498
+ if (existing?.dir) throw new Error(`illegal operation on a directory ${filePath}`);
499
+ let contents = data;
500
+ if (append && existing) {
501
+ contents = new Uint8Array(existing.data.length + data.length);
502
+ contents.set(existing.data);
503
+ contents.set(data, existing.data.length);
504
+ }
505
+ this.nodes.set(target, { dir: false, data: contents, mtime: Date.now(), etag: `${this.etagPrefix}-${++this.version}` });
506
+ }
507
+ read(filePath) {
508
+ const node = this.stat(filePath);
509
+ if (!node) throw new Error(`no such file or directory, open ${filePath}`);
510
+ if (node.dir) throw new Error(`illegal operation on a directory ${filePath}`);
511
+ return node.data;
512
+ }
513
+ readdir(dirPath) {
514
+ const target = this.normalize(dirPath);
515
+ if (!this.nodes.get(target)?.dir) throw new Error(`no such file or directory ${dirPath}`);
516
+ const prefix = target === "/" ? "/" : target + "/";
517
+ return [...this.nodes.keys()].filter((key) => key.startsWith(prefix) && key !== target && !key.slice(prefix.length).includes("/")).map((key) => key.slice(prefix.length));
518
+ }
519
+ remove(filePath, options) {
520
+ const target = this.normalize(filePath);
521
+ const node = this.nodes.get(target);
522
+ if (!node) throw new Error(`no such file or directory ${filePath}`);
523
+ if (node.dir !== options.dir) throw new Error(options.dir ? `not a directory ${filePath}` : `operation not permitted ${filePath}`);
524
+ if (node.dir) {
525
+ const children = this.readdir(target);
526
+ if (children.length && !options.recursive) throw new Error(`directory not empty ${filePath}`);
527
+ for (const key of [...this.nodes.keys()]) if (key.startsWith(target + "/")) this.nodes.delete(key);
528
+ }
529
+ this.nodes.delete(target);
530
+ }
531
+ /** 文件 API 经开发服务器调用的操作,参数和返回值的字段名与基础库一致 */
532
+ execute(operation, args) {
533
+ if (!Object.prototype.hasOwnProperty.call(this.operations, operation)) {
534
+ throw new Error(`unsupported filesystem operation ${operation}`);
535
+ }
536
+ return this.operations[operation](args);
537
+ }
538
+ writeEncoded(args, append) {
539
+ this.write(args.filePath, encodeText(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8"), append);
540
+ return {};
541
+ }
542
+ existing(filePath) {
543
+ const node = this.stat(filePath);
544
+ if (!node) throw new Error(`no such file or directory ${filePath}`);
545
+ return node;
546
+ }
547
+ assertNotRoot(dirPath) {
548
+ const target = this.normalize(dirPath);
549
+ const prefix = target === "/" ? "/" : target + "/";
550
+ if (this.roots.some((root) => root === target || root.startsWith(prefix))) {
551
+ throw new Error(`operation not permitted ${dirPath}`);
552
+ }
553
+ }
554
+ };
555
+
556
+ // src/runtime/networkPanel.ts
557
+ var PROXY_PREFIX = "/__wxa__/proxy/";
558
+ var MOCK_PREFIX = "/__wxa__/mock/";
559
+ var MOCK_ID_HEADER = "x-wxa-mock-id";
560
+ function fromServerPath(prefix, serverPath) {
561
+ if (!serverPath.startsWith(prefix)) return null;
562
+ const match = /^(https?)\/([^/?#]+)(.*)$/.exec(serverPath.slice(prefix.length));
563
+ if (!match) return null;
564
+ const url = `${match[1]}://${match[2]}${match[3]}`;
565
+ try {
566
+ new URL(url);
567
+ return url;
568
+ } catch {
569
+ return null;
570
+ }
571
+ }
572
+ function toHeaders(header) {
573
+ const headers = new Headers();
574
+ for (const [name, value] of Object.entries(header)) {
575
+ if (value === void 0 || value === null) continue;
576
+ try {
577
+ headers.set(name, String(value));
578
+ } catch {
579
+ }
580
+ }
581
+ return headers;
582
+ }
583
+
584
+ // src/server/proxyFetch.ts
585
+ var DROPPED_REQUEST_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "expect"]);
586
+ var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding", "connection", "set-cookie"]);
587
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
588
+ async function proxyFetch(request, options = {}) {
589
+ const url = new URL(request.url);
590
+ const path = url.pathname + url.search;
591
+ const target = fromServerPath(PROXY_PREFIX, path);
592
+ if (!target) return proxyError(400, `invalid url "${path}"`);
593
+ try {
594
+ let current = new URL(target);
595
+ if (options.isAllowed && !options.isAllowed(current)) return forbiddenHost(current, 0);
596
+ const headers = new Headers(
597
+ forwardedRequestHeaders(request.headers.get("x-wxa-headers") ?? void 0, request.headers.get("content-type") ?? "")
598
+ );
599
+ let method = request.method;
600
+ let body = request.body && !["GET", "HEAD"].includes(method) ? await request.arrayBuffer() : void 0;
601
+ const send = options.fetch ?? fetch;
602
+ let upstream;
603
+ for (let hop = 0; ; hop++) {
604
+ upstream = await send(current.href, {
605
+ method,
606
+ headers,
607
+ body,
608
+ redirect: options.isAllowed ? "manual" : "follow",
609
+ signal: request.signal
610
+ });
611
+ const location = upstream.headers.get("location");
612
+ if (!options.isAllowed || !REDIRECT_STATUSES.has(upstream.status) || location === null) break;
613
+ await upstream.body?.cancel();
614
+ const next = new URL(location, current);
615
+ if (hop >= 20) return proxyError(403, `host "${next.hostname}": redirect hop ${hop + 1} exceeds limit 20`);
616
+ if (!["http:", "https:"].includes(next.protocol) || !options.isAllowed(next)) return forbiddenHost(next, hop + 1);
617
+ if (upstream.status === 303 && method !== "GET" && method !== "HEAD" || (upstream.status === 301 || upstream.status === 302) && method === "POST") {
618
+ method = "GET";
619
+ body = void 0;
620
+ for (const name of ["content-type", "content-encoding", "content-language", "content-location"]) headers.delete(name);
621
+ }
622
+ if (next.origin !== current.origin) {
623
+ for (const name of ["authorization", "proxy-authorization", "cookie"]) headers.delete(name);
624
+ }
625
+ current = next;
626
+ }
627
+ return relayResponse(upstream);
628
+ } catch (error) {
629
+ return proxyError(502, fetchErrorMessage(error));
630
+ }
631
+ }
632
+ function forbiddenHost(url, hop) {
633
+ return proxyError(403, `host "${url.hostname}" is not in the allowlist (redirect hop ${hop})`);
634
+ }
635
+ function proxyError(status, message) {
636
+ return new Response(message, { status, headers: { "x-wxa-proxy-error": "1", "content-type": "text/html; charset=utf-8" } });
637
+ }
638
+ function fetchErrorMessage(error) {
639
+ const detail = error;
640
+ return detail?.cause?.message || detail?.message || String(error);
641
+ }
642
+ function forwardedRequestHeaders(encoded, browserContentType = "") {
643
+ const multipart = browserContentType.startsWith("multipart/form-data");
644
+ const headers = {};
645
+ for (const [key, value] of Object.entries(parseHeaders(encoded))) {
646
+ const name = key.toLowerCase();
647
+ if (DROPPED_REQUEST_HEADERS.has(name) || multipart && name === "content-type" || value === void 0 || value === null) continue;
648
+ headers[key] = String(value);
649
+ }
650
+ if (multipart) headers["content-type"] = browserContentType;
651
+ return headers;
652
+ }
653
+ function parseHeaders(encoded = "{}") {
654
+ try {
655
+ const parsed = JSON.parse(encoded);
656
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
657
+ } catch {
658
+ return {};
659
+ }
660
+ }
661
+ function relayResponse(upstream) {
662
+ const responseHeaders = {};
663
+ const headers = new Headers();
664
+ upstream.headers.forEach((value, key) => {
665
+ responseHeaders[key] = value;
666
+ if (!DROPPED_RESPONSE_HEADERS.has(key)) headers.set(key, value);
667
+ });
668
+ headers.set("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
669
+ const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
670
+ headers.set("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
671
+ return new Response(upstream.body, { status: upstream.status, statusText: upstream.statusText, headers });
672
+ }
673
+
674
+ // src/runtime/mockResponse.ts
675
+ var NULL_BODY_STATUSES = /* @__PURE__ */ new Set([204, 205, 304]);
676
+ async function mockedResponse(client, id) {
677
+ if (!client) return Response.error();
678
+ const reply = await new Promise((resolve) => {
679
+ const channel = new MessageChannel();
680
+ channel.port1.onmessage = (event) => {
681
+ channel.port1.close();
682
+ resolve(event.data);
683
+ };
684
+ client.postMessage({ type: "wxa-mock-response", id }, [channel.port2]);
685
+ });
686
+ if (!reply) return Response.error();
687
+ try {
688
+ return new Response(NULL_BODY_STATUSES.has(reply.statusCode) ? null : reply.body, {
689
+ status: reply.statusCode,
690
+ headers: toHeaders(reply.header)
691
+ });
692
+ } catch {
693
+ return Response.error();
694
+ }
695
+ }
696
+
697
+ // src/runtime/setupModule.ts
698
+ var SETUP_MODULE = "@open-wxa/wxa-runner-core/setup";
699
+ var SETUP_MODULE_SOURCE = "export function defineSetup(setup) { return setup; }";
700
+ //# sourceMappingURL=web.js.map