@open-wxa/wxa-runner-core 0.1.0 → 0.2.1
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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +82 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +83 -6
- package/dist/index.mjs.map +1 -1
- package/dist/runtime/frame.js +227 -5
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/host.js +115 -22
- package/dist/runtime/host.js.map +1 -1
- package/dist/test-api.d.mts +1 -1
- package/dist/test-api.d.ts +1 -1
- package/dist/{testApi-CvOYhts4.d.ts → testApi-BTPLQvFs.d.ts} +2 -0
- package/dist/{testApi-c4IFZcVw.d.mts → testApi-D5o7hK-Z.d.mts} +2 -0
- package/package.json +3 -3
package/dist/runtime/host.js
CHANGED
|
@@ -236,6 +236,14 @@
|
|
|
236
236
|
};
|
|
237
237
|
function createMiscApis(appid, options) {
|
|
238
238
|
const fs = new MemoryFileSystem();
|
|
239
|
+
const realLogin = async (api, url) => {
|
|
240
|
+
try {
|
|
241
|
+
return await postDevServer(url);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
options?.onRealLoginFail?.(api, error.message);
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
239
247
|
const guard = (api, action) => {
|
|
240
248
|
try {
|
|
241
249
|
return ok(api, action() || {});
|
|
@@ -281,13 +289,13 @@
|
|
|
281
289
|
const bytes = Uint8Array.from(atob(String(args.base64Data || "")), (char) => char.charCodeAt(0));
|
|
282
290
|
return { tempFilePath: await saveTempFile(bytes, String(args.fileType || "png")) };
|
|
283
291
|
},
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
292
|
+
// 基础库经这两个接口向后台拉各种配置和小程序信息
|
|
293
|
+
operateWXData: (args, { api }) => args.data?.api_name === "webapi_getwxanetwork" ? bizDomainUnavailable(api) : ok(api),
|
|
294
|
+
getAppContact: (args, { api }) => args.keyPath === "WxaAppInfo.Network.BizDomain" ? bizDomainUnavailable(api) : ok(api),
|
|
287
295
|
// 真实登录失败时明确返回 fail,不退回假数据:配置或登录态的问题要让开发者马上发现
|
|
288
|
-
login: options?.realLogin ? async (_args, { api }) => ok(api, { code: (await
|
|
296
|
+
login: options?.realLogin ? async (_args, { api }) => ok(api, { code: (await realLogin(api, "/__wxa__/api/login")).code }) : (_args, { api }) => ok(api, { code: `wxa_mock_code_${Date.now().toString(36)}` }),
|
|
289
297
|
// 基础库只从 JSON.parse(data) 里取手机号的 code
|
|
290
|
-
getPhoneNumber: options?.realLogin ? async (_args, { api }) => ok(api, await
|
|
298
|
+
getPhoneNumber: options?.realLogin ? async (_args, { api }) => ok(api, await realLogin(api, "/__wxa__/api/phone-number")) : (_args, { api }) => ok(api, { data: JSON.stringify({ code: `mock_phone_code_${Date.now()}` }), encryptedData: "mock_encrypted", iv: "mock_iv" }),
|
|
291
299
|
checkSession: () => void 0,
|
|
292
300
|
authorize: () => void 0,
|
|
293
301
|
getSetting: (_args, { api }) => ok(api, {
|
|
@@ -321,6 +329,9 @@
|
|
|
321
329
|
function mockUser() {
|
|
322
330
|
return { nickName: "wxa-runner", avatarUrl: "", gender: 0, country: "", province: "", city: "", language: "zh_CN" };
|
|
323
331
|
}
|
|
332
|
+
function bizDomainUnavailable(api) {
|
|
333
|
+
return fail(api, "not available in wxa-runner");
|
|
334
|
+
}
|
|
324
335
|
|
|
325
336
|
// src/runtime/host/api/network.ts
|
|
326
337
|
var RequestLog = class {
|
|
@@ -479,20 +490,26 @@
|
|
|
479
490
|
}
|
|
480
491
|
return ok(api);
|
|
481
492
|
};
|
|
482
|
-
|
|
493
|
+
const createTask = {
|
|
483
494
|
createRequestTask,
|
|
484
495
|
createRequestTaskAsync: createRequestTask,
|
|
485
|
-
operateRequestTask: (args) => abortTask(args, "requestTaskId"),
|
|
486
496
|
createDownloadTask,
|
|
487
497
|
createDownloadTaskAsync: createDownloadTask,
|
|
488
|
-
operateDownloadTask: (args) => abortTask(args, "downloadTaskId"),
|
|
489
498
|
createUploadTask,
|
|
490
499
|
createUploadTaskAsync: createUploadTask,
|
|
500
|
+
createSocketTask
|
|
501
|
+
};
|
|
502
|
+
return {
|
|
503
|
+
...Object.fromEntries(Object.entries(createTask).map(([api, handler]) => [api, rejectBaseLibInternal(handler)])),
|
|
504
|
+
operateRequestTask: (args) => abortTask(args, "requestTaskId"),
|
|
505
|
+
operateDownloadTask: (args) => abortTask(args, "downloadTaskId"),
|
|
491
506
|
operateUploadTask: (args) => abortTask(args, "uploadTaskId"),
|
|
492
|
-
createSocketTask,
|
|
493
507
|
operateSocketTask
|
|
494
508
|
};
|
|
495
509
|
}
|
|
510
|
+
function rejectBaseLibInternal(handler) {
|
|
511
|
+
return (args, context) => args?.__skipDomainCheck__ === true ? fail(context.api, "\u57FA\u7840\u5E93\u5185\u90E8\u7684\u7F51\u7EDC\u8BF7\u6C42\uFF0Cwxa-runner \u4E0D\u53D1\u51FA") : handler(args, context);
|
|
512
|
+
}
|
|
496
513
|
async function proxyFetch(url, init) {
|
|
497
514
|
if (!/^https?:\/\//i.test(url)) throw new Error(`invalid url "${url}"`);
|
|
498
515
|
const hasBody = init.body !== void 0 && init.body !== "" && !["GET", "HEAD"].includes(init.method);
|
|
@@ -1395,7 +1412,14 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1395
1412
|
.wxa-toolbar button { border: 0; background: #111214; color: #fff; border-radius: 999px; padding: 4px 12px; font-size: 12px; cursor: pointer; }
|
|
1396
1413
|
.wxa-toolbar .wxa-scale { min-width: 52px; background: #fff; color: #333; box-shadow: inset 0 0 0 1px rgba(0,0,0,.12); font-variant-numeric: tabular-nums; }
|
|
1397
1414
|
.wxa-route { font-family: Menlo, monospace; }
|
|
1415
|
+
.wxa-notice { position: fixed; top: 16px; right: 16px; z-index: 100; display: flex; align-items: flex-start; gap: 10px; width: max-content; max-width: min(320px, calc(100vw - 32px)); padding: 12px 10px 12px 14px; box-sizing: border-box; border-radius: 12px; background: #fff; box-shadow: 0 0 0 1px rgba(15,23,42,.06), 0 12px 32px -8px rgba(15,23,42,.28); font-size: 13px; line-height: 20px; color: #555; animation: wxa-notice-in .18s ease-out; }
|
|
1416
|
+
.wxa-notice-icon { flex: none; width: 18px; height: 18px; margin-top: 1px; border-radius: 50%; background: #fa5151; color: #fff; font-size: 12px; font-weight: 700; line-height: 18px; text-align: center; }
|
|
1417
|
+
.wxa-notice-text { flex: 1; min-width: 0; word-break: break-word; text-wrap: pretty; }
|
|
1418
|
+
.wxa-notice-title { font-weight: 600; color: #111214; }
|
|
1419
|
+
.wxa-notice-close { flex: none; width: 20px; height: 20px; padding: 0; border: 0; border-radius: 50%; background: none; color: #999; font-size: 16px; line-height: 20px; cursor: pointer; }
|
|
1420
|
+
.wxa-notice-close:hover { background: rgba(0,0,0,.06); color: #111214; }
|
|
1398
1421
|
@keyframes wxa-spin { to { transform: rotate(360deg); } }
|
|
1422
|
+
@keyframes wxa-notice-in { from { opacity: 0; transform: translateY(-8px); } }
|
|
1399
1423
|
`;
|
|
1400
1424
|
|
|
1401
1425
|
// src/runtime/host/ui/Picker.ts
|
|
@@ -1484,6 +1508,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1484
1508
|
// src/runtime/host/ui/PhoneShell.ts
|
|
1485
1509
|
var SCALE_STORAGE_KEY = "wxa-runner:scale";
|
|
1486
1510
|
var MIN_SCALE = 0.5;
|
|
1511
|
+
var NOTICE_DURATION = 6e3;
|
|
1487
1512
|
function loadScaleMode() {
|
|
1488
1513
|
try {
|
|
1489
1514
|
const saved = localStorage.getItem(SCALE_STORAGE_KEY);
|
|
@@ -1498,6 +1523,8 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1498
1523
|
this.scaleMode = loadScaleMode();
|
|
1499
1524
|
this.toastEl = null;
|
|
1500
1525
|
this.toastTimer = null;
|
|
1526
|
+
this.noticeEl = null;
|
|
1527
|
+
this.noticeTimer = null;
|
|
1501
1528
|
this.tabBarConfig = null;
|
|
1502
1529
|
this.onTabTap = () => void 0;
|
|
1503
1530
|
this.navigation = { title: "", backgroundColor: "#000", textStyle: "white", showBack: false, custom: false };
|
|
@@ -1533,6 +1560,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1533
1560
|
<span class="wxa-route"></span><button class="wxa-scale"></button><button class="wxa-reload">\u5237\u65B0</button>
|
|
1534
1561
|
</div>
|
|
1535
1562
|
</div>`;
|
|
1563
|
+
this.stage = root.querySelector(".wxa-stage");
|
|
1536
1564
|
this.slot = root.querySelector(".wxa-device-slot");
|
|
1537
1565
|
this.device = root.querySelector(".wxa-device");
|
|
1538
1566
|
this.toolbar = root.querySelector(".wxa-toolbar");
|
|
@@ -1762,6 +1790,30 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1762
1790
|
showImagePreview(urls, current) {
|
|
1763
1791
|
openImagePreview(this.overlay, urls, current);
|
|
1764
1792
|
}
|
|
1793
|
+
/**
|
|
1794
|
+
* 在手机外面提示 wxa-runner 自身的问题(如真实登录失败),不混进小程序的界面。
|
|
1795
|
+
* 同一时间只显示一条,新的替换旧的:业务代码反复重试时不会堆满屏幕
|
|
1796
|
+
*/
|
|
1797
|
+
showNotice(title, message) {
|
|
1798
|
+
this.hideNotice();
|
|
1799
|
+
const notice = element("div", "wxa-notice");
|
|
1800
|
+
notice.setAttribute("role", "alert");
|
|
1801
|
+
const text = element("div", "wxa-notice-text");
|
|
1802
|
+
text.append(element("div", "wxa-notice-title", title), element("div", "wxa-notice-message", message));
|
|
1803
|
+
const close = element("button", "wxa-notice-close", "\xD7");
|
|
1804
|
+
close.setAttribute("aria-label", "\u5173\u95ED");
|
|
1805
|
+
close.addEventListener("click", () => this.hideNotice());
|
|
1806
|
+
notice.append(element("span", "wxa-notice-icon", "!"), text, close);
|
|
1807
|
+
this.stage.appendChild(notice);
|
|
1808
|
+
this.noticeEl = notice;
|
|
1809
|
+
this.noticeTimer = setTimeout(() => this.hideNotice(), NOTICE_DURATION);
|
|
1810
|
+
}
|
|
1811
|
+
hideNotice() {
|
|
1812
|
+
if (this.noticeTimer) clearTimeout(this.noticeTimer);
|
|
1813
|
+
this.noticeTimer = null;
|
|
1814
|
+
this.noticeEl?.remove();
|
|
1815
|
+
this.noticeEl = null;
|
|
1816
|
+
}
|
|
1765
1817
|
showError(message) {
|
|
1766
1818
|
this.clearError();
|
|
1767
1819
|
const el = element("div", "wxa-error", message);
|
|
@@ -1799,6 +1851,8 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1799
1851
|
/** setup 和测试 API 声明的请求拦截 */
|
|
1800
1852
|
this.routes = new RequestRoutes();
|
|
1801
1853
|
this.service = null;
|
|
1854
|
+
/** 新版基础库的业务子上下文;旧版基础库业务代码直接在逻辑层 iframe 里 */
|
|
1855
|
+
this.appContext = null;
|
|
1802
1856
|
this.serviceReady = new Promise((resolve) => {
|
|
1803
1857
|
this.serviceLoaded = resolve;
|
|
1804
1858
|
});
|
|
@@ -1843,6 +1897,9 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1843
1897
|
this.pages.get(webviewId)?.attach(win, bridge);
|
|
1844
1898
|
}
|
|
1845
1899
|
}
|
|
1900
|
+
registerAppContext(win) {
|
|
1901
|
+
this.appContext = win;
|
|
1902
|
+
}
|
|
1846
1903
|
frameLoaded(kind, webviewId) {
|
|
1847
1904
|
if (kind === "service") this.serviceLoaded();
|
|
1848
1905
|
else this.pages.get(webviewId)?.loaded();
|
|
@@ -1851,7 +1908,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1851
1908
|
const webviewId = kind === "webview" ? this.webviewIdOf(win) : this.currentPage()?.id ?? 0;
|
|
1852
1909
|
const result = this.api.invoke(api, args, { api, from: kind, webviewId });
|
|
1853
1910
|
const deliver = (res) => {
|
|
1854
|
-
if (callback) callback(cloneInto(win, res));
|
|
1911
|
+
if (callback) callback(kind === "service" ? this.cloneForService(res) : cloneInto(win, res));
|
|
1855
1912
|
};
|
|
1856
1913
|
if (result && typeof result.then === "function") {
|
|
1857
1914
|
result.then(deliver);
|
|
@@ -1873,7 +1930,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1873
1930
|
const fromId = this.webviewIdOf(win);
|
|
1874
1931
|
const service = this.service;
|
|
1875
1932
|
if (!service) return;
|
|
1876
|
-
const payload =
|
|
1933
|
+
const payload = this.cloneForService(data);
|
|
1877
1934
|
queueMicrotask(() => service.bridge.__dispatchSub(event, payload, fromId));
|
|
1878
1935
|
}
|
|
1879
1936
|
reportError(kind, webviewId, error) {
|
|
@@ -1887,10 +1944,10 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1887
1944
|
}
|
|
1888
1945
|
/**
|
|
1889
1946
|
* 业务代码只运行在逻辑层,所以只记逻辑层调用、名字在 wx 上的 API,每个 API 一次。
|
|
1890
|
-
* 其余是基础库内部调用的原生接口(如
|
|
1947
|
+
* 其余是基础库内部调用的原生接口(如 getPermissionBytes、loadLibFilesSync),业务既没调用也 mock 不了
|
|
1891
1948
|
*/
|
|
1892
1949
|
reportUnimplemented({ api, from }) {
|
|
1893
|
-
const wx = this.
|
|
1950
|
+
const wx = this.serviceWindow()?.wx;
|
|
1894
1951
|
if (from !== "service" || typeof wx?.[api] !== "function") return;
|
|
1895
1952
|
if (this.errors.some((error) => error.api === api)) return;
|
|
1896
1953
|
this.errors.push({
|
|
@@ -1913,8 +1970,16 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1913
1970
|
livePages() {
|
|
1914
1971
|
return [...this.pages.values()];
|
|
1915
1972
|
}
|
|
1973
|
+
/** 业务代码所在的 window:getCurrentPages / wx 等在它上面 */
|
|
1916
1974
|
serviceWindow() {
|
|
1917
|
-
return this.service?.win ?? null;
|
|
1975
|
+
return this.appContext ?? this.service?.win ?? null;
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* 交给逻辑层的值属于业务代码所在的全局环境,而不是逻辑层 iframe:新版基础库不一定在业务子上下文里
|
|
1979
|
+
* 重建这些值,属于别的 iframe 的对象在业务代码里 instanceof 不成立,setData 也会把这样的数组当成普通对象
|
|
1980
|
+
*/
|
|
1981
|
+
cloneForService(value) {
|
|
1982
|
+
return cloneInto(this.serviceWindow(), value);
|
|
1918
1983
|
}
|
|
1919
1984
|
onRoute(listener) {
|
|
1920
1985
|
this.routeListeners.push(listener);
|
|
@@ -2073,7 +2138,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2073
2138
|
emitToService(event, data, webviewId) {
|
|
2074
2139
|
const service = this.service;
|
|
2075
2140
|
if (!service) return;
|
|
2076
|
-
const payload =
|
|
2141
|
+
const payload = this.cloneForService(data);
|
|
2077
2142
|
queueMicrotask(() => service.bridge.__dispatchOn(event, payload, webviewId));
|
|
2078
2143
|
}
|
|
2079
2144
|
/** 派发给渲染层的原生事件(渲染层里 WeixinJSBridge.on 收到) */
|
|
@@ -2132,7 +2197,10 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2132
2197
|
documentOf: (webviewId) => this.pages.get(webviewId)?.win?.document,
|
|
2133
2198
|
emitToWebview: (webviewId, event, data) => this.emitToWebview(webviewId, event, data)
|
|
2134
2199
|
}),
|
|
2135
|
-
...createMiscApis(this.info.appid, {
|
|
2200
|
+
...createMiscApis(this.info.appid, {
|
|
2201
|
+
realLogin: this.info.realLogin,
|
|
2202
|
+
onRealLoginFail: (api, reason2) => this.shell.showNotice(`wx.${api} \u771F\u5B9E\u767B\u5F55\u5931\u8D25`, reason2)
|
|
2203
|
+
})
|
|
2136
2204
|
});
|
|
2137
2205
|
}
|
|
2138
2206
|
};
|
|
@@ -2176,7 +2244,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2176
2244
|
const sheets = win.__styleSheetManager2__?._pathMap;
|
|
2177
2245
|
if (!sheets) throw new Error("\u57FA\u7840\u5E93\u6CA1\u6709\u6837\u5F0F\u8868\u7BA1\u7406\u5668\uFF0C\u65E0\u6CD5\u70ED\u66FF\u6362\u6837\u5F0F");
|
|
2178
2246
|
const applied = Object.keys(sheets);
|
|
2179
|
-
win
|
|
2247
|
+
runScript(win, code);
|
|
2180
2248
|
for (const key of applied) {
|
|
2181
2249
|
const [, file, classPrefix] = /^\.\/(.+\.wxss):(.*)$/.exec(key) || [];
|
|
2182
2250
|
const inject = file && win.__wxAppCode__[file];
|
|
@@ -2196,7 +2264,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2196
2264
|
function updateTemplates(win, code, units) {
|
|
2197
2265
|
const cache = win.__WXML_GLOBAL__;
|
|
2198
2266
|
if (cache) Object.assign(cache, { ops_cached: {}, ops_set: {}, ops_init: {}, debuginfo_set: {}, modules: {} });
|
|
2199
|
-
win
|
|
2267
|
+
runScript(win, code);
|
|
2200
2268
|
for (const id of units) {
|
|
2201
2269
|
const generate = win.__wxAppCode__[id + ".wxml"];
|
|
2202
2270
|
if (generate) win.__virtualDOM__.updateWxAppCode(id + ".wxml", generate);
|
|
@@ -2231,11 +2299,16 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2231
2299
|
}
|
|
2232
2300
|
function loadModule(service, script, hotId) {
|
|
2233
2301
|
let factory;
|
|
2234
|
-
service.
|
|
2235
|
-
${script}
|
|
2236
|
-
})`)((_id, fn) => {
|
|
2302
|
+
service.__wxaHotDefine__ = (_id, fn) => {
|
|
2237
2303
|
factory = fn;
|
|
2238
|
-
}
|
|
2304
|
+
};
|
|
2305
|
+
try {
|
|
2306
|
+
runScript(service, `(function (define) {
|
|
2307
|
+
${script}
|
|
2308
|
+
})(__wxaHotDefine__);`);
|
|
2309
|
+
} finally {
|
|
2310
|
+
delete service.__wxaHotDefine__;
|
|
2311
|
+
}
|
|
2239
2312
|
if (typeof factory !== "function") throw new Error("\u6A21\u5757\u811A\u672C\u6CA1\u6709 define \u6A21\u5757");
|
|
2240
2313
|
service.define(hotId, factory);
|
|
2241
2314
|
const { Page, Component } = service;
|
|
@@ -2264,6 +2337,26 @@ ${script}
|
|
|
2264
2337
|
const added = Object.keys(data).filter((key) => !(key in instance.data));
|
|
2265
2338
|
if (added.length) instance.setData(Object.fromEntries(added.map((key) => [key, data[key]])));
|
|
2266
2339
|
}
|
|
2340
|
+
function runScript(win, code) {
|
|
2341
|
+
let failed = false;
|
|
2342
|
+
let error;
|
|
2343
|
+
const onError = (event) => {
|
|
2344
|
+
failed = true;
|
|
2345
|
+
error = event.error ?? new Error(event.message);
|
|
2346
|
+
event.stopImmediatePropagation();
|
|
2347
|
+
event.preventDefault();
|
|
2348
|
+
};
|
|
2349
|
+
const script = win.document.createElement("script");
|
|
2350
|
+
script.text = code;
|
|
2351
|
+
win.addEventListener("error", onError, true);
|
|
2352
|
+
try {
|
|
2353
|
+
win.document.head.appendChild(script);
|
|
2354
|
+
} finally {
|
|
2355
|
+
win.removeEventListener("error", onError, true);
|
|
2356
|
+
script.remove();
|
|
2357
|
+
}
|
|
2358
|
+
if (failed) throw error;
|
|
2359
|
+
}
|
|
2267
2360
|
async function fetchText(url) {
|
|
2268
2361
|
const response = await fetch(url);
|
|
2269
2362
|
if (!response.ok) throw new Error(`${url} ${response.status}`);
|