@open-wxa/wxa-runner-core 0.3.1 → 0.3.2

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.
@@ -205,6 +205,158 @@
205
205
  };
206
206
  }
207
207
 
208
+ // src/runtime/host/api/tempFiles.ts
209
+ var TEMP_DIR = "/__wxa__/tmp";
210
+ async function saveTempFile(data, type) {
211
+ const res = await fetch(`${TEMP_DIR}?type=${encodeURIComponent(type)}`, { method: "POST", body: data });
212
+ if (!res.ok) throw new Error(await res.text());
213
+ const { url } = await res.json();
214
+ return url;
215
+ }
216
+ function tempFileRootPath(url) {
217
+ const prefix = `${location.origin}${TEMP_DIR}/`;
218
+ return url.startsWith(prefix) ? url.slice(location.origin.length) : url;
219
+ }
220
+ function tempFileType(name, mimeType, fallback) {
221
+ const fromName = /\.([a-z\d]+)$/i.exec(name)?.[1];
222
+ const fromType = /^[a-z]+\/([a-z\d]+)\s*(;|$)/i.exec(mimeType)?.[1];
223
+ return (fromName || fromType || fallback).toLowerCase();
224
+ }
225
+ function isTempFile(filePath) {
226
+ return typeof filePath === "string" && filePath.startsWith(`${location.origin}${TEMP_DIR}/`);
227
+ }
228
+ async function tempFileSize(url) {
229
+ const res = await fetch(url, { method: "HEAD" });
230
+ return res.ok ? Number(res.headers.get("content-length")) : null;
231
+ }
232
+ async function removeTempFile(url) {
233
+ await fetch(url, { method: "DELETE" });
234
+ }
235
+
236
+ // src/runtime/host/api/fs.ts
237
+ var DIR_MODE = 16877;
238
+ var FILE_MODE = 33188;
239
+ var MemoryFileSystem = class {
240
+ constructor() {
241
+ this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: "", mtime: Date.now() }]]);
242
+ }
243
+ normalize(filePath) {
244
+ const stripped = String(filePath || "").replace(/^[a-z]+:\/\/[^/]*/i, "");
245
+ const parts = [];
246
+ for (const part of stripped.split("/")) {
247
+ if (!part || part === ".") continue;
248
+ if (part === "..") parts.pop();
249
+ else parts.push(part);
250
+ }
251
+ return "/" + parts.join("/");
252
+ }
253
+ stat(filePath) {
254
+ return this.nodes.get(this.normalize(filePath));
255
+ }
256
+ mkdir(dirPath, recursive = false) {
257
+ const target = this.normalize(dirPath);
258
+ if (this.nodes.has(target)) throw new Error(`file already exists ${dirPath}`);
259
+ const parent = target.slice(0, target.lastIndexOf("/")) || "/";
260
+ if (!this.nodes.get(parent)?.dir) {
261
+ if (!recursive) throw new Error(`no such file or directory ${dirPath}`);
262
+ this.mkdir(parent, true);
263
+ }
264
+ this.nodes.set(target, { dir: true, data: "", mtime: Date.now() });
265
+ }
266
+ write(filePath, data, append = false) {
267
+ const target = this.normalize(filePath);
268
+ const parent = target.slice(0, target.lastIndexOf("/")) || "/";
269
+ if (!this.nodes.get(parent)?.dir) throw new Error(`no such file or directory ${filePath}`);
270
+ const existing = this.nodes.get(target);
271
+ if (existing?.dir) throw new Error(`illegal operation on a directory ${filePath}`);
272
+ this.nodes.set(target, { dir: false, data: append && existing ? existing.data + data : data, mtime: Date.now() });
273
+ }
274
+ read(filePath) {
275
+ const node = this.stat(filePath);
276
+ if (!node) throw new Error(`no such file or directory, open ${filePath}`);
277
+ if (node.dir) throw new Error(`illegal operation on a directory ${filePath}`);
278
+ return node.data;
279
+ }
280
+ readdir(dirPath) {
281
+ const target = this.normalize(dirPath);
282
+ if (!this.nodes.get(target)?.dir) throw new Error(`no such file or directory ${dirPath}`);
283
+ const prefix = target === "/" ? "/" : target + "/";
284
+ return [...this.nodes.keys()].filter((key) => key.startsWith(prefix) && key !== target && !key.slice(prefix.length).includes("/")).map((key) => key.slice(prefix.length));
285
+ }
286
+ remove(filePath, options) {
287
+ const target = this.normalize(filePath);
288
+ const node = this.nodes.get(target);
289
+ if (!node) throw new Error(`no such file or directory ${filePath}`);
290
+ if (node.dir !== options.dir) throw new Error(options.dir ? `not a directory ${filePath}` : `operation not permitted ${filePath}`);
291
+ if (node.dir) {
292
+ const children = this.readdir(target);
293
+ if (children.length && !options.recursive) throw new Error(`directory not empty ${filePath}`);
294
+ for (const key of [...this.nodes.keys()]) if (key.startsWith(target + "/")) this.nodes.delete(key);
295
+ }
296
+ this.nodes.delete(target);
297
+ }
298
+ };
299
+ function createFileSystemApis() {
300
+ const fs = new MemoryFileSystem();
301
+ const guard = (api, action) => {
302
+ try {
303
+ return ok(api, action() || {});
304
+ } catch (error) {
305
+ return fail(api, error.message);
306
+ }
307
+ };
308
+ const statResult = (node) => {
309
+ const stats = {
310
+ mode: node.dir ? DIR_MODE : FILE_MODE,
311
+ size: node.data.length,
312
+ lastAccessedTime: Math.floor(node.mtime / 1e3),
313
+ lastModifiedTime: Math.floor(node.mtime / 1e3)
314
+ };
315
+ return { stats, ...stats };
316
+ };
317
+ const readMemoryFile = (args, { api }) => guard(api, () => ({ data: fs.read(args.filePath) }));
318
+ const apis = {
319
+ access: (args, { api }) => fs.stat(args.path) ? ok(api) : fail(api, `no such file or directory ${args.path}`),
320
+ stat: (args, { api }) => {
321
+ const node = fs.stat(args.path);
322
+ return node ? ok(api, statResult(node)) : fail(api, `no such file or directory ${args.path}`);
323
+ },
324
+ mkdir: (args, { api }) => guard(api, () => fs.mkdir(args.dirPath, !!args.recursive)),
325
+ writeFile: (args, { api }) => guard(api, () => fs.write(args.filePath, String(args.data ?? ""))),
326
+ appendFile: (args, { api }) => guard(api, () => fs.write(args.filePath, String(args.data ?? ""), true)),
327
+ readdir: (args, { api }) => guard(api, () => ({ files: fs.readdir(args.dirPath) })),
328
+ unlink: (args, { api }) => guard(api, () => fs.remove(args.filePath, { dir: false })),
329
+ rmdir: (args, { api }) => guard(api, () => fs.remove(args.dirPath, { dir: true, recursive: !!args.recursive })),
330
+ getFileInfo: (args, { api }) => {
331
+ if (isTempFile(args.filePath)) {
332
+ return tempFileSize(args.filePath).then((size) => size === null ? fail(api, "file not exist") : ok(api, { size }));
333
+ }
334
+ const node = fs.stat(args.filePath);
335
+ return node ? ok(api, { size: node.data.length }) : fail(api, "file not exist");
336
+ }
337
+ };
338
+ for (const name of Object.keys(apis)) apis[name + "Sync"] = apis[name];
339
+ return {
340
+ ...apis,
341
+ fs_appendFile: apis.appendFile,
342
+ // 不带协议的是代码包里的文件,要向开发服务器取;readFileSync 同步返回不了,只读内存文件系统。
343
+ // 内存文件系统的路径去掉了协议,不能先在里面找:用户目录下的同名文件会顶替代码包里的
344
+ readFile: (args, context) => /^[a-z]+:\/\//i.test(String(args.filePath)) ? readMemoryFile(args, context) : readPackageFile(fs.normalize(args.filePath), args.encoding).then(
345
+ (data) => ok(context.api, { data }),
346
+ (error) => fail(context.api, error.message)
347
+ ),
348
+ readFileSync: readMemoryFile
349
+ };
350
+ }
351
+ async function readPackageFile(filePath, encoding) {
352
+ if (encoding !== void 0 && encoding !== "utf8" && encoding !== "utf-8") {
353
+ throw new Error(`\u8BFB\u53D6\u4EE3\u7801\u5305\u91CC\u7684\u6587\u4EF6\u53EA\u652F\u6301 utf8 \u7F16\u7801\uFF0C\u6216\u4E0D\u6307\u5B9A\u7F16\u7801\u5F97\u5230 ArrayBuffer\uFF0C\u4E0D\u652F\u6301 ${encoding}`);
354
+ }
355
+ const response = await fetch(filePath.split("/").map(encodeURIComponent).join("/"));
356
+ if (!response.ok) throw new Error(`no such file or directory, open ${filePath}`);
357
+ return encoding ? response.text() : response.arrayBuffer();
358
+ }
359
+
208
360
  // src/runtime/syncScript.ts
