@ait-co/devtools 0.1.141 → 0.1.143

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.
Files changed (92) hide show
  1. package/README.en.md +4 -4
  2. package/README.md +4 -4
  3. package/dist/{attach-orchestrator-BG8TGJOZ.js → attach-orchestrator-0F0m_UqQ.js} +71 -9
  4. package/dist/{attach-orchestrator-BG8TGJOZ.js.map → attach-orchestrator-0F0m_UqQ.js.map} +1 -1
  5. package/dist/{attach-orchestrator-6kz_ZdWL.js → attach-orchestrator-D65KxFy_.js} +71 -9
  6. package/dist/{attach-orchestrator-6kz_ZdWL.js.map → attach-orchestrator-D65KxFy_.js.map} +1 -1
  7. package/dist/{attach-orchestrator-BEiz3wAx.js → attach-orchestrator-DL3NQ9ca.js} +71 -9
  8. package/dist/{attach-orchestrator-BEiz3wAx.js.map → attach-orchestrator-DL3NQ9ca.js.map} +1 -1
  9. package/dist/{cell-BMlFh08P.js → cell-BaLvusOl.js} +2 -2
  10. package/dist/{cell-BMlFh08P.js.map → cell-BaLvusOl.js.map} +1 -1
  11. package/dist/{cell-Dtgtusog.js → cell-CBUS3-nT.js} +2 -2
  12. package/dist/{cell-Dtgtusog.js.map → cell-CBUS3-nT.js.map} +1 -1
  13. package/dist/{cell-KsRXFdDs.js → cell-EBKKpAAT.js} +2 -2
  14. package/dist/{cell-KsRXFdDs.js.map → cell-EBKKpAAT.js.map} +1 -1
  15. package/dist/{debug-server-DWat7d-9.js → debug-server-B3ABDrRI.js} +4 -4
  16. package/dist/{debug-server-DWat7d-9.js.map → debug-server-B3ABDrRI.js.map} +1 -1
  17. package/dist/{debug-server-6c22qSWa.js → debug-server-BWhwrVXa.js} +4 -4
  18. package/dist/{debug-server-6c22qSWa.js.map → debug-server-BWhwrVXa.js.map} +1 -1
  19. package/dist/{debug-server-BQHbaKPT.js → debug-server-CfQNxxGW.js} +6 -6
  20. package/dist/{debug-server-BQHbaKPT.js.map → debug-server-CfQNxxGW.js.map} +1 -1
  21. package/dist/in-app/auto.js +299 -3
  22. package/dist/in-app/auto.js.map +1 -1
  23. package/dist/in-app/index.d.ts +161 -14
  24. package/dist/in-app/index.d.ts.map +1 -1
  25. package/dist/in-app/index.js +300 -4
  26. package/dist/in-app/index.js.map +1 -1
  27. package/dist/mcp/cli.js +82 -16
  28. package/dist/mcp/cli.js.map +1 -1
  29. package/dist/mcp/server.js +1 -1
  30. package/dist/mock/index.d.ts +156 -98
  31. package/dist/mock/index.d.ts.map +1 -1
  32. package/dist/mock/index.js +52 -21
  33. package/dist/mock/index.js.map +1 -1
  34. package/dist/panel/index.js +9 -4
  35. package/dist/panel/index.js.map +1 -1
  36. package/dist/{pool-CrP5CPvU.d.ts → pool-DcaaOwUq.d.ts} +2 -2
  37. package/dist/{pool-CrP5CPvU.d.ts.map → pool-DcaaOwUq.d.ts.map} +1 -1
  38. package/dist/{qr-http-server-Cz1mA07b.cjs → qr-http-server--gl2-WKc.cjs} +9 -5
  39. package/dist/qr-http-server--gl2-WKc.cjs.map +1 -0
  40. package/dist/{qr-http-server-D4rGz8M7.js → qr-http-server-Bb7lMeqi.js} +9 -5
  41. package/dist/qr-http-server-Bb7lMeqi.js.map +1 -0
  42. package/dist/{qr-http-server-D1hpoyBF.js → qr-http-server-C_lqOrgc.js} +9 -5
  43. package/dist/qr-http-server-C_lqOrgc.js.map +1 -0
  44. package/dist/{qr-http-server-CTLdclg_.js → qr-http-server-CopuMbub.js} +9 -5
  45. package/dist/qr-http-server-CopuMbub.js.map +1 -0
  46. package/dist/{qr-http-server-D16Lq2ag.cjs → qr-http-server-D-Off6K1.cjs} +9 -5
  47. package/dist/qr-http-server-D-Off6K1.cjs.map +1 -0
  48. package/dist/{qr-http-server-DIjCV7h_.js → qr-http-server-DrbIVDjO.js} +9 -5
  49. package/dist/qr-http-server-DrbIVDjO.js.map +1 -0
  50. package/dist/qr-http-server-n1twN18z.js +1644 -0
  51. package/dist/qr-http-server-n1twN18z.js.map +1 -0
  52. package/dist/{relay-factory-C1X3G3WY.js → relay-factory-N9QobQxG.js} +6 -6
  53. package/dist/{relay-factory-C1X3G3WY.js.map → relay-factory-N9QobQxG.js.map} +1 -1
  54. package/dist/{relay-worker-BUXI0K0b.js → relay-worker-B5HKkGUY.js} +4 -4
  55. package/dist/{relay-worker-BUXI0K0b.js.map → relay-worker-B5HKkGUY.js.map} +1 -1
  56. package/dist/{relay-worker-DcyboK43.d.ts → relay-worker-YdlpZQl9.d.ts} +2 -2
  57. package/dist/{relay-worker-DcyboK43.d.ts.map → relay-worker-YdlpZQl9.d.ts.map} +1 -1
  58. package/dist/{runtime-BiigOuvb.d.ts → runtime-kn9DxOeg.d.ts} +2 -1
  59. package/dist/runtime-kn9DxOeg.d.ts.map +1 -0
  60. package/dist/test-runner/bin.js +8 -8
  61. package/dist/test-runner/bin.js.map +1 -1
  62. package/dist/test-runner/config.d.ts +1 -1
  63. package/dist/test-runner/config.js +1 -1
  64. package/dist/test-runner/pool.d.ts +1 -1
  65. package/dist/test-runner/pool.js +1 -1
  66. package/dist/test-runner/relay-factory.js +5 -5
  67. package/dist/test-runner/relay-worker.d.ts +1 -1
  68. package/dist/test-runner/relay-worker.js +1 -1
  69. package/dist/test-runner/report.d.ts +2 -2
  70. package/dist/test-runner/rpc.d.ts +1 -1
  71. package/dist/test-runner/runtime.d.ts +1 -1
  72. package/dist/test-runner/runtime.js +4 -0
  73. package/dist/test-runner/runtime.js.map +1 -1
  74. package/dist/test-runner/task-graph.d.ts +1 -1
  75. package/dist/{tunnel-DjtSENJU.cjs → tunnel-BVSMXctM.cjs} +2 -2
  76. package/dist/{tunnel-DjtSENJU.cjs.map → tunnel-BVSMXctM.cjs.map} +1 -1
  77. package/dist/{tunnel-5eBncITa.js → tunnel-D7xkimBu.js} +2 -2
  78. package/dist/{tunnel-5eBncITa.js.map → tunnel-D7xkimBu.js.map} +1 -1
  79. package/dist/unplugin/index.cjs +1 -1
  80. package/dist/unplugin/index.js +1 -1
  81. package/dist/unplugin/tunnel.cjs +1 -1
  82. package/dist/unplugin/tunnel.js +1 -1
  83. package/package.json +2 -2
  84. package/dist/qr-http-server-CTLdclg_.js.map +0 -1
  85. package/dist/qr-http-server-Cz1mA07b.cjs.map +0 -1
  86. package/dist/qr-http-server-D16Lq2ag.cjs.map +0 -1
  87. package/dist/qr-http-server-D1hpoyBF.js.map +0 -1
  88. package/dist/qr-http-server-D4rGz8M7.js.map +0 -1
  89. package/dist/qr-http-server-DIjCV7h_.js.map +0 -1
  90. package/dist/qr-http-server-wy3L7Yut.js +0 -1640
  91. package/dist/qr-http-server-wy3L7Yut.js.map +0 -1
  92. package/dist/runtime-BiigOuvb.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/in-app/gate.ts","../../src/in-app/attach.ts","../../src/in-app/eruda-overlay.ts","../../src/in-app/index.ts"],"mappings":";;AAwFA;;;;;;;;;AASA;;;;;AAmBA;;;;;AAQA;;;;;;;;;;;AA8EA;;;;;AAqCA;;;;;AAuBA;;;;;AA2EA;;;;;;;;;;;;AC5RA;;;;;AAUC;;;;;;;;;;AAgPD;;;;;AA4CA;;;;;;;;AC3SA;AAAA,UFwCiB,gBAAA;EAAA,SACN,MAAA;EEzCyB;EAAA,SF2CzB,QAAA;;WAEA,YAAA;AAAA;AG3CX;AAAA,UH+CiB,iBAAA;EAAA,SACN,MAAA;EGhDuB;;;;;;;;;;;;;;EAAA,SH+DvB,MAAA;AAAA;AAAA,KAGC,UAAA,GAAa,gBAAA,GAAmB,iBAAA;;;;;;;UAQ3B,SAAA;;;;;;;;;;;;;WAaN,QAAA;;;;;;;WAQA,YAAA,EAAc,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;WA0Bd,cAAA,IAAkB,IAAA;AAAA;;;;;;;;;;;;iBA+Bb,iBAAA,CAAkB,QAAA;;;;;;;iBAqClB,cAAA,CAAe,QAAA;;;;;;;;;;;;;;;;;;;;iBAuBf,mBAAA,CAAoB,QAAA;;;;;;;;;;;;;;;;;;;;;;iBA2EpB,iBAAA,CAAkB,KAAA,EAAO,SAAA,GAAY,UAAA;;;;;;AA7NrD;;;;;AAQA;;;;;;;;;;;AA8EA;;;;;AAqCA;;;;;AAuBA;;;;iBCjNgB,qBAAA,CAAsB,QAAA,UAAkB,MAAA;AAAA,QAoChD,MAAA;EAAA,UACI,MAAA;IDuP6B;;;;;;;IC/OrC,uBAAA;EAAA;AAAA;AA7CJ;;;;;AAUC;;;;;;;;;;;;;;AA4RD;;;;;;;;AC3SA;;;;;;;;ACEA;;;iBF6PgB,iBAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;iBA4CA,WAAA,CAAY,UAAA,GAAY,UAAA;;;;ADnQxC;;;;;;;;;AASA;;;;;AAmBA;;;;;AAQA;;;;;;;;;;;AA8EA;;;;;AAqCA;;;;;AAuBA;;;iBEtNsB,UAAA,CAAA,GAAc,OAAA;;;;;;AF0JpC;;;;;AAqCA;;;;;AAuBA;iBGpNgB,cAAA,CAAA,GAAkB,UAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/in-app/gate.ts","../../src/in-app/attach.ts","../../src/in-app/bridge-observer.ts","../../src/in-app/eruda-overlay.ts","../../src/in-app/index.ts"],"mappings":";;AAwFA;;;;;;;;;AASA;;;;;AAmBA;;;;;AAQA;;;;;;;;;;;AA8EA;;;;;AAqCA;;;;;AAuBA;;;;;AA2EA;;;;;;;;;;;;AC3RA;;;;;AAUC;;;;;;;;;;AA+OD;;;;;AAkKA;;;;;AA4CA;;;;UDraiB,gBAAA;EAAA,SACN,MAAA;;WAEA,QAAA;EEhDM;EAAA,SFkDN,YAAA;AAAA;;UAIM,iBAAA;EAAA,SACN,MAAA;EEhDoB;;;;;;;;AAY/B;;;;;;EAZ+B,SF+DpB,MAAA;AAAA;AAAA,KAGC,UAAA,GAAa,gBAAA,GAAmB,iBAAA;;;;;;;UAQ3B,SAAA;EEpDa;;;;AAAqB;;;;;;;;EAArB,SFiEnB,QAAA;EEtCL;;;;;;EAAA,SF8CK,YAAA,EAAc,eAAA;EExClB;;;;;;;;;AA2HP;;;;;AAoFA;;;;;;;;ACjQA;;EDkDO,SFkEI,cAAA,IAAkB,IAAA;AAAA;;AGpF7B;;;;;;;;ACxBA;;iBJ2IgB,iBAAA,CAAkB,QAAA;;;;;;;iBAqClB,cAAA,CAAe,QAAA;;;;;;;;;;;;;;;;;;;;iBAuBf,mBAAA,CAAoB,QAAA;;;;;;;;;;;;;;;;;;;;;;iBA2EpB,iBAAA,CAAkB,KAAA,EAAO,SAAA,GAAY,UAAA;;;;;;AA7NrD;;;;;AAQA;;;;;;;;;;;AA8EA;;;;;AAqCA;;;;;AAuBA;;;;iBChNgB,qBAAA,CAAsB,QAAA,UAAkB,MAAA;AAAA,QAoChD,MAAA;EAAA,UACI,MAAA;IDsP6B;;;;;;;IC9OrC,uBAAA;EAAA;AAAA;AA7CJ;;;;;AAUC;;;;;;;;;;AA+OD;;;;;AAkKA;;;;;AA4CA;;AAvcA,iBAyPgB,kBAAA,CAAA;;;;ACjPhB;;;;;;;;;;;;;;;AAUA;;;;;iBDyYgB,iBAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;iBA4CA,WAAA,CAAY,UAAA,GAAY,UAAA;;;;ADraxC;;;;;;;;;AASA;;;;;AAmBA;;;;;AAQA;;;;;;;;;;;AA8EA;;;;;AAqCA;;;;;AAuBA;AAAA,UE3NiB,iBAAA;EACf,MAAA;EF0NkC;EExNlC,SAAA;AAAA;;UAIe,cAAA;EACf,MAAA;EF8RuC;EE5RvC,EAAA;EACA,MAAA;AAAA;;;;;ADAF;UCQiB,mBAAA;EACf,OAAA,EAAS,MAAA,SAAe,iBAAA;EACxB,IAAA,EAAM,cAAA;AAAA;ADAP;;;;;AAAA,cCQY,iBAAA;AAAA,QAWL,MAAA;EAAA,UACI,MAAA;IDee;;AA4M3B;;;;;ICnNI,YAAA,GAAe,mBAAA;IDqXc;;;;AA4CjC;IC3ZI,wBAAA;MACE,eAAA,IAAmB,IAAA,UAAc,MAAA;MAAA,CAChC,GAAA;IAAA;;IAGH,kBAAA;MACE,WAAA,IAAe,OAAA;MAAA,CACd,GAAA;IAAA;IA9D2B;IAiE9B,wBAAA;MACE,IAAA,IAAQ,KAAA,UAAe,IAAA;MAAA,CACtB,GAAA;IAAA;EAAA;AAAA;;;;;;AAhDP;;;;;;iBAsKgB,qBAAA,CAAA;;;;;;iBAoFA,uBAAA,CAAA;;;;AFhOhB;;;;;;;;;AASA;;;;;AAmBA;;;;;AAQA;;;;;;;;;;;AA8EA;;;;;AAqCA;;;;;AAuBA;;;iBG/MsB,UAAA,CAAA,GAAc,OAAA;;AH0RpC;;;;;;;;;;;;iBG1PgB,YAAA,CAAA;;;;;AHmHhB;;;;;AAqCA;;;;;AAuBA;;iBIvMgB,cAAA,CAAA,GAAkB,UAAA"}
