@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/index.mjs
CHANGED
|
@@ -3,9 +3,135 @@ import {
|
|
|
3
3
|
} from "./chunk-NGXYLLYS.mjs";
|
|
4
4
|
|
|
5
5
|
// src/server/DevServer.ts
|
|
6
|
-
import { randomUUID as
|
|
6
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
7
|
+
|
|
8
|
+
// src/server/FileStore.ts
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
var FileStore = class {
|
|
11
|
+
/** @param roots 预先创建的目录(如用户目录)。rmdir 不能删除它们和它们的上级目录:删掉之后再写 USER_DATA_PATH 都会失败 */
|
|
12
|
+
constructor(roots = []) {
|
|
13
|
+
this.etagPrefix = randomUUID();
|
|
14
|
+
this.version = 0;
|
|
15
|
+
this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" }]]);
|
|
16
|
+
this.roots = roots.map((root) => this.normalize(root));
|
|
17
|
+
for (const root of this.roots) this.mkdir(root, true);
|
|
18
|
+
}
|
|
19
|
+
normalize(filePath) {
|
|
20
|
+
const stripped = String(filePath || "").replace(/^[a-z]+:\/\/[^/]*/i, "");
|
|
21
|
+
const parts = [];
|
|
22
|
+
for (const part of stripped.split("/")) {
|
|
23
|
+
if (!part || part === ".") continue;
|
|
24
|
+
if (part === "..") parts.pop();
|
|
25
|
+
else parts.push(part);
|
|
26
|
+
}
|
|
27
|
+
return "/" + parts.join("/");
|
|
28
|
+
}
|
|
29
|
+
stat(filePath) {
|
|
30
|
+
return this.nodes.get(this.normalize(filePath));
|
|
31
|
+
}
|
|
32
|
+
mkdir(dirPath, recursive = false) {
|
|
33
|
+
const target = this.normalize(dirPath);
|
|
34
|
+
if (this.nodes.has(target)) throw new Error(`file already exists ${dirPath}`);
|
|
35
|
+
const parent = target.slice(0, target.lastIndexOf("/")) || "/";
|
|
36
|
+
if (!this.nodes.get(parent)?.dir) {
|
|
37
|
+
if (!recursive) throw new Error(`no such file or directory ${dirPath}`);
|
|
38
|
+
this.mkdir(parent, true);
|
|
39
|
+
}
|
|
40
|
+
this.nodes.set(target, { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" });
|
|
41
|
+
}
|
|
42
|
+
write(filePath, input, append = false) {
|
|
43
|
+
const data = Buffer.from(input);
|
|
44
|
+
const target = this.normalize(filePath);
|
|
45
|
+
const parent = target.slice(0, target.lastIndexOf("/")) || "/";
|
|
46
|
+
if (!this.nodes.get(parent)?.dir) throw new Error(`no such file or directory ${filePath}`);
|
|
47
|
+
const existing = this.nodes.get(target);
|
|
48
|
+
if (existing?.dir) throw new Error(`illegal operation on a directory ${filePath}`);
|
|
49
|
+
const contents = append && existing ? Buffer.concat([existing.data, data]) : data;
|
|
50
|
+
this.nodes.set(target, { dir: false, data: contents, mtime: Date.now(), etag: `${this.etagPrefix}-${++this.version}` });
|
|
51
|
+
}
|
|
52
|
+
read(filePath) {
|
|
53
|
+
const node = this.stat(filePath);
|
|
54
|
+
if (!node) throw new Error(`no such file or directory, open ${filePath}`);
|
|
55
|
+
if (node.dir) throw new Error(`illegal operation on a directory ${filePath}`);
|
|
56
|
+
return node.data;
|
|
57
|
+
}
|
|
58
|
+
readdir(dirPath) {
|
|
59
|
+
const target = this.normalize(dirPath);
|
|
60
|
+
if (!this.nodes.get(target)?.dir) throw new Error(`no such file or directory ${dirPath}`);
|
|
61
|
+
const prefix = target === "/" ? "/" : target + "/";
|
|
62
|
+
return [...this.nodes.keys()].filter((key) => key.startsWith(prefix) && key !== target && !key.slice(prefix.length).includes("/")).map((key) => key.slice(prefix.length));
|
|
63
|
+
}
|
|
64
|
+
remove(filePath, options) {
|
|
65
|
+
const target = this.normalize(filePath);
|
|
66
|
+
const node = this.nodes.get(target);
|
|
67
|
+
if (!node) throw new Error(`no such file or directory ${filePath}`);
|
|
68
|
+
if (node.dir !== options.dir) throw new Error(options.dir ? `not a directory ${filePath}` : `operation not permitted ${filePath}`);
|
|
69
|
+
if (node.dir) {
|
|
70
|
+
const children = this.readdir(target);
|
|
71
|
+
if (children.length && !options.recursive) throw new Error(`directory not empty ${filePath}`);
|
|
72
|
+
for (const key of [...this.nodes.keys()]) if (key.startsWith(target + "/")) this.nodes.delete(key);
|
|
73
|
+
}
|
|
74
|
+
this.nodes.delete(target);
|
|
75
|
+
}
|
|
76
|
+
execute(operation, args) {
|
|
77
|
+
switch (operation) {
|
|
78
|
+
case "readFile": {
|
|
79
|
+
const data = this.read(args.filePath);
|
|
80
|
+
return args.encoding === void 0 ? { data: data.toString("base64"), binary: true } : { data: data.toString(args.encoding) };
|
|
81
|
+
}
|
|
82
|
+
case "writeFile":
|
|
83
|
+
case "appendFile": {
|
|
84
|
+
const data = Buffer.from(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8");
|
|
85
|
+
this.write(args.filePath, data, operation === "appendFile");
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
case "copyFile":
|
|
89
|
+
this.write(args.destPath, this.read(args.srcPath));
|
|
90
|
+
return {};
|
|
91
|
+
case "access":
|
|
92
|
+
if (!this.stat(args.path)) throw new Error(`no such file or directory ${args.path}`);
|
|
93
|
+
return {};
|
|
94
|
+
case "stat": {
|
|
95
|
+
const node = this.stat(args.path);
|
|
96
|
+
if (!node) throw new Error(`no such file or directory ${args.path}`);
|
|
97
|
+
const stats = {
|
|
98
|
+
mode: node.dir ? 16877 : 33188,
|
|
99
|
+
size: node.data.length,
|
|
100
|
+
lastAccessedTime: Math.floor(node.mtime / 1e3),
|
|
101
|
+
lastModifiedTime: Math.floor(node.mtime / 1e3)
|
|
102
|
+
};
|
|
103
|
+
return { stats, ...stats };
|
|
104
|
+
}
|
|
105
|
+
case "getFileInfo":
|
|
106
|
+
return { size: this.read(args.filePath).length };
|
|
107
|
+
case "mkdir":
|
|
108
|
+
this.mkdir(args.dirPath, !!args.recursive);
|
|
109
|
+
return {};
|
|
110
|
+
case "readdir":
|
|
111
|
+
return { files: this.readdir(args.dirPath) };
|
|
112
|
+
case "unlink":
|
|
113
|
+
this.remove(args.filePath, { dir: false });
|
|
114
|
+
return {};
|
|
115
|
+
case "rmdir": {
|
|
116
|
+
const target = this.normalize(args.dirPath);
|
|
117
|
+
const prefix = target === "/" ? "/" : target + "/";
|
|
118
|
+
if (this.roots.some((root) => root === target || root.startsWith(prefix))) {
|
|
119
|
+
throw new Error(`operation not permitted ${args.dirPath}`);
|
|
120
|
+
}
|
|
121
|
+
this.remove(args.dirPath, { dir: true, recursive: !!args.recursive });
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
default:
|
|
125
|
+
throw new Error(`unsupported filesystem operation ${operation}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// src/server/DevServer.ts
|
|
7
131
|
import fs4 from "fs";
|
|
8
132
|
import http from "http";
|
|
133
|
+
import net from "net";
|
|
134
|
+
import os from "os";
|
|
9
135
|
import path4 from "path";
|
|
10
136
|
import express2 from "express";
|
|
11
137
|
import { WebSocket as WebSocket2, WebSocketServer } from "ws";
|
|
@@ -445,6 +571,194 @@ function readJsonFile(file) {
|
|
|
445
571
|
}
|
|
446
572
|
}
|
|
447
573
|
|
|
574
|
+
// src/runtime/networkPanel.ts
|
|
575
|
+
var PROXY_PREFIX = "/__wxa__/proxy/";
|
|
576
|
+
var MOCK_PREFIX = "/__wxa__/mock/";
|
|
577
|
+
function fromServerPath(prefix, serverPath) {
|
|
578
|
+
if (!serverPath.startsWith(prefix)) return null;
|
|
579
|
+
const match = /^(https?)\/([^/?#]+)(.*)$/.exec(serverPath.slice(prefix.length));
|
|
580
|
+
if (!match) return null;
|
|
581
|
+
const url = `${match[1]}://${match[2]}${match[3]}`;
|
|
582
|
+
try {
|
|
583
|
+
new URL(url);
|
|
584
|
+
return url;
|
|
585
|
+
} catch {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/server/plugins.ts
|
|
591
|
+
import { readWxvpkgFile, readWxvpkgIndex } from "@open-wxa/wxa-runner-extractor";
|
|
592
|
+
|
|
593
|
+
// src/server/wechatDev.ts
|
|
594
|
+
import { TICKET_ENV } from "@open-wxa/wxa-runner-extractor";
|
|
595
|
+
var BASE_URL = "https://servicewechat.com";
|
|
596
|
+
var CLIENT_VERSION = "1021810240";
|
|
597
|
+
var DEFAULT_TIMEOUT = 1e4;
|
|
598
|
+
var ERROR_HINTS = {
|
|
599
|
+
// 40001:开发者工具刷新过 ticket,旧的随即作废
|
|
600
|
+
40001: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
|
|
601
|
+
40029: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
|
|
602
|
+
85023: "\uFF08\u5F53\u524D\u5FAE\u4FE1\u8D26\u53F7\u4E0D\u662F\u8BE5\u5C0F\u7A0B\u5E8F\u7684\u5F00\u53D1\u8005\uFF09"
|
|
603
|
+
};
|
|
604
|
+
function requireUsableTicket(ticket) {
|
|
605
|
+
if (!ticket) throw new Error(`\u672A\u627E\u5230\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\uFF0C\u8BF7\u767B\u5F55\u5F00\u53D1\u8005\u5DE5\u5177\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${TICKET_ENV}`);
|
|
606
|
+
if (ticket.expiredAt !== void 0 && ticket.expiredAt <= Date.now()) {
|
|
607
|
+
throw new Error("\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u6253\u5F00\u5F00\u53D1\u8005\u5DE5\u5177\u5237\u65B0\u767B\u5F55\u6001");
|
|
608
|
+
}
|
|
609
|
+
return ticket.newticket;
|
|
610
|
+
}
|
|
611
|
+
function explainErrcode(errcode) {
|
|
612
|
+
return errcode !== void 0 && ERROR_HINTS[errcode] || "";
|
|
613
|
+
}
|
|
614
|
+
function requireOk(body, action) {
|
|
615
|
+
const errcode = body.baseresponse?.errcode;
|
|
616
|
+
if (errcode !== 0) throw new Error(`${action} ${errcode}: ${body.baseresponse?.errmsg || "\u672A\u77E5\u9519\u8BEF"}${explainErrcode(errcode)}`);
|
|
617
|
+
return body;
|
|
618
|
+
}
|
|
619
|
+
var WechatDevClient = class {
|
|
620
|
+
/**
|
|
621
|
+
* @param findTicket 每次请求都重新取:开发者工具重新登录、切换账号后不用重启服务器
|
|
622
|
+
*/
|
|
623
|
+
constructor(appid, findTicket) {
|
|
624
|
+
this.appid = appid;
|
|
625
|
+
this.findTicket = findTicket;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* POST JSON 到 path(如 wxa-dev-logic/jslogin),带上 appid 和开发者 ticket
|
|
629
|
+
*
|
|
630
|
+
* @throws 没有可用的登录态、超时、连不上、HTTP 状态不是 2xx 时,消息是给用户看的原因
|
|
631
|
+
*/
|
|
632
|
+
async post(path5, body, timeout = DEFAULT_TIMEOUT) {
|
|
633
|
+
const query = new URLSearchParams({ appid: this.appid, newticket: requireUsableTicket(await this.findTicket()), clientversion: CLIENT_VERSION });
|
|
634
|
+
const response = await fetch(`${BASE_URL}/${path5}?${query}`, {
|
|
635
|
+
method: "POST",
|
|
636
|
+
headers: { "Content-Type": "application/json" },
|
|
637
|
+
body: JSON.stringify(body),
|
|
638
|
+
signal: AbortSignal.timeout(timeout)
|
|
639
|
+
}).catch((error) => {
|
|
640
|
+
throw new Error(error.name === "TimeoutError" ? `\u8BF7\u6C42\u5FAE\u4FE1\u670D\u52A1\u5668\u8D85\u65F6\uFF08${timeout / 1e3}s\uFF09` : `\u8BF7\u6C42\u5FAE\u4FE1\u670D\u52A1\u5668\u5931\u8D25\uFF1A${error.cause?.message ?? error.message}`);
|
|
641
|
+
});
|
|
642
|
+
if (!response.ok) throw new Error(`\u5FAE\u4FE1\u670D\u52A1\u5668\u8FD4\u56DE HTTP ${response.status}`);
|
|
643
|
+
return response;
|
|
644
|
+
}
|
|
645
|
+
/** 同 post,返回解析后的 JSON */
|
|
646
|
+
async postJson(path5, body) {
|
|
647
|
+
return (await this.post(path5, body)).json();
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
// src/server/plugins.ts
|
|
652
|
+
var DOWNLOAD_TIMEOUT = 3e4;
|
|
653
|
+
var PACKAGE_MAGIC = 190;
|
|
654
|
+
var PluginStore = class {
|
|
655
|
+
/**
|
|
656
|
+
* @param wechat 开启 --devtools-login 时才有,没有时不下载插件
|
|
657
|
+
*/
|
|
658
|
+
constructor(wechat, logger) {
|
|
659
|
+
this.wechat = wechat;
|
|
660
|
+
this.logger = logger;
|
|
661
|
+
/** provider@version → 下载结果;失败的不留,下次打开小程序时重试 */
|
|
662
|
+
this.downloads = /* @__PURE__ */ new Map();
|
|
663
|
+
/**
|
|
664
|
+
* 这次打开小程序时决定用哪些插件。同一次打开的逻辑层、业务模块、渲染层等请求都按它处理:
|
|
665
|
+
* 各自去下载的话,下载失败时每个请求都会重试,结果还可能前后不一,页面列表和逻辑层对不上
|
|
666
|
+
*/
|
|
667
|
+
this.selection = null;
|
|
668
|
+
/** 没开 --devtools-login 的提示每个插件只说一次 */
|
|
669
|
+
this.warned = /* @__PURE__ */ new Set();
|
|
670
|
+
}
|
|
671
|
+
/** 打开小程序时(宿主取 app-info)调用:重新决定用哪些插件,下载过的直接用,上次失败的重试 */
|
|
672
|
+
async refresh(build2) {
|
|
673
|
+
this.selection = this.select(build2);
|
|
674
|
+
return withPlugins(build2, await this.selection);
|
|
675
|
+
}
|
|
676
|
+
/** 其余请求沿用这次打开时的决定,不触发下载;还没决定过时(如直接请求逻辑层)先决定一次 */
|
|
677
|
+
async current(build2) {
|
|
678
|
+
this.selection ??= this.select(build2);
|
|
679
|
+
return withPlugins(build2, await this.selection);
|
|
680
|
+
}
|
|
681
|
+
/** app.json 声明的插件;下载不了的跳过,在终端说明原因,小程序自己的页面照常可用 */
|
|
682
|
+
async select(build2) {
|
|
683
|
+
const declared = build2.wxConfig.plugins || {};
|
|
684
|
+
const results = await Promise.all(Object.entries(declared).map(
|
|
685
|
+
([alias, { provider, version }]) => provider && version ? this.load(alias, provider, version) : null
|
|
686
|
+
));
|
|
687
|
+
return results.filter((plugin) => !!plugin);
|
|
688
|
+
}
|
|
689
|
+
async load(alias, provider, version) {
|
|
690
|
+
const label = `\u63D2\u4EF6 ${alias}\uFF08${provider}@${version}\uFF09`;
|
|
691
|
+
if (!this.wechat) {
|
|
692
|
+
this.warnOnce(label, `[wxa-runner] ${label} \u7684\u4EE3\u7801\u8981\u7528\u5F00\u53D1\u8005\u7684\u767B\u5F55\u6001\u5411\u5FAE\u4FE1\u4E0B\u8F7D\uFF0C\u542F\u52A8\u65F6\u52A0 --devtools-login`);
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
const key = `${provider}@${version}`;
|
|
696
|
+
let download = this.downloads.get(key);
|
|
697
|
+
if (!download) {
|
|
698
|
+
download = this.download(this.wechat, alias, provider, version);
|
|
699
|
+
this.downloads.set(key, download);
|
|
700
|
+
download.catch(() => this.downloads.delete(key));
|
|
701
|
+
}
|
|
702
|
+
try {
|
|
703
|
+
return await download;
|
|
704
|
+
} catch (error) {
|
|
705
|
+
this.logger.warn(`[wxa-runner] ${label} \u4E0B\u8F7D\u5931\u8D25\uFF1A${error.message}`);
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
/** @throws 没有登录态、连不上、微信拒绝或返回的不是插件代码包时,消息是原因 */
|
|
710
|
+
async download(wechat, alias, provider, version) {
|
|
711
|
+
const response = await wechat.post("wxa-dev/getplugincode", { plugin_id: provider, plugin_version: version }, DOWNLOAD_TIMEOUT);
|
|
712
|
+
const data = Buffer.from(await response.arrayBuffer());
|
|
713
|
+
if (data[0] !== PACKAGE_MAGIC) {
|
|
714
|
+
let body = {};
|
|
715
|
+
try {
|
|
716
|
+
body = JSON.parse(data.toString("utf8"));
|
|
717
|
+
} catch {
|
|
718
|
+
}
|
|
719
|
+
if (body.baseresponse?.errcode) requireOk(body, "\u5FAE\u4FE1\u62D2\u7EDD\u4E0B\u8F7D");
|
|
720
|
+
throw new Error("\u5FAE\u4FE1\u6CA1\u6709\u8FD4\u56DE\u63D2\u4EF6\u4EE3\u7801");
|
|
721
|
+
}
|
|
722
|
+
const files = new Map(readWxvpkgIndex(data).map((entry) => [entry.name, Buffer.from(readWxvpkgFile(data, entry))]));
|
|
723
|
+
const pluginJson = JSON.parse(files.get("/plugin.json")?.toString("utf8") || "{}");
|
|
724
|
+
return {
|
|
725
|
+
alias,
|
|
726
|
+
provider,
|
|
727
|
+
version,
|
|
728
|
+
files,
|
|
729
|
+
pages: Object.values(pluginJson.pages || {}),
|
|
730
|
+
pageConfigs: pluginJson.page || {}
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
warnOnce(key, message) {
|
|
734
|
+
if (this.warned.has(key)) return;
|
|
735
|
+
this.warned.add(key);
|
|
736
|
+
this.logger.warn(message);
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
function withPlugins(build2, plugins) {
|
|
740
|
+
if (!plugins.length) return { ...build2, plugins };
|
|
741
|
+
return {
|
|
742
|
+
...build2,
|
|
743
|
+
plugins,
|
|
744
|
+
pages: [...build2.pages, ...plugins.flatMap((plugin) => plugin.pages)],
|
|
745
|
+
wxConfig: {
|
|
746
|
+
...build2.wxConfig,
|
|
747
|
+
page: { ...build2.wxConfig.page, ...Object.assign({}, ...plugins.map((plugin) => plugin.pageConfigs)) }
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
function findPluginPage(build2, pagePath) {
|
|
752
|
+
for (const plugin of build2.plugins) {
|
|
753
|
+
const prefix = `__plugin__/${plugin.provider}/`;
|
|
754
|
+
if (plugin.pages.includes(pagePath)) return { plugin, innerPath: pagePath.slice(prefix.length) };
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
function pluginScriptUrl(plugin, file) {
|
|
759
|
+
return `/__wxa__/plugin/${encodeURIComponent(plugin.provider)}/${encodeURIComponent(plugin.version)}/${file}`;
|
|
760
|
+
}
|
|
761
|
+
|
|
448
762
|
// src/server/html.ts
|
|
449
763
|
function hostHtml() {
|
|
450
764
|
return `<!DOCTYPE html>
|
|
@@ -471,10 +785,11 @@ function appserviceHtml(build2, launch, options) {
|
|
|
471
785
|
...options.isolated ? { isIsolateContext: true } : {}
|
|
472
786
|
};
|
|
473
787
|
const v = build2.version;
|
|
788
|
+
const pluginUrls = build2.plugins.map((plugin) => pluginScriptUrl(plugin, "appservice.js"));
|
|
474
789
|
const moduleUrls = build2.modules.map((id) => `/__wxa__/module/${encodeURI(id)}?v=${v}`);
|
|
475
790
|
const bootstrapUrl = `/__wxa__/service-bootstrap.js?v=${v}`;
|
|
476
791
|
if (options.isolated) {
|
|
477
|
-
const scripts = [...moduleUrls, bootstrapUrl];
|
|
792
|
+
const scripts = [...pluginUrls, ...moduleUrls, bootstrapUrl];
|
|
478
793
|
return `<!DOCTYPE html>
|
|
479
794
|
<html>
|
|
480
795
|
<head>
|
|
@@ -487,7 +802,7 @@ function appserviceHtml(build2, launch, options) {
|
|
|
487
802
|
<body></body>
|
|
488
803
|
</html>`;
|
|
489
804
|
}
|
|
490
|
-
const modules = moduleUrls.map((src) => `<script src="${src}"></script>`).join("\n");
|
|
805
|
+
const modules = [...pluginUrls, ...moduleUrls].map((src) => `<script src="${src}"></script>`).join("\n");
|
|
491
806
|
return `<!DOCTYPE html>
|
|
492
807
|
<html>
|
|
493
808
|
<head>
|
|
@@ -521,29 +836,13 @@ ${empty ? "" : '<script src="/__baselib__/WASubContext.js"></script>\n'}<script>
|
|
|
521
836
|
</html>`;
|
|
522
837
|
}
|
|
523
838
|
function webviewHtml(build2, pagePath, webviewId, options) {
|
|
524
|
-
const pageConfig = build2.wxConfig.page?.[pagePath + ".html"];
|
|
525
|
-
const window = { ...build2.wxConfig.global?.window, ...pageConfig?.window };
|
|
526
|
-
const wxConfig = { ...build2.wxConfig, window };
|
|
527
839
|
const id = Number(webviewId) || 0;
|
|
528
840
|
return `<!DOCTYPE html>
|
|
529
841
|
<html>
|
|
530
842
|
<head>
|
|
531
|
-
|
|
532
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
533
|
-
<title>${escapeHtml(pagePath)}</title>
|
|
534
|
-
<!-- \u5C0F\u7A0B\u5E8F\u6E32\u67D3\u5C42\u7684\u9ED8\u8BA4\u5B57\u53F7\u662F 16px\uFF1B\u4E0D\u58F0\u660E\u7684\u8BDD\u4F1A\u7EE7\u627F\u6D4F\u89C8\u5668\u8BBE\u7F6E\u91CC\u7684\u9ED8\u8BA4\u5B57\u53F7\u3002\u653E\u5728\u6700\u524D\u9762\uFF0C\u57FA\u7840\u5E93\u548C\u9875\u9762\u6837\u5F0F\u90FD\u80FD\u8986\u76D6\u5B83 -->
|
|
535
|
-
<style>html{font-size:16px}</style>
|
|
536
|
-
<!-- \u771F\u673A\u7684\u6EDA\u52A8\u6761\u662F\u4E0D\u5360\u5E03\u5C40\u7684\u6D6E\u5C42\uFF1B\u684C\u9762\u6D4F\u89C8\u5668\u7684\u5E38\u89C4\u6EDA\u52A8\u6761\u548C\u81EA\u5B9A\u4E49 ::-webkit-scrollbar \u4F1A\u6324\u7A84\u9875\u9762\uFF0C\u4E00\u5F8B\u9690\u85CF\u3002
|
|
537
|
-
scrollbar-width \u4F18\u5148\u4E8E ::-webkit-scrollbar\uFF0C\u9875\u9762\u6837\u5F0F\u4E0D\u4F1A\u518D\u628A\u6EDA\u52A8\u6761\u52A0\u56DE\u6765 -->
|
|
538
|
-
<style>*{scrollbar-width:none}::-webkit-scrollbar{display:none}</style>
|
|
539
|
-
<!-- \u903B\u8F91\u5C42\u5EFA\u7684 2d\u3001webgl \u753B\u5E03\u642C\u8FDB wx-canvas \u540E\u94FA\u6EE1\u5B83\uFF08\u89C1 frame.ts \u7684 moveCanvasesIntoPages\uFF09\u3002
|
|
540
|
-
\u57FA\u7840\u5E93\u8FD8\u4F1A\u6309\u903B\u8F91\u5C42\u91CC\u7684\u5E03\u5C40\u6539\u5B83\u7684\u4F4D\u7F6E\u548C\u663E\u9690\uFF0C\u8FD9\u91CC\u4E00\u5F8B\u538B\u4F4F\uFF1B\u89E6\u6478\u7167\u5E38\u4EA4\u7ED9 wx-canvas \u81EA\u5DF1\u7684 canvas -->
|
|
541
|
-
<style>.wxa-service-canvas{position:absolute!important;left:0!important;top:0!important;width:100%!important;height:100%!important;visibility:inherit!important;pointer-events:none}</style>
|
|
542
|
-
<script>var __wxConfig = ${toJs(wxConfig)};</script>
|
|
543
|
-
<script src="/__wxa__/runtime/frame.js" data-kind="webview" data-webview-id="${id}"></script>
|
|
544
|
-
<script src="/__baselib__/WAWebview.js"></script>
|
|
843
|
+
${webviewHead(build2, pagePath, id)}
|
|
545
844
|
${enableHotUpdate(options.hot)}<script src="/__wxa__/page-frame.js?v=${build2.version}"></script>
|
|
546
|
-
${
|
|
845
|
+
${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
547
846
|
</head>
|
|
548
847
|
<body>
|
|
549
848
|
<script>
|
|
@@ -565,7 +864,7 @@ ${window.disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
|
565
864
|
}
|
|
566
865
|
function workerScript(build2, baseLib) {
|
|
567
866
|
const modules = build2.workerModules.map((id) => `/__wxa__/module/${encodeURI(id)}?v=${build2.version}`);
|
|
568
|
-
const prelude = `var __wxConfig = ${toJs(build2.wxConfig)};
|
|
867
|
+
const prelude = `var __wxConfig = ${toJs(build2.wxConfig)}; __wxConfig.env.USER_DATA_PATH = location.origin + "/__wxa__/usr/" + new URL(location.href).searchParams.get("session");
|
|
569
868
|
importScripts('/__wxa__/runtime/worker.js');
|
|
570
869
|
`;
|
|
571
870
|
if (!baseLib.hasWorker) {
|
|
@@ -579,6 +878,42 @@ self.console = __wxaConsole;
|
|
|
579
878
|
importScripts(${modules.map((url) => toJs(url)).join(", ")});
|
|
580
879
|
`;
|
|
581
880
|
}
|
|
881
|
+
function pluginWebviewHtml(build2, plugin, innerPath, pagePath, webviewId) {
|
|
882
|
+
const id = Number(webviewId) || 0;
|
|
883
|
+
const page = plugin.files.get(`/${innerPath}.html`)?.toString("utf8") ?? `<script>console.error(${JSON.stringify(`[wxa-runner] \u63D2\u4EF6 ${plugin.alias} \u91CC\u6CA1\u6709\u9875\u9762 ${innerPath}`)})</script>`;
|
|
884
|
+
return `<!DOCTYPE html>
|
|
885
|
+
<html>
|
|
886
|
+
<head>
|
|
887
|
+
${webviewHead(build2, pagePath, id)}
|
|
888
|
+
<script src="${pluginScriptUrl(plugin, "pageframe.js")}"></script>
|
|
889
|
+
${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
890
|
+
</head>
|
|
891
|
+
<body>
|
|
892
|
+
${page}
|
|
893
|
+
<script>parent.__wxaHost.frameLoaded('webview', ${id});</script>
|
|
894
|
+
</body>
|
|
895
|
+
</html>`;
|
|
896
|
+
}
|
|
897
|
+
function pageWindow(build2, pagePath) {
|
|
898
|
+
return { ...build2.wxConfig.global?.window, ...build2.wxConfig.page?.[pagePath + ".html"]?.window };
|
|
899
|
+
}
|
|
900
|
+
function webviewHead(build2, pagePath, id) {
|
|
901
|
+
const wxConfig = { ...build2.wxConfig, window: pageWindow(build2, pagePath) };
|
|
902
|
+
return `<meta charset="utf-8">
|
|
903
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
904
|
+
<title>${escapeHtml(pagePath)}</title>
|
|
905
|
+
<!-- \u5C0F\u7A0B\u5E8F\u6E32\u67D3\u5C42\u7684\u9ED8\u8BA4\u5B57\u53F7\u662F 16px\uFF1B\u4E0D\u58F0\u660E\u7684\u8BDD\u4F1A\u7EE7\u627F\u6D4F\u89C8\u5668\u8BBE\u7F6E\u91CC\u7684\u9ED8\u8BA4\u5B57\u53F7\u3002\u653E\u5728\u6700\u524D\u9762\uFF0C\u57FA\u7840\u5E93\u548C\u9875\u9762\u6837\u5F0F\u90FD\u80FD\u8986\u76D6\u5B83 -->
|
|
906
|
+
<style>html{font-size:16px}</style>
|
|
907
|
+
<!-- \u771F\u673A\u7684\u6EDA\u52A8\u6761\u662F\u4E0D\u5360\u5E03\u5C40\u7684\u6D6E\u5C42\uFF1B\u684C\u9762\u6D4F\u89C8\u5668\u7684\u5E38\u89C4\u6EDA\u52A8\u6761\u548C\u81EA\u5B9A\u4E49 ::-webkit-scrollbar \u4F1A\u6324\u7A84\u9875\u9762\uFF0C\u4E00\u5F8B\u9690\u85CF\u3002
|
|
908
|
+
scrollbar-width \u4F18\u5148\u4E8E ::-webkit-scrollbar\uFF0C\u9875\u9762\u6837\u5F0F\u4E0D\u4F1A\u518D\u628A\u6EDA\u52A8\u6761\u52A0\u56DE\u6765 -->
|
|
909
|
+
<style>*{scrollbar-width:none}::-webkit-scrollbar{display:none}</style>
|
|
910
|
+
<!-- \u903B\u8F91\u5C42\u5EFA\u7684 2d\u3001webgl \u753B\u5E03\u642C\u8FDB wx-canvas \u540E\u94FA\u6EE1\u5B83\uFF08\u89C1 frame.ts \u7684 moveCanvasesIntoPages\uFF09\u3002
|
|
911
|
+
\u57FA\u7840\u5E93\u8FD8\u4F1A\u6309\u903B\u8F91\u5C42\u91CC\u7684\u5E03\u5C40\u6539\u5B83\u7684\u4F4D\u7F6E\u548C\u663E\u9690\uFF0C\u8FD9\u91CC\u4E00\u5F8B\u538B\u4F4F\uFF1B\u89E6\u6478\u7167\u5E38\u4EA4\u7ED9 wx-canvas \u81EA\u5DF1\u7684 canvas -->
|
|
912
|
+
<style>.wxa-service-canvas{position:absolute!important;left:0!important;top:0!important;width:100%!important;height:100%!important;visibility:inherit!important;pointer-events:none}</style>
|
|
913
|
+
<script>var __wxConfig = ${toJs(wxConfig)};</script>
|
|
914
|
+
<script src="/__wxa__/runtime/frame.js" data-kind="webview" data-webview-id="${id}"></script>
|
|
915
|
+
<script src="/__baselib__/WAWebview.js"></script>`;
|
|
916
|
+
}
|
|
582
917
|
function escapeHtml(text) {
|
|
583
918
|
return text.replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch]);
|
|
584
919
|
}
|
|
@@ -611,13 +946,62 @@ function vlq(value) {
|
|
|
611
946
|
return out;
|
|
612
947
|
}
|
|
613
948
|
|
|
949
|
+
// src/server/forwardConsole.ts
|
|
950
|
+
var MAX_LENGTH = 4e3;
|
|
951
|
+
var LIMIT_PER_SECOND = 20;
|
|
952
|
+
var CONTROL_CHARS = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
953
|
+
function printBrowserReports(socket, logger) {
|
|
954
|
+
let windowStart = 0;
|
|
955
|
+
let printed = 0;
|
|
956
|
+
let dropped = 0;
|
|
957
|
+
socket.on("message", (data) => {
|
|
958
|
+
const report = parseReport(data);
|
|
959
|
+
if (!report) return;
|
|
960
|
+
const now = Date.now();
|
|
961
|
+
if (now - windowStart >= 1e3) {
|
|
962
|
+
windowStart = now;
|
|
963
|
+
printed = 0;
|
|
964
|
+
}
|
|
965
|
+
if (printed >= LIMIT_PER_SECOND) {
|
|
966
|
+
if (dropped++ === 0) {
|
|
967
|
+
setTimeout(() => {
|
|
968
|
+
logger.warn(`[wxa-runner] \u6D4F\u89C8\u5668 1 \u79D2\u5185\u8F6C\u53D1\u7684\u62A5\u9519\u592A\u591A\uFF0C\u7701\u7565\u4E86 ${dropped} \u6761`);
|
|
969
|
+
dropped = 0;
|
|
970
|
+
}, windowStart + 1e3 - now).unref();
|
|
971
|
+
}
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
printed++;
|
|
975
|
+
logger[report.level](`[wxa-runner] ${clean(report.text)}`);
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
function parseReport(data) {
|
|
979
|
+
try {
|
|
980
|
+
const report = JSON.parse(data.toString());
|
|
981
|
+
if (report?.type === "console" && (report.level === "warn" || report.level === "error") && typeof report.text === "string") return report;
|
|
982
|
+
} catch {
|
|
983
|
+
}
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
function clean(text) {
|
|
987
|
+
const printable = text.replace(CONTROL_CHARS, "");
|
|
988
|
+
return printable.length > MAX_LENGTH ? `${printable.slice(0, MAX_LENGTH)}\u2026\uFF08\u5DF2\u622A\u65AD\uFF09` : printable;
|
|
989
|
+
}
|
|
990
|
+
function ignoreConsoleHook(sourceMap) {
|
|
991
|
+
const map = JSON.parse(sourceMap);
|
|
992
|
+
map.x_google_ignoreList = map.sources.flatMap((source, index) => source.endsWith("consoleHook.ts") ? [index] : []);
|
|
993
|
+
return JSON.stringify(map);
|
|
994
|
+
}
|
|
995
|
+
|
|
614
996
|
// src/server/proxy.ts
|
|
997
|
+
import { Readable } from "stream";
|
|
998
|
+
import { pipeline } from "stream/promises";
|
|
615
999
|
var DROPPED_REQUEST_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "expect"]);
|
|
616
1000
|
var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding", "connection", "set-cookie"]);
|
|
617
1001
|
async function handleProxy(req, res) {
|
|
618
|
-
const target =
|
|
619
|
-
if (
|
|
620
|
-
res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${
|
|
1002
|
+
const target = fromServerPath(PROXY_PREFIX, req.originalUrl);
|
|
1003
|
+
if (!target) {
|
|
1004
|
+
res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${req.originalUrl}"`);
|
|
621
1005
|
return;
|
|
622
1006
|
}
|
|
623
1007
|
let requestHeaders = {};
|
|
@@ -639,31 +1023,47 @@ async function handleProxy(req, res) {
|
|
|
639
1023
|
headers["content-type"] = browserContentType;
|
|
640
1024
|
}
|
|
641
1025
|
const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
|
|
642
|
-
|
|
1026
|
+
const controller = new AbortController();
|
|
1027
|
+
const cancel = () => controller.abort();
|
|
1028
|
+
res.once("close", cancel);
|
|
643
1029
|
try {
|
|
644
|
-
upstream
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
1030
|
+
let upstream;
|
|
1031
|
+
try {
|
|
1032
|
+
upstream = await fetch(target, {
|
|
1033
|
+
method: req.method,
|
|
1034
|
+
headers,
|
|
1035
|
+
body: hasBody ? req.body : void 0,
|
|
1036
|
+
redirect: "follow",
|
|
1037
|
+
signal: controller.signal
|
|
1038
|
+
});
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
if (res.destroyed || controller.signal.aborted) return;
|
|
1041
|
+
const cause = error?.cause?.message;
|
|
1042
|
+
res.status(502).set("x-wxa-proxy-error", "1").send(cause || error.message);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const responseHeaders = {};
|
|
1046
|
+
upstream.headers.forEach((value, key) => {
|
|
1047
|
+
responseHeaders[key] = value;
|
|
649
1048
|
});
|
|
1049
|
+
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
1050
|
+
res.status(upstream.status);
|
|
1051
|
+
upstream.headers.forEach((value, key) => {
|
|
1052
|
+
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
1053
|
+
});
|
|
1054
|
+
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
1055
|
+
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
1056
|
+
res.flushHeaders();
|
|
1057
|
+
if (!upstream.body) {
|
|
1058
|
+
res.end();
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
await pipeline(Readable.fromWeb(upstream.body), res);
|
|
650
1062
|
} catch (error) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
1063
|
+
if (!res.destroyed) res.destroy(error);
|
|
1064
|
+
} finally {
|
|
1065
|
+
res.off("close", cancel);
|
|
654
1066
|
}
|
|
655
|
-
const responseHeaders = {};
|
|
656
|
-
upstream.headers.forEach((value, key) => {
|
|
657
|
-
responseHeaders[key] = value;
|
|
658
|
-
});
|
|
659
|
-
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
660
|
-
res.status(upstream.status);
|
|
661
|
-
upstream.headers.forEach((value, key) => {
|
|
662
|
-
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
663
|
-
});
|
|
664
|
-
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
665
|
-
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
666
|
-
res.send(Buffer.from(await upstream.arrayBuffer()));
|
|
667
1067
|
}
|
|
668
1068
|
|
|
669
1069
|
// src/server/hmr.ts
|
|
@@ -681,10 +1081,10 @@ function planHotUpdate(files, units) {
|
|
|
681
1081
|
|
|
682
1082
|
// src/server/developerTicket.ts
|
|
683
1083
|
import { execFile } from "child_process";
|
|
684
|
-
import { randomUUID } from "crypto";
|
|
1084
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
685
1085
|
import { setTimeout as sleep } from "timers/promises";
|
|
686
1086
|
import { WebSocket } from "ws";
|
|
687
|
-
import { findDeveloperTicket, findElectronDevtoolsCli, TICKET_ENV } from "@open-wxa/wxa-runner-extractor";
|
|
1087
|
+
import { findDeveloperTicket, findElectronDevtoolsCli, TICKET_ENV as TICKET_ENV2 } from "@open-wxa/wxa-runner-extractor";
|
|
688
1088
|
var AUTO_PORT = 9420;
|
|
689
1089
|
var CLI_TIMEOUT = 6e4;
|
|
690
1090
|
var AUTOMATOR_TIMEOUT = 5e3;
|
|
@@ -697,7 +1097,7 @@ function developerTicketSource(projectPath, logger) {
|
|
|
697
1097
|
const startOnce = () => {
|
|
698
1098
|
if (!automatorPort) {
|
|
699
1099
|
const starting = startAutomation(cli, projectPath).then((port) => {
|
|
700
|
-
logger.log(`[wxa-runner] \
|
|
1100
|
+
logger.log(`[wxa-runner] --devtools-login\uFF1A\u5DF2\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u4E3A\u9879\u76EE\u5F00\u542F\u81EA\u52A8\u5316\u63A5\u53E3\uFF08\u7AEF\u53E3 ${port}\uFF09\uFF0Cticket \u4ECE\u5F00\u53D1\u8005\u5DE5\u5177\u5B9E\u65F6\u83B7\u53D6`);
|
|
701
1101
|
return port;
|
|
702
1102
|
});
|
|
703
1103
|
starting.catch(() => {
|
|
@@ -708,7 +1108,7 @@ function developerTicketSource(projectPath, logger) {
|
|
|
708
1108
|
return automatorPort;
|
|
709
1109
|
};
|
|
710
1110
|
return async () => {
|
|
711
|
-
if (process.env[
|
|
1111
|
+
if (process.env[TICKET_ENV2]) return { newticket: process.env[TICKET_ENV2] };
|
|
712
1112
|
const port = startOnce();
|
|
713
1113
|
try {
|
|
714
1114
|
return await requestTicketOnceWindowReady(await port, projectPath);
|
|
@@ -773,7 +1173,7 @@ function cliErrorMessage(stdout) {
|
|
|
773
1173
|
}
|
|
774
1174
|
function requestTicket(port) {
|
|
775
1175
|
return new Promise((resolve, reject) => {
|
|
776
|
-
const id = `wxa-runner-${
|
|
1176
|
+
const id = `wxa-runner-${randomUUID2()}`;
|
|
777
1177
|
const socket = new WebSocket(`ws://127.0.0.1:${port}`);
|
|
778
1178
|
const settle = (finish) => {
|
|
779
1179
|
clearTimeout(timer);
|
|
@@ -813,74 +1213,62 @@ function explainAutomatorError(message) {
|
|
|
813
1213
|
return new Error(`\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u8FD4\u56DE\u9519\u8BEF\uFF1A${message}`);
|
|
814
1214
|
}
|
|
815
1215
|
|
|
816
|
-
// src/server/
|
|
1216
|
+
// src/server/devtoolsLogin.ts
|
|
817
1217
|
import express from "express";
|
|
818
|
-
import { findDeveloperTicket as findDeveloperTicket2
|
|
819
|
-
var DEV_LOGIC = "https://servicewechat.com/wxa-dev-logic";
|
|
820
|
-
var CLIENT_VERSION = "1021810240";
|
|
821
|
-
var UPSTREAM_TIMEOUT = 1e4;
|
|
1218
|
+
import { findDeveloperTicket as findDeveloperTicket2 } from "@open-wxa/wxa-runner-extractor";
|
|
822
1219
|
var LOOPBACK_ADDRESSES = ["127.0.0.1", "::1", "::ffff:127.0.0.1"];
|
|
823
1220
|
var LOOPBACK_HOSTS = ["localhost", "127.0.0.1", "[::1]"];
|
|
824
|
-
var
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
40029: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
|
|
828
|
-
85023: "\uFF08\u5F53\u524D\u5FAE\u4FE1\u8D26\u53F7\u4E0D\u662F\u8BE5\u5C0F\u7A0B\u5E8F\u7684\u5F00\u53D1\u8005\uFF09"
|
|
829
|
-
};
|
|
830
|
-
function requireUsableTicket(ticket) {
|
|
831
|
-
if (!ticket) throw new Error(`\u672A\u627E\u5230\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\uFF0C\u8BF7\u767B\u5F55\u5F00\u53D1\u8005\u5DE5\u5177\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${TICKET_ENV2}`);
|
|
832
|
-
if (ticket.expiredAt !== void 0 && ticket.expiredAt <= Date.now()) {
|
|
833
|
-
throw new Error("\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u6253\u5F00\u5F00\u53D1\u8005\u5DE5\u5177\u5237\u65B0\u767B\u5F55\u6001");
|
|
834
|
-
}
|
|
835
|
-
return ticket.newticket;
|
|
836
|
-
}
|
|
837
|
-
function realLoginRouter(appid, findTicket = findDeveloperTicket2) {
|
|
838
|
-
const callDevLogic = async (endpoint, body) => {
|
|
839
|
-
const query = new URLSearchParams({ appid, newticket: requireUsableTicket(await findTicket()), clientversion: CLIENT_VERSION });
|
|
840
|
-
const response = await fetch(`${DEV_LOGIC}/${endpoint}?${query}`, {
|
|
841
|
-
method: "POST",
|
|
842
|
-
headers: { "Content-Type": "application/json" },
|
|
843
|
-
body: JSON.stringify(body),
|
|
844
|
-
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT)
|
|
845
|
-
}).catch((error) => {
|
|
846
|
-
throw new Error(error.name === "TimeoutError" ? `\u8BF7\u6C42\u5FAE\u4FE1\u670D\u52A1\u5668\u8D85\u65F6\uFF08${UPSTREAM_TIMEOUT / 1e3}s\uFF09` : `\u8BF7\u6C42\u5FAE\u4FE1\u670D\u52A1\u5668\u5931\u8D25\uFF1A${error.cause?.message ?? error.message}`);
|
|
847
|
-
});
|
|
848
|
-
if (!response.ok) throw new Error(`\u5FAE\u4FE1\u670D\u52A1\u5668\u8FD4\u56DE HTTP ${response.status}`);
|
|
849
|
-
return response.json();
|
|
850
|
-
};
|
|
1221
|
+
var FORWARDED_WX_DATA_APIS = ["webapi_servicemarket_jsapi_comm"];
|
|
1222
|
+
function devtoolsLoginRouter(appid, findTicket = findDeveloperTicket2) {
|
|
1223
|
+
const wechat = new WechatDevClient(appid, findTicket);
|
|
851
1224
|
const router = express.Router();
|
|
852
1225
|
router.use(onlyLoopback);
|
|
853
1226
|
router.post("/login", respond(async () => {
|
|
854
|
-
const body = await
|
|
855
|
-
|
|
856
|
-
if (errcode !== 0 || !body.code) {
|
|
857
|
-
throw new Error(`\u5FAE\u4FE1\u767B\u5F55\u5931\u8D25 ${errcode}: ${body.baseresponse?.errmsg || "\u672A\u77E5\u9519\u8BEF"}${ERROR_HINTS[errcode] ?? ""}`);
|
|
858
|
-
}
|
|
1227
|
+
const body = requireOk(await wechat.postJson("wxa-dev-logic/jslogin", { scope: ["snsapi_base"] }), "\u5FAE\u4FE1\u767B\u5F55\u5931\u8D25");
|
|
1228
|
+
if (!body.code) throw new Error("\u5FAE\u4FE1\u767B\u5F55\u5931\u8D25\uFF1A\u5FAE\u4FE1\u6CA1\u6709\u8FD4\u56DE code");
|
|
859
1229
|
return { code: body.code };
|
|
860
1230
|
}));
|
|
861
1231
|
router.post("/phone-number", respond(async () => {
|
|
862
|
-
const body = await
|
|
1232
|
+
const body = await wechat.postJson("wxa-dev-logic/jsgetuserwxphone", { appid });
|
|
863
1233
|
const errcode = body.jsapi_baseresponse?.errcode ?? body.BaseResponse?.Ret;
|
|
864
1234
|
if (errcode !== 0 || typeof body.data !== "string") {
|
|
865
1235
|
const errmsg = body.jsapi_baseresponse?.errmsg || body.BaseResponse?.ErrMsg?.String || "\u672A\u77E5\u9519\u8BEF";
|
|
866
|
-
throw new Error(`\u83B7\u53D6\u624B\u673A\u53F7\u5931\u8D25 ${errcode}: ${errmsg}${
|
|
1236
|
+
throw new Error(`\u83B7\u53D6\u624B\u673A\u53F7\u5931\u8D25 ${errcode}: ${errmsg}${explainErrcode(errcode)}`);
|
|
867
1237
|
}
|
|
868
1238
|
const { encryptedData, iv } = JSON.parse(body.data);
|
|
869
1239
|
return { data: body.data, encryptedData, iv };
|
|
870
1240
|
}));
|
|
1241
|
+
router.post("/operate-wx-data", express.json({ limit: "10mb" }), (req, res, next) => {
|
|
1242
|
+
const apiName = req.body?.data?.api_name;
|
|
1243
|
+
if (FORWARDED_WX_DATA_APIS.includes(apiName)) return next();
|
|
1244
|
+
res.status(403).json({ error: `operateWXData \u53EA\u8F6C\u53D1\u670D\u52A1\u5E02\u573A\u7684\u8BF7\u6C42\uFF0C\u4E0D\u8F6C\u53D1 ${apiName}` });
|
|
1245
|
+
}, respond(async (req) => {
|
|
1246
|
+
const body = requireOk(await wechat.postJson("wxa-dev-logic/jsoperatewxdata", { data: JSON.stringify(req.body.data) }), "\u670D\u52A1\u5E02\u573A\u8C03\u7528\u5931\u8D25");
|
|
1247
|
+
if (typeof body.data !== "string") throw new Error("\u670D\u52A1\u5E02\u573A\u8C03\u7528\u5931\u8D25\uFF1A\u5FAE\u4FE1\u6CA1\u6709\u8FD4\u56DE\u7ED3\u679C");
|
|
1248
|
+
return { data: JSON.parse(body.data) };
|
|
1249
|
+
}));
|
|
1250
|
+
router.post("/verify-plugin", express.json(), respond(async (req) => {
|
|
1251
|
+
const body = requireOk(await wechat.postJson("wxa-dev-logic/verifyplugin", { ext_info: JSON.stringify(req.body?.data || {}) }), "\u63D2\u4EF6\u6821\u9A8C\u5931\u8D25");
|
|
1252
|
+
return { data: { list: body.list || [] } };
|
|
1253
|
+
}));
|
|
1254
|
+
router.post("/batch-get-contact", express.json(), respond(async (req) => {
|
|
1255
|
+
const body = requireOk(await wechat.postJson("wxa-dev-logic/batchgetcontact", { appid_list: req.body?.appIds || [] }), "\u83B7\u53D6\u63D2\u4EF6\u4FE1\u606F\u5931\u8D25");
|
|
1256
|
+
const contacts = body.contact_list || [];
|
|
1257
|
+
return { contactList: contacts.filter((contact) => contact.plugin_info).map((contact) => ({ appId: contact.appid, plugin_list: contact.plugin_info.plugin_list })) };
|
|
1258
|
+
}));
|
|
871
1259
|
return router;
|
|
872
1260
|
}
|
|
873
1261
|
function onlyLoopback(req, res, next) {
|
|
874
1262
|
if (!LOOPBACK_ADDRESSES.includes(req.socket.remoteAddress || "") || !LOOPBACK_HOSTS.includes(req.hostname)) {
|
|
875
|
-
res.status(403).json({ error: "\
|
|
1263
|
+
res.status(403).json({ error: "\u5F00\u53D1\u8005\u767B\u5F55\u6001\u63A5\u53E3\u53EA\u5141\u8BB8\u672C\u673A\u8BBF\u95EE" });
|
|
876
1264
|
return;
|
|
877
1265
|
}
|
|
878
1266
|
next();
|
|
879
1267
|
}
|
|
880
1268
|
function respond(action) {
|
|
881
|
-
return async (
|
|
1269
|
+
return async (req, res) => {
|
|
882
1270
|
try {
|
|
883
|
-
res.json(await action());
|
|
1271
|
+
res.json(await action(req));
|
|
884
1272
|
} catch (error) {
|
|
885
1273
|
res.status(500).json({ error: error.message });
|
|
886
1274
|
}
|
|
@@ -1007,24 +1395,37 @@ var DevServer = class {
|
|
|
1007
1395
|
this.sockets = null;
|
|
1008
1396
|
this.watcher = null;
|
|
1009
1397
|
this.address = "";
|
|
1398
|
+
this.lanAccess = false;
|
|
1010
1399
|
/** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
|
|
1011
|
-
this.
|
|
1400
|
+
this.fileSessions = /* @__PURE__ */ new Map();
|
|
1012
1401
|
this.logger = options.logger || console;
|
|
1013
1402
|
this.project = new MiniProgram(path4.resolve(options.projectPath));
|
|
1014
1403
|
this.baseLib = resolveBaseLib({ version: options.baseLibVersion, cacheDir: options.baseLibCacheDir });
|
|
1015
1404
|
if (!this.baseLib.compatible) {
|
|
1016
1405
|
this.logger.warn(`[wxa-runner] \u57FA\u7840\u5E93 ${this.baseLib.version} \u9AD8\u4E8E\u5DF2\u9A8C\u8BC1\u7684\u7248\u672C\u8303\u56F4\uFF0C\u53EF\u80FD\u65E0\u6CD5\u6B63\u5E38\u8FD0\u884C`);
|
|
1017
1406
|
}
|
|
1018
|
-
|
|
1407
|
+
if (options.realLogin !== void 0 && options.devtoolsLogin === void 0) {
|
|
1408
|
+
this.logger.warn("[wxa-runner] realLogin\uFF08--real-login\uFF09\u5DF2\u6539\u540D\u4E3A devtoolsLogin\uFF08--devtools-login\uFF09\uFF0C\u65E7\u540D\u4EE5\u540E\u4F1A\u79FB\u9664");
|
|
1409
|
+
}
|
|
1410
|
+
this.devtoolsLogin = (options.devtoolsLogin ?? options.realLogin) === true;
|
|
1411
|
+
this.ticketSource = this.devtoolsLogin ? developerTicketSource(this.project.projectPath, this.logger) : null;
|
|
1412
|
+
this.plugins = new PluginStore(this.ticketSource && new WechatDevClient(this.project.appid, this.ticketSource), this.logger);
|
|
1019
1413
|
this.device = { ...DEFAULT_DEVICE, ...options.device };
|
|
1020
1414
|
this.builder = new AppBuilder(this.project);
|
|
1021
1415
|
this.setupFile = resolveSetupFile(options.setup, this.project.projectPath);
|
|
1022
1416
|
this.setupEnv = collectSetupEnv(process.env);
|
|
1023
1417
|
this.app = this.createApp();
|
|
1024
1418
|
}
|
|
1419
|
+
/** 本机访问的地址 */
|
|
1025
1420
|
get url() {
|
|
1026
1421
|
return this.address;
|
|
1027
1422
|
}
|
|
1423
|
+
/** 局域网里其他设备访问的地址,每块网卡一个;只监听本机时为空 */
|
|
1424
|
+
get networkUrls() {
|
|
1425
|
+
if (!this.lanAccess) return [];
|
|
1426
|
+
const { port } = new URL(this.address);
|
|
1427
|
+
return Object.values(os.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
|
|
1428
|
+
}
|
|
1028
1429
|
async start() {
|
|
1029
1430
|
try {
|
|
1030
1431
|
const build2 = await this.builder.build();
|
|
@@ -1034,7 +1435,11 @@ var DevServer = class {
|
|
|
1034
1435
|
${error.message}`);
|
|
1035
1436
|
}
|
|
1036
1437
|
const server = http.createServer(this.app);
|
|
1037
|
-
this.sockets = new WebSocketServer({ noServer: true });
|
|
1438
|
+
this.sockets = new WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
|
|
1439
|
+
this.sockets.on("connection", (socket) => {
|
|
1440
|
+
socket.on("error", () => void 0);
|
|
1441
|
+
if (this.options.forwardConsole) printBrowserReports(socket, this.logger);
|
|
1442
|
+
});
|
|
1038
1443
|
server.on("upgrade", (req, socket, head) => {
|
|
1039
1444
|
if (req.url?.startsWith("/__wxa__/ws")) {
|
|
1040
1445
|
this.sockets.handleUpgrade(req, socket, head, (ws) => this.sockets.emit("connection", ws, req));
|
|
@@ -1043,15 +1448,12 @@ ${error.message}`);
|
|
|
1043
1448
|
}
|
|
1044
1449
|
});
|
|
1045
1450
|
const host = this.options.host || "localhost";
|
|
1046
|
-
await
|
|
1047
|
-
server.once("error", reject);
|
|
1048
|
-
server.listen(this.options.port ?? 3e3, host, () => resolve());
|
|
1049
|
-
});
|
|
1451
|
+
const port = await listen(server, this.options.port, host, this.logger);
|
|
1050
1452
|
this.server = server;
|
|
1051
|
-
|
|
1052
|
-
this.address = `http://${host}:${port}`;
|
|
1453
|
+
this.lanAccess = LOCAL_HOST_OF_ANY_ADDRESS.has(host);
|
|
1454
|
+
this.address = `http://${LOCAL_HOST_OF_ANY_ADDRESS.get(host) ?? host}:${port}`;
|
|
1053
1455
|
if (this.options.watch !== false) this.startWatching();
|
|
1054
|
-
this.ticketSource?.().then(requireUsableTicket).catch((error) => this.logger.warn(`[wxa-runner] \
|
|
1456
|
+
this.ticketSource?.().then(requireUsableTicket).catch((error) => this.logger.warn(`[wxa-runner] \u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\u6682\u4E0D\u53EF\u7528\uFF08--devtools-login\uFF09\uFF1A${error.message}`));
|
|
1055
1457
|
return this.address;
|
|
1056
1458
|
}
|
|
1057
1459
|
async close() {
|
|
@@ -1064,6 +1466,7 @@ ${error.message}`);
|
|
|
1064
1466
|
server.closeAllConnections();
|
|
1065
1467
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
1066
1468
|
}
|
|
1469
|
+
this.fileSessions.clear();
|
|
1067
1470
|
}
|
|
1068
1471
|
createApp() {
|
|
1069
1472
|
const app = express2();
|
|
@@ -1078,9 +1481,15 @@ ${error.message}`);
|
|
|
1078
1481
|
});
|
|
1079
1482
|
app.get("/__wxa__/app-info.json", async (_req, res) => {
|
|
1080
1483
|
noStore(res);
|
|
1081
|
-
const base = {
|
|
1484
|
+
const base = {
|
|
1485
|
+
appid: this.project.appid,
|
|
1486
|
+
baseLibVersion: this.baseLib.version,
|
|
1487
|
+
device: this.device,
|
|
1488
|
+
devtoolsLogin: this.devtoolsLogin,
|
|
1489
|
+
forwardConsole: this.options.forwardConsole ?? false
|
|
1490
|
+
};
|
|
1082
1491
|
try {
|
|
1083
|
-
const build2 = await this.builder.build();
|
|
1492
|
+
const build2 = await this.plugins.refresh(await this.builder.build());
|
|
1084
1493
|
const info = { ...base, version: build2.version, wxConfig: build2.wxConfig, pages: build2.pages };
|
|
1085
1494
|
if (this.setupFile) info.setup = await this.setupInfo(this.setupFile);
|
|
1086
1495
|
res.json(info);
|
|
@@ -1116,7 +1525,19 @@ ${error.message}`);
|
|
|
1116
1525
|
app.get(/^\/__wxa__\/webview\/(.+)\.html$/, this.withBuild((build2, req, res, next) => {
|
|
1117
1526
|
const pagePath = decodeURIComponent(req.params[0]);
|
|
1118
1527
|
if (!build2.pages.includes(pagePath)) return next();
|
|
1119
|
-
|
|
1528
|
+
const pluginPage = findPluginPage(build2, pagePath);
|
|
1529
|
+
res.type("html").send(pluginPage ? pluginWebviewHtml(build2, pluginPage.plugin, pluginPage.innerPath, pagePath, Number(req.query.id)) : webviewHtml(build2, pagePath, Number(req.query.id), { hot: this.hotEnabled }));
|
|
1530
|
+
}));
|
|
1531
|
+
app.get("/__wxa__/plugin/:provider/:version/:file(appservice.js|pageframe.js)", this.withBuild((build2, req, res, next) => {
|
|
1532
|
+
const { provider, version, file } = req.params;
|
|
1533
|
+
const code = build2.plugins.find((plugin) => plugin.provider === provider && plugin.version === version)?.files.get(`/${file}`);
|
|
1534
|
+
if (!code) return next();
|
|
1535
|
+
res.type("js").send(code);
|
|
1536
|
+
}));
|
|
1537
|
+
app.get(/^\/__plugin__\/([^/]+)(\/.+)$/, this.withBuild((build2, req, res, next) => {
|
|
1538
|
+
const file = build2.plugins.find((plugin) => plugin.provider === req.params[0])?.files.get(decodeURIComponent(req.params[1]));
|
|
1539
|
+
if (!file) return next();
|
|
1540
|
+
res.type(path4.extname(req.params[1]) || "application/octet-stream").send(file);
|
|
1120
1541
|
}));
|
|
1121
1542
|
app.get(/^\/__wxa__\/module\/(.+\.js)$/, this.withBuild((build2, req, res) => {
|
|
1122
1543
|
const id = decodeURIComponent(req.params[0]);
|
|
@@ -1145,7 +1566,18 @@ ${error.message}`);
|
|
|
1145
1566
|
app.get("/__wxa__/page-frame.js", this.withBuild((build2, _req, res) => {
|
|
1146
1567
|
res.type("js").send(build2.pageFrame);
|
|
1147
1568
|
}));
|
|
1148
|
-
app.
|
|
1569
|
+
app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
|
|
1570
|
+
const file = path4.join(this.runtimeDir(), "frame.js.map");
|
|
1571
|
+
if (!fs4.existsSync(file)) return next();
|
|
1572
|
+
res.type("json").send(ignoreConsoleHook(fs4.readFileSync(file, "utf8")));
|
|
1573
|
+
});
|
|
1574
|
+
app.use("/__wxa__/runtime", express2.static(this.runtimeDir(), {
|
|
1575
|
+
cacheControl: false,
|
|
1576
|
+
// Service Worker 默认只能接管自己所在目录下的页面,宿主页面在根路径
|
|
1577
|
+
setHeaders: (res, file) => {
|
|
1578
|
+
if (path4.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
|
|
1579
|
+
}
|
|
1580
|
+
}));
|
|
1149
1581
|
const sourceMaps = /* @__PURE__ */ new Map();
|
|
1150
1582
|
app.get(/^\/__baselib__\/(.+\.js)\.map$/, (req, res, next) => {
|
|
1151
1583
|
const file = path4.resolve(this.baseLib.dir, req.params[0]);
|
|
@@ -1159,16 +1591,45 @@ ${error.message}`);
|
|
|
1159
1591
|
if (file.endsWith(".js")) res.setHeader("SourceMap", `${path4.basename(file)}.map`);
|
|
1160
1592
|
}
|
|
1161
1593
|
}));
|
|
1162
|
-
app.
|
|
1163
|
-
app.
|
|
1594
|
+
app.use(PROXY_PREFIX, express2.raw({ type: () => true, limit: "100mb" }), handleProxy);
|
|
1595
|
+
app.use(MOCK_PREFIX, (_req, res) => {
|
|
1596
|
+
res.status(503).type("text").send("\u8FD9\u4E2A\u8BF7\u6C42\u5DF2\u88AB\u62E6\u622A\u89C4\u5219\u62E6\u4E0B\uFF0C\u8FD9\u91CC\u53EA\u7528\u4E8E\u5728 Network \u9762\u677F\u91CC\u663E\u793A\u5B83\u3002Service Worker \u6CA1\u6709\u751F\u6548\uFF08\u5982 DevTools \u52FE\u9009\u4E86 Bypass for network\uFF09\uFF0C\u770B\u4E0D\u5230\u62E6\u622A\u89C4\u5219\u7ED9\u51FA\u7684\u54CD\u5E94");
|
|
1597
|
+
});
|
|
1598
|
+
const sessionFiles = (session) => {
|
|
1599
|
+
if (!/^[a-z0-9-]{1,80}$/i.test(session)) return null;
|
|
1600
|
+
let files = this.fileSessions.get(session);
|
|
1601
|
+
if (!files) {
|
|
1602
|
+
files = new FileStore([`/__wxa__/usr/${session}`, `/__wxa__/tmp/${session}`]);
|
|
1603
|
+
this.fileSessions.set(session, files);
|
|
1604
|
+
}
|
|
1605
|
+
return files;
|
|
1606
|
+
};
|
|
1607
|
+
app.post("/__wxa__/fs/:session", express2.json({ limit: "150mb" }), (req, res) => {
|
|
1608
|
+
const files = sessionFiles(req.params.session);
|
|
1609
|
+
if (!files) {
|
|
1610
|
+
res.status(400).json({ error: `invalid filesystem session ${req.params.session}` });
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
try {
|
|
1614
|
+
res.json({ result: files.execute(req.body.operation, req.body.args || {}) });
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
res.json({ error: error.message });
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
app.post("/__wxa__/tmp/:session", express2.raw({ type: () => true, limit: "100mb" }), (req, res) => {
|
|
1164
1620
|
const type = String(req.query.type || "png");
|
|
1165
1621
|
if (!/^[a-z0-9]+$/i.test(type)) {
|
|
1166
1622
|
res.status(400).type("text").send(`\u975E\u6CD5\u7684\u6587\u4EF6\u7C7B\u578B ${type}`);
|
|
1167
1623
|
return;
|
|
1168
1624
|
}
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
|
|
1625
|
+
const files = sessionFiles(req.params.session);
|
|
1626
|
+
if (!files) {
|
|
1627
|
+
res.status(400).type("text").send(`\u975E\u6CD5\u7684\u4F1A\u8BDD\u6807\u8BC6 ${req.params.session}`);
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
const filePath = `/__wxa__/tmp/${req.params.session}/${randomUUID3()}.${type}`;
|
|
1631
|
+
files.write(filePath, Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0));
|
|
1632
|
+
res.json({ url: `${req.protocol}://${req.get("host")}${filePath}` });
|
|
1172
1633
|
});
|
|
1173
1634
|
app.get("/__wxa__/region/:kind", (req, res) => {
|
|
1174
1635
|
const kind = req.params.kind;
|
|
@@ -1179,14 +1640,16 @@ ${error.message}`);
|
|
|
1179
1640
|
}
|
|
1180
1641
|
res.type("text/plain; charset=utf-8").set("Cache-Control", "public, max-age=86400").send(data);
|
|
1181
1642
|
});
|
|
1182
|
-
app.get("/__wxa__
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1643
|
+
app.get("/__wxa__/:kind(usr|tmp)/:session/*", (req, res) => {
|
|
1644
|
+
const files = this.fileSessions.get(req.params.session);
|
|
1645
|
+
const node = files?.stat(`/__wxa__/${req.params.kind}/${req.params.session}/` + req.params[0]);
|
|
1646
|
+
if (!node || node.dir) {
|
|
1185
1647
|
res.status(404).end();
|
|
1186
1648
|
return;
|
|
1187
1649
|
}
|
|
1188
|
-
|
|
1189
|
-
res.
|
|
1650
|
+
const data = node.data;
|
|
1651
|
+
res.type(path4.extname(req.params[0]));
|
|
1652
|
+
res.setHeader("ETag", `"${node.etag}"`);
|
|
1190
1653
|
res.setHeader("Accept-Ranges", "bytes");
|
|
1191
1654
|
const ranges = req.range(data.length);
|
|
1192
1655
|
if (ranges === -1) {
|
|
@@ -1202,10 +1665,17 @@ ${error.message}`);
|
|
|
1202
1665
|
}
|
|
1203
1666
|
res.send(data);
|
|
1204
1667
|
});
|
|
1205
|
-
app.delete("/__wxa__/tmp/:
|
|
1206
|
-
|
|
1668
|
+
app.delete("/__wxa__/tmp/:session/*", (req, res) => {
|
|
1669
|
+
try {
|
|
1670
|
+
const files = this.fileSessions.get(req.params.session);
|
|
1671
|
+
if (!files) throw new Error("file not exist");
|
|
1672
|
+
files.remove(`/__wxa__/tmp/${req.params.session}/` + req.params[0], { dir: false });
|
|
1673
|
+
res.status(204).end();
|
|
1674
|
+
} catch {
|
|
1675
|
+
res.status(404).end();
|
|
1676
|
+
}
|
|
1207
1677
|
});
|
|
1208
|
-
if (this.ticketSource) app.use("/__wxa__/api",
|
|
1678
|
+
if (this.ticketSource) app.use("/__wxa__/api", devtoolsLoginRouter(this.project.appid, this.ticketSource));
|
|
1209
1679
|
app.use("/__wxa__/webview", express2.static(root));
|
|
1210
1680
|
app.use(express2.static(root));
|
|
1211
1681
|
app.use((error, _req, res, _next) => {
|
|
@@ -1223,13 +1693,17 @@ ${error.message}`);
|
|
|
1223
1693
|
return { env: this.setupEnv, error: error.message };
|
|
1224
1694
|
}
|
|
1225
1695
|
}
|
|
1696
|
+
/** 构建结果并入这次打开小程序时决定用的插件 */
|
|
1697
|
+
async currentBuild() {
|
|
1698
|
+
return this.plugins.current(await this.builder.build());
|
|
1699
|
+
}
|
|
1226
1700
|
/** 需要构建结果的路由;构建失败时返回错误文本 */
|
|
1227
1701
|
withBuild(handler) {
|
|
1228
1702
|
return async (req, res, next) => {
|
|
1229
1703
|
res.setHeader("Cache-Control", "no-store");
|
|
1230
1704
|
let build2;
|
|
1231
1705
|
try {
|
|
1232
|
-
build2 = await this.
|
|
1706
|
+
build2 = await this.currentBuild();
|
|
1233
1707
|
} catch (error) {
|
|
1234
1708
|
res.status(500).type("text").send(error.message);
|
|
1235
1709
|
return;
|
|
@@ -1288,6 +1762,38 @@ ${message}`);
|
|
|
1288
1762
|
});
|
|
1289
1763
|
}
|
|
1290
1764
|
};
|
|
1765
|
+
var DEFAULT_PORT = 3e3;
|
|
1766
|
+
var LOCAL_HOST_OF_ANY_ADDRESS = /* @__PURE__ */ new Map([["0.0.0.0", "127.0.0.1"], ["::", "localhost"]]);
|
|
1767
|
+
async function listen(server, port, host, logger) {
|
|
1768
|
+
for (let candidate = port ?? DEFAULT_PORT; ; candidate++) {
|
|
1769
|
+
const taken = LOCAL_HOST_OF_ANY_ADDRESS.has(host) && await takenOnLoopback(candidate) || !await bind(server, candidate, host);
|
|
1770
|
+
if (!taken) {
|
|
1771
|
+
const actual = server.address().port;
|
|
1772
|
+
if (port === void 0 && actual !== DEFAULT_PORT) logger.warn(`[wxa-runner] \u7AEF\u53E3 ${DEFAULT_PORT} \u5DF2\u88AB\u5360\u7528\uFF0C\u6539\u7528 ${actual}`);
|
|
1773
|
+
return actual;
|
|
1774
|
+
}
|
|
1775
|
+
if (port !== void 0) throw new Error(`\u7AEF\u53E3 ${port} \u5DF2\u88AB\u5360\u7528\uFF1A\u5148\u5173\u95ED\u5360\u7528\u5B83\u7684\u8FDB\u7A0B\uFF0C\u6216\u7528 --port \u6362\u4E00\u4E2A\u7AEF\u53E3`);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
function bind(server, port, host) {
|
|
1779
|
+
return new Promise((resolve, reject) => {
|
|
1780
|
+
const onError = (error) => error.code === "EADDRINUSE" ? resolve(false) : reject(error);
|
|
1781
|
+
server.once("error", onError);
|
|
1782
|
+
server.listen(port, host, () => {
|
|
1783
|
+
server.off("error", onError);
|
|
1784
|
+
resolve(true);
|
|
1785
|
+
});
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
async function takenOnLoopback(port) {
|
|
1789
|
+
for (const address of ["127.0.0.1", "::1"]) {
|
|
1790
|
+
const probe = net.createServer();
|
|
1791
|
+
const free = await bind(probe, port, address).catch(() => true);
|
|
1792
|
+
if (!free) return true;
|
|
1793
|
+
await new Promise((resolve) => probe.close(resolve));
|
|
1794
|
+
}
|
|
1795
|
+
return false;
|
|
1796
|
+
}
|
|
1291
1797
|
function resolveSetupFile(option, projectPath) {
|
|
1292
1798
|
if (option === false) return null;
|
|
1293
1799
|
if (!option) return findSetupFile(projectPath);
|