209
361
  function readSync(url) {
210
362
  const xhr = new XMLHttpRequest();
@@ -277,34 +429,6 @@
277
429
  }
278
430
  };
279
431
 
280
- // src/runtime/host/api/tempFiles.ts
281
- var TEMP_DIR = "/__wxa__/tmp";
282
- async function saveTempFile(data, type) {
283
- const res = await fetch(`${TEMP_DIR}?type=${encodeURIComponent(type)}`, { method: "POST", body: data });
284
- if (!res.ok) throw new Error(await res.text());
285
- const { url } = await res.json();
286
- return url;
287
- }
288
- function tempFileRootPath(url) {
289
- const prefix = `${location.origin}${TEMP_DIR}/`;
290
- return url.startsWith(prefix) ? url.slice(location.origin.length) : url;
291
- }
292
- function tempFileType(name, mimeType, fallback) {
293
- const fromName = /\.([a-z\d]+)$/i.exec(name)?.[1];
294
- const fromType = /^[a-z]+\/([a-z\d]+)\s*(;|$)/i.exec(mimeType)?.[1];
295
- return (fromName || fromType || fallback).toLowerCase();
296
- }
297
- function isTempFile(filePath) {
298
- return typeof filePath === "string" && filePath.startsWith(`${location.origin}${TEMP_DIR}/`);
299
- }
300
- async function tempFileSize(url) {
301
- const res = await fetch(url, { method: "HEAD" });
302
- return res.ok ? Number(res.headers.get("content-length")) : null;
303
- }
304
- async function removeTempFile(url) {
305
- await fetch(url, { method: "DELETE" });
306
- }
307
-
308
432
  // src/runtime/host/api/media.ts
