@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.js
CHANGED
|
@@ -39,13 +39,139 @@ __export(src_exports, {
|
|
|
39
39
|
module.exports = __toCommonJS(src_exports);
|
|
40
40
|
|
|
41
41
|
// src/server/DevServer.ts
|
|
42
|
-
var
|
|
42
|
+
var import_crypto4 = require("crypto");
|
|
43
|
+
|
|
44
|
+
// src/server/FileStore.ts
|
|
45
|
+
var import_crypto = require("crypto");
|
|
46
|
+
var FileStore = class {
|
|
47
|
+
/** @param roots 预先创建的目录(如用户目录)。rmdir 不能删除它们和它们的上级目录:删掉之后再写 USER_DATA_PATH 都会失败 */
|
|
48
|
+
constructor(roots = []) {
|
|
49
|
+
this.etagPrefix = (0, import_crypto.randomUUID)();
|
|
50
|
+
this.version = 0;
|
|
51
|
+
this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" }]]);
|
|
52
|
+
this.roots = roots.map((root) => this.normalize(root));
|
|
53
|
+
for (const root of this.roots) this.mkdir(root, true);
|
|
54
|
+
}
|
|
55
|
+
normalize(filePath) {
|
|
56
|
+
const stripped = String(filePath || "").replace(/^[a-z]+:\/\/[^/]*/i, "");
|
|
57
|
+
const parts = [];
|
|
58
|
+
for (const part of stripped.split("/")) {
|
|
59
|
+
if (!part || part === ".") continue;
|
|
60
|
+
if (part === "..") parts.pop();
|
|
61
|
+
else parts.push(part);
|
|
62
|
+
}
|
|
63
|
+
return "/" + parts.join("/");
|
|
64
|
+
}
|
|
65
|
+
stat(filePath) {
|
|
66
|
+
return this.nodes.get(this.normalize(filePath));
|
|
67
|
+
}
|
|
68
|
+
mkdir(dirPath, recursive = false) {
|
|
69
|
+
const target = this.normalize(dirPath);
|
|
70
|
+
if (this.nodes.has(target)) throw new Error(`file already exists ${dirPath}`);
|
|
71
|
+
const parent = target.slice(0, target.lastIndexOf("/")) || "/";
|
|
72
|
+
if (!this.nodes.get(parent)?.dir) {
|
|
73
|
+
if (!recursive) throw new Error(`no such file or directory ${dirPath}`);
|
|
74
|
+
this.mkdir(parent, true);
|
|
75
|
+
}
|
|
76
|
+
this.nodes.set(target, { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" });
|
|
77
|
+
}
|
|
78
|
+
write(filePath, input, append = false) {
|
|
79
|
+
const data = Buffer.from(input);
|
|
80
|
+
const target = this.normalize(filePath);
|
|
81
|
+
const parent = target.slice(0, target.lastIndexOf("/")) || "/";
|
|
82
|
+
if (!this.nodes.get(parent)?.dir) throw new Error(`no such file or directory ${filePath}`);
|
|
83
|
+
const existing = this.nodes.get(target);
|
|
84
|
+
if (existing?.dir) throw new Error(`illegal operation on a directory ${filePath}`);
|
|
85
|
+
const contents = append && existing ? Buffer.concat([existing.data, data]) : data;
|
|
86
|
+
this.nodes.set(target, { dir: false, data: contents, mtime: Date.now(), etag: `${this.etagPrefix}-${++this.version}` });
|
|
87
|
+
}
|
|
88
|
+
read(filePath) {
|
|
89
|
+
const node = this.stat(filePath);
|
|
90
|
+
if (!node) throw new Error(`no such file or directory, open ${filePath}`);
|
|
91
|
+
if (node.dir) throw new Error(`illegal operation on a directory ${filePath}`);
|
|
92
|
+
return node.data;
|
|
93
|
+
}
|
|
94
|
+
readdir(dirPath) {
|
|
95
|
+
const target = this.normalize(dirPath);
|
|
96
|
+
if (!this.nodes.get(target)?.dir) throw new Error(`no such file or directory ${dirPath}`);
|
|
97
|
+
const prefix = target === "/" ? "/" : target + "/";
|
|
98
|
+
return [...this.nodes.keys()].filter((key) => key.startsWith(prefix) && key !== target && !key.slice(prefix.length).includes("/")).map((key) => key.slice(prefix.length));
|
|
99
|
+
}
|
|
100
|
+
remove(filePath, options) {
|
|
101
|
+
const target = this.normalize(filePath);
|
|
102
|
+
const node = this.nodes.get(target);
|
|
103
|
+
if (!node) throw new Error(`no such file or directory ${filePath}`);
|
|
104
|
+
if (node.dir !== options.dir) throw new Error(options.dir ? `not a directory ${filePath}` : `operation not permitted ${filePath}`);
|
|
105
|
+
if (node.dir) {
|
|
106
|
+
const children = this.readdir(target);
|
|
107
|
+
if (children.length && !options.recursive) throw new Error(`directory not empty ${filePath}`);
|
|
108
|
+
for (const key of [...this.nodes.keys()]) if (key.startsWith(target + "/")) this.nodes.delete(key);
|
|
109
|
+
}
|
|
110
|
+
this.nodes.delete(target);
|
|
111
|
+
}
|
|
112
|
+
execute(operation, args) {
|
|
113
|
+
switch (operation) {
|
|
114
|
+
case "readFile": {
|
|
115
|
+
const data = this.read(args.filePath);
|
|
116
|
+
return args.encoding === void 0 ? { data: data.toString("base64"), binary: true } : { data: data.toString(args.encoding) };
|
|
117
|
+
}
|
|
118
|
+
case "writeFile":
|
|
119
|
+
case "appendFile": {
|
|
120
|
+
const data = Buffer.from(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8");
|
|
121
|
+
this.write(args.filePath, data, operation === "appendFile");
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
case "copyFile":
|
|
125
|
+
this.write(args.destPath, this.read(args.srcPath));
|
|
126
|
+
return {};
|
|
127
|
+
case "access":
|
|
128
|
+
if (!this.stat(args.path)) throw new Error(`no such file or directory ${args.path}`);
|
|
129
|
+
return {};
|
|
130
|
+
case "stat": {
|
|
131
|
+
const node = this.stat(args.path);
|
|
132
|
+
if (!node) throw new Error(`no such file or directory ${args.path}`);
|
|
133
|
+
const stats = {
|
|
134
|
+
mode: node.dir ? 16877 : 33188,
|
|
135
|
+
size: node.data.length,
|
|
136
|
+
lastAccessedTime: Math.floor(node.mtime / 1e3),
|
|
137
|
+
lastModifiedTime: Math.floor(node.mtime / 1e3)
|
|
138
|
+
};
|
|
139
|
+
return { stats, ...stats };
|
|
140
|
+
}
|
|
141
|
+
case "getFileInfo":
|
|
142
|
+
return { size: this.read(args.filePath).length };
|
|
143
|
+
case "mkdir":
|
|
144
|
+
this.mkdir(args.dirPath, !!args.recursive);
|
|
145
|
+
return {};
|
|
146
|
+
case "readdir":
|
|
147
|
+
return { files: this.readdir(args.dirPath) };
|
|
148
|
+
case "unlink":
|
|
149
|
+
this.remove(args.filePath, { dir: false });
|
|
150
|
+
return {};
|
|
151
|
+
case "rmdir": {
|
|
152
|
+
const target = this.normalize(args.dirPath);
|
|
153
|
+
const prefix = target === "/" ? "/" : target + "/";
|
|
154
|
+
if (this.roots.some((root) => root === target || root.startsWith(prefix))) {
|
|
155
|
+
throw new Error(`operation not permitted ${args.dirPath}`);
|
|
156
|
+
}
|
|
157
|
+
this.remove(args.dirPath, { dir: true, recursive: !!args.recursive });
|
|
158
|
+
return {};
|
|
159
|
+
}
|
|
160
|
+
default:
|
|
161
|
+
throw new Error(`unsupported filesystem operation ${operation}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// src/server/DevServer.ts
|
|
43
167
|
var import_fs4 = __toESM(require("fs"));
|
|
44
168
|
var import_http = __toESM(require("http"));
|
|
169
|
+
var import_net = __toESM(require("net"));
|
|
170
|
+
var import_os = __toESM(require("os"));
|
|
45
171
|
var import_path4 = __toESM(require("path"));
|
|
46
172
|
var import_express2 = __toESM(require("express"));
|
|
47
173
|
var import_ws2 = require("ws");
|
|
48
|
-
var
|
|
174
|
+
var import_wxa_runner_extractor5 = require("@open-wxa/wxa-runner-extractor");
|
|
49
175
|
|
|
50
176
|
// src/project/AppBuilder.ts
|
|
51
177
|
var import_wxa_runner_compiler = require("@open-wxa/wxa-runner-compiler");
|
|
@@ -481,6 +607,194 @@ function readJsonFile(file) {
|
|
|
481
607
|
}
|
|
482
608
|
}
|
|
483
609
|
|
|
610
|
+
// src/runtime/networkPanel.ts
|
|
611
|
+
var PROXY_PREFIX = "/__wxa__/proxy/";
|
|
612
|
+
var MOCK_PREFIX = "/__wxa__/mock/";
|
|
613
|
+
function fromServerPath(prefix, serverPath) {
|
|
614
|
+
if (!serverPath.startsWith(prefix)) return null;
|
|
615
|
+
const match = /^(https?)\/([^/?#]+)(.*)$/.exec(serverPath.slice(prefix.length));
|
|
616
|
+
if (!match) return null;
|
|
617
|
+
const url = `${match[1]}://${match[2]}${match[3]}`;
|
|
618
|
+
try {
|
|
619
|
+
new URL(url);
|
|
620
|
+
return url;
|
|
621
|
+
} catch {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/server/plugins.ts
|
|
627
|
+
var import_wxa_runner_extractor2 = require("@open-wxa/wxa-runner-extractor");
|
|
628
|
+
|
|
629
|
+
// src/server/wechatDev.ts
|
|
630
|
+
var import_wxa_runner_extractor = require("@open-wxa/wxa-runner-extractor");
|
|
631
|
+
var BASE_URL = "https://servicewechat.com";
|
|
632
|
+
var CLIENT_VERSION = "1021810240";
|
|
633
|
+
var DEFAULT_TIMEOUT = 1e4;
|
|
634
|
+
var ERROR_HINTS = {
|
|
635
|
+
// 40001:开发者工具刷新过 ticket,旧的随即作废
|
|
636
|
+
40001: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
|
|
637
|
+
40029: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
|
|
638
|
+
85023: "\uFF08\u5F53\u524D\u5FAE\u4FE1\u8D26\u53F7\u4E0D\u662F\u8BE5\u5C0F\u7A0B\u5E8F\u7684\u5F00\u53D1\u8005\uFF09"
|
|
639
|
+
};
|
|
640
|
+
function requireUsableTicket(ticket) {
|
|
641
|
+
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 ${import_wxa_runner_extractor.TICKET_ENV}`);
|
|
642
|
+
if (ticket.expiredAt !== void 0 && ticket.expiredAt <= Date.now()) {
|
|
643
|
+
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");
|
|
644
|
+
}
|
|
645
|
+
return ticket.newticket;
|
|
646
|
+
}
|
|
647
|
+
function explainErrcode(errcode) {
|
|
648
|
+
return errcode !== void 0 && ERROR_HINTS[errcode] || "";
|
|
649
|
+
}
|
|
650
|
+
function requireOk(body, action) {
|
|
651
|
+
const errcode = body.baseresponse?.errcode;
|
|
652
|
+
if (errcode !== 0) throw new Error(`${action} ${errcode}: ${body.baseresponse?.errmsg || "\u672A\u77E5\u9519\u8BEF"}${explainErrcode(errcode)}`);
|
|
653
|
+
return body;
|
|
654
|
+
}
|
|
655
|
+
var WechatDevClient = class {
|
|
656
|
+
/**
|
|
657
|
+
* @param findTicket 每次请求都重新取:开发者工具重新登录、切换账号后不用重启服务器
|
|
658
|
+
*/
|
|
659
|
+
constructor(appid, findTicket) {
|
|
660
|
+
this.appid = appid;
|
|
661
|
+
this.findTicket = findTicket;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* POST JSON 到 path(如 wxa-dev-logic/jslogin),带上 appid 和开发者 ticket
|
|
665
|
+
*
|
|
666
|
+
* @throws 没有可用的登录态、超时、连不上、HTTP 状态不是 2xx 时,消息是给用户看的原因
|
|
667
|
+
*/
|
|
668
|
+
async post(path5, body, timeout = DEFAULT_TIMEOUT) {
|
|
669
|
+
const query = new URLSearchParams({ appid: this.appid, newticket: requireUsableTicket(await this.findTicket()), clientversion: CLIENT_VERSION });
|
|
670
|
+
const response = await fetch(`${BASE_URL}/${path5}?${query}`, {
|
|
671
|
+
method: "POST",
|
|
672
|
+
headers: { "Content-Type": "application/json" },
|
|
673
|
+
body: JSON.stringify(body),
|
|
674
|
+
signal: AbortSignal.timeout(timeout)
|
|
675
|
+
}).catch((error) => {
|
|
676
|
+
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}`);
|
|
677
|
+
});
|
|
678
|
+
if (!response.ok) throw new Error(`\u5FAE\u4FE1\u670D\u52A1\u5668\u8FD4\u56DE HTTP ${response.status}`);
|
|
679
|
+
return response;
|
|
680
|
+
}
|
|
681
|
+
/** 同 post,返回解析后的 JSON */
|
|
682
|
+
async postJson(path5, body) {
|
|
683
|
+
return (await this.post(path5, body)).json();
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// src/server/plugins.ts
|
|
688
|
+
var DOWNLOAD_TIMEOUT = 3e4;
|
|
689
|
+
var PACKAGE_MAGIC = 190;
|
|
690
|
+
var PluginStore = class {
|
|
691
|
+
/**
|
|
692
|
+
* @param wechat 开启 --devtools-login 时才有,没有时不下载插件
|
|
693
|
+
*/
|
|
694
|
+
constructor(wechat, logger) {
|
|
695
|
+
this.wechat = wechat;
|
|
696
|
+
this.logger = logger;
|
|
697
|
+
/** provider@version → 下载结果;失败的不留,下次打开小程序时重试 */
|
|
698
|
+
this.downloads = /* @__PURE__ */ new Map();
|
|
699
|
+
/**
|
|
700
|
+
* 这次打开小程序时决定用哪些插件。同一次打开的逻辑层、业务模块、渲染层等请求都按它处理:
|
|
701
|
+
* 各自去下载的话,下载失败时每个请求都会重试,结果还可能前后不一,页面列表和逻辑层对不上
|
|
702
|
+
*/
|
|
703
|
+
this.selection = null;
|
|
704
|
+
/** 没开 --devtools-login 的提示每个插件只说一次 */
|
|
705
|
+
this.warned = /* @__PURE__ */ new Set();
|
|
706
|
+
}
|
|
707
|
+
/** 打开小程序时(宿主取 app-info)调用:重新决定用哪些插件,下载过的直接用,上次失败的重试 */
|
|
708
|
+
async refresh(build2) {
|
|
709
|
+
this.selection = this.select(build2);
|
|
710
|
+
return withPlugins(build2, await this.selection);
|
|
711
|
+
}
|
|
712
|
+
/** 其余请求沿用这次打开时的决定,不触发下载;还没决定过时(如直接请求逻辑层)先决定一次 */
|
|
713
|
+
async current(build2) {
|
|
714
|
+
this.selection ??= this.select(build2);
|
|
715
|
+
return withPlugins(build2, await this.selection);
|
|
716
|
+
}
|
|
717
|
+
/** app.json 声明的插件;下载不了的跳过,在终端说明原因,小程序自己的页面照常可用 */
|
|
718
|
+
async select(build2) {
|
|
719
|
+
const declared = build2.wxConfig.plugins || {};
|
|
720
|
+
const results = await Promise.all(Object.entries(declared).map(
|
|
721
|
+
([alias, { provider, version }]) => provider && version ? this.load(alias, provider, version) : null
|
|
722
|
+
));
|
|
723
|
+
return results.filter((plugin) => !!plugin);
|
|
724
|
+
}
|
|
725
|
+
async load(alias, provider, version) {
|
|
726
|
+
const label = `\u63D2\u4EF6 ${alias}\uFF08${provider}@${version}\uFF09`;
|
|
727
|
+
if (!this.wechat) {
|
|
728
|
+
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`);
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
const key = `${provider}@${version}`;
|
|
732
|
+
let download = this.downloads.get(key);
|
|
733
|
+
if (!download) {
|
|
734
|
+
download = this.download(this.wechat, alias, provider, version);
|
|
735
|
+
this.downloads.set(key, download);
|
|
736
|
+
download.catch(() => this.downloads.delete(key));
|
|
737
|
+
}
|
|
738
|
+
try {
|
|
739
|
+
return await download;
|
|
740
|
+
} catch (error) {
|
|
741
|
+
this.logger.warn(`[wxa-runner] ${label} \u4E0B\u8F7D\u5931\u8D25\uFF1A${error.message}`);
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/** @throws 没有登录态、连不上、微信拒绝或返回的不是插件代码包时,消息是原因 */
|
|
746
|
+
async download(wechat, alias, provider, version) {
|
|
747
|
+
const response = await wechat.post("wxa-dev/getplugincode", { plugin_id: provider, plugin_version: version }, DOWNLOAD_TIMEOUT);
|
|
748
|
+
const data = Buffer.from(await response.arrayBuffer());
|
|
749
|
+
if (data[0] !== PACKAGE_MAGIC) {
|
|
750
|
+
let body = {};
|
|
751
|
+
try {
|
|
752
|
+
body = JSON.parse(data.toString("utf8"));
|
|
753
|
+
} catch {
|
|
754
|
+
}
|
|
755
|
+
if (body.baseresponse?.errcode) requireOk(body, "\u5FAE\u4FE1\u62D2\u7EDD\u4E0B\u8F7D");
|
|
756
|
+
throw new Error("\u5FAE\u4FE1\u6CA1\u6709\u8FD4\u56DE\u63D2\u4EF6\u4EE3\u7801");
|
|
757
|
+
}
|
|
758
|
+
const files = new Map((0, import_wxa_runner_extractor2.readWxvpkgIndex)(data).map((entry) => [entry.name, Buffer.from((0, import_wxa_runner_extractor2.readWxvpkgFile)(data, entry))]));
|
|
759
|
+
const pluginJson = JSON.parse(files.get("/plugin.json")?.toString("utf8") || "{}");
|
|
760
|
+
return {
|
|
761
|
+
alias,
|
|
762
|
+
provider,
|
|
763
|
+
version,
|
|
764
|
+
files,
|
|
765
|
+
pages: Object.values(pluginJson.pages || {}),
|
|
766
|
+
pageConfigs: pluginJson.page || {}
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
warnOnce(key, message) {
|
|
770
|
+
if (this.warned.has(key)) return;
|
|
771
|
+
this.warned.add(key);
|
|
772
|
+
this.logger.warn(message);
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
function withPlugins(build2, plugins) {
|
|
776
|
+
if (!plugins.length) return { ...build2, plugins };
|
|
777
|
+
return {
|
|
778
|
+
...build2,
|
|
779
|
+
plugins,
|
|
780
|
+
pages: [...build2.pages, ...plugins.flatMap((plugin) => plugin.pages)],
|
|
781
|
+
wxConfig: {
|
|
782
|
+
...build2.wxConfig,
|
|
783
|
+
page: { ...build2.wxConfig.page, ...Object.assign({}, ...plugins.map((plugin) => plugin.pageConfigs)) }
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function findPluginPage(build2, pagePath) {
|
|
788
|
+
for (const plugin of build2.plugins) {
|
|
789
|
+
const prefix = `__plugin__/${plugin.provider}/`;
|
|
790
|
+
if (plugin.pages.includes(pagePath)) return { plugin, innerPath: pagePath.slice(prefix.length) };
|
|
791
|
+
}
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
function pluginScriptUrl(plugin, file) {
|
|
795
|
+
return `/__wxa__/plugin/${encodeURIComponent(plugin.provider)}/${encodeURIComponent(plugin.version)}/${file}`;
|
|
796
|
+
}
|
|
797
|
+
|
|
484
798
|
// src/server/html.ts
|
|
485
799
|
function hostHtml() {
|
|
486
800
|
return `<!DOCTYPE html>
|
|
@@ -507,10 +821,11 @@ function appserviceHtml(build2, launch, options) {
|
|
|
507
821
|
...options.isolated ? { isIsolateContext: true } : {}
|
|
508
822
|
};
|
|
509
823
|
const v = build2.version;
|
|
824
|
+
const pluginUrls = build2.plugins.map((plugin) => pluginScriptUrl(plugin, "appservice.js"));
|
|
510
825
|
const moduleUrls = build2.modules.map((id) => `/__wxa__/module/${encodeURI(id)}?v=${v}`);
|
|
511
826
|
const bootstrapUrl = `/__wxa__/service-bootstrap.js?v=${v}`;
|
|
512
827
|
if (options.isolated) {
|
|
513
|
-
const scripts = [...moduleUrls, bootstrapUrl];
|
|
828
|
+
const scripts = [...pluginUrls, ...moduleUrls, bootstrapUrl];
|
|
514
829
|
return `<!DOCTYPE html>
|
|
515
830
|
<html>
|
|
516
831
|
<head>
|
|
@@ -523,7 +838,7 @@ function appserviceHtml(build2, launch, options) {
|
|
|
523
838
|
<body></body>
|
|
524
839
|
</html>`;
|
|
525
840
|
}
|
|
526
|
-
const modules = moduleUrls.map((src) => `<script src="${src}"></script>`).join("\n");
|
|
841
|
+
const modules = [...pluginUrls, ...moduleUrls].map((src) => `<script src="${src}"></script>`).join("\n");
|
|
527
842
|
return `<!DOCTYPE html>
|
|
528
843
|
<html>
|
|
529
844
|
<head>
|
|
@@ -557,29 +872,13 @@ ${empty ? "" : '<script src="/__baselib__/WASubContext.js"></script>\n'}<script>
|
|
|
557
872
|
</html>`;
|
|
558
873
|
}
|
|
559
874
|
function webviewHtml(build2, pagePath, webviewId, options) {
|
|
560
|
-
const pageConfig = build2.wxConfig.page?.[pagePath + ".html"];
|
|
561
|
-
const window = { ...build2.wxConfig.global?.window, ...pageConfig?.window };
|
|
562
|
-
const wxConfig = { ...build2.wxConfig, window };
|
|
563
875
|
const id = Number(webviewId) || 0;
|
|
564
876
|
return `<!DOCTYPE html>
|
|
565
877
|
<html>
|
|
566
878
|
<head>
|
|
567
|
-
|
|
568
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
569
|
-
<title>${escapeHtml(pagePath)}</title>
|
|
570
|
-
<!-- \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 -->
|
|
571
|
-
<style>html{font-size:16px}</style>
|
|
572
|
-
<!-- \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
|
|
573
|
-
scrollbar-width \u4F18\u5148\u4E8E ::-webkit-scrollbar\uFF0C\u9875\u9762\u6837\u5F0F\u4E0D\u4F1A\u518D\u628A\u6EDA\u52A8\u6761\u52A0\u56DE\u6765 -->
|
|
574
|
-
<style>*{scrollbar-width:none}::-webkit-scrollbar{display:none}</style>
|
|
575
|
-
<!-- \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
|
|
576
|
-
\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 -->
|
|
577
|
-
<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>
|
|
578
|
-
<script>var __wxConfig = ${toJs(wxConfig)};</script>
|
|
579
|
-
<script src="/__wxa__/runtime/frame.js" data-kind="webview" data-webview-id="${id}"></script>
|
|
580
|
-
<script src="/__baselib__/WAWebview.js"></script>
|
|
879
|
+
${webviewHead(build2, pagePath, id)}
|
|
581
880
|
${enableHotUpdate(options.hot)}<script src="/__wxa__/page-frame.js?v=${build2.version}"></script>
|
|
582
|
-
${
|
|
881
|
+
${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
583
882
|
</head>
|
|
584
883
|
<body>
|
|
585
884
|
<script>
|
|
@@ -601,7 +900,7 @@ ${window.disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
|
601
900
|
}
|
|
602
901
|
function workerScript(build2, baseLib) {
|
|
603
902
|
const modules = build2.workerModules.map((id) => `/__wxa__/module/${encodeURI(id)}?v=${build2.version}`);
|
|
604
|
-
const prelude = `var __wxConfig = ${toJs(build2.wxConfig)};
|
|
903
|
+
const prelude = `var __wxConfig = ${toJs(build2.wxConfig)}; __wxConfig.env.USER_DATA_PATH = location.origin + "/__wxa__/usr/" + new URL(location.href).searchParams.get("session");
|
|
605
904
|
importScripts('/__wxa__/runtime/worker.js');
|
|
606
905
|
`;
|
|
607
906
|
if (!baseLib.hasWorker) {
|
|
@@ -615,6 +914,42 @@ self.console = __wxaConsole;
|
|
|
615
914
|
importScripts(${modules.map((url) => toJs(url)).join(", ")});
|
|
616
915
|
`;
|
|
617
916
|
}
|
|
917
|
+
function pluginWebviewHtml(build2, plugin, innerPath, pagePath, webviewId) {
|
|
918
|
+
const id = Number(webviewId) || 0;
|
|
919
|
+
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>`;
|
|
920
|
+
return `<!DOCTYPE html>
|
|
921
|
+
<html>
|
|
922
|
+
<head>
|
|
923
|
+
${webviewHead(build2, pagePath, id)}
|
|
924
|
+
<script src="${pluginScriptUrl(plugin, "pageframe.js")}"></script>
|
|
925
|
+
${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
926
|
+
</head>
|
|
927
|
+
<body>
|
|
928
|
+
${page}
|
|
929
|
+
<script>parent.__wxaHost.frameLoaded('webview', ${id});</script>
|
|
930
|
+
</body>
|
|
931
|
+
</html>`;
|
|
932
|
+
}
|
|
933
|
+
function pageWindow(build2, pagePath) {
|
|
934
|
+
return { ...build2.wxConfig.global?.window, ...build2.wxConfig.page?.[pagePath + ".html"]?.window };
|
|
935
|
+
}
|
|
936
|
+
function webviewHead(build2, pagePath, id) {
|
|
937
|
+
const wxConfig = { ...build2.wxConfig, window: pageWindow(build2, pagePath) };
|
|
938
|
+
return `<meta charset="utf-8">
|
|
939
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
940
|
+
<title>${escapeHtml(pagePath)}</title>
|
|
941
|
+
<!-- \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 -->
|
|
942
|
+
<style>html{font-size:16px}</style>
|
|
943
|
+
<!-- \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
|
|
944
|
+
scrollbar-width \u4F18\u5148\u4E8E ::-webkit-scrollbar\uFF0C\u9875\u9762\u6837\u5F0F\u4E0D\u4F1A\u518D\u628A\u6EDA\u52A8\u6761\u52A0\u56DE\u6765 -->
|
|
945
|
+
<style>*{scrollbar-width:none}::-webkit-scrollbar{display:none}</style>
|
|
946
|
+
<!-- \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
|
|
947
|
+
\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 -->
|
|
948
|
+
<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>
|
|
949
|
+
<script>var __wxConfig = ${toJs(wxConfig)};</script>
|
|
950
|
+
<script src="/__wxa__/runtime/frame.js" data-kind="webview" data-webview-id="${id}"></script>
|
|
951
|
+
<script src="/__baselib__/WAWebview.js"></script>`;
|
|
952
|
+
}
|
|
618
953
|
function escapeHtml(text) {
|
|
619
954
|
return text.replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch]);
|
|
620
955
|
}
|
|
@@ -647,13 +982,62 @@ function vlq(value) {
|
|
|
647
982
|
return out;
|
|
648
983
|
}
|
|
649
984
|
|
|
985
|
+
// src/server/forwardConsole.ts
|
|
986
|
+
var MAX_LENGTH = 4e3;
|
|
987
|
+
var LIMIT_PER_SECOND = 20;
|
|
988
|
+
var CONTROL_CHARS = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
989
|
+
function printBrowserReports(socket, logger) {
|
|
990
|
+
let windowStart = 0;
|
|
991
|
+
let printed = 0;
|
|
992
|
+
let dropped = 0;
|
|
993
|
+
socket.on("message", (data) => {
|
|
994
|
+
const report = parseReport(data);
|
|
995
|
+
if (!report) return;
|
|
996
|
+
const now = Date.now();
|
|
997
|
+
if (now - windowStart >= 1e3) {
|
|
998
|
+
windowStart = now;
|
|
999
|
+
printed = 0;
|
|
1000
|
+
}
|
|
1001
|
+
if (printed >= LIMIT_PER_SECOND) {
|
|
1002
|
+
if (dropped++ === 0) {
|
|
1003
|
+
setTimeout(() => {
|
|
1004
|
+
logger.warn(`[wxa-runner] \u6D4F\u89C8\u5668 1 \u79D2\u5185\u8F6C\u53D1\u7684\u62A5\u9519\u592A\u591A\uFF0C\u7701\u7565\u4E86 ${dropped} \u6761`);
|
|
1005
|
+
dropped = 0;
|
|
1006
|
+
}, windowStart + 1e3 - now).unref();
|
|
1007
|
+
}
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
printed++;
|
|
1011
|
+
logger[report.level](`[wxa-runner] ${clean(report.text)}`);
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
function parseReport(data) {
|
|
1015
|
+
try {
|
|
1016
|
+
const report = JSON.parse(data.toString());
|
|
1017
|
+
if (report?.type === "console" && (report.level === "warn" || report.level === "error") && typeof report.text === "string") return report;
|
|
1018
|
+
} catch {
|
|
1019
|
+
}
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
function clean(text) {
|
|
1023
|
+
const printable = text.replace(CONTROL_CHARS, "");
|
|
1024
|
+
return printable.length > MAX_LENGTH ? `${printable.slice(0, MAX_LENGTH)}\u2026\uFF08\u5DF2\u622A\u65AD\uFF09` : printable;
|
|
1025
|
+
}
|
|
1026
|
+
function ignoreConsoleHook(sourceMap) {
|
|
1027
|
+
const map = JSON.parse(sourceMap);
|
|
1028
|
+
map.x_google_ignoreList = map.sources.flatMap((source, index) => source.endsWith("consoleHook.ts") ? [index] : []);
|
|
1029
|
+
return JSON.stringify(map);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
650
1032
|
// src/server/proxy.ts
|
|
1033
|
+
var import_node_stream = require("stream");
|
|
1034
|
+
var import_promises = require("stream/promises");
|
|
651
1035
|
var DROPPED_REQUEST_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "expect"]);
|
|
652
1036
|
var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding", "connection", "set-cookie"]);
|
|
653
1037
|
async function handleProxy(req, res) {
|
|
654
|
-
const target =
|
|
655
|
-
if (
|
|
656
|
-
res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${
|
|
1038
|
+
const target = fromServerPath(PROXY_PREFIX, req.originalUrl);
|
|
1039
|
+
if (!target) {
|
|
1040
|
+
res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${req.originalUrl}"`);
|
|
657
1041
|
return;
|
|
658
1042
|
}
|
|
659
1043
|
let requestHeaders = {};
|
|
@@ -675,31 +1059,47 @@ async function handleProxy(req, res) {
|
|
|
675
1059
|
headers["content-type"] = browserContentType;
|
|
676
1060
|
}
|
|
677
1061
|
const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
|
|
678
|
-
|
|
1062
|
+
const controller = new AbortController();
|
|
1063
|
+
const cancel = () => controller.abort();
|
|
1064
|
+
res.once("close", cancel);
|
|
679
1065
|
try {
|
|
680
|
-
upstream
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
1066
|
+
let upstream;
|
|
1067
|
+
try {
|
|
1068
|
+
upstream = await fetch(target, {
|
|
1069
|
+
method: req.method,
|
|
1070
|
+
headers,
|
|
1071
|
+
body: hasBody ? req.body : void 0,
|
|
1072
|
+
redirect: "follow",
|
|
1073
|
+
signal: controller.signal
|
|
1074
|
+
});
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
if (res.destroyed || controller.signal.aborted) return;
|
|
1077
|
+
const cause = error?.cause?.message;
|
|
1078
|
+
res.status(502).set("x-wxa-proxy-error", "1").send(cause || error.message);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
const responseHeaders = {};
|
|
1082
|
+
upstream.headers.forEach((value, key) => {
|
|
1083
|
+
responseHeaders[key] = value;
|
|
685
1084
|
});
|
|
1085
|
+
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
1086
|
+
res.status(upstream.status);
|
|
1087
|
+
upstream.headers.forEach((value, key) => {
|
|
1088
|
+
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
1089
|
+
});
|
|
1090
|
+
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
1091
|
+
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
1092
|
+
res.flushHeaders();
|
|
1093
|
+
if (!upstream.body) {
|
|
1094
|
+
res.end();
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(upstream.body), res);
|
|
686
1098
|
} catch (error) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1099
|
+
if (!res.destroyed) res.destroy(error);
|
|
1100
|
+
} finally {
|
|
1101
|
+
res.off("close", cancel);
|
|
690
1102
|
}
|
|
691
|
-
const responseHeaders = {};
|
|
692
|
-
upstream.headers.forEach((value, key) => {
|
|
693
|
-
responseHeaders[key] = value;
|
|
694
|
-
});
|
|
695
|
-
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
696
|
-
res.status(upstream.status);
|
|
697
|
-
upstream.headers.forEach((value, key) => {
|
|
698
|
-
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
699
|
-
});
|
|
700
|
-
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
701
|
-
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
702
|
-
res.send(Buffer.from(await upstream.arrayBuffer()));
|
|
703
1103
|
}
|
|
704
1104
|
|
|
705
1105
|
// src/server/hmr.ts
|
|
@@ -717,23 +1117,23 @@ function planHotUpdate(files, units) {
|
|
|
717
1117
|
|
|
718
1118
|
// src/server/developerTicket.ts
|
|
719
1119
|
var import_child_process = require("child_process");
|
|
720
|
-
var
|
|
721
|
-
var
|
|
1120
|
+
var import_crypto2 = require("crypto");
|
|
1121
|
+
var import_promises2 = require("timers/promises");
|
|
722
1122
|
var import_ws = require("ws");
|
|
723
|
-
var
|
|
1123
|
+
var import_wxa_runner_extractor3 = require("@open-wxa/wxa-runner-extractor");
|
|
724
1124
|
var AUTO_PORT = 9420;
|
|
725
1125
|
var CLI_TIMEOUT = 6e4;
|
|
726
1126
|
var AUTOMATOR_TIMEOUT = 5e3;
|
|
727
1127
|
var WINDOW_TIMEOUT = 1e4;
|
|
728
1128
|
var WINDOW_RETRY_INTERVAL = 500;
|
|
729
1129
|
function developerTicketSource(projectPath, logger) {
|
|
730
|
-
const cli = (0,
|
|
731
|
-
if (!cli) return async () => (0,
|
|
1130
|
+
const cli = (0, import_wxa_runner_extractor3.findElectronDevtoolsCli)();
|
|
1131
|
+
if (!cli) return async () => (0, import_wxa_runner_extractor3.findDeveloperTicket)();
|
|
732
1132
|
let automatorPort = null;
|
|
733
1133
|
const startOnce = () => {
|
|
734
1134
|
if (!automatorPort) {
|
|
735
1135
|
const starting = startAutomation(cli, projectPath).then((port) => {
|
|
736
|
-
logger.log(`[wxa-runner] \
|
|
1136
|
+
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`);
|
|
737
1137
|
return port;
|
|
738
1138
|
});
|
|
739
1139
|
starting.catch(() => {
|
|
@@ -744,7 +1144,7 @@ function developerTicketSource(projectPath, logger) {
|
|
|
744
1144
|
return automatorPort;
|
|
745
1145
|
};
|
|
746
1146
|
return async () => {
|
|
747
|
-
if (process.env[
|
|
1147
|
+
if (process.env[import_wxa_runner_extractor3.TICKET_ENV]) return { newticket: process.env[import_wxa_runner_extractor3.TICKET_ENV] };
|
|
748
1148
|
const port = startOnce();
|
|
749
1149
|
try {
|
|
750
1150
|
return await requestTicketOnceWindowReady(await port, projectPath);
|
|
@@ -767,7 +1167,7 @@ async function requestTicketOnceWindowReady(port, projectPath) {
|
|
|
767
1167
|
} catch (error) {
|
|
768
1168
|
if (!(error instanceof WindowNotFound)) throw error;
|
|
769
1169
|
if (Date.now() >= deadline) throw new Error(`\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u627E\u4E0D\u5230\u9879\u76EE\u7A97\u53E3\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u6253\u5F00\u9879\u76EE ${projectPath}`);
|
|
770
|
-
await (0,
|
|
1170
|
+
await (0, import_promises2.setTimeout)(WINDOW_RETRY_INTERVAL);
|
|
771
1171
|
}
|
|
772
1172
|
}
|
|
773
1173
|
}
|
|
@@ -809,7 +1209,7 @@ function cliErrorMessage(stdout) {
|
|
|
809
1209
|
}
|
|
810
1210
|
function requestTicket(port) {
|
|
811
1211
|
return new Promise((resolve, reject) => {
|
|
812
|
-
const id = `wxa-runner-${(0,
|
|
1212
|
+
const id = `wxa-runner-${(0, import_crypto2.randomUUID)()}`;
|
|
813
1213
|
const socket = new import_ws.WebSocket(`ws://127.0.0.1:${port}`);
|
|
814
1214
|
const settle = (finish) => {
|
|
815
1215
|
clearTimeout(timer);
|
|
@@ -849,74 +1249,62 @@ function explainAutomatorError(message) {
|
|
|
849
1249
|
return new Error(`\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u8FD4\u56DE\u9519\u8BEF\uFF1A${message}`);
|
|
850
1250
|
}
|
|
851
1251
|
|
|
852
|
-
// src/server/
|
|
1252
|
+
// src/server/devtoolsLogin.ts
|
|
853
1253
|
var import_express = __toESM(require("express"));
|
|
854
|
-
var
|
|
855
|
-
var DEV_LOGIC = "https://servicewechat.com/wxa-dev-logic";
|
|
856
|
-
var CLIENT_VERSION = "1021810240";
|
|
857
|
-
var UPSTREAM_TIMEOUT = 1e4;
|
|
1254
|
+
var import_wxa_runner_extractor4 = require("@open-wxa/wxa-runner-extractor");
|
|
858
1255
|
var LOOPBACK_ADDRESSES = ["127.0.0.1", "::1", "::ffff:127.0.0.1"];
|
|
859
1256
|
var LOOPBACK_HOSTS = ["localhost", "127.0.0.1", "[::1]"];
|
|
860
|
-
var
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
40029: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
|
|
864
|
-
85023: "\uFF08\u5F53\u524D\u5FAE\u4FE1\u8D26\u53F7\u4E0D\u662F\u8BE5\u5C0F\u7A0B\u5E8F\u7684\u5F00\u53D1\u8005\uFF09"
|
|
865
|
-
};
|
|
866
|
-
function requireUsableTicket(ticket) {
|
|
867
|
-
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 ${import_wxa_runner_extractor2.TICKET_ENV}`);
|
|
868
|
-
if (ticket.expiredAt !== void 0 && ticket.expiredAt <= Date.now()) {
|
|
869
|
-
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");
|
|
870
|
-
}
|
|
871
|
-
return ticket.newticket;
|
|
872
|
-
}
|
|
873
|
-
function realLoginRouter(appid, findTicket = import_wxa_runner_extractor2.findDeveloperTicket) {
|
|
874
|
-
const callDevLogic = async (endpoint, body) => {
|
|
875
|
-
const query = new URLSearchParams({ appid, newticket: requireUsableTicket(await findTicket()), clientversion: CLIENT_VERSION });
|
|
876
|
-
const response = await fetch(`${DEV_LOGIC}/${endpoint}?${query}`, {
|
|
877
|
-
method: "POST",
|
|
878
|
-
headers: { "Content-Type": "application/json" },
|
|
879
|
-
body: JSON.stringify(body),
|
|
880
|
-
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT)
|
|
881
|
-
}).catch((error) => {
|
|
882
|
-
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}`);
|
|
883
|
-
});
|
|
884
|
-
if (!response.ok) throw new Error(`\u5FAE\u4FE1\u670D\u52A1\u5668\u8FD4\u56DE HTTP ${response.status}`);
|
|
885
|
-
return response.json();
|
|
886
|
-
};
|
|
1257
|
+
var FORWARDED_WX_DATA_APIS = ["webapi_servicemarket_jsapi_comm"];
|
|
1258
|
+
function devtoolsLoginRouter(appid, findTicket = import_wxa_runner_extractor4.findDeveloperTicket) {
|
|
1259
|
+
const wechat = new WechatDevClient(appid, findTicket);
|
|
887
1260
|
const router = import_express.default.Router();
|
|
888
1261
|
router.use(onlyLoopback);
|
|
889
1262
|
router.post("/login", respond(async () => {
|
|
890
|
-
const body = await
|
|
891
|
-
|
|
892
|
-
if (errcode !== 0 || !body.code) {
|
|
893
|
-
throw new Error(`\u5FAE\u4FE1\u767B\u5F55\u5931\u8D25 ${errcode}: ${body.baseresponse?.errmsg || "\u672A\u77E5\u9519\u8BEF"}${ERROR_HINTS[errcode] ?? ""}`);
|
|
894
|
-
}
|
|
1263
|
+
const body = requireOk(await wechat.postJson("wxa-dev-logic/jslogin", { scope: ["snsapi_base"] }), "\u5FAE\u4FE1\u767B\u5F55\u5931\u8D25");
|
|
1264
|
+
if (!body.code) throw new Error("\u5FAE\u4FE1\u767B\u5F55\u5931\u8D25\uFF1A\u5FAE\u4FE1\u6CA1\u6709\u8FD4\u56DE code");
|
|
895
1265
|
return { code: body.code };
|
|
896
1266
|
}));
|
|
897
1267
|
router.post("/phone-number", respond(async () => {
|
|
898
|
-
const body = await
|
|
1268
|
+
const body = await wechat.postJson("wxa-dev-logic/jsgetuserwxphone", { appid });
|
|
899
1269
|
const errcode = body.jsapi_baseresponse?.errcode ?? body.BaseResponse?.Ret;
|
|
900
1270
|
if (errcode !== 0 || typeof body.data !== "string") {
|
|
901
1271
|
const errmsg = body.jsapi_baseresponse?.errmsg || body.BaseResponse?.ErrMsg?.String || "\u672A\u77E5\u9519\u8BEF";
|
|
902
|
-
throw new Error(`\u83B7\u53D6\u624B\u673A\u53F7\u5931\u8D25 ${errcode}: ${errmsg}${
|
|
1272
|
+
throw new Error(`\u83B7\u53D6\u624B\u673A\u53F7\u5931\u8D25 ${errcode}: ${errmsg}${explainErrcode(errcode)}`);
|
|
903
1273
|
}
|
|
904
1274
|
const { encryptedData, iv } = JSON.parse(body.data);
|
|
905
1275
|
return { data: body.data, encryptedData, iv };
|
|
906
1276
|
}));
|
|
1277
|
+
router.post("/operate-wx-data", import_express.default.json({ limit: "10mb" }), (req, res, next) => {
|
|
1278
|
+
const apiName = req.body?.data?.api_name;
|
|
1279
|
+
if (FORWARDED_WX_DATA_APIS.includes(apiName)) return next();
|
|
1280
|
+
res.status(403).json({ error: `operateWXData \u53EA\u8F6C\u53D1\u670D\u52A1\u5E02\u573A\u7684\u8BF7\u6C42\uFF0C\u4E0D\u8F6C\u53D1 ${apiName}` });
|
|
1281
|
+
}, respond(async (req) => {
|
|
1282
|
+
const body = requireOk(await wechat.postJson("wxa-dev-logic/jsoperatewxdata", { data: JSON.stringify(req.body.data) }), "\u670D\u52A1\u5E02\u573A\u8C03\u7528\u5931\u8D25");
|
|
1283
|
+
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");
|
|
1284
|
+
return { data: JSON.parse(body.data) };
|
|
1285
|
+
}));
|
|
1286
|
+
router.post("/verify-plugin", import_express.default.json(), respond(async (req) => {
|
|
1287
|
+
const body = requireOk(await wechat.postJson("wxa-dev-logic/verifyplugin", { ext_info: JSON.stringify(req.body?.data || {}) }), "\u63D2\u4EF6\u6821\u9A8C\u5931\u8D25");
|
|
1288
|
+
return { data: { list: body.list || [] } };
|
|
1289
|
+
}));
|
|
1290
|
+
router.post("/batch-get-contact", import_express.default.json(), respond(async (req) => {
|
|
1291
|
+
const body = requireOk(await wechat.postJson("wxa-dev-logic/batchgetcontact", { appid_list: req.body?.appIds || [] }), "\u83B7\u53D6\u63D2\u4EF6\u4FE1\u606F\u5931\u8D25");
|
|
1292
|
+
const contacts = body.contact_list || [];
|
|
1293
|
+
return { contactList: contacts.filter((contact) => contact.plugin_info).map((contact) => ({ appId: contact.appid, plugin_list: contact.plugin_info.plugin_list })) };
|
|
1294
|
+
}));
|
|
907
1295
|
return router;
|
|
908
1296
|
}
|
|
909
1297
|
function onlyLoopback(req, res, next) {
|
|
910
1298
|
if (!LOOPBACK_ADDRESSES.includes(req.socket.remoteAddress || "") || !LOOPBACK_HOSTS.includes(req.hostname)) {
|
|
911
|
-
res.status(403).json({ error: "\
|
|
1299
|
+
res.status(403).json({ error: "\u5F00\u53D1\u8005\u767B\u5F55\u6001\u63A5\u53E3\u53EA\u5141\u8BB8\u672C\u673A\u8BBF\u95EE" });
|
|
912
1300
|
return;
|
|
913
1301
|
}
|
|
914
1302
|
next();
|
|
915
1303
|
}
|
|
916
1304
|
function respond(action) {
|
|
917
|
-
return async (
|
|
1305
|
+
return async (req, res) => {
|
|
918
1306
|
try {
|
|
919
|
-
res.json(await action());
|
|
1307
|
+
res.json(await action(req));
|
|
920
1308
|
} catch (error) {
|
|
921
1309
|
res.status(500).json({ error: error.message });
|
|
922
1310
|
}
|
|
@@ -972,7 +1360,7 @@ function builtinSetupModule() {
|
|
|
972
1360
|
}
|
|
973
1361
|
|
|
974
1362
|
// src/server/watcher.ts
|
|
975
|
-
var
|
|
1363
|
+
var import_crypto3 = require("crypto");
|
|
976
1364
|
var import_fs3 = __toESM(require("fs"));
|
|
977
1365
|
var import_path3 = __toESM(require("path"));
|
|
978
1366
|
var import_chokidar = __toESM(require("chokidar"));
|
|
@@ -1016,7 +1404,7 @@ function updateFingerprint(fingerprints, file, event) {
|
|
|
1016
1404
|
if (event !== "add" && event !== "change" || !FINGERPRINTED.test(file)) return false;
|
|
1017
1405
|
let next;
|
|
1018
1406
|
try {
|
|
1019
|
-
next = (0,
|
|
1407
|
+
next = (0, import_crypto3.createHash)("sha1").update(import_fs3.default.readFileSync(file)).digest("hex");
|
|
1020
1408
|
} catch {
|
|
1021
1409
|
return false;
|
|
1022
1410
|
}
|
|
@@ -1043,24 +1431,37 @@ var DevServer = class {
|
|
|
1043
1431
|
this.sockets = null;
|
|
1044
1432
|
this.watcher = null;
|
|
1045
1433
|
this.address = "";
|
|
1434
|
+
this.lanAccess = false;
|
|
1046
1435
|
/** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
|
|
1047
|
-
this.
|
|
1436
|
+
this.fileSessions = /* @__PURE__ */ new Map();
|
|
1048
1437
|
this.logger = options.logger || console;
|
|
1049
1438
|
this.project = new MiniProgram(import_path4.default.resolve(options.projectPath));
|
|
1050
|
-
this.baseLib = (0,
|
|
1439
|
+
this.baseLib = (0, import_wxa_runner_extractor5.resolveBaseLib)({ version: options.baseLibVersion, cacheDir: options.baseLibCacheDir });
|
|
1051
1440
|
if (!this.baseLib.compatible) {
|
|
1052
1441
|
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`);
|
|
1053
1442
|
}
|
|
1054
|
-
|
|
1443
|
+
if (options.realLogin !== void 0 && options.devtoolsLogin === void 0) {
|
|
1444
|
+
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");
|
|
1445
|
+
}
|
|
1446
|
+
this.devtoolsLogin = (options.devtoolsLogin ?? options.realLogin) === true;
|
|
1447
|
+
this.ticketSource = this.devtoolsLogin ? developerTicketSource(this.project.projectPath, this.logger) : null;
|
|
1448
|
+
this.plugins = new PluginStore(this.ticketSource && new WechatDevClient(this.project.appid, this.ticketSource), this.logger);
|
|
1055
1449
|
this.device = { ...DEFAULT_DEVICE, ...options.device };
|
|
1056
1450
|
this.builder = new AppBuilder(this.project);
|
|
1057
1451
|
this.setupFile = resolveSetupFile(options.setup, this.project.projectPath);
|
|
1058
1452
|
this.setupEnv = collectSetupEnv(process.env);
|
|
1059
1453
|
this.app = this.createApp();
|
|
1060
1454
|
}
|
|
1455
|
+
/** 本机访问的地址 */
|
|
1061
1456
|
get url() {
|
|
1062
1457
|
return this.address;
|
|
1063
1458
|
}
|
|
1459
|
+
/** 局域网里其他设备访问的地址,每块网卡一个;只监听本机时为空 */
|
|
1460
|
+
get networkUrls() {
|
|
1461
|
+
if (!this.lanAccess) return [];
|
|
1462
|
+
const { port } = new URL(this.address);
|
|
1463
|
+
return Object.values(import_os.default.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
|
|
1464
|
+
}
|
|
1064
1465
|
async start() {
|
|
1065
1466
|
try {
|
|
1066
1467
|
const build2 = await this.builder.build();
|
|
@@ -1070,7 +1471,11 @@ var DevServer = class {
|
|
|
1070
1471
|
${error.message}`);
|
|
1071
1472
|
}
|
|
1072
1473
|
const server = import_http.default.createServer(this.app);
|
|
1073
|
-
this.sockets = new import_ws2.WebSocketServer({ noServer: true });
|
|
1474
|
+
this.sockets = new import_ws2.WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
|
|
1475
|
+
this.sockets.on("connection", (socket) => {
|
|
1476
|
+
socket.on("error", () => void 0);
|
|
1477
|
+
if (this.options.forwardConsole) printBrowserReports(socket, this.logger);
|
|
1478
|
+
});
|
|
1074
1479
|
server.on("upgrade", (req, socket, head) => {
|
|
1075
1480
|
if (req.url?.startsWith("/__wxa__/ws")) {
|
|
1076
1481
|
this.sockets.handleUpgrade(req, socket, head, (ws) => this.sockets.emit("connection", ws, req));
|
|
@@ -1079,15 +1484,12 @@ ${error.message}`);
|
|
|
1079
1484
|
}
|
|
1080
1485
|
});
|
|
1081
1486
|
const host = this.options.host || "localhost";
|
|
1082
|
-
await
|
|
1083
|
-
server.once("error", reject);
|
|
1084
|
-
server.listen(this.options.port ?? 3e3, host, () => resolve());
|
|
1085
|
-
});
|
|
1487
|
+
const port = await listen(server, this.options.port, host, this.logger);
|
|
1086
1488
|
this.server = server;
|
|
1087
|
-
|
|
1088
|
-
this.address = `http://${host}:${port}`;
|
|
1489
|
+
this.lanAccess = LOCAL_HOST_OF_ANY_ADDRESS.has(host);
|
|
1490
|
+
this.address = `http://${LOCAL_HOST_OF_ANY_ADDRESS.get(host) ?? host}:${port}`;
|
|
1089
1491
|
if (this.options.watch !== false) this.startWatching();
|
|
1090
|
-
this.ticketSource?.().then(requireUsableTicket).catch((error) => this.logger.warn(`[wxa-runner] \
|
|
1492
|
+
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}`));
|
|
1091
1493
|
return this.address;
|
|
1092
1494
|
}
|
|
1093
1495
|
async close() {
|
|
@@ -1100,6 +1502,7 @@ ${error.message}`);
|
|
|
1100
1502
|
server.closeAllConnections();
|
|
1101
1503
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
1102
1504
|
}
|
|
1505
|
+
this.fileSessions.clear();
|
|
1103
1506
|
}
|
|
1104
1507
|
createApp() {
|
|
1105
1508
|
const app = (0, import_express2.default)();
|
|
@@ -1114,9 +1517,15 @@ ${error.message}`);
|
|
|
1114
1517
|
});
|
|
1115
1518
|
app.get("/__wxa__/app-info.json", async (_req, res) => {
|
|
1116
1519
|
noStore(res);
|
|
1117
|
-
const base = {
|
|
1520
|
+
const base = {
|
|
1521
|
+
appid: this.project.appid,
|
|
1522
|
+
baseLibVersion: this.baseLib.version,
|
|
1523
|
+
device: this.device,
|
|
1524
|
+
devtoolsLogin: this.devtoolsLogin,
|
|
1525
|
+
forwardConsole: this.options.forwardConsole ?? false
|
|
1526
|
+
};
|
|
1118
1527
|
try {
|
|
1119
|
-
const build2 = await this.builder.build();
|
|
1528
|
+
const build2 = await this.plugins.refresh(await this.builder.build());
|
|
1120
1529
|
const info = { ...base, version: build2.version, wxConfig: build2.wxConfig, pages: build2.pages };
|
|
1121
1530
|
if (this.setupFile) info.setup = await this.setupInfo(this.setupFile);
|
|
1122
1531
|
res.json(info);
|
|
@@ -1139,7 +1548,7 @@ ${error.message}`);
|
|
|
1139
1548
|
app.get("/__wxa__/appservice.html", this.withBuild((build2, req, res) => {
|
|
1140
1549
|
const launchPath = String(req.query.path || "").replace(/^\//, "");
|
|
1141
1550
|
const launch = build2.pages.includes(launchPath) ? { path: launchPath, query: Object.fromEntries(new URLSearchParams(String(req.query.query || ""))) } : { path: build2.pages[0], query: {} };
|
|
1142
|
-
res.type("html").send(appserviceHtml(build2, launch, { hot: this.hotEnabled, isolated: (0,
|
|
1551
|
+
res.type("html").send(appserviceHtml(build2, launch, { hot: this.hotEnabled, isolated: (0, import_wxa_runner_extractor5.usesIsolatedContext)(this.baseLib.version) }));
|
|
1143
1552
|
}));
|
|
1144
1553
|
app.get("/__wxa__/subcontext.html", (req, res) => {
|
|
1145
1554
|
const id = Number(req.query.id);
|
|
@@ -1152,7 +1561,19 @@ ${error.message}`);
|
|
|
1152
1561
|
app.get(/^\/__wxa__\/webview\/(.+)\.html$/, this.withBuild((build2, req, res, next) => {
|
|
1153
1562
|
const pagePath = decodeURIComponent(req.params[0]);
|
|
1154
1563
|
if (!build2.pages.includes(pagePath)) return next();
|
|
1155
|
-
|
|
1564
|
+
const pluginPage = findPluginPage(build2, pagePath);
|
|
1565
|
+
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 }));
|
|
1566
|
+
}));
|
|
1567
|
+
app.get("/__wxa__/plugin/:provider/:version/:file(appservice.js|pageframe.js)", this.withBuild((build2, req, res, next) => {
|
|
1568
|
+
const { provider, version, file } = req.params;
|
|
1569
|
+
const code = build2.plugins.find((plugin) => plugin.provider === provider && plugin.version === version)?.files.get(`/${file}`);
|
|
1570
|
+
if (!code) return next();
|
|
1571
|
+
res.type("js").send(code);
|
|
1572
|
+
}));
|
|
1573
|
+
app.get(/^\/__plugin__\/([^/]+)(\/.+)$/, this.withBuild((build2, req, res, next) => {
|
|
1574
|
+
const file = build2.plugins.find((plugin) => plugin.provider === req.params[0])?.files.get(decodeURIComponent(req.params[1]));
|
|
1575
|
+
if (!file) return next();
|
|
1576
|
+
res.type(import_path4.default.extname(req.params[1]) || "application/octet-stream").send(file);
|
|
1156
1577
|
}));
|
|
1157
1578
|
app.get(/^\/__wxa__\/module\/(.+\.js)$/, this.withBuild((build2, req, res) => {
|
|
1158
1579
|
const id = decodeURIComponent(req.params[0]);
|
|
@@ -1181,7 +1602,18 @@ ${error.message}`);
|
|
|
1181
1602
|
app.get("/__wxa__/page-frame.js", this.withBuild((build2, _req, res) => {
|
|
1182
1603
|
res.type("js").send(build2.pageFrame);
|
|
1183
1604
|
}));
|
|
1184
|
-
app.
|
|
1605
|
+
app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
|
|
1606
|
+
const file = import_path4.default.join(this.runtimeDir(), "frame.js.map");
|
|
1607
|
+
if (!import_fs4.default.existsSync(file)) return next();
|
|
1608
|
+
res.type("json").send(ignoreConsoleHook(import_fs4.default.readFileSync(file, "utf8")));
|
|
1609
|
+
});
|
|
1610
|
+
app.use("/__wxa__/runtime", import_express2.default.static(this.runtimeDir(), {
|
|
1611
|
+
cacheControl: false,
|
|
1612
|
+
// Service Worker 默认只能接管自己所在目录下的页面,宿主页面在根路径
|
|
1613
|
+
setHeaders: (res, file) => {
|
|
1614
|
+
if (import_path4.default.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
|
|
1615
|
+
}
|
|
1616
|
+
}));
|
|
1185
1617
|
const sourceMaps = /* @__PURE__ */ new Map();
|
|
1186
1618
|
app.get(/^\/__baselib__\/(.+\.js)\.map$/, (req, res, next) => {
|
|
1187
1619
|
const file = import_path4.default.resolve(this.baseLib.dir, req.params[0]);
|
|
@@ -1195,34 +1627,65 @@ ${error.message}`);
|
|
|
1195
1627
|
if (file.endsWith(".js")) res.setHeader("SourceMap", `${import_path4.default.basename(file)}.map`);
|
|
1196
1628
|
}
|
|
1197
1629
|
}));
|
|
1198
|
-
app.
|
|
1199
|
-
app.
|
|
1630
|
+
app.use(PROXY_PREFIX, import_express2.default.raw({ type: () => true, limit: "100mb" }), handleProxy);
|
|
1631
|
+
app.use(MOCK_PREFIX, (_req, res) => {
|
|
1632
|
+
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");
|
|
1633
|
+
});
|
|
1634
|
+
const sessionFiles = (session) => {
|
|
1635
|
+
if (!/^[a-z0-9-]{1,80}$/i.test(session)) return null;
|
|
1636
|
+
let files = this.fileSessions.get(session);
|
|
1637
|
+
if (!files) {
|
|
1638
|
+
files = new FileStore([`/__wxa__/usr/${session}`, `/__wxa__/tmp/${session}`]);
|
|
1639
|
+
this.fileSessions.set(session, files);
|
|
1640
|
+
}
|
|
1641
|
+
return files;
|
|
1642
|
+
};
|
|
1643
|
+
app.post("/__wxa__/fs/:session", import_express2.default.json({ limit: "150mb" }), (req, res) => {
|
|
1644
|
+
const files = sessionFiles(req.params.session);
|
|
1645
|
+
if (!files) {
|
|
1646
|
+
res.status(400).json({ error: `invalid filesystem session ${req.params.session}` });
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
try {
|
|
1650
|
+
res.json({ result: files.execute(req.body.operation, req.body.args || {}) });
|
|
1651
|
+
} catch (error) {
|
|
1652
|
+
res.json({ error: error.message });
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
app.post("/__wxa__/tmp/:session", import_express2.default.raw({ type: () => true, limit: "100mb" }), (req, res) => {
|
|
1200
1656
|
const type = String(req.query.type || "png");
|
|
1201
1657
|
if (!/^[a-z0-9]+$/i.test(type)) {
|
|
1202
1658
|
res.status(400).type("text").send(`\u975E\u6CD5\u7684\u6587\u4EF6\u7C7B\u578B ${type}`);
|
|
1203
1659
|
return;
|
|
1204
1660
|
}
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1661
|
+
const files = sessionFiles(req.params.session);
|
|
1662
|
+
if (!files) {
|
|
1663
|
+
res.status(400).type("text").send(`\u975E\u6CD5\u7684\u4F1A\u8BDD\u6807\u8BC6 ${req.params.session}`);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
const filePath = `/__wxa__/tmp/${req.params.session}/${(0, import_crypto4.randomUUID)()}.${type}`;
|
|
1667
|
+
files.write(filePath, Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0));
|
|
1668
|
+
res.json({ url: `${req.protocol}://${req.get("host")}${filePath}` });
|
|
1208
1669
|
});
|
|
1209
1670
|
app.get("/__wxa__/region/:kind", (req, res) => {
|
|
1210
1671
|
const kind = req.params.kind;
|
|
1211
|
-
const data = kind === "districts" || kind === "streets" ? (0,
|
|
1672
|
+
const data = kind === "districts" || kind === "streets" ? (0, import_wxa_runner_extractor5.readRegionData)(kind) : null;
|
|
1212
1673
|
if (!data) {
|
|
1213
1674
|
res.status(404).end();
|
|
1214
1675
|
return;
|
|
1215
1676
|
}
|
|
1216
1677
|
res.type("text/plain; charset=utf-8").set("Cache-Control", "public, max-age=86400").send(data);
|
|
1217
1678
|
});
|
|
1218
|
-
app.get("/__wxa__
|
|
1219
|
-
const
|
|
1220
|
-
|
|
1679
|
+
app.get("/__wxa__/:kind(usr|tmp)/:session/*", (req, res) => {
|
|
1680
|
+
const files = this.fileSessions.get(req.params.session);
|
|
1681
|
+
const node = files?.stat(`/__wxa__/${req.params.kind}/${req.params.session}/` + req.params[0]);
|
|
1682
|
+
if (!node || node.dir) {
|
|
1221
1683
|
res.status(404).end();
|
|
1222
1684
|
return;
|
|
1223
1685
|
}
|
|
1224
|
-
|
|
1225
|
-
res.
|
|
1686
|
+
const data = node.data;
|
|
1687
|
+
res.type(import_path4.default.extname(req.params[0]));
|
|
1688
|
+
res.setHeader("ETag", `"${node.etag}"`);
|
|
1226
1689
|
res.setHeader("Accept-Ranges", "bytes");
|
|
1227
1690
|
const ranges = req.range(data.length);
|
|
1228
1691
|
if (ranges === -1) {
|
|
@@ -1238,10 +1701,17 @@ ${error.message}`);
|
|
|
1238
1701
|
}
|
|
1239
1702
|
res.send(data);
|
|
1240
1703
|
});
|
|
1241
|
-
app.delete("/__wxa__/tmp/:
|
|
1242
|
-
|
|
1704
|
+
app.delete("/__wxa__/tmp/:session/*", (req, res) => {
|
|
1705
|
+
try {
|
|
1706
|
+
const files = this.fileSessions.get(req.params.session);
|
|
1707
|
+
if (!files) throw new Error("file not exist");
|
|
1708
|
+
files.remove(`/__wxa__/tmp/${req.params.session}/` + req.params[0], { dir: false });
|
|
1709
|
+
res.status(204).end();
|
|
1710
|
+
} catch {
|
|
1711
|
+
res.status(404).end();
|
|
1712
|
+
}
|
|
1243
1713
|
});
|
|
1244
|
-
if (this.ticketSource) app.use("/__wxa__/api",
|
|
1714
|
+
if (this.ticketSource) app.use("/__wxa__/api", devtoolsLoginRouter(this.project.appid, this.ticketSource));
|
|
1245
1715
|
app.use("/__wxa__/webview", import_express2.default.static(root));
|
|
1246
1716
|
app.use(import_express2.default.static(root));
|
|
1247
1717
|
app.use((error, _req, res, _next) => {
|
|
@@ -1259,13 +1729,17 @@ ${error.message}`);
|
|
|
1259
1729
|
return { env: this.setupEnv, error: error.message };
|
|
1260
1730
|
}
|
|
1261
1731
|
}
|
|
1732
|
+
/** 构建结果并入这次打开小程序时决定用的插件 */
|
|
1733
|
+
async currentBuild() {
|
|
1734
|
+
return this.plugins.current(await this.builder.build());
|
|
1735
|
+
}
|
|
1262
1736
|
/** 需要构建结果的路由;构建失败时返回错误文本 */
|
|
1263
1737
|
withBuild(handler) {
|
|
1264
1738
|
return async (req, res, next) => {
|
|
1265
1739
|
res.setHeader("Cache-Control", "no-store");
|
|
1266
1740
|
let build2;
|
|
1267
1741
|
try {
|
|
1268
|
-
build2 = await this.
|
|
1742
|
+
build2 = await this.currentBuild();
|
|
1269
1743
|
} catch (error) {
|
|
1270
1744
|
res.status(500).type("text").send(error.message);
|
|
1271
1745
|
return;
|
|
@@ -1324,6 +1798,38 @@ ${message}`);
|
|
|
1324
1798
|
});
|
|
1325
1799
|
}
|
|
1326
1800
|
};
|
|
1801
|
+
var DEFAULT_PORT = 3e3;
|
|
1802
|
+
var LOCAL_HOST_OF_ANY_ADDRESS = /* @__PURE__ */ new Map([["0.0.0.0", "127.0.0.1"], ["::", "localhost"]]);
|
|
1803
|
+
async function listen(server, port, host, logger) {
|
|
1804
|
+
for (let candidate = port ?? DEFAULT_PORT; ; candidate++) {
|
|
1805
|
+
const taken = LOCAL_HOST_OF_ANY_ADDRESS.has(host) && await takenOnLoopback(candidate) || !await bind(server, candidate, host);
|
|
1806
|
+
if (!taken) {
|
|
1807
|
+
const actual = server.address().port;
|
|
1808
|
+
if (port === void 0 && actual !== DEFAULT_PORT) logger.warn(`[wxa-runner] \u7AEF\u53E3 ${DEFAULT_PORT} \u5DF2\u88AB\u5360\u7528\uFF0C\u6539\u7528 ${actual}`);
|
|
1809
|
+
return actual;
|
|
1810
|
+
}
|
|
1811
|
+
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`);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
function bind(server, port, host) {
|
|
1815
|
+
return new Promise((resolve, reject) => {
|
|
1816
|
+
const onError = (error) => error.code === "EADDRINUSE" ? resolve(false) : reject(error);
|
|
1817
|
+
server.once("error", onError);
|
|
1818
|
+
server.listen(port, host, () => {
|
|
1819
|
+
server.off("error", onError);
|
|
1820
|
+
resolve(true);
|
|
1821
|
+
});
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
async function takenOnLoopback(port) {
|
|
1825
|
+
for (const address of ["127.0.0.1", "::1"]) {
|
|
1826
|
+
const probe = import_net.default.createServer();
|
|
1827
|
+
const free = await bind(probe, port, address).catch(() => true);
|
|
1828
|
+
if (!free) return true;
|
|
1829
|
+
await new Promise((resolve) => probe.close(resolve));
|
|
1830
|
+
}
|
|
1831
|
+
return false;
|
|
1832
|
+
}
|
|
1327
1833
|
function resolveSetupFile(option, projectPath) {
|
|
1328
1834
|
if (option === false) return null;
|
|
1329
1835
|
if (!option) return findSetupFile(projectPath);
|