@open-wxa/wxa-runner-core 0.2.4 → 0.3.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.
@@ -76,6 +76,7 @@
76
76
  return { ...extra, errMsg: `${api}:fail ${reason2}` };
77
77
  }
78
78
  function normalize(api, result) {
79
+ if (typeof result === "string") return result;
79
80
  if (!result) return { errMsg: `${api}:ok` };
80
81
  return result.errMsg ? result : { ...result, errMsg: `${api}:ok` };
81
82
  }
@@ -84,6 +85,198 @@
84
85
  return { errMsg: `${api}:fail ${message}` };
85
86
  }
86
87
 
88
+ // src/runtime/host/api/audio.ts
89
+ var STATES = {
90
+ canplay: "canplay",
91
+ playing: "play",
92
+ ended: "ended",
93
+ waiting: "waiting",
94
+ seeking: "seeking",
95
+ seeked: "seeked"
96
+ };
97
+ var HAVE_METADATA = 1;
98
+ var ERROR_CODES = { 2: 10002, 3: 10004, 4: 10004 };
99
+ var audioSeq = 0;
100
+ function createAudioApis(options) {
101
+ const { emit, createAudio = () => new Audio() } = options;
102
+ const instances = /* @__PURE__ */ new Map();
103
+ const createAudioInstance = (_args, { api }) => {
104
+ const audioId = ++audioSeq;
105
+ const audio = createAudio();
106
+ const listening = new AbortController();
107
+ const instance = {
108
+ audio,
109
+ src: "",
110
+ startTime: 0,
111
+ played: false,
112
+ stopped: false,
113
+ report: (state, extra) => emit("onAudioStateChange", { audioId, state, ...extra }),
114
+ destroy: () => {
115
+ listening.abort();
116
+ audio.pause();
117
+ audio.removeAttribute("src");
118
+ audio.load();
119
+ }
120
+ };
121
+ const on = (type, listener) => audio.addEventListener(type, listener, { signal: listening.signal });
122
+ for (const [type, state] of Object.entries(STATES)) {
123
+ on(type, () => {
124
+ if (!instance.stopped) instance.report(state);
125
+ });
126
+ }
127
+ on("loadedmetadata", () => {
128
+ if (!instance.played && instance.startTime > 0) audio.currentTime = instance.startTime / 1e3;
129
+ });
130
+ on("error", () => {
131
+ const code = audio.error?.code ?? 0;
132
+ instance.report("error", { errCode: ERROR_CODES[code] ?? -1, errMsg: audio.error?.message || `MediaError ${code}` });
133
+ });
134
+ instances.set(audioId, instance);
135
+ return ok(api, { audioId });
136
+ };
137
+ const withInstance = (args, api, action) => {
138
+ const instance = instances.get(args.audioId);
139
+ return instance ? ok(api, action(instance) || {}) : fail(api, `audio instance ${args.audioId} not found`);
140
+ };
141
+ return {
142
+ createAudioInstance,
143
+ createAudioInstanceAsync: createAudioInstance,
144
+ // 状态里的 paused、currentTime 等是基础库缓存的旧值,只取能设置的属性
145
+ setAudioState: (args, { api }) => withInstance(args, api, (instance) => {
146
+ const { audio } = instance;
147
+ if (typeof args.autoplay === "boolean") audio.autoplay = args.autoplay;
148
+ if (typeof args.loop === "boolean") audio.loop = args.loop;
149
+ if (typeof args.volume === "number") audio.volume = args.volume;
150
+ if (typeof args.playbackRate === "number") audio.playbackRate = args.playbackRate;
151
+ if (typeof args.src === "string" && args.src !== instance.src) {
152
+ Object.assign(instance, { src: args.src, played: false, stopped: false });
153
+ audio.src = args.src;
154
+ }
155
+ if (typeof args.startTime === "number" && args.startTime !== instance.startTime) {
156
+ instance.startTime = args.startTime;
157
+ if (!instance.played && audio.readyState >= HAVE_METADATA) audio.currentTime = args.startTime / 1e3;
158
+ }
159
+ }),
160
+ getAudioState: (args, { api }) => withInstance(args, api, ({ audio, src, startTime }) => ({
161
+ src,
162
+ startTime,
163
+ paused: audio.paused,
164
+ currentTime: Math.round(audio.currentTime * 1e3),
165
+ duration: Number.isFinite(audio.duration) ? Math.round(audio.duration * 1e3) : 0,
166
+ buffered: audio.buffered.length ? Math.round(audio.buffered.end(audio.buffered.length - 1) * 1e3) : 0,
167
+ volume: audio.volume,
168
+ autoplay: audio.autoplay,
169
+ loop: audio.loop,
170
+ playbackRate: audio.playbackRate
171
+ })),
172
+ operateAudio: (args, { api }) => withInstance(args, api, (instance) => {
173
+ const { audio } = instance;
174
+ switch (args.operationType) {
175
+ case "play":
176
+ Object.assign(instance, { played: true, stopped: false });
177
+ audio.play().catch((error) => {
178
+ if (error.name === "NotAllowedError") instance.report("error", { errCode: -1, errMsg: `${error.name}: ${error.message}` });
179
+ });
180
+ break;
181
+ // 浏览器的 pause 事件不转发:它在 stop 之后才异步派发,播到结尾时也会先派发一次
182
+ case "pause":
183
+ if (!audio.paused) instance.report("pause");
184
+ audio.pause();
185
+ break;
186
+ case "stop":
187
+ instance.stopped = true;
188
+ audio.pause();
189
+ audio.currentTime = 0;
190
+ instance.report("stop");
191
+ break;
192
+ case "seek":
193
+ instance.stopped = false;
194
+ audio.currentTime = Number(args.currentTime) / 1e3;
195
+ break;
196
+ default:
197
+ throw new Error(`unknown operationType ${args.operationType}`);
198
+ }
199
+ }),
200
+ destroyAudioInstance: (args, { api }) => {
201
+ instances.get(args.audioId)?.destroy();
202
+ instances.delete(args.audioId);
203
+ return ok(api);
204
+ }
205
+ };
206
+ }
207
+
208
+ // src/runtime/syncScript.ts
209
+ function readSync(url) {
210
+ const xhr = new XMLHttpRequest();
211
+ xhr.open("GET", url, false);
212
+ xhr.send();
213
+ if (xhr.status !== 200) throw new Error(`[wxa-runner] \u52A0\u8F7D ${url} \u5931\u8D25\uFF1A${xhr.status}`);
214
+ return xhr.responseText;
215
+ }
216
+ function runScript(win, url, code) {
217
+ const href = new URL(url, location.href).href;
218
+ const sourceMap = url.startsWith("/__baselib__/") ? `
219
+ //# sourceMappingURL=${href}.map` : "";
220
+ const script = win.document.createElement("script");
221
+ script.text = `${code}
222
+ //# sourceURL=${href}${sourceMap}`;
223
+ win.document.head.appendChild(script);
224
+ }
225
+
226
+ // src/runtime/host/api/libFiles.ts
227
+ function createLibFileApis(options) {
228
+ const loadLibFiles = (args, { api, from, webviewId }) => {
229
+ const win = from === "webview" ? options.windowOf(webviewId) : null;
230
+ if (!win) return ok(api);
231
+ const codes = args.paths.map((file) => {
232
+ const url = `/__baselib__/${file}`;
233
+ const code = readSync(url);
234
+ if (file.endsWith(".js")) runScript(win, url, code);
235
+ return code;
236
+ });
237
+ return ok(api, { __codes: codes });
238
+ };
239
+ return { loadLibFiles, loadLibFilesSync: loadLibFiles };
240
+ }
241
+
242
+ // src/runtime/host/api/map.ts
243
+ function createMapApis({ registry, operations }) {
244
+ return {
245
+ private_geolocation: async (_args, context) => {
246
+ const location2 = await registry.invoke("getLocation", { type: "gcj02" }, { ...context, api: "getLocation" });
247
+ if (location2.errMsg !== "getLocation:ok") return JSON.stringify({ message: location2.errMsg });
248
+ return JSON.stringify({ result: { location: { lat: location2.latitude, lng: location2.longitude } } });
249
+ },
250
+ // 开发者工具上没有原生地图,照基础库的格式转给当前页的 map 组件
251
+ moveToMapLocation: (args, { webviewId }) => operations.send(webviewId, { ...args, method: "moveToMapLocation" })
252
+ };
253
+ }
254
+ var MapOperations = class {
255
+ /** @param publish 发给渲染层,渲染层的 WeixinJSBridge.subscribe 收到 */
256
+ constructor(publish) {
257
+ this.publish = publish;
258
+ this.pending = /* @__PURE__ */ new Map();
259
+ this.seq = 0;
260
+ }
261
+ /** map 组件做完才返回;页面上没有这个 map 时一直不返回,同基础库自己发的操作 */
262
+ send(webviewId, operation) {
263
+ const callbackId = `wxa_${++this.seq}`;
264
+ return new Promise((resolve) => {
265
+ this.pending.set(callbackId, resolve);
266
+ this.publish(webviewId, "operateMap", { data: { ...operation, callbackId }, options: { timestamp: Date.now() } });
267
+ });
268
+ }
269
+ /** map 组件发回的 operateMapCallback:是宿主发的操作时收下并返回 true,不再交给逻辑层 */
270
+ receive(message) {
271
+ const reply = message?.data;
272
+ const resolve = this.pending.get(reply?.callbackId);
273
+ if (!resolve) return false;
274
+ this.pending.delete(reply.callbackId);
275
+ resolve({ errMsg: reply.errMsg });
276
+ return true;
277
+ }
278
+ };
279
+
87
280
  // src/runtime/host/api/tempFiles.ts