309
433
  var SOURCES = ["camera", "album"];
310
434
  var SOURCE_LABELS = { camera: "\u62CD\u6444", album: "\u4ECE\u624B\u673A\u76F8\u518C\u9009\u62E9" };
@@ -380,70 +504,7 @@
380
504
  }
381
505
 
382
506
  // src/runtime/host/api/misc.ts
383
- var DIR_MODE = 16877;
384
- var FILE_MODE = 33188;
385
- var MemoryFileSystem = class {
386
- constructor() {
387
- this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: "", mtime: Date.now() }]]);
388
- }
389
- normalize(filePath) {
390
- const stripped = String(filePath || "").replace(/^[a-z]+:\/\/[^/]*/i, "");
391
- const parts = [];
392
- for (const part of stripped.split("/")) {
393
- if (!part || part === ".") continue;
394
- if (part === "..") parts.pop();
395
- else parts.push(part);
396
- }
397
- return "/" + parts.join("/");
398
- }
399
- stat(filePath) {
400
- return this.nodes.get(this.normalize(filePath));
401
- }
402
- mkdir(dirPath, recursive = false) {
403
- const target = this.normalize(dirPath);
404
- if (this.nodes.has(target)) throw new Error(`file already exists ${dirPath}`);
405
- const parent = target.slice(0, target.lastIndexOf("/")) || "/";
406
- if (!this.nodes.get(parent)?.dir) {
407
- if (!recursive) throw new Error(`no such file or directory ${dirPath}`);
408
- this.mkdir(parent, true);
409
- }
410
- this.nodes.set(target, { dir: true, data: "", mtime: Date.now() });
411
- }
412
- write(filePath, data, append = false) {
413
- const target = this.normalize(filePath);
414
- const parent = target.slice(0, target.lastIndexOf("/")) || "/";
415
- if (!this.nodes.get(parent)?.dir) throw new Error(`no such file or directory ${filePath}`);
416
- const existing = this.nodes.get(target);
417
- if (existing?.dir) throw new Error(`illegal operation on a directory ${filePath}`);
418
- this.nodes.set(target, { dir: false, data: append && existing ? existing.data + data : data, mtime: Date.now() });
419
- }
420
- read(filePath) {
421
- const node = this.stat(filePath);
422
- if (!node) throw new Error(`no such file or directory, open ${filePath}`);
423
- if (node.dir) throw new Error(`illegal operation on a directory ${filePath}`);
424
- return node.data;
425
- }
426
- readdir(dirPath) {
427
- const target = this.normalize(dirPath);
428
- if (!this.nodes.get(target)?.dir) throw new Error(`no such file or directory ${dirPath}`);
429
- const prefix = target === "/" ? "/" : target + "/";
430
- return [...this.nodes.keys()].filter((key) => key.startsWith(prefix) && key !== target && !key.slice(prefix.length).includes("/")).map((key) => key.slice(prefix.length));
431
- }
432
- remove(filePath, options) {
433
- const target = this.normalize(filePath);
434
- const node = this.nodes.get(target);
435
- if (!node) throw new Error(`no such file or directory ${filePath}`);
436
- if (node.dir !== options.dir) throw new Error(options.dir ? `not a directory ${filePath}` : `operation not permitted ${filePath}`);
437
- if (node.dir) {
438
- const children = this.readdir(target);
439
- if (children.length && !options.recursive) throw new Error(`directory not empty ${filePath}`);
440
- for (const key of [...this.nodes.keys()]) if (key.startsWith(target + "/")) this.nodes.delete(key);
441
- }
442
- this.nodes.delete(target);
443
- }
444
- };
445
507
  function createMiscApis(appid, options) {
446
- const fs = new MemoryFileSystem();
447
508
  const realLogin = async (api, url) => {
448
509
  try {
449
510
  return await postDevServer(url);
@@ -452,54 +513,7 @@
452
513
  throw error;
453
514
  }
454
515
  };
455
- const guard = (api, action) => {
456
- try {
457
- return ok(api, action() || {});
458
- } catch (error) {
459
- return fail(api, error.message);
460
- }
461
- };
462
- const statResult = (node) => {
463
- const stats = {
464
- mode: node.dir ? DIR_MODE : FILE_MODE,
465
- size: node.data.length,
466
- lastAccessedTime: Math.floor(node.mtime / 1e3),
467
- lastModifiedTime: Math.floor(node.mtime / 1e3)
468
- };
469
- return { stats, ...stats };
470
- };
471
- const fileApis = {
472
- access: (args, { api }) => fs.stat(args.path) ? ok(api) : fail(api, `no such file or directory ${args.path}`),
473
- stat: (args, { api }) => {
474
- const node = fs.stat(args.path);
475
- return node ? ok(api, statResult(node)) : fail(api, `no such file or directory ${args.path}`);
476
- },
477
- mkdir: (args, { api }) => guard(api, () => fs.mkdir(args.dirPath, !!args.recursive)),
478
- writeFile: (args, { api }) => guard(api, () => fs.write(args.filePath, String(args.data ?? ""))),
479
- appendFile: (args, { api }) => guard(api, () => fs.write(args.filePath, String(args.data ?? ""), true)),
480
- fs_appendFile: (args, { api }) => guard(api, () => fs.write(args.filePath, String(args.data ?? ""), true)),
481
- readFile: (args, { api }) => guard(api, () => ({ data: fs.read(args.filePath) })),
482
- readdir: (args, { api }) => guard(api, () => ({ files: fs.readdir(args.dirPath) })),
483
- unlink: (args, { api }) => guard(api, () => fs.remove(args.filePath, { dir: false })),
484
- rmdir: (args, { api }) => guard(api, () => fs.remove(args.dirPath, { dir: true, recursive: !!args.recursive })),
485
- getFileInfo: (args, { api }) => {
486
- if (isTempFile(args.filePath)) {
487
- return tempFileSize(args.filePath).then((size) => size === null ? fail(api, "file not exist") : ok(api, { size }));
488
- }
489
- const node = fs.stat(args.filePath);
490
- return node ? ok(api, { size: node.data.length }) : fail(api, "file not exist");
491
- }
492
- };
493
- for (const name of Object.keys(fileApis)) {
494
- if (!name.startsWith("fs_")) fileApis[name + "Sync"] = fileApis[name];
495
- }
496
- const readMemoryFile = fileApis.readFile;
497
- fileApis.readFile = (args, context) => /^[a-z]+:\/\//i.test(String(args.filePath)) ? readMemoryFile(args, context) : readPackageFile(fs.normalize(args.filePath), args.encoding).then(
498
- (data) => ok(context.api, { data }),
499
- (error) => fail(context.api, error.message)
500
- );
501
516
  return {
502
- ...fileApis,
503
517
  // canvas 导出图片、拍照、录像的结果都经过这里
504
518
  base64ToTempFilePath: async (args) => {
505
519
  const bytes = Uint8Array.from(atob(String(args.base64Data || "")), (char) => char.charCodeAt(0));
@@ -546,14 +560,6 @@
546
560
  if (!response.ok || !body) throw new Error(body?.error || `\u5F00\u53D1\u670D\u52A1\u5668\u8FD4\u56DE HTTP ${response.status}`);
547
561
  return body;
548
562
  }
549
- async function readPackageFile(filePath, encoding) {
550
- if (encoding !== void 0 && encoding !== "utf8" && encoding !== "utf-8") {
551
- throw new Error(`\u8BFB\u53D6\u4EE3\u7801\u5305\u91CC\u7684\u6587\u4EF6\u53EA\u652F\u6301 utf8 \u7F16\u7801\uFF0C\u6216\u4E0D\u6307\u5B9A\u7F16\u7801\u5F97\u5230 ArrayBuffer\uFF0C\u4E0D\u652F\u6301 ${encoding}`);
552
- }
553
- const response = await fetch(filePath.split("/").map(encodeURIComponent).join("/"));
554
- if (!response.ok) throw new Error(`no such file or directory, open ${filePath}`);
555
- return encoding ? response.text() : response.arrayBuffer();
556
- }
557
563
  function mockUser() {
558
564
  return { nickName: "wxa-runner", avatarUrl: "", gender: 0, country: "", province: "", city: "", language: "zh_CN" };
559
565
  }
@@ -1559,6 +1565,30 @@
1559
1565
  }