@@ -247,6 +247,186 @@ const RELAY_AUTH_REJECT_CLOSE_CODE = 4401;
247
247
  */
248
248
  const RELAY_AUTH_REJECT_REASON = "totp-rejected";
249
249
  //#endregion
250
+ //#region src/in-app/bridge-observer.ts
251
+ /**
252
+ * CustomEvent fired (no detail) on every start/settle so the indicator badge
253
+ * re-renders promptly. SECRET-HANDLING: carries no detail payload at all — the
254
+ * badge reads the enum-only `window.__ait_bridge` snapshot on receipt.
255
+ */
256
+ const BRIDGE_CALL_EVENT = "ait:bridge-call";
257
+ /**
258
+ * Pending entries older than this are pruned on the next start — a safety net
259
+ * for the fallback path where a settle signal might be missed, so the pending
260
+ * list can never grow unbounded or show a forever-stuck row. Generous enough
261
+ * that a genuinely slow native call (the exact signal we want to surface) still
262
+ * shows while it is plausibly in flight.
263
+ */
264
+ const MAX_PENDING_AGE_MS = 12e4;
265
+ /** Guard so the observer wraps the bridge at most once per page lifecycle. */
266
+ let bridgeObserverInstalled = false;
267
+ /** Monotonic id source for the primary (3.0) path, where native gives us none. */
268
+ let callIdCounter = 0;
269
+ /** Undo hooks that {@link uninstallBridgeObserver} runs to restore originals. */
270
+ let restoreHooks = [];
271
+ /** Drops pending entries older than {@link MAX_PENDING_AGE_MS}. */
272
+ function pruneStale(state, now) {
273
+ for (const id of Object.keys(state.pending)) {
274
+ const entry = state.pending[id];
275
+ if (entry !== void 0 && now - entry.startedAt > MAX_PENDING_AGE_MS) delete state.pending[id];
276
+ }
277
+ }
278
+ /** Fires the payload-less notify event so the badge re-renders. */
279
+ function broadcast() {
280
+ if (typeof window === "undefined") return;
281
+ window.dispatchEvent(new CustomEvent(BRIDGE_CALL_EVENT));
282
+ }
283
+ /** Records a call start: add to pending, set last=pending, notify. */
284
+ function startCall(state, id, method, now) {
285
+ pruneStale(state, now);
286
+ state.pending[id] = {
287
+ method,
288
+ startedAt: now
289
+ };
290
+ state.last = {
291
+ method,
292
+ at: now,
293
+ status: "pending"
294
+ };
295
+ broadcast();
296
+ }
297
+ /** Records a call settle: remove from pending, stamp last, notify. */
298
+ function settleCall(state, id, status, now) {
299
+ const entry = state.pending[id];
300
+ delete state.pending[id];
301
+ state.last = {
302
+ method: entry?.method ?? state.last?.method ?? "unknown",
303
+ at: now,
304
+ status
305
+ };
306
+ broadcast();
307
+ }
308
+ /**
309
+ * Parses an outbound `ReactNativeWebView.postMessage` JSON envelope and records
310
+ * a START for async request/response calls only. Event subscriptions
311
+ * (`addEventListener`/`removeEventListener`/`callEventMethod`), cleanup, and
312
+ * constants are ignored — they are not the "spinner is a pending call" signal.
313
+ *
314
+ * SECRET-HANDLING: reads `type`/`name`/`functionName`/`callbackId`/`eventId`
315
+ * ONLY — never `params`/`args`.
316
+ */
317
+ function observeOutbound(state, message) {
318
+ if (typeof message !== "string") return;
319
+ let parsed;
320
+ try {
321
+ parsed = JSON.parse(message);
322
+ } catch {
323
+ return;
324
+ }
325
+ const now = Date.now();
326
+ if (parsed.type === "callAsyncMethod" && typeof parsed.name === "string" && typeof parsed.callbackId === "string") {
327
+ startCall(state, parsed.callbackId, parsed.name, now);
328
+ return;
329
+ }
330
+ if (parsed.type === "method" && typeof parsed.functionName === "string" && typeof parsed.eventId === "string") startCall(state, parsed.eventId, parsed.functionName, now);
331
+ }
332
+ /**
333
+ * Parses a `__GRANITE_NATIVE_EMITTER.emit` event name and records a SETTLE for
334
+ * 2.x async resolves/rejects (`<method>/resolve/<eventId>` |
335
+ * `<method>/reject/<eventId>`). Event-bridge emits (`.../onEvent/...`) are
336
+ * ignored. SECRET-HANDLING: reads the event NAME only — never the emitted args.
337
+ */
338
+ function observeSettle(state, event) {
339
+ if (typeof event !== "string") return;
340
+ const match = /\/(resolve|reject)\/([^/]+)$/.exec(event);
341
+ if (match === null) return;
342
+ settleCall(state, match[2], match[1] === "resolve" ? "resolved" : "rejected", Date.now());
343
+ }
344
+ /**
345
+ * Installs the native-bridge call observer (#749). Idempotent per page
346
+ * lifecycle. Called by {@link maybeAttach} after the gate passes (debug builds
347
+ * only). Prefers the 3.0 single-dispatcher wrap; falls back to the universal
348
+ * 2.x postMessage-start + emitter-settle pair. A context with no observable
349
+ * bridge (env 2 mock) leaves `window.__ait_bridge` as an empty snapshot — the
350
+ * badge then shows the heartbeat only, which is correct.
351
+ *
352
+ * Never throws into the host app — a wrapped hook that somehow fails is caught
353
+ * and the original behavior is always preserved.
354
+ */
355
+ function installBridgeObserver() {
356
+ if (bridgeObserverInstalled) return;
357
+ if (typeof window === "undefined") return;
358
+ bridgeObserverInstalled = true;
359
+ const state = {
360
+ pending: Object.create(null),
361
+ last: null
362
+ };
363
+ window.__ait_bridge = state;
364
+ const nativeBridge = window.__appsInTossNativeBridge;
365
+ if (nativeBridge !== void 0 && typeof nativeBridge.callAsyncMethod === "function") {
366
+ const original = nativeBridge.callAsyncMethod;
367
+ const wrapped = function(name, params) {
368
+ const id = `c${++callIdCounter}`;
369
+ startCall(state, id, String(name), Date.now());
370
+ let result;
371
+ try {
372
+ result = original.call(this, name, params);
373
+ } catch (err) {
374
+ settleCall(state, id, "rejected", Date.now());
375
+ throw err;
376
+ }
377
+ if (result !== null && typeof result.then === "function") result.then(() => settleCall(state, id, "resolved", Date.now()), () => settleCall(state, id, "rejected", Date.now()));
378
+ else settleCall(state, id, "resolved", Date.now());
379
+ return result;
380
+ };
381
+ nativeBridge.callAsyncMethod = wrapped;
382
+ restoreHooks.push(() => {
383
+ nativeBridge.callAsyncMethod = original;
384
+ });
385
+ return;
386
+ }
387
+ const webView = window.ReactNativeWebView;
388
+ if (webView !== void 0 && typeof webView.postMessage === "function") {
389
+ const originalPost = webView.postMessage;
390
+ webView.postMessage = function(message) {
391
+ try {
392
+ observeOutbound(state, message);
393
+ } catch {}
394
+ originalPost.call(this, message);
395
+ };
396
+ restoreHooks.push(() => {
397
+ webView.postMessage = originalPost;
398
+ });
399
+ }
400
+ const emitter = window.__GRANITE_NATIVE_EMITTER;
401
+ if (emitter !== void 0 && typeof emitter.emit === "function") {
402
+ const originalEmit = emitter.emit;
403
+ emitter.emit = function(event, args) {
404
+ try {
405
+ observeSettle(state, event);
406
+ } catch {}
407
+ originalEmit.call(this, event, args);
408
+ };
409
+ restoreHooks.push(() => {
410
+ emitter.emit = originalEmit;
411
+ });
412
+ }
413
+ }
414
+ /**
415
+ * Restores every wrapped bridge hook and removes `window.__ait_bridge` (#749).
416
+ * Idempotent; safe to call when nothing was installed. Wired into
417
+ * {@link detachDebugSurface} (#748) so no bridge wrap survives a run's end.
418
+ */
419
+ function uninstallBridgeObserver() {
420
+ if (!bridgeObserverInstalled) return;
421
+ bridgeObserverInstalled = false;
422
+ const hooks = restoreHooks;
423
+ restoreHooks = [];
424
+ for (const undo of hooks) try {
425
+ undo();
426
+ } catch {}
427
+ if (typeof window !== "undefined") window.__ait_bridge = void 0;
428
+ }
429
+ //#endregion
250
430
  //#region src/in-app/eruda-overlay.ts
