@open-wxa/wxa-runner-core 0.6.0 → 0.8.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 +34 -12
- package/dist/index.d.ts +34 -12
- package/dist/index.js +637 -131
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +627 -121
- package/dist/index.mjs.map +1 -1
- package/dist/runtime/frame.js +44 -2
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/host.js +387 -173
- package/dist/runtime/host.js.map +1 -1
- package/dist/runtime/sw.js +56 -0
- package/dist/runtime/sw.js.map +1 -0
- package/dist/{setup-Cv340Lvj.d.mts → setup-Bac8XdhR.d.mts} +7 -2
- package/dist/{setup-Cv340Lvj.d.ts → setup-Bac8XdhR.d.ts} +7 -2
- package/dist/setup.d.mts +1 -1
- package/dist/setup.d.ts +1 -1
- package/dist/test-api.d.mts +2 -2
- package/dist/test-api.d.ts +2 -2
- package/dist/{testApi-DO2FyR78.d.mts → testApi-BOZSXSnV.d.ts} +3 -3
- package/dist/{testApi-nKsjjEhz.d.ts → testApi-DHuEW5Cl.d.mts} +3 -3
- package/package.json +4 -4
package/dist/runtime/host.js
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
(() => {
|
|
3
|
+
// src/runtime/host/ConsoleForwarder.ts
|
|
4
|
+
var DEDUPE_MS = 1e3;
|
|
5
|
+
var MAX_ARG_LENGTH = 1e3;
|
|
6
|
+
var MAX_TEXT_LENGTH = 8e3;
|
|
7
|
+
var LAYERS = { service: "\u903B\u8F91\u5C42", webview: "\u6E32\u67D3\u5C42" };
|
|
8
|
+
var MINI_PROGRAM_ERROR = /^(\(in promise\) )?MiniProgramError\n/;
|
|
9
|
+
var MODULE_URL = /\bhttps?:\/\/[^/\s]+\/__wxa__\/module\/([^?\s)]+?)(?:\?[^:\s)]*)?:(\d+):(\d+)/;
|
|
10
|
+
var ConsoleForwarder = class {
|
|
11
|
+
/** @param pageOf 出错的页面路径,不知道时返回 undefined */
|
|
12
|
+
constructor(mode, send, pageOf) {
|
|
13
|
+
this.mode = mode;
|
|
14
|
+
this.send = send;
|
|
15
|
+
this.pageOf = pageOf;
|
|
16
|
+
this.recentErrors = /* @__PURE__ */ new Map();
|
|
17
|
+
}
|
|
18
|
+
/** 运行时错误:全局 error、未处理的 Promise 拒绝、基础库捕获的业务代码错误 */
|
|
19
|
+
error(kind, webviewId, message, stack) {
|
|
20
|
+
const frames = businessFrames(stack);
|
|
21
|
+
if (!this.mode || this.isRepeated(`${kind}:${webviewId}:${message}
|
|
22
|
+
${frames}`)) return;
|
|
23
|
+
this.post("error", `${this.origin(kind, webviewId)}\u62A5\u9519\uFF1A${message}${frames ? `
|
|
24
|
+
${frames}` : ""}`);
|
|
25
|
+
}
|
|
26
|
+
/** 运行时自己的提醒,如调用了未实现的 wx API */
|
|
27
|
+
warn(text) {
|
|
28
|
+
if (this.mode) this.post("warn", text);
|
|
29
|
+
}
|
|
30
|
+
/** iframe 里的 console.warn / console.error;基础库捕获业务代码错误后打印的 console.error 按报错转发 */
|
|
31
|
+
console(kind, webviewId, level, args) {
|
|
32
|
+
const thrown = level === "error" ? miniProgramError(args[0]) : null;
|
|
33
|
+
if (thrown) {
|
|
34
|
+
this.error(kind, webviewId, thrown.message, thrown.stack);
|
|
35
|
+
} else if (this.mode === "all") {
|
|
36
|
+
this.post(level, `${this.origin(kind, webviewId)} console.${level}\uFF1A${args.map(formatArg).join(" ")}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
post(level, text) {
|
|
40
|
+
this.send({ type: "console", level, text: text.length > MAX_TEXT_LENGTH ? `${text.slice(0, MAX_TEXT_LENGTH)}\u2026` : text });
|
|
41
|
+
}
|
|
42
|
+
origin(kind, webviewId) {
|
|
43
|
+
const page = this.pageOf(kind, webviewId);
|
|
44
|
+
return page ? `${LAYERS[kind]}\uFF08${page}\uFF09` : LAYERS[kind];
|
|
45
|
+
}
|
|
46
|
+
/** @param key 来源、错误信息和业务代码的帧:两路报来的同一个错误相同,不同位置抛出的同名错误不同 */
|
|
47
|
+
isRepeated(key) {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
for (const [recent, time] of this.recentErrors) {
|
|
50
|
+
if (now - time >= DEDUPE_MS) this.recentErrors.delete(recent);
|
|
51
|
+
}
|
|
52
|
+
if (this.recentErrors.has(key)) return true;
|
|
53
|
+
this.recentErrors.set(key, now);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
function isErrorLike(value) {
|
|
58
|
+
return typeof value === "object" && value !== null && typeof value.message === "string";
|
|
59
|
+
}
|
|
60
|
+
function miniProgramError(value) {
|
|
61
|
+
if (isErrorLike(value) && MINI_PROGRAM_ERROR.test(value.message)) {
|
|
62
|
+
return { message: value.message.replace(MINI_PROGRAM_ERROR, ""), stack: value.stack };
|
|
63
|
+
}
|
|
64
|
+
if (typeof value === "string" && MINI_PROGRAM_ERROR.test(value)) {
|
|
65
|
+
const text = value.replace(MINI_PROGRAM_ERROR, "");
|
|
66
|
+
const firstFrame2 = text.search(/^\s*at /m);
|
|
67
|
+
const head = (firstFrame2 < 0 ? text : text.slice(0, firstFrame2)).replace(/\n$/, "");
|
|
68
|
+
const message = /^([\s\S]*)\n[^\n]*?: \1$/.exec(head)?.[1] ?? head;
|
|
69
|
+
return { message, stack: firstFrame2 < 0 ? void 0 : text.slice(firstFrame2) };
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function businessFrames(stack) {
|
|
74
|
+
return (stack || "").split("\n").filter((line) => /^\s*at /.test(line) && MODULE_URL.test(line)).map((line) => ` ${line.trim().replace(MODULE_URL, "$1:$2:$3")}`).join("\n");
|
|
75
|
+
}
|
|
76
|
+
function formatArg(arg) {
|
|
77
|
+
if (typeof arg === "string") return truncate(arg);
|
|
78
|
+
if (isErrorLike(arg)) {
|
|
79
|
+
const frames = businessFrames(arg.stack);
|
|
80
|
+
return truncate(`${arg.name || "Error"}: ${arg.message}`) + (frames ? `
|
|
81
|
+
${frames}` : "");
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const json = JSON.stringify(arg);
|
|
85
|
+
if (json !== void 0) return truncate(json);
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
return truncate(String(arg));
|
|
89
|
+
}
|
|
90
|
+
function truncate(text) {
|
|
91
|
+
return text.length > MAX_ARG_LENGTH ? `${text.slice(0, MAX_ARG_LENGTH)}\u2026` : text;
|
|
92
|
+
}
|
|
93
|
+
|
|
3
94
|
// src/runtime/host/api/registry.ts
|
|
4
95
|
var SILENT_APIS = /* @__PURE__ */ new Set([
|
|
5
96
|
"reportKeyValue",
|
|
@@ -205,154 +296,103 @@
|
|
|
205
296
|
};
|
|
206
297
|
}
|
|
207
298
|
|
|
208
|
-
// src/runtime/
|
|
209
|
-
var
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (!
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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" });
|
|
299
|
+
// src/runtime/fileSession.ts
|
|
300
|
+
var KEY = "__wxa_file_session__";
|
|
301
|
+
function fileSession() {
|
|
302
|
+
let session = localStorage.getItem(KEY);
|
|
303
|
+
if (!session) {
|
|
304
|
+
session = crypto.randomUUID();
|
|
305
|
+
localStorage.setItem(KEY, session);
|
|
306
|
+
}
|
|
307
|
+
return session;
|
|
234
308
|
}
|
|
235
309
|
|
|
236
310
|
// src/runtime/host/api/fs.ts
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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 }));
|
|
311
|
+
function requestFile(operation, args, sync) {
|
|
312
|
+
const url = "/__wxa__/fs/" + fileSession();
|
|
313
|
+
const body = JSON.stringify({ operation, args });
|
|
314
|
+
if (!sync)
|
|
315
|
+
return fetch(url, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
headers: { "content-type": "application/json" },
|
|
318
|
+
body
|
|
319
|
+
}).then(async (response) => fileResult(response.status, await response.text()));
|
|
320
|
+
const request = new XMLHttpRequest();
|
|
321
|
+
request.open("POST", url, false);
|
|
322
|
+
request.setRequestHeader("content-type", "application/json");
|
|
323
|
+
request.send(body);
|
|
324
|
+
return fileResult(request.status, request.responseText);
|
|
325
|
+
}
|
|
326
|
+
function fileResult(status, text) {
|
|
327
|
+
if (status !== 200) throw new Error(`filesystem HTTP ${status}`);
|
|
328
|
+
const { result, error } = JSON.parse(text);
|
|
329
|
+
if (error) throw new Error(error);
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
function createFileSystemApis(request = requestFile) {
|
|
333
|
+
const apis = {};
|
|
334
|
+
for (const operation of [
|
|
335
|
+
"access",
|
|
336
|
+
"stat",
|
|
337
|
+
"mkdir",
|
|
338
|
+
"writeFile",
|
|
339
|
+
"appendFile",
|
|
340
|
+
"readdir",
|
|
341
|
+
"unlink",
|
|
342
|
+
"rmdir",
|
|
343
|
+
"getFileInfo",
|
|
344
|
+
"copyFile",
|
|
345
|
+
"readFile"
|
|
346
|
+
]) {
|
|
347
|
+
const handler = (args, { api }) => {
|
|
348
|
+
try {
|
|
349
|
+
const input = { ...args };
|
|
350
|
+
const isBuffer = Object.prototype.toString.call(input.data) === "[object ArrayBuffer]";
|
|
351
|
+
if (isBuffer || ArrayBuffer.isView(input.data)) {
|
|
352
|
+
const view = isBuffer ? new Uint8Array(input.data) : new Uint8Array(input.data.buffer, input.data.byteOffset, input.data.byteLength);
|
|
353
|
+
let binary = "";
|
|
354
|
+
for (let offset = 0; offset < view.length; offset += 32768) {
|
|
355
|
+
binary += String.fromCharCode(...view.subarray(offset, offset + 32768));
|
|
356
|
+
}
|
|
357
|
+
input.data = btoa(binary);
|
|
358
|
+
input.binary = true;
|
|
359
|
+
}
|
|
360
|
+
const decode = (result2) => {
|
|
361
|
+
if (result2.binary) {
|
|
362
|
+
result2.data = Uint8Array.from(atob(result2.data), (character) => character.charCodeAt(0)).buffer;
|
|
363
|
+
delete result2.binary;
|
|
364
|
+
}
|
|
365
|
+
return ok(api, result2);
|
|
366
|
+
};
|
|
367
|
+
const result = request(operation, input, api.endsWith("Sync"));
|
|
368
|
+
return result instanceof Promise ? result.then(decode, (error) => fail(api, error.message)) : decode(result);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
return fail(api, error.message);
|
|
333
371
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
),
|
|
348
|
-
readFileSync: readMemoryFile
|
|
349
|
-
};
|
|
372
|
+
};
|
|
373
|
+
apis[operation] = handler;
|
|
374
|
+
apis[operation + "Sync"] = handler;
|
|
375
|
+
apis["fs_" + operation] = handler;
|
|
376
|
+
apis["fs_" + operation + "Sync"] = handler;
|
|
377
|
+
}
|
|
378
|
+
const readUserFile = apis.readFile;
|
|
379
|
+
apis.readFile = (args, context) => /^[a-z]+:\/\//i.test(String(args.filePath)) || /^\/__wxa__\/(tmp|usr)\//.test(String(args.filePath)) ? readUserFile(args, context) : readPackageFile(args.filePath, args.encoding).then(
|
|
380
|
+
(data) => ok(context.api, { data }),
|
|
381
|
+
(error) => fail(context.api, error.message)
|
|
382
|
+
);
|
|
383
|
+
apis.fs_readFile = apis.readFile;
|
|
384
|
+
return apis;
|
|
350
385
|
}
|
|
351
386
|
async function readPackageFile(filePath, encoding) {
|
|
352
387
|
if (encoding !== void 0 && encoding !== "utf8" && encoding !== "utf-8") {
|
|
353
388
|
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
389
|
}
|
|
355
|
-
const
|
|
390
|
+
const parts = [];
|
|
391
|
+
for (const part of String(filePath).split("/")) {
|
|
392
|
+
if (part === "..") parts.pop();
|
|
393
|
+
else if (part && part !== ".") parts.push(part);
|
|
394
|
+
}
|
|
395
|
+
const response = await fetch("/" + parts.map(encodeURIComponent).join("/"));
|
|
356
396
|
if (!response.ok) throw new Error(`no such file or directory, open ${filePath}`);
|
|
357
397
|
return encoding ? response.text() : response.arrayBuffer();
|
|
358
398
|
}
|
|
@@ -429,6 +469,34 @@
|
|
|
429
469
|
}
|
|
430
470
|
};
|
|
431
471
|
|
|
472
|
+
// src/runtime/host/api/tempFiles.ts
|
|
473
|
+
var TEMP_DIR = "/__wxa__/tmp";
|
|
474
|
+
async function saveTempFile(data, type) {
|
|
475
|
+
const res = await fetch(`${TEMP_DIR}/${fileSession()}?type=${encodeURIComponent(type)}`, { method: "POST", body: data });
|
|
476
|
+
if (!res.ok) throw new Error(await res.text());
|
|
477
|
+
const { url } = await res.json();
|
|
478
|
+
return url;
|
|
479
|
+
}
|
|
480
|
+
function tempFileRootPath(url) {
|
|
481
|
+
const prefix = `${location.origin}${TEMP_DIR}/`;
|
|
482
|
+
return url.startsWith(prefix) ? url.slice(location.origin.length) : url;
|
|
483
|
+
}
|
|
484
|
+
function tempFileType(name, mimeType, fallback) {
|
|
485
|
+
const fromName = /\.([a-z\d]+)$/i.exec(name)?.[1];
|
|
486
|
+
const fromType = /^[a-z]+\/([a-z\d]+)\s*(;|$)/i.exec(mimeType)?.[1];
|
|
487
|
+
return (fromName || fromType || fallback).toLowerCase();
|
|
488
|
+
}
|
|
489
|
+
function isTempFile(filePath) {
|
|
490
|
+
return typeof filePath === "string" && filePath.startsWith(`${location.origin}${TEMP_DIR}/`);
|
|
491
|
+
}
|
|
492
|
+
async function tempFileSize(url) {
|
|
493
|
+
const res = await fetch(url, { method: "HEAD" });
|
|
494
|
+
return res.ok ? Number(res.headers.get("content-length")) : null;
|
|
495
|
+
}
|
|
496
|
+
async function removeTempFile(url) {
|
|
497
|
+
await fetch(url, { method: "DELETE" });
|
|
498
|
+
}
|
|
499
|
+
|
|
432
500
|
// src/runtime/host/api/media.ts
|
|
433
501
|
var SOURCES = ["camera", "album"];
|
|
434
502
|
var SOURCE_LABELS = { camera: "\u62CD\u6444", album: "\u4ECE\u624B\u673A\u76F8\u518C\u9009\u62E9" };
|
|
@@ -551,12 +619,13 @@
|
|
|
551
619
|
}
|
|
552
620
|
|
|
553
621
|
// src/runtime/host/api/misc.ts
|
|
622
|
+
var SERVICE_MARKET = "webapi_servicemarket_jsapi_comm";
|
|
554
623
|
function createMiscApis(appid, options) {
|
|
555
|
-
const
|
|
624
|
+
const viaDevtools = async (api, url, json) => {
|
|
556
625
|
try {
|
|
557
|
-
return await postDevServer(url);
|
|
626
|
+
return await postDevServer(url, json);
|
|
558
627
|
} catch (error) {
|
|
559
|
-
options?.
|
|
628
|
+
options?.onDevtoolsLoginFail?.(api, error.message);
|
|
560
629
|
throw error;
|
|
561
630
|
}
|
|
562
631
|
};
|
|
@@ -568,15 +637,28 @@
|
|
|
568
637
|
},
|
|
569
638
|
// 基础库经这两个接口向后台拉各种配置和小程序信息
|
|
570
639
|
operateWXData: (args, { api }) => {
|
|
640
|
+
if (args.data?.api_name === SERVICE_MARKET) {
|
|
641
|
+
const serviceMarketFail = (reason2) => fail(api, reason2, { err_code: -1 });
|
|
642
|
+
if (!options?.devtoolsLogin) return serviceMarketFail("wx.serviceMarket \u8981\u5411\u5FAE\u4FE1\u670D\u52A1\u5E02\u573A\u771F\u5B9E\u8C03\u7528\uFF0C\u542F\u52A8\u65F6\u52A0 --devtools-login");
|
|
643
|
+
return viaDevtools("serviceMarket.invokeService", "/__wxa__/api/operate-wx-data", { data: args.data }).then(
|
|
644
|
+
({ data }) => ok(api, { data }),
|
|
645
|
+
(error) => serviceMarketFail(error.message)
|
|
646
|
+
);
|
|
647
|
+
}
|
|
571
648
|
if (args.data?.api_name === "webapi_getwxanetwork") return bizDomainUnavailable(api);
|
|
572
649
|
if (args.data?.api_name === "webapi_getwxauserprivacyauthinfo") return ok(api, { data: { data: JSON.stringify(PRIVACY_AUTH_INFO) } });
|
|
573
650
|
return ok(api);
|
|
574
651
|
},
|
|
575
652
|
getAppContact: (args, { api }) => args.keyPath === "WxaAppInfo.Network.BizDomain" ? bizDomainUnavailable(api) : ok(api),
|
|
576
|
-
//
|
|
577
|
-
|
|
653
|
+
// 插件:基础库启动时校验插件、取插件信息,插件里的网络请求要等拿到这两样才发出。插件代码也只在 --devtools-login 时下载
|
|
654
|
+
...options?.devtoolsLogin ? {
|
|
655
|
+
verifyPlugin: (async (args, { api }) => ok(api, await viaDevtools(api, "/__wxa__/api/verify-plugin", { data: args.data }))),
|
|
656
|
+
batchGetContact: (async (args, { api }) => ok(api, await viaDevtools(api, "/__wxa__/api/batch-get-contact", { appIds: args.appIds })))
|
|
657
|
+
} : {},
|
|
658
|
+
// 向微信取不到时明确返回 fail,不退回假数据:配置或登录态的问题要让开发者马上发现
|
|
659
|
+
login: options?.devtoolsLogin ? async (_args, { api }) => ok(api, { code: (await viaDevtools(api, "/__wxa__/api/login")).code }) : (_args, { api }) => ok(api, { code: `wxa_mock_code_${Date.now().toString(36)}` }),
|
|
578
660
|
// 基础库只从 JSON.parse(data) 里取手机号的 code
|
|
579
|
-
getPhoneNumber: options?.
|
|
661
|
+
getPhoneNumber: options?.devtoolsLogin ? async (_args, { api }) => ok(api, await viaDevtools(api, "/__wxa__/api/phone-number")) : (_args, { api }) => ok(api, { data: JSON.stringify({ code: `mock_phone_code_${Date.now()}` }), encryptedData: "mock_encrypted", iv: "mock_iv" }),
|
|
580
662
|
checkSession: () => void 0,
|
|
581
663
|
authorize: () => void 0,
|
|
582
664
|
getSetting: (_args, { api }) => ok(api, {
|
|
@@ -601,8 +683,9 @@
|
|
|
601
683
|
};
|
|
602
684
|
}
|
|
603
685
|
var clipboard = "";
|
|
604
|
-
async function postDevServer(url) {
|
|
605
|
-
const
|
|
686
|
+
async function postDevServer(url, json) {
|
|
687
|
+
const init = json ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(json) } : { method: "POST" };
|
|
688
|
+
const response = await fetch(url, init);
|
|
606
689
|
const body = await response.json().catch(() => null);
|
|
607
690
|
if (!response.ok || !body) throw new Error(body?.error || `\u5F00\u53D1\u670D\u52A1\u5668\u8FD4\u56DE HTTP ${response.status}`);
|
|
608
691
|
return body;
|
|
@@ -615,6 +698,32 @@
|
|
|
615
698
|
return fail(api, "not available in wxa-runner");
|
|
616
699
|
}
|
|
617
700
|
|
|
701
|
+
// src/runtime/networkPanel.ts
|
|
702
|
+
var PROXY_PREFIX = "/__wxa__/proxy/";
|
|
703
|
+
var MOCK_PREFIX = "/__wxa__/mock/";
|
|
704
|
+
var MOCK_ID_HEADER = "x-wxa-mock-id";
|
|
705
|
+
function toServerPath(prefix, url) {
|
|
706
|
+
let target;
|
|
707
|
+
try {
|
|
708
|
+
target = new URL(url);
|
|
709
|
+
} catch {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") return null;
|
|
713
|
+
return `${prefix}${target.protocol.slice(0, -1)}/${target.host}${target.pathname}${target.search}`;
|
|
714
|
+
}
|
|
715
|
+
function toHeaders(header) {
|
|
716
|
+
const headers = new Headers();
|
|
717
|
+
for (const [name, value] of Object.entries(header)) {
|
|
718
|
+
if (value === void 0 || value === null) continue;
|
|
719
|
+
try {
|
|
720
|
+
headers.set(name, String(value));
|
|
721
|
+
} catch {
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return headers;
|
|
725
|
+
}
|
|
726
|
+
|
|
618
727
|
// src/runtime/host/api/network.ts
|
|
619
728
|
var RequestLog = class {
|
|
620
729
|
constructor(limit = 1e3) {
|
|
@@ -661,8 +770,9 @@
|
|
|
661
770
|
options.onRequest?.({ ...request, header: { ...request.header }, routedBy: route ? route.pattern : null });
|
|
662
771
|
const requestTaskId = startTask(args, async (signal, id) => {
|
|
663
772
|
try {
|
|
664
|
-
const mocked = route
|
|
665
|
-
|
|
773
|
+
const mocked = route?.respond(signal);
|
|
774
|
+
if (mocked) options.onMocked?.(request, mocked, signal);
|
|
775
|
+
const res = mocked ? await mocked.then(({ statusCode, header, body }) => ({ statusCode, header, cookies: [], response: new Response(body) })) : await proxyFetch(request.url, { method: request.method, header: args.header, body: args.data, signal });
|
|
666
776
|
emit("onRequestTaskStateChange", {
|
|
667
777
|
requestTaskId: id,
|
|
668
778
|
state: "headersReceived",
|
|
@@ -670,7 +780,36 @@
|
|
|
670
780
|
statusCode: res.statusCode,
|
|
671
781
|
cookies: res.cookies
|
|
672
782
|
});
|
|
673
|
-
|
|
783
|
+
let data;
|
|
784
|
+
if (args.enableChunked && res.response.body) {
|
|
785
|
+
const reader = res.response.body.getReader();
|
|
786
|
+
const chunks = [];
|
|
787
|
+
let length = 0;
|
|
788
|
+
try {
|
|
789
|
+
for (; ; ) {
|
|
790
|
+
const { done, value } = await reader.read();
|
|
791
|
+
if (done) break;
|
|
792
|
+
chunks.push(value);
|
|
793
|
+
length += value.byteLength;
|
|
794
|
+
emit("onRequestTaskStateChange", {
|
|
795
|
+
requestTaskId: id,
|
|
796
|
+
state: "chunked",
|
|
797
|
+
data: value.slice().buffer
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
} finally {
|
|
801
|
+
reader.releaseLock();
|
|
802
|
+
}
|
|
803
|
+
const bytes = new Uint8Array(length);
|
|
804
|
+
let offset = 0;
|
|
805
|
+
for (const chunk of chunks) {
|
|
806
|
+
bytes.set(chunk, offset);
|
|
807
|
+
offset += chunk.byteLength;
|
|
808
|
+
}
|
|
809
|
+
data = args.responseType === "arraybuffer" ? bytes.buffer : new TextDecoder().decode(bytes);
|
|
810
|
+
} else {
|
|
811
|
+
data = args.responseType === "arraybuffer" ? await res.response.arrayBuffer() : await res.response.text();
|
|
812
|
+
}
|
|
674
813
|
emit("onRequestTaskStateChange", {
|
|
675
814
|
requestTaskId: id,
|
|
676
815
|
state: "success",
|
|
@@ -718,7 +857,9 @@
|
|
|
718
857
|
const createUploadTask = (args, { api }) => {
|
|
719
858
|
const uploadTaskId = startTask(args, async (signal, id) => {
|
|
720
859
|
try {
|
|
721
|
-
const
|
|
860
|
+
const source = await fetch(args.filePath);
|
|
861
|
+
if (!source.ok) throw new Error(`file not exist ${args.filePath}`);
|
|
862
|
+
const file = await source.blob();
|
|
722
863
|
const form = new FormData();
|
|
723
864
|
for (const [key, value] of Object.entries(args.formData || {})) form.append(key, String(value));
|
|
724
865
|
form.append(args.name || "file", file, String(args.filePath).split("/").pop() || "file");
|
|
@@ -800,18 +941,21 @@
|
|
|
800
941
|
}
|
|
801
942
|
var rejectBaseLib = (_args, { api }) => fail(api, "\u57FA\u7840\u5E93\u5185\u90E8\u7684\u7F51\u7EDC\u8BF7\u6C42\uFF0Cwxa-runner \u4E0D\u53D1\u51FA");
|
|
802
943
|
function byCaller(forApp, forBaseLib = rejectBaseLib) {
|
|
803
|
-
return (args, context) => args?.__skipDomainCheck__ === true ? forBaseLib(args, context) : forApp(args, context);
|
|
944
|
+
return (args, context) => args?.__skipDomainCheck__ === true && !hasPluginSign(args) ? forBaseLib(args, context) : forApp(args, context);
|
|
945
|
+
}
|
|
946
|
+
function hasPluginSign(args) {
|
|
947
|
+
return Object.keys(args.header || {}).some((name) => name.toLowerCase() === "x-wechat-hostsign");
|
|
804
948
|
}
|
|
805
949
|
function isAudioDownload(args) {
|
|
806
950
|
return args.responseType === "arraybuffer" && args.enableCache === true;
|
|
807
951
|
}
|
|
808
952
|
async function proxyFetch(url, init) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
const response = await fetch(
|
|
953
|
+
const path = toServerPath(PROXY_PREFIX, url);
|
|
954
|
+
if (!path) throw new Error(`invalid url "${url}"`);
|
|
955
|
+
const response = await fetch(path, {
|
|
812
956
|
method: init.method,
|
|
813
957
|
headers: { "x-wxa-headers": JSON.stringify(init.header || {}) },
|
|
814
|
-
body:
|
|
958
|
+
body: hasRequestBody(init.method, init.body) ? init.body : void 0,
|
|
815
959
|
signal: init.signal
|
|
816
960
|
});
|
|
817
961
|
if (response.headers.get("x-wxa-proxy-error")) {
|
|
@@ -824,11 +968,56 @@
|
|
|
824
968
|
response
|
|
825
969
|
};
|
|
826
970
|
}
|
|
971
|
+
function hasRequestBody(method, body) {
|
|
972
|
+
return body !== void 0 && body !== "" && !["GET", "HEAD"].includes(method);
|
|
973
|
+
}
|
|
827
974
|
function reason(error) {
|
|
828
975
|
if (error instanceof DOMException && error.name === "AbortError") return "abort";
|
|
829
976
|
return error instanceof Error ? error.message : String(error);
|
|
830
977
|
}
|
|
831
978
|
|
|
979
|
+
// src/runtime/host/api/mockMirror.ts
|
|
980
|
+
var SERVICE_WORKER_URL = "/__wxa__/runtime/sw.js";
|
|
981
|
+
var MockMirror = class {
|
|
982
|
+
constructor(serviceWorker) {
|
|
983
|
+
this.serviceWorker = serviceWorker;
|
|
984
|
+
/** 正在显示的请求,Service Worker 按 id 来要响应 */
|
|
985
|
+
this.replies = /* @__PURE__ */ new Map();
|
|
986
|
+
this.seq = 0;
|
|
987
|
+
serviceWorker.addEventListener("message", (event) => this.answer(event));
|
|
988
|
+
serviceWorker.startMessages();
|
|
989
|
+
}
|
|
990
|
+
/** response 失败(规则出错、请求被取消)时这一行显示为失败;signal abort 时显示为取消 */
|
|
991
|
+
show(request, response, signal) {
|
|
992
|
+
const path = toServerPath(MOCK_PREFIX, request.url);
|
|
993
|
+
if (!path || !this.serviceWorker.controller) return;
|
|
994
|
+
const id = String(++this.seq);
|
|
995
|
+
const cancelled = new Promise((resolve) => signal.addEventListener("abort", () => resolve(null), { once: true }));
|
|
996
|
+
this.replies.set(id, Promise.race([response.catch(() => null), cancelled]));
|
|
997
|
+
const headers = toHeaders(request.header);
|
|
998
|
+
headers.set(MOCK_ID_HEADER, id);
|
|
999
|
+
fetch(path, {
|
|
1000
|
+
method: request.method,
|
|
1001
|
+
headers,
|
|
1002
|
+
body: hasRequestBody(request.method, request.data) ? request.data : void 0,
|
|
1003
|
+
// 拦截规则给出 3xx 时只显示这个响应:跟随 Location 会真的发出请求,小程序那边也不跟随
|
|
1004
|
+
redirect: "manual",
|
|
1005
|
+
signal
|
|
1006
|
+
}).catch(() => void 0).finally(() => this.replies.delete(id));
|
|
1007
|
+
}
|
|
1008
|
+
answer(event) {
|
|
1009
|
+
const query = event.data;
|
|
1010
|
+
const port = event.ports[0];
|
|
1011
|
+
if (query?.type !== "wxa-mock-response" || !port) return;
|
|
1012
|
+
(this.replies.get(query.id) ?? Promise.resolve(null)).then((reply) => port.postMessage(reply));
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
function installMockMirror() {
|
|
1016
|
+
if (navigator.webdriver || !navigator.serviceWorker) return null;
|
|
1017
|
+
navigator.serviceWorker.register(SERVICE_WORKER_URL, { scope: "/" }).catch((error) => console.warn("[wxa-runner] Service Worker \u6CE8\u518C\u5931\u8D25\uFF0C\u88AB\u62E6\u622A\u7684\u8BF7\u6C42\u4E0D\u4F1A\u663E\u793A\u5728 Network \u9762\u677F\u91CC", error));
|
|
1018
|
+
return new MockMirror(navigator.serviceWorker);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
832
1021
|
// src/runtime/host/api/picker.ts
|
|
833
1022
|
async function fetchRegionData(kind) {
|
|
834
1023
|
const res = await fetch(`/__wxa__/region/${kind}`);
|
|
@@ -2214,7 +2403,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2214
2403
|
openImagePreview(this.overlay, urls, current);
|
|
2215
2404
|
}
|
|
2216
2405
|
/**
|
|
2217
|
-
* 在手机外面提示 wxa-runner
|
|
2406
|
+
* 在手机外面提示 wxa-runner 自身的问题(如借用开发者工具登录态失败),不混进小程序的界面。
|
|
2218
2407
|
* 同一时间只显示一条,新的替换旧的:业务代码反复重试时不会堆满屏幕
|
|
2219
2408
|
*/
|
|
2220
2409
|
showNotice(title, message) {
|
|
@@ -2267,7 +2456,8 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2267
2456
|
var BINDING_CANVAS_TYPES = /* @__PURE__ */ new Set(["2d", "webgl", "webgl2"]);
|
|
2268
2457
|
var PROBE_TOP = -54321;
|
|
2269
2458
|
var Host = class {
|
|
2270
|
-
|
|
2459
|
+
/** @param sendReport 把报错发给开发服务器打印到终端,见 AppInfo.forwardConsole */
|
|
2460
|
+
constructor(info, root, sendReport = () => void 0) {
|
|
2271
2461
|
this.info = info;
|
|
2272
2462
|
this.errors = [];
|
|
2273
2463
|
/** 小程序发出的 wx.request */
|
|
@@ -2275,6 +2465,8 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2275
2465
|
this.api = new ApiRegistry((context) => this.reportUnimplemented(context));
|
|
2276
2466
|
/** setup 和测试 API 声明的请求拦截 */
|
|
2277
2467
|
this.routes = new RequestRoutes();
|
|
2468
|
+
/** 把被拦截的请求显示在 Network 面板里 */
|
|
2469
|
+
this.mockMirror = installMockMirror();
|
|
2278
2470
|
this.mapOperations = new MapOperations((webviewId, event, data) => this.publishToPages(event, data, [webviewId]));
|
|
2279
2471
|
this.service = null;
|
|
2280
2472
|
/** 新版基础库的业务子上下文;旧版基础库业务代码直接在逻辑层 iframe 里 */
|
|
@@ -2291,6 +2483,11 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2291
2483
|
/** 渲染层报来的 2d、webgl 画布,按编号记下所在页面和最近的位置,供 placeCanvas 认出逻辑层的画布元素 */
|
|
2292
2484
|
this.canvases = /* @__PURE__ */ new Map();
|
|
2293
2485
|
this.device = info.device;
|
|
2486
|
+
this.forwarder = new ConsoleForwarder(
|
|
2487
|
+
info.forwardConsole ?? false,
|
|
2488
|
+
sendReport,
|
|
2489
|
+
(kind, webviewId) => kind === "service" ? this.currentPage()?.path : this.pages.get(webviewId)?.path
|
|
2490
|
+
);
|
|
2294
2491
|
this.shell = new PhoneShell(root, info.device);
|
|
2295
2492
|
this.tabPaths = new Set((info.wxConfig.tabBar?.list || []).map((item) => stripHtml(item.pagePath)));
|
|
2296
2493
|
this.storage = new MiniProgramStorage(window.localStorage, info.appid);
|
|
@@ -2409,27 +2606,36 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2409
2606
|
}
|
|
2410
2607
|
reportError(kind, webviewId, error) {
|
|
2411
2608
|
const err = typeof error?.message === "string" ? error : null;
|
|
2412
|
-
|
|
2609
|
+
const report = {
|
|
2413
2610
|
kind,
|
|
2414
2611
|
webviewId,
|
|
2415
2612
|
message: err ? err.message : String(error),
|
|
2416
2613
|
stack: typeof err?.stack === "string" ? err.stack : void 0
|
|
2417
|
-
}
|
|
2614
|
+
};
|
|
2615
|
+
this.errors.push(report);
|
|
2616
|
+
this.forwarder.error(kind, webviewId, report.message, report.stack);
|
|
2617
|
+
}
|
|
2618
|
+
get forwardConsole() {
|
|
2619
|
+
return this.forwarder.mode;
|
|
2620
|
+
}
|
|
2621
|
+
reportConsole(kind, webviewId, level, args) {
|
|
2622
|
+
this.forwarder.console(kind, webviewId, level, args);
|
|
2418
2623
|
}
|
|
2419
2624
|
/**
|
|
2420
|
-
*
|
|
2625
|
+
* 只记录逻辑层的公开 wx API 和 FileSystemManager 方法,每个原生接口一次。
|
|
2421
2626
|
* 其余是基础库内部调用的原生接口(如 getPermissionBytes),业务既没调用也 mock 不了
|
|
2422
2627
|
*/
|
|
2423
2628
|
reportUnimplemented({ api, from }) {
|
|
2629
|
+
if (from !== "service") return;
|
|
2424
2630
|
const wx = this.serviceWindow()?.wx;
|
|
2425
|
-
|
|
2631
|
+
const fileMethod = api.startsWith("fs_") ? api.slice(3) : api;
|
|
2632
|
+
const isFileApi = typeof wx?.getFileSystemManager?.()[fileMethod] === "function";
|
|
2633
|
+
if (typeof wx?.[api] !== "function" && !isFileApi) return;
|
|
2426
2634
|
if (this.errors.some((error) => error.api === api)) return;
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
message: `wx.${api} \u5C1A\u672A\u5B9E\u73B0\uFF0C\u8FD4\u56DE\u4E86\u9ED8\u8BA4\u6210\u529F\u3002\u53EF\u4EE5\u7528 mock \u8986\u76D6\uFF0C\u542F\u52A8\u9636\u6BB5\u5C31\u8C03\u7528\u7684\u8981\u5728 setup \u91CC mock`
|
|
2432
|
-
});
|
|
2635
|
+
const name = isFileApi ? `wx.getFileSystemManager().${fileMethod}` : `wx.${api}`;
|
|
2636
|
+
const message = `${name} \u5C1A\u672A\u5B9E\u73B0\uFF0C\u8FD4\u56DE\u4E86\u9ED8\u8BA4\u6210\u529F\u3002\u53EF\u4EE5\u7528 mock \u8986\u76D6\uFF0C\u542F\u52A8\u9636\u6BB5\u5C31\u8C03\u7528\u7684\u8981\u5728 setup \u91CC mock`;
|
|
2637
|
+
this.errors.push({ kind: "service", webviewId: 0, api, message });
|
|
2638
|
+
this.forwarder.warn(message);
|
|
2433
2639
|
}
|
|
2434
2640
|
// ---------------------------------------------------------------------------
|
|
2435
2641
|
// 路由
|
|
@@ -2663,7 +2869,8 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2663
2869
|
emit: (event, data) => this.emitToService(event, data),
|
|
2664
2870
|
defaultTimeout: () => this.info.wxConfig.networkTimeout?.request || 6e4,
|
|
2665
2871
|
routes: this.routes,
|
|
2666
|
-
onRequest: (request) => this.requests.add(request)
|
|
2872
|
+
onRequest: (request) => this.requests.add(request),
|
|
2873
|
+
onMocked: (request, response, signal) => this.mockMirror?.show(request, response, signal)
|
|
2667
2874
|
}),
|
|
2668
2875
|
...createAudioApis({ emit: (event, data) => this.emitToService(event, data) }),
|
|
2669
2876
|
...createMapApis({ registry: this.api, operations: this.mapOperations }),
|
|
@@ -2685,8 +2892,8 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2685
2892
|
emitToWebview: (webviewId, event, data) => this.emitToWebview(webviewId, event, data)
|
|
2686
2893
|
}),
|
|
2687
2894
|
...createMiscApis(this.info.appid, {
|
|
2688
|
-
|
|
2689
|
-
|
|
2895
|
+
devtoolsLogin: this.info.devtoolsLogin,
|
|
2896
|
+
onDevtoolsLoginFail: (api, reason2) => this.shell.showNotice(`wx.${api} \u501F\u7528\u5F00\u53D1\u8005\u5DE5\u5177\u767B\u5F55\u6001\u5931\u8D25`, reason2)
|
|
2690
2897
|
})
|
|
2691
2898
|
});
|
|
2692
2899
|
}
|
|
@@ -3106,14 +3313,14 @@ ${script}
|
|
|
3106
3313
|
const root = document.getElementById("wxa-root");
|
|
3107
3314
|
const info = await (await fetch("/__wxa__/app-info.json")).json();
|
|
3108
3315
|
const restore = new PageStackRestore(info.appid);
|
|
3109
|
-
connectDevServer(restore);
|
|
3316
|
+
const sendReport = connectDevServer(restore);
|
|
3110
3317
|
if (info.buildError) {
|
|
3111
3318
|
new PhoneShell(root, info.device).showError(`\u7F16\u8BD1\u5931\u8D25
|
|
3112
3319
|
|
|
3113
3320
|
${info.buildError}`);
|
|
3114
3321
|
return;
|
|
3115
3322
|
}
|
|
3116
|
-
const host = new Host(info, root);
|
|
3323
|
+
const host = new Host(info, root, sendReport);
|
|
3117
3324
|
window.__wxaHost = host;
|
|
3118
3325
|
let markReady;
|
|
3119
3326
|
const ready = new Promise((resolve) => {
|
|
@@ -3181,6 +3388,13 @@ ${message.message}`);
|
|
|
3181
3388
|
else restore.reload(host);
|
|
3182
3389
|
}
|
|
3183
3390
|
};
|
|
3391
|
+
const pending = [];
|
|
3392
|
+
socket.onopen = () => pending.splice(0).forEach((data) => socket.send(data));
|
|
3393
|
+
return (report) => {
|
|
3394
|
+
const data = JSON.stringify(report);
|
|
3395
|
+
if (socket.readyState === WebSocket.OPEN) socket.send(data);
|
|
3396
|
+
else if (socket.readyState === WebSocket.CONNECTING) pending.push(data);
|
|
3397
|
+
};
|
|
3184
3398
|
}
|
|
3185
3399
|
function currentHost() {
|
|
3186
3400
|
return window.__wxaHost;
|