1560
1566
  };
1561
1567
 
1568
+ // src/runtime/host/realm.ts
1569
+ function cloneInto(win, value) {
1570
+ if (value === void 0) return value;
1571
+ return win.JSON.parse(JSON.stringify(value));
1572
+ }
1573
+ var BUFFER_KEY = "__wxa_array_buffer__";
1574
+ function cloneNativeInto(win, value) {
1575
+ if (value === void 0) return value;
1576
+ const buffers = [];
1577
+ const json = JSON.stringify(value, (_key, item) => {
1578
+ if (Object.prototype.toString.call(item) !== "[object ArrayBuffer]") return item;
1579
+ buffers.push(item);
1580
+ return { [BUFFER_KEY]: buffers.length - 1 };
1581
+ });
1582
+ const targetJson = win.JSON;
1583
+ if (!buffers.length) return targetJson.parse(json);
1584
+ return targetJson.parse(json, (_key, item) => item?.[BUFFER_KEY] === void 0 ? item : copyBuffer(win, buffers[item[BUFFER_KEY]]));
1585
+ }
1586
+ function copyBuffer(win, source) {
1587
+ const copy = new win.ArrayBuffer(source.byteLength);
1588
+ new Uint8Array(copy).set(new Uint8Array(source));
1589
+ return copy;
1590
+ }
1591
+
1562
1592
  // src/runtime/host/ui/fileChooser.ts
