@dimina-kit/devtools 0.4.0-dev.20260630070008 → 0.4.0-dev.20260630123203

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.
@@ -4731,6 +4731,9 @@ function buildServiceHostSpawnUrl(opts) {
4731
4731
  if (opts.hostEnvSnapshot) {
4732
4732
  url.searchParams.set("hostEnv", encodeURIComponent(JSON.stringify(opts.hostEnvSnapshot)));
4733
4733
  }
4734
+ if (opts.apiNamespaces && opts.apiNamespaces.length > 0) {
4735
+ url.searchParams.set("apiNamespaces", opts.apiNamespaces.join(","));
4736
+ }
4734
4737
  return url.toString();
4735
4738
  }
4736
4739
  function navigateServiceHost(win, url) {
@@ -5624,6 +5627,52 @@ function findSimulatorWebContents() {
5624
5627
  return null;
5625
5628
  }
5626
5629
 
5630
+ // src/main/ipc/page-scroll.ts
5631
+ function buildPageScrollScript(params) {
5632
+ const rawTop = Number(params.scrollTop);
5633
+ const top = Number.isFinite(rawTop) ? rawTop : 0;
5634
+ const duration = params.duration === void 0 ? 300 : Number(params.duration);
5635
+ const behavior = Number.isFinite(duration) && duration > 0 ? "smooth" : "auto";
5636
+ return `window.scrollTo({ top: ${top}, left: 0, behavior: ${JSON.stringify(behavior)} })`;
5637
+ }
5638
+
5639
+ // src/main/ipc/app-lifecycle.ts
5640
+ function createAppLifecycleController() {
5641
+ const sessions = /* @__PURE__ */ new Map();
5642
+ return {
5643
+ register(appSessionId, event, callbackId) {
5644
+ if (callbackId === void 0 || callbackId === null) return;
5645
+ let events = sessions.get(appSessionId);
5646
+ if (!events) {
5647
+ events = /* @__PURE__ */ new Map();
5648
+ sessions.set(appSessionId, events);
5649
+ }
5650
+ let ids = events.get(event);
5651
+ if (!ids) {
5652
+ ids = /* @__PURE__ */ new Set();
5653
+ events.set(event, ids);
5654
+ }
5655
+ ids.add(callbackId);
5656
+ },
5657
+ unregister(appSessionId, event, callbackId) {
5658
+ const events = sessions.get(appSessionId);
5659
+ if (!events) return;
5660
+ if (callbackId === void 0 || callbackId === null) {
5661
+ events.delete(event);
5662
+ return;
5663
+ }
5664
+ events.get(event)?.delete(callbackId);
5665
+ },
5666
+ listeners(appSessionId, event) {
5667
+ const ids = sessions.get(appSessionId)?.get(event);
5668
+ return ids ? Array.from(ids) : [];
5669
+ },
5670
+ dispose(appSessionId) {
5671
+ sessions.delete(appSessionId);
5672
+ }
5673
+ };
5674
+ }
5675
+
5627
5676
  // src/main/ipc/bridge-router.ts
5628
5677
  function rewriteSourceMappingUrl(source, scriptUrl) {
5629
5678
  if (typeof source !== "string" || !source) return source;
@@ -5703,6 +5752,7 @@ function installBridgeRouter(ctx) {
5703
5752
  },
5704
5753
  connections: ctx.connections,
5705
5754
  debugTap: createDebugTap({ enabled: resolveDebugTapEnabled() }),
5755
+ appLifecycle: createAppLifecycleController(),
5706
5756
  evictAppDataBridges: (ap) => {
5707
5757
  if (!ctx.appData) return;
5708
5758
  for (const page of ap.pages.values()) ctx.appData.evictBridge(ap.appId, page.bridgeId);
@@ -5720,6 +5770,7 @@ function installBridgeRouter(ctx) {
5720
5770
  ctx.registry.add(() => clearTimeout(warmTimer));
5721
5771
  ctx.registry.add(() => state.pool?.dispose());
5722
5772
  }
5773
+ installAppLifecycleDriver(ctx, state);
5723
5774
  installResourceProtocolHandlers(ctx, state);
5724
5775
  let currentDevice = null;
5725
5776
  const onNativeHostQuery = (event) => {
@@ -5984,6 +6035,7 @@ async function handleSpawn(state, ctx, event, opts) {
5984
6035
  const workspaceProjectPath = typeof ctx.workspace.getProjectPath === "function" ? ctx.workspace.getProjectPath() : "";
5985
6036
  const pkgRoot = path16.resolve(opts.pkgRoot || workspaceProjectPath || process.cwd());
5986
6037
  const root = opts.root || "main";
6038
+ const apiNamespaces = ctx.apiNamespaces ?? [];
5987
6039
  let resourceServer = null;
5988
6040
  let resourceBaseUrl;
5989
6041
  if (opts.resourceBaseUrl) {
@@ -6025,7 +6077,8 @@ async function handleSpawn(state, ctx, event, opts) {
6025
6077
  pkgRoot,
6026
6078
  root,
6027
6079
  resourceBaseUrl,
6028
- hostEnvSnapshot: hostEnv
6080
+ hostEnvSnapshot: hostEnv,
6081
+ apiNamespaces
6029
6082
  });
6030
6083
  }
6031
6084
  const appSession = {
@@ -6038,6 +6091,7 @@ async function handleSpawn(state, ctx, event, opts) {
6038
6091
  serviceWc: serviceWindow.webContents,
6039
6092
  simulatorWc,
6040
6093
  serviceLoaded: false,
6094
+ logicInjected: null,
6041
6095
  resourceBaseUrl,
6042
6096
  resourceServer,
6043
6097
  hostEnv,
@@ -6059,6 +6113,7 @@ async function handleSpawn(state, ctx, event, opts) {
6059
6113
  renderWc: null,
6060
6114
  renderLoaded: false,
6061
6115
  resourceLoadedSent: false,
6116
+ renderLoadPending: false,
6062
6117
  windowConfig: rootWindowConfig
6063
6118
  };
6064
6119
  state.appSessions.set(appSessionId, appSession);
@@ -6093,7 +6148,7 @@ async function handleSpawn(state, ctx, event, opts) {
6093
6148
  }
6094
6149
  if (!serviceWindow.webContents.getURL().includes("service.html")) return;
6095
6150
  serviceWindow.webContents.removeListener("did-finish-load", bootOnServiceLoad);
6096
- void bootServiceHost(state, appSession);
6151
+ void bootServiceHost(state, appSession, ctx);
6097
6152
  };
6098
6153
  appSession.onServiceBoot = bootOnServiceLoad;
6099
6154
  serviceWindow.webContents.on("did-finish-load", bootOnServiceLoad);
@@ -6106,12 +6161,13 @@ async function handleSpawn(state, ctx, event, opts) {
6106
6161
  pkgRoot,
6107
6162
  root,
6108
6163
  resourceBaseUrl,
6109
- hostEnvSnapshot: hostEnv
6164
+ hostEnvSnapshot: hostEnv,
6165
+ apiNamespaces
6110
6166
  })
6111
6167
  );
6112
6168
  } else {
6113
6169
  serviceWindow.webContents.once("did-finish-load", () => {
6114
- void bootServiceHost(state, appSession);
6170
+ void bootServiceHost(state, appSession, ctx);
6115
6171
  });
6116
6172
  }
6117
6173
  return {
@@ -6145,6 +6201,7 @@ async function handlePageOpen(state, event, opts) {
6145
6201
  renderWc: null,
6146
6202
  renderLoaded: false,
6147
6203
  resourceLoadedSent: false,
6204
+ renderLoadPending: false,
6148
6205
  windowConfig
6149
6206
  };
6150
6207
  state.pageSessions.set(bridgeId, page);
@@ -6191,23 +6248,49 @@ function handleNavCallback(state, sender, payload) {
6191
6248
  }
6192
6249
  sendCallback(ap, payload.callbacks.complete, result);
6193
6250
  }
6194
- async function bootServiceHost(state, ap) {
6251
+ async function bootServiceHost(state, ap, ctx) {
6195
6252
  if (state.appSessions.get(ap.appSessionId) !== ap) return;
6196
- await injectLogicBundle(ap);
6253
+ ap.logicInjected = await injectLogicBundle(ap);
6254
+ if (!ap.logicInjected) {
6255
+ reportLogicLoadFailure(ap, ctx);
6256
+ return;
6257
+ }
6197
6258
  forwardToService(ap, makeLoadResource(ap, ap.pages.get(ap.appSessionId), "service"));
6259
+ for (const page of ap.pages.values()) {
6260
+ if (page.renderLoadPending) {
6261
+ page.renderLoadPending = false;
6262
+ sendRenderLoadResource(ap, page);
6263
+ }
6264
+ }
6265
+ }
6266
+ function sendRenderLoadResource(ap, page) {
6267
+ if (page.renderWc && !page.renderWc.isDestroyed()) {
6268
+ page.renderWc.send(BRIDGE_CHANNELS.TO_RENDER, { msg: makeLoadResource(ap, page, "render") });
6269
+ }
6270
+ }
6271
+ function logicBundleUrl(ap) {
6272
+ return ap.resourceServer ? new URL("logic.js", ap.resourceBaseUrl).toString() : new URL(`${ap.appId}/${ap.root}/logic.js`, ap.resourceBaseUrl).toString();
6198
6273
  }
6199
6274
  async function injectLogicBundle(ap) {
6200
- const logicUrl = ap.resourceServer ? new URL("logic.js", ap.resourceBaseUrl).toString() : new URL(`${ap.appId}/${ap.root}/logic.js`, ap.resourceBaseUrl).toString();
6275
+ const logicUrl = logicBundleUrl(ap);
6201
6276
  try {
6202
6277
  const res = await fetch(logicUrl);
6203
6278
  if (!res.ok) throw new Error(`logic.js fetch ${res.status} at ${logicUrl}`);
6204
6279
  const logicContent = rewriteSourceMappingUrl(await res.text(), logicUrl);
6205
6280
  await ap.serviceWc.executeJavaScript(`${logicContent}
6206
6281
  //# sourceURL=${logicUrl}`, true);
6282
+ return true;
6207
6283
  } catch (error) {
6208
6284
  console.warn("[bridge-router] unable to inject service logic.js:", error);
6285
+ return false;
6209
6286
  }
6210
6287
  }
6288
+ function reportLogicLoadFailure(ap, ctx) {
6289
+ const hint = ap.appId === "unknown" ? ' appId could not be resolved (it fell back to "unknown") \u2014 the mini-program likely failed to compile or its project manifest/app config is missing.' : "";
6290
+ const message = `[dimina-kit] Failed to load the mini-program logic bundle from ${logicBundleUrl(ap)}. The service runtime has no registered modules, so no page can mount.` + hint + ` Verify the project compiled successfully and that the resource server serves "${ap.appId}/${ap.root}/".`;
6291
+ console.error(message);
6292
+ ctx.guestConsole?.emit({ source: "service", level: "error", args: [message] });
6293
+ }
6211
6294
  function maybeSendResourceLoaded(ap, page) {
6212
6295
  if (page.resourceLoadedSent || !ap.serviceLoaded || !page.renderLoaded) return;
6213
6296
  page.resourceLoadedSent = true;
@@ -6242,9 +6325,12 @@ function routeFromService(state, ap, defaultPage, msg, ctx) {
6242
6325
  }
6243
6326
  function routeFromRender(state, ap, page, msg, ctx) {
6244
6327
  if (msg.type === "renderHostReady") {
6245
- if (page.renderWc && !page.renderWc.isDestroyed()) {
6246
- page.renderWc.send(BRIDGE_CHANNELS.TO_RENDER, { msg: makeLoadResource(ap, page, "render") });
6328
+ if (ap.logicInjected === false) return;
6329
+ if (ap.logicInjected === null) {
6330
+ page.renderLoadPending = true;
6331
+ return;
6247
6332
  }
6333
+ sendRenderLoadResource(ap, page);
6248
6334
  return;
6249
6335
  }
6250
6336
  if (msg.type === "renderResourceLoaded") {
@@ -6281,9 +6367,15 @@ function handleContainerMsg(ap, page, msg, ctx, state) {
6281
6367
  case "invokeAPI":
6282
6368
  void handleSimulatorApi(state, ap, page, msg.body, ctx);
6283
6369
  break;
6284
- case "serviceHostError":
6370
+ case "serviceHostError": {
6285
6371
  console.warn("[bridge-router] service host error:", msg.body);
6372
+ const errBody = msg.body;
6373
+ const errArg = errBody?.message ?? msg.body;
6374
+ for (const id of state.appLifecycle.listeners(ap.appSessionId, "onError")) {
6375
+ sendCallback(ap, id, errArg);
6376
+ }
6286
6377
  break;
6378
+ }
6287
6379
  case "consoleLog":
6288
6380
  ctx.guestConsole?.emit(msg.body);
6289
6381
  break;
@@ -6315,6 +6407,40 @@ var TAB_ACTION_NAMES = /* @__PURE__ */ new Set([
6315
6407
  "showTabBarRedDot",
6316
6408
  "hideTabBarRedDot"
6317
6409
  ]);
6410
+ function installAppLifecycleDriver(ctx, state) {
6411
+ const win = ctx.windows?.mainWindow;
6412
+ if (!win || typeof win.on !== "function") return;
6413
+ const emit = (serviceEvent, listenerEvent) => {
6414
+ for (const ap of state.appSessions.values()) {
6415
+ forwardToService(ap, { type: serviceEvent, target: "service", body: {} });
6416
+ for (const id of state.appLifecycle.listeners(ap.appSessionId, listenerEvent)) {
6417
+ sendCallback(ap, id, {});
6418
+ }
6419
+ }
6420
+ };
6421
+ const onHide = () => emit("appHide", "onAppHide");
6422
+ const onShow = () => emit("appShow", "onAppShow");
6423
+ win.on("hide", onHide);
6424
+ win.on("minimize", onHide);
6425
+ win.on("show", onShow);
6426
+ win.on("restore", onShow);
6427
+ ctx.registry.add(() => {
6428
+ win.off("hide", onHide);
6429
+ win.off("minimize", onHide);
6430
+ win.off("show", onShow);
6431
+ win.off("restore", onShow);
6432
+ });
6433
+ }
6434
+ var APP_LIFECYCLE_REGISTER = {
6435
+ onAppShow: "onAppShow",
6436
+ onAppHide: "onAppHide",
6437
+ onError: "onError"
6438
+ };
6439
+ var APP_LIFECYCLE_UNREGISTER = {
6440
+ offAppShow: "onAppShow",
6441
+ offAppHide: "onAppHide",
6442
+ offError: "onError"
6443
+ };
6318
6444
  async function handleSimulatorApi(state, ap, page, body, ctx) {
6319
6445
  const name = String(body.name ?? "");
6320
6446
  const params = normalizeParams(body.params);
@@ -6365,6 +6491,27 @@ async function handleSimulatorApi(state, ap, page, body, ctx) {
6365
6491
  }
6366
6492
  return;
6367
6493
  }
6494
+ const lifecycleRegister = APP_LIFECYCLE_REGISTER[name];
6495
+ if (lifecycleRegister) {
6496
+ state.appLifecycle.register(ap.appSessionId, lifecycleRegister, params.success);
6497
+ return;
6498
+ }
6499
+ const lifecycleUnregister = APP_LIFECYCLE_UNREGISTER[name];
6500
+ if (lifecycleUnregister) {
6501
+ state.appLifecycle.unregister(ap.appSessionId, lifecycleUnregister, params.success);
6502
+ return;
6503
+ }
6504
+ if (name === "pageScrollTo") {
6505
+ const renderWc = page.renderWc;
6506
+ if (renderWc && !renderWc.isDestroyed()) {
6507
+ void renderWc.executeJavaScript(buildPageScrollScript(params)).catch(() => {
6508
+ });
6509
+ }
6510
+ const successResult = { errMsg: "pageScrollTo:ok" };
6511
+ sendCallback(ap, params.success, successResult);
6512
+ sendCallback(ap, params.complete, successResult);
6513
+ return;
6514
+ }
6368
6515
  if (ctx.storageApi && STORAGE_API_NAMES.has(name)) {
6369
6516
  try {
6370
6517
  const result = await ctx.storageApi.invoke(ap.appId, name, params);
@@ -6589,6 +6736,7 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
6589
6736
  const ap = state.appSessions.get(appSessionId);
6590
6737
  if (!ap) return;
6591
6738
  state.appSessions.delete(appSessionId);
6739
+ state.appLifecycle.dispose(appSessionId);
6592
6740
  state.evictAppDataBridges(ap);
6593
6741
  for (const [requestId, pending] of state.pendingApiCalls) {
6594
6742
  if (pending.appSessionId !== appSessionId) continue;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Per-app-session registry for the app-level lifecycle listeners registered
3
+ * from the mini-program service layer: `wx.onAppShow` / `onAppHide` / `onError`
4
+ * (and their `off*` removers).
5
+ *
6
+ * These arrive as keep subscriptions — `wx.onAppShow(cb)` encodes `cb` as a
7
+ * persistent callback id in `params.success` (service `callback.store(fn,
8
+ * keep=true)`), so the router stores the id here and re-fires it via
9
+ * `sendCallback` on every app foreground / background / error.
10
+ *
11
+ * `wx.offAppShow(cb)` re-encodes the SAME `cb`: with `keep=true`,
12
+ * `callback.store` dedups by function and returns the original evtId, so `off*`
13
+ * carries that id and removes exactly that listener. `wx.offAppShow()` with no
14
+ * argument carries no id and clears every listener of the event (WeChat
15
+ * contract).
16
+ */
17
+ export type AppLifecycleEvent = 'onAppShow' | 'onAppHide' | 'onError';
18
+ export interface AppLifecycleController {
19
+ /** Store a keep callback id for an event. Null/undefined ids are ignored. */
20
+ register(appSessionId: string, event: AppLifecycleEvent, callbackId: unknown): void;
21
+ /**
22
+ * Remove a listener. With `callbackId`, removes only that id; without one,
23
+ * clears every listener of the event for the session.
24
+ */
25
+ unregister(appSessionId: string, event: AppLifecycleEvent, callbackId?: unknown): void;
26
+ /** Snapshot of the registered callback ids (empty for unknown session/event). */
27
+ listeners(appSessionId: string, event: AppLifecycleEvent): unknown[];
28
+ /** Drop all listeners for a torn-down session. */
29
+ dispose(appSessionId: string): void;
30
+ }
31
+ export declare function createAppLifecycleController(): AppLifecycleController;
32
+ //# sourceMappingURL=app-lifecycle.d.ts.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Per-app-session registry for the app-level lifecycle listeners registered
3
+ * from the mini-program service layer: `wx.onAppShow` / `onAppHide` / `onError`
4
+ * (and their `off*` removers).
5
+ *
6
+ * These arrive as keep subscriptions — `wx.onAppShow(cb)` encodes `cb` as a
7
+ * persistent callback id in `params.success` (service `callback.store(fn,
8
+ * keep=true)`), so the router stores the id here and re-fires it via
9
+ * `sendCallback` on every app foreground / background / error.
10
+ *
11
+ * `wx.offAppShow(cb)` re-encodes the SAME `cb`: with `keep=true`,
12
+ * `callback.store` dedups by function and returns the original evtId, so `off*`
13
+ * carries that id and removes exactly that listener. `wx.offAppShow()` with no
14
+ * argument carries no id and clears every listener of the event (WeChat
15
+ * contract).
16
+ */
17
+ export function createAppLifecycleController() {
18
+ const sessions = new Map();
19
+ return {
20
+ register(appSessionId, event, callbackId) {
21
+ if (callbackId === undefined || callbackId === null)
22
+ return;
23
+ let events = sessions.get(appSessionId);
24
+ if (!events) {
25
+ events = new Map();
26
+ sessions.set(appSessionId, events);
27
+ }
28
+ let ids = events.get(event);
29
+ if (!ids) {
30
+ ids = new Set();
31
+ events.set(event, ids);
32
+ }
33
+ ids.add(callbackId);
34
+ },
35
+ unregister(appSessionId, event, callbackId) {
36
+ const events = sessions.get(appSessionId);
37
+ if (!events)
38
+ return;
39
+ if (callbackId === undefined || callbackId === null) {
40
+ events.delete(event);
41
+ return;
42
+ }
43
+ events.get(event)?.delete(callbackId);
44
+ },
45
+ listeners(appSessionId, event) {
46
+ const ids = sessions.get(appSessionId)?.get(event);
47
+ return ids ? Array.from(ids) : [];
48
+ },
49
+ dispose(appSessionId) {
50
+ sessions.delete(appSessionId);
51
+ },
52
+ };
53
+ }
54
+ //# sourceMappingURL=app-lifecycle.js.map