251
431
  /**
252
432
  * In-app eruda console overlay for the debug attach flow.
@@ -283,6 +463,12 @@ const RELAY_AUTH_REJECT_REASON = "totp-rejected";
283
463
  /** Module-level guard against double mount across repeated `maybeAttach` calls. */
284
464
  let erudaMounted = false;
285
465
  /**
466
+ * The loaded eruda module, captured on a successful {@link mountEruda} so
467
+ * {@link unmountEruda} can call `.destroy()` on the same instance during
468
+ * graceful detach (#748). `null` when eruda was never mounted.
469
+ */
470
+ let erudaModule = null;
471
+ /**
286
472
  * Mounts the eruda in-page console once.
287
473
  *
288
474
  * Idempotent: repeated calls after a successful mount are no-ops, mirroring the
@@ -298,12 +484,38 @@ async function mountEruda() {
298
484
  if (erudaMounted || typeof document === "undefined") return;
299
485
  erudaMounted = true;
300
486
  try {
301
- (await import("eruda")).default.init();
487
+ const eruda = (await import("eruda")).default;
488
+ eruda.init();
489
+ erudaModule = eruda;
302
490
  } catch (err) {
303
491
  erudaMounted = false;
304
492
  console.debug("[@ait-co/devtools] eruda console mount skipped:", err);
305
493
  }
306
494
  }
495
+ /**
496
+ * Unmounts the eruda in-page console (#748 graceful detach).
497
+ *
498
+ * Calls `eruda.destroy()`, which removes eruda's floating entry button, any
499
+ * open panel, and its `#eruda` shadow host — returning the phone screen to a
500
+ * clean, non-debug state when a debug session ends. After a successful unmount
501
+ * the guard is reset so a later {@link mountEruda} (a fresh attach) can
502
+ * re-mount.
503
+ *
504
+ * Idempotent: a call when eruda was never mounted (or already unmounted) is a
505
+ * no-op. Fail-silent: a `destroy()` throw is swallowed — teardown must never
506
+ * throw into the host app.
507
+ */
508
+ function unmountEruda() {
509
+ if (!erudaMounted || erudaModule === null) return;
510
+ try {
511
+ erudaModule.destroy();
512
+ } catch (err) {
513
+ console.debug("[@ait-co/devtools] eruda console unmount skipped:", err);
514
+ } finally {
515
+ erudaMounted = false;
516
+ erudaModule = null;
517
+ }
518
+ }
307
519
  //#endregion
