@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.
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/runtime/frame.js +19 -14
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/host.js +291 -37
- package/dist/runtime/host.js.map +1 -1
- package/dist/{setup-COzZ6vzS.d.mts → setup-Cv340Lvj.d.mts} +7 -3
- package/dist/{setup-COzZ6vzS.d.ts → setup-Cv340Lvj.d.ts} +7 -3
- package/dist/setup.d.mts +1 -1
- package/dist/setup.d.ts +1 -1
- package/dist/setup.js.map +1 -1
- package/dist/setup.mjs.map +1 -1
- package/dist/test-api.d.mts +2 -2
- package/dist/test-api.d.ts +2 -2
- package/dist/{testApi-D5o7hK-Z.d.mts → testApi-DO2FyR78.d.mts} +2 -2
- package/dist/{testApi-BTPLQvFs.d.ts → testApi-nKsjjEhz.d.ts} +2 -2
- package/package.json +3 -3
package/dist/runtime/host.js
CHANGED
|
@@ -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 }) =>
|
|
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: [],
|
|
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
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
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
|
-
|
|
529
|
-
|
|
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
|
|
1034
|
+
const isJson = body !== void 0 && typeof body !== "string" && !isBinary(body);
|
|
811
1035
|
return {
|
|
812
1036
|
statusCode: response.status ?? 200,
|
|
813
|
-
header: { ...
|
|
814
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2672
|
+
function runScript2(win, code) {
|
|
2419
2673
|
let failed = false;
|
|
2420
2674
|
let error;
|
|
2421
2675
|
const onError = (event) => {
|