@open-wxa/wxa-runner-core 0.2.0 → 0.2.2
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 +3 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -1
- package/dist/runtime/host.js +129 -5
- package/dist/runtime/host.js.map +1 -1
- package/package.json +3 -3
package/dist/runtime/host.js
CHANGED
|
@@ -85,17 +85,32 @@
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// src/runtime/host/api/tempFiles.ts
|
|
88
|
+
var TEMP_DIR = "/__wxa__/tmp";
|
|
88
89
|
async function saveTempFile(data, type) {
|
|
89
|
-
const res = await fetch(
|
|
90
|
+
const res = await fetch(`${TEMP_DIR}?type=${encodeURIComponent(type)}`, { method: "POST", body: data });
|
|
90
91
|
if (!res.ok) throw new Error(await res.text());
|
|
91
92
|
const { url } = await res.json();
|
|
92
93
|
return url;
|
|
93
94
|
}
|
|
95
|
+
function tempFileRootPath(url) {
|
|
96
|
+
const prefix = `${location.origin}${TEMP_DIR}/`;
|
|
97
|
+
return url.startsWith(prefix) ? url.slice(location.origin.length) : url;
|
|
98
|
+
}
|
|
94
99
|
function tempFileType(name, mimeType, fallback) {
|
|
95
100
|
const fromName = /\.([a-z\d]+)$/i.exec(name)?.[1];
|
|
96
101
|
const fromType = /^[a-z]+\/([a-z\d]+)\s*(;|$)/i.exec(mimeType)?.[1];
|
|
97
102
|
return (fromName || fromType || fallback).toLowerCase();
|
|
98
103
|
}
|
|
104
|
+
function isTempFile(filePath) {
|
|
105
|
+
return typeof filePath === "string" && filePath.startsWith(`${location.origin}${TEMP_DIR}/`);
|
|
106
|
+
}
|
|
107
|
+
async function tempFileSize(url) {
|
|
108
|
+
const res = await fetch(url, { method: "HEAD" });
|
|
109
|
+
return res.ok ? Number(res.headers.get("content-length")) : null;
|
|
110
|
+
}
|
|
111
|
+
async function removeTempFile(url) {
|
|
112
|
+
await fetch(url, { method: "DELETE" });
|
|
113
|
+
}
|
|
99
114
|
|
|
100
115
|
// src/runtime/host/api/media.ts
|
|
101
116
|
var SOURCES = ["camera", "album"];
|
|
@@ -236,6 +251,14 @@
|
|
|
236
251
|
};
|
|
237
252
|
function createMiscApis(appid, options) {
|
|
238
253
|
const fs = new MemoryFileSystem();
|
|
254
|
+
const realLogin = async (api, url) => {
|
|
255
|
+
try {
|
|
256
|
+
return await postDevServer(url);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
options?.onRealLoginFail?.(api, error.message);
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
239
262
|
const guard = (api, action) => {
|
|
240
263
|
try {
|
|
241
264
|
return ok(api, action() || {});
|
|
@@ -267,6 +290,9 @@
|
|
|
267
290
|
unlink: (args, { api }) => guard(api, () => fs.remove(args.filePath, { dir: false })),
|
|
268
291
|
rmdir: (args, { api }) => guard(api, () => fs.remove(args.dirPath, { dir: true, recursive: !!args.recursive })),
|
|
269
292
|
getFileInfo: (args, { api }) => {
|
|
293
|
+
if (isTempFile(args.filePath)) {
|
|
294
|
+
return tempFileSize(args.filePath).then((size) => size === null ? fail(api, "file not exist") : ok(api, { size }));
|
|
295
|
+
}
|
|
270
296
|
const node = fs.stat(args.filePath);
|
|
271
297
|
return node ? ok(api, { size: node.data.length }) : fail(api, "file not exist");
|
|
272
298
|
}
|
|
@@ -285,9 +311,9 @@
|
|
|
285
311
|
operateWXData: (args, { api }) => args.data?.api_name === "webapi_getwxanetwork" ? bizDomainUnavailable(api) : ok(api),
|
|
286
312
|
getAppContact: (args, { api }) => args.keyPath === "WxaAppInfo.Network.BizDomain" ? bizDomainUnavailable(api) : ok(api),
|
|
287
313
|
// 真实登录失败时明确返回 fail,不退回假数据:配置或登录态的问题要让开发者马上发现
|
|
288
|
-
login: options?.realLogin ? async (_args, { api }) => ok(api, { code: (await
|
|
314
|
+
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
315
|
// 基础库只从 JSON.parse(data) 里取手机号的 code
|
|
290
|
-
getPhoneNumber: options?.realLogin ? async (_args, { api }) => ok(api, await
|
|
316
|
+
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
317
|
checkSession: () => void 0,
|
|
292
318
|
authorize: () => void 0,
|
|
293
319
|
getSetting: (_args, { api }) => ok(api, {
|
|
@@ -823,6 +849,50 @@
|
|
|
823
849
|
return text.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
824
850
|
}
|
|
825
851
|
|
|
852
|
+
// src/runtime/host/api/savedFiles.ts
|
|
853
|
+
var NAMESPACE = "savedFiles";
|
|
854
|
+
function createSavedFileApis(storage) {
|
|
855
|
+
const record = (filePath) => storage.get(NAMESPACE, filePath)?.data;
|
|
856
|
+
const liveRecord = async (filePath) => {
|
|
857
|
+
const saved = record(filePath);
|
|
858
|
+
if (!saved) return void 0;
|
|
859
|
+
if (await tempFileSize(filePath) !== null) return saved;
|
|
860
|
+
storage.remove(NAMESPACE, filePath);
|
|
861
|
+
return void 0;
|
|
862
|
+
};
|
|
863
|
+
return {
|
|
864
|
+
// 和真机一样:存成新文件,原来的临时文件随之删除
|
|
865
|
+
saveFile: async (args) => {
|
|
866
|
+
if (args.filePath) throw new Error("wxa-runner \u6682\u4E0D\u652F\u6301\u6307\u5B9A filePath\uFF0C\u4E0D\u4F20\u65F6\u4FDD\u5B58\u6210\u529F\u540E\u8FD4\u56DE savedFilePath");
|
|
867
|
+
const res = isTempFile(args.tempFilePath) ? await fetch(args.tempFilePath) : null;
|
|
868
|
+
if (!res?.ok) throw new Error(`tempFilePath file not exist ${args.tempFilePath}`);
|
|
869
|
+
const data = await res.blob();
|
|
870
|
+
const savedFilePath = await saveTempFile(data, tempFileType(args.tempFilePath, data.type, "tmp"));
|
|
871
|
+
await removeTempFile(args.tempFilePath);
|
|
872
|
+
storage.set(NAMESPACE, savedFilePath, { size: data.size, createTime: Math.floor(Date.now() / 1e3) });
|
|
873
|
+
return { savedFilePath };
|
|
874
|
+
},
|
|
875
|
+
getSavedFileList: async () => {
|
|
876
|
+
const fileList = [];
|
|
877
|
+
for (const filePath of storage.keys(NAMESPACE)) {
|
|
878
|
+
const saved = await liveRecord(filePath);
|
|
879
|
+
if (saved) fileList.push({ filePath, ...saved });
|
|
880
|
+
}
|
|
881
|
+
return { fileList };
|
|
882
|
+
},
|
|
883
|
+
getSavedFileInfo: async (args) => {
|
|
884
|
+
const saved = await liveRecord(args.filePath);
|
|
885
|
+
if (!saved) throw new Error(`file not exist ${args.filePath}`);
|
|
886
|
+
return { ...saved };
|
|
887
|
+
},
|
|
888
|
+
removeSavedFile: async (args) => {
|
|
889
|
+
if (!record(args.filePath)) throw new Error(`file not exist ${args.filePath}`);
|
|
890
|
+
storage.remove(NAMESPACE, args.filePath);
|
|
891
|
+
await removeTempFile(args.filePath);
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
|
|
826
896
|
// src/runtime/host/api/storage.ts
|
|
827
897
|
var LIMIT_SIZE_KB = 10 * 1024;
|
|
828
898
|
var MiniProgramStorage = class {
|
|
@@ -1404,7 +1474,14 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1404
1474
|
.wxa-toolbar button { border: 0; background: #111214; color: #fff; border-radius: 999px; padding: 4px 12px; font-size: 12px; cursor: pointer; }
|
|
1405
1475
|
.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; }
|
|
1406
1476
|
.wxa-route { font-family: Menlo, monospace; }
|
|
1477
|
+
.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; }
|
|
1478
|
+
.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; }
|
|
1479
|
+
.wxa-notice-text { flex: 1; min-width: 0; word-break: break-word; text-wrap: pretty; }
|
|
1480
|
+
.wxa-notice-title { font-weight: 600; color: #111214; }
|
|
1481
|
+
.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; }
|
|
1482
|
+
.wxa-notice-close:hover { background: rgba(0,0,0,.06); color: #111214; }
|
|
1407
1483
|
@keyframes wxa-spin { to { transform: rotate(360deg); } }
|
|
1484
|
+
@keyframes wxa-notice-in { from { opacity: 0; transform: translateY(-8px); } }
|
|
1408
1485
|
`;
|
|
1409
1486
|
|
|
1410
1487
|
// src/runtime/host/ui/Picker.ts
|
|
@@ -1493,6 +1570,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1493
1570
|
// src/runtime/host/ui/PhoneShell.ts
|
|
1494
1571
|
var SCALE_STORAGE_KEY = "wxa-runner:scale";
|
|
1495
1572
|
var MIN_SCALE = 0.5;
|
|
1573
|
+
var NOTICE_DURATION = 6e3;
|
|
1496
1574
|
function loadScaleMode() {
|
|
1497
1575
|
try {
|
|
1498
1576
|
const saved = localStorage.getItem(SCALE_STORAGE_KEY);
|
|
@@ -1507,6 +1585,8 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1507
1585
|
this.scaleMode = loadScaleMode();
|
|
1508
1586
|
this.toastEl = null;
|
|
1509
1587
|
this.toastTimer = null;
|
|
1588
|
+
this.noticeEl = null;
|
|
1589
|
+
this.noticeTimer = null;
|
|
1510
1590
|
this.tabBarConfig = null;
|
|
1511
1591
|
this.onTabTap = () => void 0;
|
|
1512
1592
|
this.navigation = { title: "", backgroundColor: "#000", textStyle: "white", showBack: false, custom: false };
|
|
@@ -1542,6 +1622,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1542
1622
|
<span class="wxa-route"></span><button class="wxa-scale"></button><button class="wxa-reload">\u5237\u65B0</button>
|
|
1543
1623
|
</div>
|
|
1544
1624
|
</div>`;
|
|
1625
|
+
this.stage = root.querySelector(".wxa-stage");
|
|
1545
1626
|
this.slot = root.querySelector(".wxa-device-slot");
|
|
1546
1627
|
this.device = root.querySelector(".wxa-device");
|
|
1547
1628
|
this.toolbar = root.querySelector(".wxa-toolbar");
|
|
@@ -1771,6 +1852,30 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1771
1852
|
showImagePreview(urls, current) {
|
|
1772
1853
|
openImagePreview(this.overlay, urls, current);
|
|
1773
1854
|
}
|
|
1855
|
+
/**
|
|
1856
|
+
* 在手机外面提示 wxa-runner 自身的问题(如真实登录失败),不混进小程序的界面。
|
|
1857
|
+
* 同一时间只显示一条,新的替换旧的:业务代码反复重试时不会堆满屏幕
|
|
1858
|
+
*/
|
|
1859
|
+
showNotice(title, message) {
|
|
1860
|
+
this.hideNotice();
|
|
1861
|
+
const notice = element("div", "wxa-notice");
|
|
1862
|
+
notice.setAttribute("role", "alert");
|
|
1863
|
+
const text = element("div", "wxa-notice-text");
|
|
1864
|
+
text.append(element("div", "wxa-notice-title", title), element("div", "wxa-notice-message", message));
|
|
1865
|
+
const close = element("button", "wxa-notice-close", "\xD7");
|
|
1866
|
+
close.setAttribute("aria-label", "\u5173\u95ED");
|
|
1867
|
+
close.addEventListener("click", () => this.hideNotice());
|
|
1868
|
+
notice.append(element("span", "wxa-notice-icon", "!"), text, close);
|
|
1869
|
+
this.stage.appendChild(notice);
|
|
1870
|
+
this.noticeEl = notice;
|
|
1871
|
+
this.noticeTimer = setTimeout(() => this.hideNotice(), NOTICE_DURATION);
|
|
1872
|
+
}
|
|
1873
|
+
hideNotice() {
|
|
1874
|
+
if (this.noticeTimer) clearTimeout(this.noticeTimer);
|
|
1875
|
+
this.noticeTimer = null;
|
|
1876
|
+
this.noticeEl?.remove();
|
|
1877
|
+
this.noticeEl = null;
|
|
1878
|
+
}
|
|
1774
1879
|
showError(message) {
|
|
1775
1880
|
this.clearError();
|
|
1776
1881
|
const el = element("div", "wxa-error", message);
|
|
@@ -1876,9 +1981,10 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
1876
1981
|
publish(kind, win, event, data, webviewIds) {
|
|
1877
1982
|
if (kind === "service") {
|
|
1878
1983
|
const targets = webviewIds?.length ? webviewIds.map((id) => this.pages.get(Number(id))) : this.livePages();
|
|
1984
|
+
const message = event === "canvasActionsChanged" ? withDrawableImages(data) : data;
|
|
1879
1985
|
for (const page of targets) {
|
|
1880
1986
|
page?.dispatch((bridge, target) => {
|
|
1881
|
-
const payload2 = cloneInto(target,
|
|
1987
|
+
const payload2 = cloneInto(target, message);
|
|
1882
1988
|
queueMicrotask(() => bridge.__dispatchSub(event, payload2, 0));
|
|
1883
1989
|
});
|
|
1884
1990
|
}
|
|
@@ -2126,6 +2232,7 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2126
2232
|
reLaunch: routeApi((args) => this.reLaunch(args.url)),
|
|
2127
2233
|
switchTab: routeApi((args) => this.switchTab(args.url)),
|
|
2128
2234
|
...createStorageApis(this.storage),
|
|
2235
|
+
...createSavedFileApis(this.storage),
|
|
2129
2236
|
...createSystemApis({
|
|
2130
2237
|
device: this.device,
|
|
2131
2238
|
baseLibVersion: this.info.baseLibVersion,
|
|
@@ -2154,7 +2261,10 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2154
2261
|
documentOf: (webviewId) => this.pages.get(webviewId)?.win?.document,
|
|
2155
2262
|
emitToWebview: (webviewId, event, data) => this.emitToWebview(webviewId, event, data)
|
|
2156
2263
|
}),
|
|
2157
|
-
...createMiscApis(this.info.appid, {
|
|
2264
|
+
...createMiscApis(this.info.appid, {
|
|
2265
|
+
realLogin: this.info.realLogin,
|
|
2266
|
+
onRealLoginFail: (api, reason2) => this.shell.showNotice(`wx.${api} \u771F\u5B9E\u767B\u5F55\u5931\u8D25`, reason2)
|
|
2267
|
+
})
|
|
2158
2268
|
});
|
|
2159
2269
|
}
|
|
2160
2270
|
};
|
|
@@ -2162,6 +2272,20 @@ html, body { margin: 0; height: 100%; background: radial-gradient(120% 80% at 50
|
|
|
2162
2272
|
if (value === void 0) return value;
|
|
2163
2273
|
return win.JSON.parse(JSON.stringify(value));
|
|
2164
2274
|
}
|
|
2275
|
+
function withDrawableImages(message) {
|
|
2276
|
+
const actions = message?.data?.actions;
|
|
2277
|
+
if (!Array.isArray(actions)) return message;
|
|
2278
|
+
return {
|
|
2279
|
+
...message,
|
|
2280
|
+
data: {
|
|
2281
|
+
...message.data,
|
|
2282
|
+
actions: actions.map((action) => {
|
|
2283
|
+
const [src, ...rest] = action.data ?? [];
|
|
2284
|
+
return action.method === "drawImage" && typeof src === "string" ? { ...action, data: [tempFileRootPath(src), ...rest] } : action;
|
|
2285
|
+
})
|
|
2286
|
+
}
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2165
2289
|
function stripHtml(path) {
|
|
2166
2290
|
return path.replace(/\.html$/, "");
|
|
2167
2291
|
}
|