308
520
  //#region src/in-app/attach.ts
309
521
  /**
@@ -467,6 +679,81 @@ function createFailFastSocket(url) {
467
679
  }, 0);
468
680
  return sock;
469
681
  }
682
+ /** Grace window before a non-terminal relay close tears the surface down. */
683
+ const RECONNECT_GRACE_MS = 5e3;
684
+ /** One-shot guard so the teardown runs at most once per page lifecycle. */
685
+ let debugSurfaceDetached = false;
686
+ /** Pending grace-window timer for a non-terminal close, or `null`. */
687
+ let pendingDetachTimer = null;
688
+ /** Cancels a scheduled teardown — called when a relay socket re-opens. */
689
+ function cancelScheduledDetach() {
690
+ if (pendingDetachTimer !== null) {
691
+ clearTimeout(pendingDetachTimer);
692
+ pendingDetachTimer = null;
693
+ }
694
+ }
695
+ /**
696
+ * Schedules {@link detachDebugSurface} after {@link RECONNECT_GRACE_MS} unless
697
+ * a reconnect cancels it first. No-op if teardown already ran or is already
698
+ * scheduled. Defensive: if `setTimeout` is somehow unavailable, tears down
699
+ * immediately rather than never.
700
+ */
701
+ function scheduleDetach() {
702
+ if (debugSurfaceDetached || pendingDetachTimer !== null) return;
703
+ if (typeof setTimeout === "undefined") {
704
+ detachDebugSurface();
705
+ return;
706
+ }
707
+ pendingDetachTimer = setTimeout(() => {
708
+ pendingDetachTimer = null;
709
+ detachDebugSurface();
710
+ }, RECONNECT_GRACE_MS);
711
+ }
712
+ /**
713
+ * Idempotent, non-throwing teardown of the in-app debug surface (issue #748).
714
+ *
715
+ * Removes every debug-surface element WE injected and restores the one side
716
+ * effect WE applied:
717
+ * 1. The CDP-injected `#__ait_debug_indicator` badge (the persistent
718
+ * "Debugger Disconnected" element). Its live heartbeat/pending-call timer
719
+ * (#749) is stopped first via the badge controller's `stop()` so no 1 Hz
720
+ * interval leaks past detach; `buildIndicatorExpression` also
721
+ * self-dismisses the node — this is the in-app hard guarantee, idempotent,
722
+ * a no-op if it is already gone.
723
+ * 2. The eruda in-page console (floating button + any open panel).
724
+ * 3. The native-bridge call observer (#749) — its `callAsyncMethod` /
725
+ * `postMessage` / emitter wraps are restored and `window.__ait_bridge` is
726
+ * removed, so nothing we wrapped survives the run's end.
727
+ * 4. keepAwake — forced on at attach; restored here so a run that ends
728
+ * WITHOUT a page unload does not leave the screen pinned awake (the
729
+ * existing `beforeunload` restore only covers the unload path).
730
+ *
731
+ * Deliberately NOT touched: the `window.WebSocket` observer proxy (kept so the
732
+ * #478 post-4401 fail-fast survives; it is non-blocking and never absorbs
733
+ * input), and any NATIVE overlay (out of our layer — see the block comment
734
+ * above and issue #748 hypothesis (a)).
735
+ *
736
+ * Never throws into the host app — every step is individually guarded.
737
+ * Exported for unit tests and for a consumer that wants to force a clean detach.
738
+ */
739
+ function detachDebugSurface() {
740
+ if (debugSurfaceDetached) return;
741
+ debugSurfaceDetached = true;
742
+ cancelScheduledDetach();
743
+ try {
744
+ if (typeof window !== "undefined") window.__ait_indicator?.stop?.();
745
+ if (typeof document !== "undefined") document.getElementById("__ait_debug_indicator")?.remove();
746
+ } catch {}
747
+ try {
748
+ unmountEruda();
749
+ } catch {}
750
+ try {
751
+ uninstallBridgeObserver();
752
+ } catch {}
753
+ try {
754
+ setScreenAwakeMode({ enabled: false }).catch(() => {});
755
+ } catch {}
756
+ }
470
757
  /**
471
758
  * Wraps `window.WebSocket` with a relay-origin-scoped observer (issue #478).
472
759
  *
@@ -489,19 +776,27 @@ function installRelayWsObserver(relayUrl) {
489
776
  if (relayKey === null) return;
490
777
  wsObserverInstalled = true;
491
778
  window.__ait_relay_ws_observed = true;
779
+ window.addEventListener("pagehide", () => detachDebugSurface(), { once: true });
492
780
  const NativeWebSocket = window.WebSocket;
493
781
  const observed = new Proxy(NativeWebSocket, { construct(target, args) {
494
782
  const url = String(args[0]);
495
783
  if (wsOriginKey(url) !== relayKey) return Reflect.construct(target, args);
496
784
  if (relayAuthExpired) return createFailFastSocket(url);
497
785
  const ws = Reflect.construct(target, args);
498
- ws.addEventListener("open", () => broadcastRelayWsState("open"));
786
+ ws.addEventListener("open", () => {
787
+ broadcastRelayWsState("open");
788
+ cancelScheduledDetach();
789
+ });
499
790
  ws.addEventListener("close", (event) => {
500
791
  broadcastRelayWsState("close");
501
792
  if (event.code === 4401) {
502
793
  relayAuthExpired = true;
503
794
  notifyAuthExpired();
504
- }
795
+ detachDebugSurface();
796
+ } else scheduleDetach();
797
+ });
798
+ ws.addEventListener("error", () => {
799
+ scheduleDetach();
505
800
  });
506
801
  return ws;
507
802
  } });
@@ -597,6 +892,7 @@ function maybeAttach(gateResult = checkDebugGate()) {
597
892
  const atCode = typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("at") : null;
598
893
  const src = deriveTargetScriptUrl(gateResult.relayUrl, atCode);
599
894
  installRelayWsObserver(gateResult.relayUrl);
895
+ installBridgeObserver();
600
896
  if (document.querySelector(`script[src="${src}"]`) !== null) {
601
897
  attached = true;
602
898
  return;
@@ -667,6 +963,6 @@ function checkDebugGate() {
667
963
  });
668
964
  }
669
965
  //#endregion
670
- export { checkDebugGate, deriveTargetScriptUrl, evaluateDebugGate, isPrivateAppsHost, isTossminiHost, isTrycloudflareHost, maybeAttach, mountEruda, reportWebViewType };
966
+ export { BRIDGE_CALL_EVENT, checkDebugGate, deriveTargetScriptUrl, detachDebugSurface, evaluateDebugGate, installBridgeObserver, isPrivateAppsHost, isTossminiHost, isTrycloudflareHost, maybeAttach, mountEruda, reportWebViewType, uninstallBridgeObserver, unmountEruda };
671
967
 
672
968
  //# sourceMappingURL=index.js.map