1563
1593
  function chooseFiles(options) {
1564
1594
  return new Promise((resolve) => {
@@ -2440,7 +2470,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2440
2470
  /** 派发给渲染层的原生事件(渲染层里 WeixinJSBridge.on 收到) */
2441
2471
  emitToWebview(webviewId, event, data) {
2442
2472
  this.pages.get(webviewId)?.dispatch((bridge, target) => {
2443
- const payload = cloneInto(target, data);
2473
+ const payload = cloneNativeInto(target, data);
2444
2474
  queueMicrotask(() => bridge.__dispatchOn(event, payload, webviewId));
2445
2475
  });
2446
2476
  }
@@ -2466,6 +2496,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2466
2496
  switchTab: routeApi((args) => this.switchTab(args.url)),
2467
2497
  ...createStorageApis(this.storage),
2468
2498
  ...createSavedFileApis(this.storage),
2499
+ ...createFileSystemApis(),
2469
2500
  ...createSystemApis({
2470
2501
  device: this.device,
2471
2502
  baseLibVersion: this.info.baseLibVersion,
@@ -2504,28 +2535,6 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2504
2535
  });
2505
2536
  }
2506
2537
  };
2507
- function cloneInto(win, value) {
2508
- if (value === void 0) return value;
2509
- return win.JSON.parse(JSON.stringify(value));
2510
- }
2511
- var BUFFER_KEY = "__wxa_array_buffer__";
2512
- function cloneNativeInto(win, value) {
2513
- if (value === void 0) return value;
2514
- const buffers = [];
2515
- const json = JSON.stringify(value, (_key, item) => {
2516
- if (Object.prototype.toString.call(item) !== "[object ArrayBuffer]") return item;
2517
- buffers.push(item);
2518
- return { [BUFFER_KEY]: buffers.length - 1 };
2519
- });
2520
- const targetJson = win.JSON;
2521
- if (!buffers.length) return targetJson.parse(json);
2522
- return targetJson.parse(json, (_key, item) => item?.[BUFFER_KEY] === void 0 ? item : copyBuffer(win, buffers[item[BUFFER_KEY]]));
2523
- }
2524
- function copyBuffer(win, source) {
2525
- const copy = new win.ArrayBuffer(source.byteLength);
2526
- new Uint8Array(copy).set(new Uint8Array(source));
2527
- return copy;
2528
- }
2529
2538
  function withDrawableImages(message) {
2530
2539
  const actions = message?.data?.actions;
2531
2540
  if (!Array.isArray(actions)) return message;