88
281
  var TEMP_DIR = "/__wxa__/tmp";
89
282
  async function saveTempFile(data, type) {
@@ -300,6 +493,11 @@
300
493
  for (const name of Object.keys(fileApis)) {
301
494
  if (!name.startsWith("fs_")) fileApis[name + "Sync"] = fileApis[name];
302
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
+ );
303
501
  return {
304
502
  ...fileApis,
305
503
  // canvas 导出图片、拍照、录像的结果都经过这里
@@ -308,7 +506,11 @@
308
506
  return { tempFilePath: await saveTempFile(bytes, String(args.fileType || "png")) };
309
507
  },
310
508
  // 基础库经这两个接口向后台拉各种配置和小程序信息
311
- operateWXData: (args, { api }) => args.data?.api_name === "webapi_getwxanetwork" ? bizDomainUnavailable(api) : ok(api),
509
+ operateWXData: (args, { api }) => {
510
+ if (args.data?.api_name === "webapi_getwxanetwork") return bizDomainUnavailable(api);
511
+ if (args.data?.api_name === "webapi_getwxauserprivacyauthinfo") return ok(api, { data: { data: JSON.stringify(PRIVACY_AUTH_INFO) } });
512
+ return ok(api);
513
+ },
312
514
  getAppContact: (args, { api }) => args.keyPath === "WxaAppInfo.Network.BizDomain" ? bizDomainUnavailable(api) : ok(api),
313
515
  // 真实登录失败时明确返回 fail,不退回假数据:配置或登录态的问题要让开发者马上发现
314
516
  login: options?.realLogin ? async (_args, { api }) => ok(api, { code: (await realLogin(api, "/__wxa__/api/login")).code }) : (_args, { api }) => ok(api, { code: `wxa_mock_code_${Date.now().toString(36)}` }),
@@ -344,9 +546,18 @@
344
546
  if (!response.ok || !body) throw new Error(body?.error || `\u5F00\u53D1\u670D\u52A1\u5668\u8FD4\u56DE HTTP ${response.status}`);
345
547
  return body;
346
548
  }
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
+ }
347
557
  function mockUser() {
348
558
  return { nickName: "wxa-runner", avatarUrl: "", gender: 0, country: "", province: "", city: "", language: "zh_CN" };
349
559
  }
