@napuketto/loader 0.0.29 → 0.0.30
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/host/self-host.cjs +262 -68
- package/package.json +1 -1
package/dist/host/self-host.cjs
CHANGED
|
@@ -856,6 +856,7 @@ function attachIpcServices(actions, services) {
|
|
|
856
856
|
forwardChannel(services.friendChannel, stops);
|
|
857
857
|
return () => {
|
|
858
858
|
for (const stop of stops) stop();
|
|
859
|
+
for (const name of full.keys()) actions.delete(name);
|
|
859
860
|
};
|
|
860
861
|
}
|
|
861
862
|
//#endregion
|
|
@@ -950,6 +951,7 @@ async function attachOb11IpcBridge(actions, services, deps = {}) {
|
|
|
950
951
|
msgChannel: services.channel,
|
|
951
952
|
groupChannel: services.groupChannel,
|
|
952
953
|
friendChannel: services.friendChannel,
|
|
954
|
+
buddyCacheEvents: services.buddyCache?.events,
|
|
953
955
|
logger: services.logger,
|
|
954
956
|
msgApi: services.msgApi,
|
|
955
957
|
groupApi: services.groupApi,
|
|
@@ -999,6 +1001,7 @@ async function attachOb11IpcBridge(actions, services, deps = {}) {
|
|
|
999
1001
|
return () => {
|
|
1000
1002
|
broadcaster.unregister(bridgeAdapter);
|
|
1001
1003
|
ob11.unsubscribeOnly();
|
|
1004
|
+
for (const name of ob11.registry.names) actions.delete(name);
|
|
1002
1005
|
};
|
|
1003
1006
|
} catch (err) {
|
|
1004
1007
|
log(`ipc-ob11: OB11 动作桥装配失败(降级纯 kernel 动作面): ${errMsg(err)}`);
|
|
@@ -1166,13 +1169,23 @@ function loadProtocolSections(kernel, uin) {
|
|
|
1166
1169
|
const BACKSLASH_RE = /\\/g;
|
|
1167
1170
|
/** adapter 入口 index.mjs 后缀(子路径导出替换用)。 */
|
|
1168
1171
|
const INDEX_MJS_RE = /index\.mjs$/;
|
|
1169
|
-
/**
|
|
1172
|
+
/**
|
|
1173
|
+
* 装配 OB11 + Satori 适配器(登录成功后,非 IPC 模式)。
|
|
1174
|
+
*
|
|
1175
|
+
* 返回停止函数(ob11/satori 适配器 stop:传输关闭 + 订阅清理;装配跳过时
|
|
1176
|
+
* no-op)——startProtocols 存入 services.stopAdapters,供未来非 IPC 软重登
|
|
1177
|
+
* 清理(当前软重登仅 IPC 模式有触发通道,见 relogin.ts)。
|
|
1178
|
+
*
|
|
1179
|
+
* ⚠️ 装配失败**抛出**(2026-09-08 修复):cli 模式跑宿主就是为了 OB11/Satori
|
|
1180
|
+
* 服务,此处吞错会让 startProtocols 误判成功、以假 ready 留驻(内部 catch
|
|
1181
|
+
* 曾把 protocols.ts「装配失败返回 null 判引导失败」的意图整个短路)。
|
|
1182
|
+
*/
|
|
1170
1183
|
async function assembleOb11AndSatori(kernel, services, loginResult) {
|
|
1171
1184
|
const adapterEntry = env.NAPUTO_ADAPTER_ENTRY;
|
|
1172
1185
|
const networkEntry = env.NAPUTO_NETWORK_ENTRY;
|
|
1173
1186
|
if (!adapterEntry || !networkEntry) {
|
|
1174
1187
|
log("bootstrap: NAPUTO_ADAPTER_ENTRY/NETWORK_ENTRY 未设置,跳过协议装配");
|
|
1175
|
-
return;
|
|
1188
|
+
return async () => {};
|
|
1176
1189
|
}
|
|
1177
1190
|
try {
|
|
1178
1191
|
const network = await import(`file://${networkEntry.replace(BACKSLASH_RE, "/")}`);
|
|
@@ -1194,12 +1207,13 @@ async function assembleOb11AndSatori(kernel, services, loginResult) {
|
|
|
1194
1207
|
const wrapperExports = services.ctx.wrapper?.exports;
|
|
1195
1208
|
const qqRoot = kernel.resolveQqUserDataRoot?.(wrapperExports) ?? null;
|
|
1196
1209
|
const mediaBaseDir = qqRoot !== null ? kernel.resolveQqGlobalPath?.(qqRoot) : void 0;
|
|
1197
|
-
|
|
1210
|
+
const ob11 = new adapter.NapukettoOneBot11Adapter({
|
|
1198
1211
|
config: ob11Config,
|
|
1199
1212
|
broadcaster,
|
|
1200
1213
|
msgChannel: channel,
|
|
1201
1214
|
groupChannel: services.groupChannel,
|
|
1202
1215
|
friendChannel: services.friendChannel,
|
|
1216
|
+
buddyCacheEvents: services.buddyCache?.events,
|
|
1203
1217
|
logger: services.logger,
|
|
1204
1218
|
msgApi: services.msgApi,
|
|
1205
1219
|
groupApi: services.groupApi,
|
|
@@ -1231,7 +1245,8 @@ async function assembleOb11AndSatori(kernel, services, loginResult) {
|
|
|
1231
1245
|
}
|
|
1232
1246
|
},
|
|
1233
1247
|
groupCache
|
|
1234
|
-
})
|
|
1248
|
+
});
|
|
1249
|
+
await ob11.start();
|
|
1235
1250
|
log("bootstrap: onebot11 adapter started");
|
|
1236
1251
|
const satoriCacheDir = typeof satoriSection["cacheDir"] === "string" && satoriSection["cacheDir"] !== "" ? satoriSection["cacheDir"] : (0, node_path.join)(env.NAPUTO_CFG_DIR || ".", "cache");
|
|
1237
1252
|
const satoriConfig = new adapterCore.ProtocolConfig({
|
|
@@ -1240,7 +1255,7 @@ async function assembleOb11AndSatori(kernel, services, loginResult) {
|
|
|
1240
1255
|
defaults: satoriAdapter.satoriConfigSchema.parse({}),
|
|
1241
1256
|
seed: satoriAdapter.satoriConfigSchema.parse(satoriSection)
|
|
1242
1257
|
});
|
|
1243
|
-
|
|
1258
|
+
const satori = new satoriAdapter.NapukettoSatoriAdapter({
|
|
1244
1259
|
config: satoriConfig,
|
|
1245
1260
|
broadcaster,
|
|
1246
1261
|
msgChannel: channel,
|
|
@@ -1252,10 +1267,16 @@ async function assembleOb11AndSatori(kernel, services, loginResult) {
|
|
|
1252
1267
|
self: services.self,
|
|
1253
1268
|
cacheDir: satoriCacheDir,
|
|
1254
1269
|
groupCache
|
|
1255
|
-
})
|
|
1270
|
+
});
|
|
1271
|
+
await satori.start();
|
|
1256
1272
|
log("bootstrap: satori adapter started");
|
|
1273
|
+
return async () => {
|
|
1274
|
+
await ob11.stop();
|
|
1275
|
+
await satori.stop();
|
|
1276
|
+
};
|
|
1257
1277
|
} catch (e) {
|
|
1258
1278
|
log(`bootstrap: 协议装配失败: ${errMsg(e)}`);
|
|
1279
|
+
throw e;
|
|
1259
1280
|
}
|
|
1260
1281
|
}
|
|
1261
1282
|
//#endregion
|
|
@@ -1299,9 +1320,11 @@ function formatMessageLog(kernel, msg) {
|
|
|
1299
1320
|
}
|
|
1300
1321
|
/**
|
|
1301
1322
|
* 消息日志订阅(onRecvMsg 回调参数为消息数组——2026-08-07 运行时实证,遍历逐条打印)。
|
|
1323
|
+
* 返回退订函数(软重登重装配时清理旧订阅,2026-09-08);通道返回面宽松
|
|
1324
|
+
* (EventChannelLike.on 返回 unknown),非函数时退化为 no-op。
|
|
1302
1325
|
*/
|
|
1303
1326
|
function setupMsgLogging(kernel, channel, logger, colorize) {
|
|
1304
|
-
channel.on("Msg/onRecvMsg", (msgs) => {
|
|
1327
|
+
const off = channel.on("Msg/onRecvMsg", (msgs) => {
|
|
1305
1328
|
forEachRawMessage(msgs, (m) => {
|
|
1306
1329
|
try {
|
|
1307
1330
|
const { plain, colored } = formatMessageLog(kernel, m);
|
|
@@ -1314,6 +1337,7 @@ function setupMsgLogging(kernel, channel, logger, colorize) {
|
|
|
1314
1337
|
}
|
|
1315
1338
|
});
|
|
1316
1339
|
});
|
|
1340
|
+
return typeof off === "function" ? off : () => {};
|
|
1317
1341
|
}
|
|
1318
1342
|
//#endregion
|
|
1319
1343
|
//#region src/host/core/kernel-services.ts
|
|
@@ -1338,21 +1362,29 @@ async function createKernelServices(kernel, ctx, loginResult) {
|
|
|
1338
1362
|
return null;
|
|
1339
1363
|
}
|
|
1340
1364
|
const channel = new kernel.NTEventChannel("Msg");
|
|
1341
|
-
new kernel.MsgBridge(session, channel)
|
|
1342
|
-
|
|
1365
|
+
const bridge = new kernel.MsgBridge(session, channel);
|
|
1366
|
+
bridge.register();
|
|
1367
|
+
const offMsgLogging = setupMsgLogging(kernel, channel, logger, !ipcMode);
|
|
1343
1368
|
const groupApi = new kernel.GroupApi(session);
|
|
1344
1369
|
const util = ctx.exports?.NodeQQNTWrapperUtil;
|
|
1345
1370
|
const msgApi = new kernel.MsgApi(session, channel, util);
|
|
1346
1371
|
const friendApi = new kernel.FriendApi(session, { uidToUin: (uids) => groupApi.uidToUin(uids) });
|
|
1347
1372
|
const groupChannel = new kernel.NTEventChannel("Group");
|
|
1348
|
-
new kernel.GroupBridge(session, groupChannel)
|
|
1373
|
+
const groupBridge = new kernel.GroupBridge(session, groupChannel);
|
|
1374
|
+
groupBridge.register();
|
|
1349
1375
|
const friendChannel = new kernel.NTEventChannel("Buddy");
|
|
1350
|
-
new kernel.FriendBridge(session, friendChannel)
|
|
1376
|
+
const friendBridge = new kernel.FriendBridge(session, friendChannel);
|
|
1377
|
+
friendBridge.register();
|
|
1351
1378
|
const groupCache = new kernel.GroupCache({
|
|
1352
1379
|
channel: groupChannel,
|
|
1353
1380
|
groupApi
|
|
1354
1381
|
});
|
|
1355
1382
|
groupCache.register();
|
|
1383
|
+
const buddyCache = new kernel.BuddyCache({
|
|
1384
|
+
channel: friendChannel,
|
|
1385
|
+
logger
|
|
1386
|
+
});
|
|
1387
|
+
buddyCache.register();
|
|
1356
1388
|
const groupNotifyApi = new kernel.GroupNotifyApi(session);
|
|
1357
1389
|
const ticketApi = new kernel.TicketApi(session);
|
|
1358
1390
|
return {
|
|
@@ -1366,6 +1398,7 @@ async function createKernelServices(kernel, ctx, loginResult) {
|
|
|
1366
1398
|
groupApi,
|
|
1367
1399
|
friendApi,
|
|
1368
1400
|
groupCache,
|
|
1401
|
+
buddyCache,
|
|
1369
1402
|
groupNotifyApi,
|
|
1370
1403
|
ticketApi,
|
|
1371
1404
|
richMediaApi: new kernel.RichMediaApi(session),
|
|
@@ -1378,7 +1411,15 @@ async function createKernelServices(kernel, ctx, loginResult) {
|
|
|
1378
1411
|
},
|
|
1379
1412
|
session,
|
|
1380
1413
|
engine: ctx.engine,
|
|
1381
|
-
util: ctx.exports?.NodeQQNTWrapperUtil
|
|
1414
|
+
util: ctx.exports?.NodeQQNTWrapperUtil,
|
|
1415
|
+
dispose: () => {
|
|
1416
|
+
bridge.unregister();
|
|
1417
|
+
groupBridge.unregister();
|
|
1418
|
+
friendBridge.unregister();
|
|
1419
|
+
groupCache.unregister();
|
|
1420
|
+
buddyCache.unregister();
|
|
1421
|
+
offMsgLogging();
|
|
1422
|
+
}
|
|
1382
1423
|
};
|
|
1383
1424
|
}
|
|
1384
1425
|
//#endregion
|
|
@@ -1408,7 +1449,7 @@ async function startProtocols(kernel, ctx, loginResult) {
|
|
|
1408
1449
|
return services;
|
|
1409
1450
|
}
|
|
1410
1451
|
try {
|
|
1411
|
-
await assembleOb11AndSatori(kernel, services, loginResult);
|
|
1452
|
+
services.stopAdapters = await assembleOb11AndSatori(kernel, services, loginResult);
|
|
1412
1453
|
} catch (e) {
|
|
1413
1454
|
log(`bootstrap: 协议装配失败: ${errMsg(e)}`);
|
|
1414
1455
|
return null;
|
|
@@ -1416,6 +1457,156 @@ async function startProtocols(kernel, ctx, loginResult) {
|
|
|
1416
1457
|
return services;
|
|
1417
1458
|
}
|
|
1418
1459
|
//#endregion
|
|
1460
|
+
//#region src/host/core/relogin.ts
|
|
1461
|
+
/**
|
|
1462
|
+
* relogin.ts:登录控制相位机 + control login 指令处理(2026-09-08 A1/A2)。
|
|
1463
|
+
*
|
|
1464
|
+
* control login(koishi 面板「重新登录/扫码登录」→ IPC control login 指令)
|
|
1465
|
+
* 的成功结果按引导相位分派三条路径:
|
|
1466
|
+
* ① login-race(初始登录竞速中):结果经 preemptRef 接管引导链——快速登录
|
|
1467
|
+
* 风控挂起时强制扫码也能走完装配链(2026-09-08 T2 既有语义)。
|
|
1468
|
+
* ② ready(引导已完成):**软重登**——重装配(清理旧服务 + 用新结果重跑
|
|
1469
|
+
* 装配链),不再整进程重启;完成后重播 ready + logged_in。
|
|
1470
|
+
* ③ assembling/aborted:迟到结果**不上报**(A1 修复)——引导失败进程将退出,
|
|
1471
|
+
* 无条件 sendLogin("logged_in") 会制造 failed→logged_in 的误导序列
|
|
1472
|
+
* (koishi driver 收到 logged_in 但子进程无协议装配,上线后所有请求失败);
|
|
1473
|
+
* 装配进行中的并发结果同样忽略(不干扰初次装配链)。
|
|
1474
|
+
*
|
|
1475
|
+
* 防重入(2026-09-08 拍板):control login 全程互斥(claim/release)——在途
|
|
1476
|
+
* 登录/重装配期间的新指令直接忽略(仅留日志,不发登录消息,面板随首个流程
|
|
1477
|
+
* 的真实状态收敛)。
|
|
1478
|
+
*/
|
|
1479
|
+
/** 登录控制相位机(bootstrapWithCore 持有,handler 与引导链共享)。 */
|
|
1480
|
+
var LoginControl = class {
|
|
1481
|
+
phase = "login-race";
|
|
1482
|
+
claimed = false;
|
|
1483
|
+
preempt;
|
|
1484
|
+
constructor(preempt) {
|
|
1485
|
+
this.preempt = preempt;
|
|
1486
|
+
}
|
|
1487
|
+
/** 当前相位。 */
|
|
1488
|
+
get currentPhase() {
|
|
1489
|
+
return this.phase;
|
|
1490
|
+
}
|
|
1491
|
+
/** 取走竞速抢占口(登录期 control login 成功接管引导链用);竞速已结束返回 null。 */
|
|
1492
|
+
takePreempt() {
|
|
1493
|
+
const resolve = this.preempt.resolve;
|
|
1494
|
+
if (resolve === null) return null;
|
|
1495
|
+
this.preempt.resolve = null;
|
|
1496
|
+
return resolve;
|
|
1497
|
+
}
|
|
1498
|
+
/** 竞速结束(初始登录 settle):关抢占口,进入装配期。 */
|
|
1499
|
+
beginAssembly() {
|
|
1500
|
+
this.preempt.resolve = null;
|
|
1501
|
+
this.phase = "assembling";
|
|
1502
|
+
}
|
|
1503
|
+
/** 装配完成:开启软重登窗口。 */
|
|
1504
|
+
markReady() {
|
|
1505
|
+
this.phase = "ready";
|
|
1506
|
+
}
|
|
1507
|
+
/** 引导失败(进程将退出):关抢占口,迟到 control login 结果不再上报。 */
|
|
1508
|
+
abort() {
|
|
1509
|
+
this.preempt.resolve = null;
|
|
1510
|
+
this.phase = "aborted";
|
|
1511
|
+
}
|
|
1512
|
+
/** control login 互斥(在途防重入):同一时刻只允许一个 control login 流程。 */
|
|
1513
|
+
claim() {
|
|
1514
|
+
if (this.claimed) return false;
|
|
1515
|
+
this.claimed = true;
|
|
1516
|
+
return true;
|
|
1517
|
+
}
|
|
1518
|
+
/** 释放互斥(core.login settle 后调用,含软重登重装配完成)。 */
|
|
1519
|
+
release() {
|
|
1520
|
+
this.claimed = false;
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
/** 登录状态字面量(IPC login 消息,与 kernel LoginState 对齐)。 */
|
|
1524
|
+
const LOGIN_STATES = [
|
|
1525
|
+
"idle",
|
|
1526
|
+
"waiting_scan",
|
|
1527
|
+
"scanned",
|
|
1528
|
+
"logged_in",
|
|
1529
|
+
"failed"
|
|
1530
|
+
];
|
|
1531
|
+
/** 宽松收窄(kernel onLoginProgress.state 是 string;bootstrap-core 登录回调共用)。 */
|
|
1532
|
+
function isLoginState(value) {
|
|
1533
|
+
return LOGIN_STATES.includes(value);
|
|
1534
|
+
}
|
|
1535
|
+
/** ready 态软重登:重装配 → 成功重播 ready/logged_in,失败交失败处理。 */
|
|
1536
|
+
async function softReloginAndReport(loginResult, selfInfo, deps) {
|
|
1537
|
+
if (!await deps.reassemble(loginResult)) {
|
|
1538
|
+
(deps.onReassembleFailed ?? defaultReassembleFailed)();
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
sendStatus("ready");
|
|
1542
|
+
sendLogin("logged_in", selfInfo);
|
|
1543
|
+
}
|
|
1544
|
+
/** control login 成功结果按相位分派(抽取自 handler,控制认知复杂度)。 */
|
|
1545
|
+
async function dispatchLoginSuccess(result, control, deps) {
|
|
1546
|
+
const selfInfo = {
|
|
1547
|
+
uin: result.uin,
|
|
1548
|
+
uid: result.uid,
|
|
1549
|
+
nick: result.nick ?? ""
|
|
1550
|
+
};
|
|
1551
|
+
const preemptResolve = control.takePreempt();
|
|
1552
|
+
if (preemptResolve !== null) {
|
|
1553
|
+
sendLogin("logged_in", selfInfo);
|
|
1554
|
+
preemptResolve(result);
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
if (control.currentPhase === "ready") {
|
|
1558
|
+
await softReloginAndReport(result, selfInfo, deps);
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
log(`relogin: control login 成功但引导相位=${control.currentPhase},忽略结果`);
|
|
1562
|
+
}
|
|
1563
|
+
/** 重装配失败默认处理:上报 failed + 退出进程(koishi driver 重启循环回收)。 */
|
|
1564
|
+
function defaultReassembleFailed() {
|
|
1565
|
+
log("relogin: 软重登重装配失败,上报 failed 并退出(driver 重启循环接管)");
|
|
1566
|
+
sendStatus("failed", "软重登重装配失败", {
|
|
1567
|
+
code: "UNKNOWN",
|
|
1568
|
+
message: "软重登重装配失败,详见 napuketto-boot.log"
|
|
1569
|
+
});
|
|
1570
|
+
node_process.default.exit(1);
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* control login 指令 → 重新登录(qr=true 强制扫码跳过快速登录,uin 指定账号)。
|
|
1574
|
+
* 成功结果按 LoginControl 相位分派(登录期抢占 / ready 软重登 / 忽略迟到)。
|
|
1575
|
+
* 该 handler 仅在 IPC 模式经 startIpcServer 的 onLogin 注册(见 bootstrapWithCore),
|
|
1576
|
+
* 非 IPC 模式无 control 通道不会触达,故无条件走 JSON 行协议、无需再判 ipcMode。
|
|
1577
|
+
*/
|
|
1578
|
+
function createLoginControlHandler(core, Appid, control, deps) {
|
|
1579
|
+
return (payload) => {
|
|
1580
|
+
if (!control.claim()) {
|
|
1581
|
+
log("relogin: control login 在途(登录/软重登进行中),忽略本次请求");
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
const opts = {
|
|
1585
|
+
appid: String(Appid),
|
|
1586
|
+
initTimeoutMs: 2e4,
|
|
1587
|
+
qrFallback: true,
|
|
1588
|
+
...payload.uin !== void 0 ? { quickUin: payload.uin } : {},
|
|
1589
|
+
...payload.qr === true ? { qrOnly: true } : {},
|
|
1590
|
+
onLoginProgress: (progress) => {
|
|
1591
|
+
if (progress.qr !== void 0) sendQr(progress.qr.pngBase64, progress.qr.qrcodeUrl);
|
|
1592
|
+
if (isLoginState(progress.state)) sendLogin(progress.state, progress.selfInfo, progress.message);
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
core.login(opts).then(async (result) => {
|
|
1596
|
+
if (result === null) {
|
|
1597
|
+
sendLogin("failed", void 0, "登录返回空结果");
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
await dispatchLoginSuccess(result, control, deps);
|
|
1601
|
+
}).catch((err) => {
|
|
1602
|
+
log(`bootstrap: control login 失败: ${errMsg(err)}`);
|
|
1603
|
+
sendLogin("failed", void 0, errMsg(err));
|
|
1604
|
+
}).finally(() => {
|
|
1605
|
+
control.release();
|
|
1606
|
+
});
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
//#endregion
|
|
1419
1610
|
//#region src/host/core/session.ts
|
|
1420
1611
|
/** 对象有效性判断:getMsgService() 调用不抛断言(cpp_impl 已激活)。
|
|
1421
1612
|
* 与 isSessionUsable 的区别:这里允许返回 null(未 init 但对象有效)。 */
|
|
@@ -1688,18 +1879,6 @@ function probeCreatedSession(state, ctx) {
|
|
|
1688
1879
|
log(`BOOT: create() 探测失败: ${errMsg(e)}`);
|
|
1689
1880
|
}
|
|
1690
1881
|
}
|
|
1691
|
-
/** 登录状态字面量(IPC login 消息,与 kernel LoginState 对齐)。 */
|
|
1692
|
-
const LOGIN_STATES = [
|
|
1693
|
-
"idle",
|
|
1694
|
-
"waiting_scan",
|
|
1695
|
-
"scanned",
|
|
1696
|
-
"logged_in",
|
|
1697
|
-
"failed"
|
|
1698
|
-
];
|
|
1699
|
-
/** 宽松收窄(kernel onLoginProgress.state 是 string)。 */
|
|
1700
|
-
function isLoginState(value) {
|
|
1701
|
-
return LOGIN_STATES.includes(value);
|
|
1702
|
-
}
|
|
1703
1882
|
/** 非 IPC 模式 QR 透出标记行前缀(cli forwardFiltered 解析后终端渲染)。 */
|
|
1704
1883
|
const QR_LINE_PREFIX = "NAPUTO_QR ";
|
|
1705
1884
|
/** 登录参数(NAPUTO_QUICK_UIN 强制指定 / ref 目标 / qrOnly 强制扫码 / 默认)。 */
|
|
@@ -1724,36 +1903,6 @@ function buildLoginOpts(Appid, forcedUin, ref, qrOnly = false) {
|
|
|
1724
1903
|
};
|
|
1725
1904
|
return opts;
|
|
1726
1905
|
}
|
|
1727
|
-
/** control login 指令 → 重新登录(qr=true 强制扫码跳过快速登录,uin 指定账号)。
|
|
1728
|
-
* 登录期(初始 doLogin 竞速中)成功结果经 preemptRef 接管引导链。 */
|
|
1729
|
-
function createLoginControlHandler(core, Appid, preemptRef) {
|
|
1730
|
-
return (payload) => {
|
|
1731
|
-
const opts = {
|
|
1732
|
-
appid: String(Appid),
|
|
1733
|
-
initTimeoutMs: 2e4,
|
|
1734
|
-
qrFallback: true,
|
|
1735
|
-
...payload.uin !== void 0 ? { quickUin: payload.uin } : {},
|
|
1736
|
-
...payload.qr === true ? { qrOnly: true } : {},
|
|
1737
|
-
onLoginProgress: (progress) => {
|
|
1738
|
-
if (progress.qr !== void 0) sendQr(progress.qr.pngBase64, progress.qr.qrcodeUrl);
|
|
1739
|
-
if (isLoginState(progress.state)) sendLogin(progress.state, progress.selfInfo, progress.message);
|
|
1740
|
-
}
|
|
1741
|
-
};
|
|
1742
|
-
core.login(opts).then((result) => {
|
|
1743
|
-
if (result !== null) {
|
|
1744
|
-
sendLogin("logged_in", {
|
|
1745
|
-
uin: result.uin,
|
|
1746
|
-
uid: result.uid,
|
|
1747
|
-
nick: result.nick ?? ""
|
|
1748
|
-
});
|
|
1749
|
-
preemptRef?.resolve?.(result);
|
|
1750
|
-
} else sendLogin("failed", void 0, "登录返回空结果");
|
|
1751
|
-
}).catch((err) => {
|
|
1752
|
-
log(`bootstrap: control login 失败: ${errMsg(err)}`);
|
|
1753
|
-
sendLogin("failed", void 0, errMsg(err));
|
|
1754
|
-
});
|
|
1755
|
-
};
|
|
1756
|
-
}
|
|
1757
1906
|
/** V2 登录后替换 session:优先 QQ 主 session,其次 vehicle 单例表;自建宿主例外。 */
|
|
1758
1907
|
function replaceSession(kernel, core, state, ctx) {
|
|
1759
1908
|
if (env.NAPUTO_SELF_HOST === "1") return null;
|
|
@@ -1849,20 +1998,72 @@ async function bootstrapWithCore(kernel, state, bootEnv, Appid) {
|
|
|
1849
1998
|
const preemptLogin = new Promise((resolve) => {
|
|
1850
1999
|
preempt.resolve = resolve;
|
|
1851
2000
|
});
|
|
2001
|
+
const control = new LoginControl(preempt);
|
|
2002
|
+
const reassembleRef = { current: null };
|
|
1852
2003
|
if (env.NAPUTO_IPC === "1") {
|
|
1853
2004
|
ipcActions = createIpcActionsForCore(core);
|
|
1854
2005
|
startIpcServer({
|
|
1855
2006
|
actions: ipcActions,
|
|
1856
|
-
onLogin: createLoginControlHandler(core, Appid,
|
|
2007
|
+
onLogin: createLoginControlHandler(core, Appid, control, { reassemble: (loginResult) => {
|
|
2008
|
+
const fn = reassembleRef.current;
|
|
2009
|
+
if (fn === null) {
|
|
2010
|
+
log("bootstrap: softRelogin 未就绪(理论不可达:相位机 ready 守卫)");
|
|
2011
|
+
return Promise.resolve(false);
|
|
2012
|
+
}
|
|
2013
|
+
return fn(loginResult);
|
|
2014
|
+
} })
|
|
1857
2015
|
});
|
|
1858
2016
|
}
|
|
1859
2017
|
const ctx = core.attachWrapper(state.wrapperExports, { ...bootEnv });
|
|
1860
2018
|
log(`bootstrap: attachWrapper OK, engine=${typeof ctx.engine}, session=${ctx.session !== null}`);
|
|
2019
|
+
let services = null;
|
|
2020
|
+
let stopIpcServices = null;
|
|
2021
|
+
let stopOb11Bridge = null;
|
|
2022
|
+
/**
|
|
2023
|
+
* 装配链尾部(登录成功后;初次引导与软重登共用)。
|
|
2024
|
+
* @returns 装配是否成功(协议服务未装配 = false,调用方判引导失败)。
|
|
2025
|
+
*/
|
|
2026
|
+
const runAssembly = async (loginResult, initial) => {
|
|
2027
|
+
const chosen = replaceSession(kernel, core, state, ctx);
|
|
2028
|
+
await activateSession(kernel, state, ctx, chosen, loginResult, Appid, bootEnv);
|
|
2029
|
+
await waitSessionReady(kernel, ctx);
|
|
2030
|
+
if (env.NAPUTO_IPC === "1") sendStatus("sessioning");
|
|
2031
|
+
if (initial) await runSmokeIfEnabled(kernel, ctx, loginResult);
|
|
2032
|
+
const newServices = await startProtocols(kernel, ctx, loginResult);
|
|
2033
|
+
if (newServices === null) {
|
|
2034
|
+
log("bootstrap: 协议服务装配失败,引导中止");
|
|
2035
|
+
return false;
|
|
2036
|
+
}
|
|
2037
|
+
if (env.NAPUTO_IPC === "1" && ipcActions !== null) {
|
|
2038
|
+
stopIpcServices = attachIpcServices(ipcActions, newServices);
|
|
2039
|
+
stopOb11Bridge = await attachOb11IpcBridge(ipcActions, newServices);
|
|
2040
|
+
}
|
|
2041
|
+
services = newServices;
|
|
2042
|
+
return true;
|
|
2043
|
+
};
|
|
2044
|
+
/**
|
|
2045
|
+
* 软重登重装配(ready 态 control login 成功,relogin.ts 经 deps 调用):
|
|
2046
|
+
* 清理旧装配面 → 用新登录结果重跑装配链(A2,2026-09-08)。
|
|
2047
|
+
* 清理顺序:协议消费方(OB11 桥退订+动作表移除)→ IPC 事件转发/动作表
|
|
2048
|
+
* → kernel 桥/缓存/消息日志(dispose,幂等)。失败返回 false(上报退出)。
|
|
2049
|
+
*/
|
|
2050
|
+
const softRelogin = async (loginResult) => {
|
|
2051
|
+
log(`bootstrap: 软重登 uin=${loginResult.uin}——清理旧装配并重跑装配链`);
|
|
2052
|
+
stopOb11Bridge?.();
|
|
2053
|
+
stopOb11Bridge = null;
|
|
2054
|
+
stopIpcServices?.();
|
|
2055
|
+
stopIpcServices = null;
|
|
2056
|
+
services?.dispose();
|
|
2057
|
+
services = null;
|
|
2058
|
+
return await runAssembly(loginResult, false);
|
|
2059
|
+
};
|
|
2060
|
+
reassembleRef.current = softRelogin;
|
|
1861
2061
|
probeCreatedSession(state, ctx);
|
|
1862
2062
|
startSessionProbe(state, ctx);
|
|
1863
2063
|
probeSessionMethods(ctx);
|
|
1864
2064
|
if (typeof core.login !== "function") {
|
|
1865
2065
|
log("bootstrap: kernel core missing login fn");
|
|
2066
|
+
control.abort();
|
|
1866
2067
|
return false;
|
|
1867
2068
|
}
|
|
1868
2069
|
const forcedUin = env.NAPUTO_QUICK_UIN;
|
|
@@ -1871,8 +2072,9 @@ async function bootstrapWithCore(kernel, state, bootEnv, Appid) {
|
|
|
1871
2072
|
await pickLoginAccount(kernel, ctx, ref, forcedUin);
|
|
1872
2073
|
if (env.NAPUTO_IPC === "1") sendStatus("logging");
|
|
1873
2074
|
const loginResult = await Promise.race([doLogin(core, buildLoginOpts(Appid, forcedUin, ref, qrOnly)), preemptLogin]);
|
|
1874
|
-
|
|
2075
|
+
control.beginAssembly();
|
|
1875
2076
|
if (loginResult === null) {
|
|
2077
|
+
control.abort();
|
|
1876
2078
|
log("bootstrap: 登录失败,引导中止");
|
|
1877
2079
|
if (env.NAPUTO_IPC === "1") sendStatus("failed", "登录失败", {
|
|
1878
2080
|
code: "NOT_LOGIN",
|
|
@@ -1886,19 +2088,11 @@ async function bootstrapWithCore(kernel, state, bootEnv, Appid) {
|
|
|
1886
2088
|
uid: loginResult.uid,
|
|
1887
2089
|
nick: loginResult.nick ?? ""
|
|
1888
2090
|
});
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
if (env.NAPUTO_IPC === "1") sendStatus("sessioning");
|
|
1892
|
-
await runSmokeIfEnabled(kernel, ctx, loginResult);
|
|
1893
|
-
const services = await startProtocols(kernel, ctx, loginResult);
|
|
1894
|
-
if (services === null) {
|
|
1895
|
-
log("bootstrap: 协议服务装配失败,引导中止");
|
|
2091
|
+
if (!await runAssembly(loginResult, true)) {
|
|
2092
|
+
control.abort();
|
|
1896
2093
|
return false;
|
|
1897
2094
|
}
|
|
1898
|
-
|
|
1899
|
-
attachIpcServices(ipcActions, services);
|
|
1900
|
-
await attachOb11IpcBridge(ipcActions, services);
|
|
1901
|
-
}
|
|
2095
|
+
control.markReady();
|
|
1902
2096
|
runProbePhase(kernel, ctx);
|
|
1903
2097
|
return true;
|
|
1904
2098
|
}
|