560
+ var PRIVACY_AUTH_INFO = { action_code: 0, need_popup_scope_list: [], unauth_scope_list: [], wording: {} };
350
561
  function bizDomainUnavailable(api) {
351
562
  return fail(api, "not available in wxa-runner");
352
563
  }
@@ -398,7 +609,7 @@
398
609
  const requestTaskId = startTask(args, async (signal, id) => {
399
610
  try {
400
611
  const mocked = route && await route.respond(signal);
401
- const res = mocked ? { statusCode: mocked.statusCode, header: mocked.header, cookies: [], text: async () => mocked.data } : await proxyFetch(request.url, { method: request.method, header: args.header, body: args.data, signal }).then((proxied) => ({ ...proxied, text: () => proxied.response.text() }));
612
+ const res = mocked ? { statusCode: mocked.statusCode, header: mocked.header, cookies: [], response: new Response(mocked.body) } : await proxyFetch(request.url, { method: request.method, header: args.header, body: args.data, signal });
402
613
  emit("onRequestTaskStateChange", {
403
614
  requestTaskId: id,
404
615
  state: "headersReceived",
@@ -406,7 +617,7 @@
406
617
  statusCode: res.statusCode,
407
618
  cookies: res.cookies
408
619
  });
409
- const data = await res.text();
620
+ const data = args.responseType === "arraybuffer" ? await res.response.arrayBuffer() : await res.response.text();
410
621
  emit("onRequestTaskStateChange", {
411
622
  requestTaskId: id,
412
623
  state: "success",
@@ -485,11 +696,7 @@
485
696
  socket.binaryType = "arraybuffer";
486
697
  sockets.set(socketTaskId, socket);
487
698
  socket.onopen = () => emit("onSocketTaskStateChange", { socketTaskId, state: "open", header: {}, profile: {} });
488
- socket.onmessage = (event) => emit("onSocketTaskStateChange", {
489
- socketTaskId,
490
- state: "message",
491
- data: typeof event.data === "string" ? event.data : "[binary message not supported]"
492
- });
699
+ socket.onmessage = (event) => emit("onSocketTaskStateChange", { socketTaskId, state: "message", data: event.data });
493
700
  socket.onerror = () => emit("onSocketTaskStateChange", { socketTaskId, state: "error", errMsg: "connectSocket:fail" });
494
701
  socket.onclose = (event) => {
495
702
  sockets.delete(socketTaskId);
@@ -508,25 +715,42 @@
508
715
  }
509
716
  return ok(api);
510
717
  };
511
- const createTask = {
512
- createRequestTask,
513
- createRequestTaskAsync: createRequestTask,
514
- createDownloadTask,
515
- createDownloadTaskAsync: createDownloadTask,
516
- createUploadTask,
517
- createUploadTaskAsync: createUploadTask,
518
- createSocketTask
718
+ const downloadAudio = (args, { api }) => {
719
+ const requestTaskId = startTask(args, async (signal, id) => {
720
+ try {
721
+ const res = await proxyFetch(args.url, { method: "GET", header: args.header, signal });
722
+ const meta = { requestTaskId: id, statusCode: res.statusCode, header: res.header, cookies: res.cookies };
723
+ emit("onRequestTaskStateChange", { ...meta, state: "headersReceived" });
724
+ emit("onRequestTaskStateChange", { ...meta, state: "success", data: await res.response.arrayBuffer(), profile: {} });
725
+ } catch (error) {
726
+ emit("onRequestTaskStateChange", { requestTaskId: id, state: "fail", errMsg: `request:fail ${reason(error)}` });
727
+ }
728
+ });
729
+ return ok(api, { requestTaskId });
519
730
  };
731
+ const requestTask = byCaller(createRequestTask, (args, context) => isAudioDownload(args) ? downloadAudio(args, context) : rejectBaseLib(args, context));
732
+ const downloadTask = byCaller(createDownloadTask);
733
+ const uploadTask = byCaller(createUploadTask);
520
734
  return {
521
- ...Object.fromEntries(Object.entries(createTask).map(([api, handler]) => [api, rejectBaseLibInternal(handler)])),
735
+ createRequestTask: requestTask,
736
+ createRequestTaskAsync: requestTask,
737
+ createDownloadTask: downloadTask,
738
+ createDownloadTaskAsync: downloadTask,
739
+ createUploadTask: uploadTask,
740
+ createUploadTaskAsync: uploadTask,
741
+ createSocketTask: byCaller(createSocketTask),
522
742
  operateRequestTask: (args) => abortTask(args, "requestTaskId"),
523
743
  operateDownloadTask: (args) => abortTask(args, "downloadTaskId"),
524
744
  operateUploadTask: (args) => abortTask(args, "uploadTaskId"),
525
745
  operateSocketTask
526
746
  };
527
747
  }
528
- function rejectBaseLibInternal(handler) {
529
- return (args, context) => args?.__skipDomainCheck__ === true ? fail(context.api, "\u57FA\u7840\u5E93\u5185\u90E8\u7684\u7F51\u7EDC\u8BF7\u6C42\uFF0Cwxa-runner \u4E0D\u53D1\u51FA") : handler(args, context);
748
+ var rejectBaseLib = (_args, { api }) => fail(api, "\u57FA\u7840\u5E93\u5185\u90E8\u7684\u7F51\u7EDC\u8BF7\u6C42\uFF0Cwxa-runner \u4E0D\u53D1\u51FA");
749
+ function byCaller(forApp, forBaseLib = rejectBaseLib) {
750
+ return (args, context) => args?.__skipDomainCheck__ === true ? forBaseLib(args, context) : forApp(args, context);
751
+ }
752
+ function isAudioDownload(args) {
753
+ return args.responseType === "arraybuffer" && args.enableCache === true;
530
754
  }
531
755
  async function proxyFetch(url, init) {
532
756
  if (!/^https?:\/\//i.test(url)) throw new Error(`invalid url "${url}"`);
@@ -807,11 +1031,11 @@
807
1031
  checkDelay(response.delay);
808
1032
  if (response.delay) await sleep(response.delay, signal);
809
1033
  const { body } = response;
810
- const isText = typeof body === "string" || body === void 0;
1034
+ const isJson = body !== void 0 && typeof body !== "string" && !isBinary(body);
811
1035
  return {
812
1036
  statusCode: response.status ?? 200,
813
- header: { ...isText ? {} : { "Content-Type": "application/json" }, ...response.header },
814
- data: body === void 0 ? "" : isText ? body : JSON.stringify(body)
1037
+ header: { ...isJson ? { "Content-Type": "application/json" } : {}, ...response.header },
1038
+ body: body === void 0 ? "" : isJson ? JSON.stringify(body) : body
815
1039
  };
816
1040
  }
817
1041
  };
@@ -826,6 +1050,9 @@
826
1050
  const regexp = globToRegExp(url);
827
1051
  return (request) => (!method || method === request.method) && regexp.test(String(request.url).replace(/[?#].*$/, ""));
828
1052
  }
1053
+ function isBinary(value) {
1054
+ return ArrayBuffer.isView(value) || Object.prototype.toString.call(value) === "[object ArrayBuffer]";
1055
+ }
829
1056
  function checkDelay(delay) {
830
1057
  if (delay !== void 0 && !(typeof delay === "number" && delay >= 0 && Number.isFinite(delay))) {
831
1058
  throw new Error(`delay \u9700\u8981\u662F\u4E0D\u5C0F\u4E8E 0 \u7684\u6BEB\u79D2\u6570\uFF0C\u5B9E\u9645\u662F ${String(delay)}`);
@@ -1912,6 +2139,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
1912
2139
  this.api = new ApiRegistry((context) => this.reportUnimplemented(context));
1913
2140
  /** setup 和测试 API 声明的请求拦截 */
1914
2141
  this.routes = new RequestRoutes();
2142
+ this.mapOperations = new MapOperations((webviewId, event, data) => this.publishToPages(event, data, [webviewId]));
1915
2143
  this.service = null;
1916
2144
  /** 新版基础库的业务子上下文;旧版基础库业务代码直接在逻辑层 iframe 里 */
1917
2145
  this.appContext = null;
@@ -1970,7 +2198,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
1970
2198
  const webviewId = kind === "webview" ? this.webviewIdOf(win) : this.currentPage()?.id ?? 0;
1971
2199
  const result = this.api.invoke(api, args, { api, from: kind, webviewId });
1972
2200
  const deliver = (res) => {
1973
- if (callback) callback(kind === "service" ? this.cloneForService(res) : cloneInto(win, res));
2201
+ if (callback) callback(cloneNativeInto(kind === "service" ? this.serviceWindow() : win, res));
1974
2202
  };
1975
2203
  if (result && typeof result.then === "function") {
1976
2204
  result.then(deliver);
@@ -1980,22 +2208,27 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
1980
2208
  }
1981
2209
  publish(kind, win, event, data, webviewIds) {
1982
2210
  if (kind === "service") {
1983
- const targets = webviewIds?.length ? webviewIds.map((id) => this.pages.get(Number(id))) : this.livePages();
1984
- const message = event === "canvasActionsChanged" ? withDrawableImages(data) : data;
1985
- for (const page of targets) {
1986
- page?.dispatch((bridge, target) => {
1987
- const payload2 = cloneInto(target, message);
1988
- queueMicrotask(() => bridge.__dispatchSub(event, payload2, 0));
1989
- });
1990
- }
2211
+ this.publishToPages(event, data, webviewIds);
1991
2212
  return;
1992
2213
  }
2214
+ if (event === "operateMapCallback" && this.mapOperations.receive(data)) return;
1993
2215
  const fromId = this.webviewIdOf(win);
1994
2216
  const service = this.service;
1995
2217
  if (!service) return;
1996
2218
  const payload = this.cloneForService(data);
1997
2219
  queueMicrotask(() => service.bridge.__dispatchSub(event, payload, fromId));
1998
2220
  }
2221
+ /** 不指定目标时发给所有页面:旧版 canvas 的绘制指令就是这样发的,由 canvasNumber 找到对应画布 */
2222
+ publishToPages(event, data, webviewIds) {
2223
+ const targets = webviewIds?.length ? webviewIds.map((id) => this.pages.get(Number(id))) : this.livePages();
2224
+ const message = event === "canvasActionsChanged" ? withDrawableImages(data) : data;
2225
+ for (const page of targets) {
2226
+ page?.dispatch((bridge, target) => {
2227
+ const payload = cloneInto(target, message);
2228
+ queueMicrotask(() => bridge.__dispatchSub(event, payload, 0));
2229
+ });
2230
+ }
2231
+ }
1999
2232
  reportError(kind, webviewId, error) {
2000
2233
  const err = typeof error?.message === "string" ? error : null;
2001
2234
  this.errors.push({
@@ -2007,7 +2240,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2007
2240
  }
2008
2241
  /**
2009
2242
  * 业务代码只运行在逻辑层,所以只记逻辑层调用、名字在 wx 上的 API,每个 API 一次。
2010
- * 其余是基础库内部调用的原生接口(如 getPermissionBytes、loadLibFilesSync),业务既没调用也 mock 不了
2243
+ * 其余是基础库内部调用的原生接口(如 getPermissionBytes),业务既没调用也 mock 不了
2011
2244
  */
2012
2245
  reportUnimplemented({ api, from }) {
2013
2246
  const wx = this.serviceWindow()?.wx;
@@ -2201,7 +2434,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2201
2434
  emitToService(event, data, webviewId) {
2202
2435
  const service = this.service;
2203
2436
  if (!service) return;
2204
- const payload = this.cloneForService(data);
2437
+ const payload = cloneNativeInto(this.serviceWindow(), data);
2205
2438
  queueMicrotask(() => service.bridge.__dispatchOn(event, payload, webviewId));
2206
2439
  }
2207
2440
  /** 派发给渲染层的原生事件(渲染层里 WeixinJSBridge.on 收到) */
@@ -2245,6 +2478,9 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2245
2478
  routes: this.routes,
2246
2479
  onRequest: (request) => this.requests.add(request)
2247
2480
  }),
2481
+ ...createAudioApis({ emit: (event, data) => this.emitToService(event, data) }),
2482
+ ...createMapApis({ registry: this.api, operations: this.mapOperations }),
2483
+ ...createLibFileApis({ windowOf: (webviewId) => this.pages.get(webviewId)?.win }),
2248
2484
  ...createUiApis({ shell: this.shell, updateNavigation: (state) => this.updateNavigation(state) }),
2249
2485
  ...createMediaApis({
2250
2486
  showActionSheet: (options) => this.shell.showActionSheet(options),
@@ -2272,6 +2508,24 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2272
2508
  if (value === void 0) return value;
2273
2509
  return win.JSON.parse(JSON.stringify(value));
2274
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
+ }
2275
2529
  function withDrawableImages(message) {
2276
2530
  const actions = message?.data?.actions;
2277
2531
  if (!Array.isArray(actions)) return message;
@@ -2322,7 +2576,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2322
2576
  const sheets = win.__styleSheetManager2__?._pathMap;
2323
2577
  if (!sheets) throw new Error("\u57FA\u7840\u5E93\u6CA1\u6709\u6837\u5F0F\u8868\u7BA1\u7406\u5668\uFF0C\u65E0\u6CD5\u70ED\u66FF\u6362\u6837\u5F0F");
2324
2578
  const applied = Object.keys(sheets);
2325
- runScript(win, code);
2579
+ runScript2(win, code);
2326
2580
  for (const key of applied) {
2327
2581
  const [, file, classPrefix] = /^\.\/(.+\.wxss):(.*)$/.exec(key) || [];
2328
2582
  const inject = file && win.__wxAppCode__[file];
@@ -2342,7 +2596,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2342
2596
  function updateTemplates(win, code, units) {
2343
2597
  const cache = win.__WXML_GLOBAL__;
2344
2598
  if (cache) Object.assign(cache, { ops_cached: {}, ops_set: {}, ops_init: {}, debuginfo_set: {}, modules: {} });
2345
- runScript(win, code);
2599
+ runScript2(win, code);
2346
2600
  for (const id of units) {
2347
2601
  const generate = win.__wxAppCode__[id + ".wxml"];
2348
2602
  if (generate) win.__virtualDOM__.updateWxAppCode(id + ".wxml", generate);
@@ -2381,7 +2635,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
2381
2635
  factory = fn;
2382
2636
  };
2383
2637
  try {
2384
- runScript(service, `(function (define) {
2638
+ runScript2(service, `(function (define) {
2385
2639
  ${script}
2386
2640
  })(__wxaHotDefine__);`);
2387
2641
  } finally {
@@ -2415,7 +2669,7 @@ ${script}
2415
2669
  const added = Object.keys(data).filter((key) => !(key in instance.data));
2416
2670
  if (added.length) instance.setData(Object.fromEntries(added.map((key) => [key, data[key]])));
2417
2671
  }
2418
- function runScript(win, code) {
2672
+ function runScript2(win, code) {
2419
2673
  let failed = false;
2420
2674
  let error;
2421
2675
  const onError = (event) => {