haltija 1.2.4 → 1.2.7

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/component.js CHANGED
@@ -46,7 +46,7 @@
46
46
  });
47
47
 
48
48
  // src/version.ts
49
- var VERSION = "1.2.4";
49
+ var VERSION = "1.2.7";
50
50
 
51
51
  // src/text-selector.ts
52
52
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
@@ -1310,9 +1310,22 @@
1310
1310
  }
1311
1311
  return summary;
1312
1312
  }
1313
+ function pruneNonInteractive(node) {
1314
+ const isInteractive = node.flags?.interactive || node.flags?.hasEvents;
1315
+ const children = node.children?.map((c) => pruneNonInteractive(c)).filter((c) => c !== null);
1316
+ const shadowChildren = node.shadowChildren?.map((c) => pruneNonInteractive(c)).filter((c) => c !== null);
1317
+ const hasInteractiveDescendant = children && children.length > 0 || shadowChildren && shadowChildren.length > 0;
1318
+ if (!isInteractive && !hasInteractiveDescendant)
1319
+ return null;
1320
+ return {
1321
+ ...node,
1322
+ children: children?.length ? children : undefined,
1323
+ shadowChildren: shadowChildren?.length ? shadowChildren : undefined
1324
+ };
1325
+ }
1313
1326
  function buildDomTree(el, options, currentDepth = 0) {
1314
1327
  const {
1315
- depth = 3,
1328
+ depth = -1,
1316
1329
  includeText = true,
1317
1330
  allAttributes = false,
1318
1331
  includeStyles = false,
@@ -1703,6 +1716,7 @@
1703
1716
  windowId;
1704
1717
  isElectron = false;
1705
1718
  browserId = uid();
1719
+ sessionToken = "";
1706
1720
  killed = false;
1707
1721
  isActive = true;
1708
1722
  homeLeft = 0;
@@ -1775,7 +1789,7 @@
1775
1789
  selectionBox = null;
1776
1790
  highlightedElements = [];
1777
1791
  static get observedAttributes() {
1778
- return ["server", "hidden"];
1792
+ return ["server", "hidden", "session"];
1779
1793
  }
1780
1794
  static async runTests() {
1781
1795
  const el = document.querySelector(TAG_NAME);
@@ -1871,10 +1885,16 @@
1871
1885
  }
1872
1886
  this.windowId = storedWindowId;
1873
1887
  }
1888
+ if (config?.session) {
1889
+ this.sessionToken = config.session;
1890
+ } else {
1891
+ this.sessionToken = uid();
1892
+ }
1874
1893
  }
1875
1894
  connectedCallback() {
1876
1895
  this.killed = false;
1877
1896
  this.serverUrl = this.getAttribute("server") || this.serverUrl;
1897
+ this.sessionToken = this.getAttribute("session") || this.sessionToken;
1878
1898
  this.render();
1879
1899
  const rect = this.getBoundingClientRect();
1880
1900
  this.homeLeft = window.innerWidth - rect.width - 16;
@@ -1902,6 +1922,11 @@
1902
1922
  this.connect();
1903
1923
  }
1904
1924
  }
1925
+ if (name === "session") {
1926
+ this.sessionToken = value;
1927
+ this.disconnect();
1928
+ this.connect();
1929
+ }
1905
1930
  }
1906
1931
  render() {
1907
1932
  if (this.shadowRoot.querySelector(".widget")) {
@@ -2087,6 +2112,25 @@
2087
2112
  .btn.info-btn:hover {
2088
2113
  background: #2563eb;
2089
2114
  }
2115
+ .session-badge {
2116
+ display: flex;
2117
+ align-items: center;
2118
+ gap: 2px;
2119
+ font-size: 9px;
2120
+ color: #666;
2121
+ background: rgba(255,255,255,0.05);
2122
+ padding: 2px 4px;
2123
+ border-radius: 3px;
2124
+ cursor: pointer;
2125
+ user-select: none;
2126
+ }
2127
+ .session-badge:hover {
2128
+ background: rgba(255,255,255,0.1);
2129
+ color: #aaa;
2130
+ }
2131
+ .session-badge.copied {
2132
+ color: #22c55e;
2133
+ }
2090
2134
  @keyframes pulse {
2091
2135
  0%, 100% { opacity: 1; }
2092
2136
  50% { opacity: 0.5; }
@@ -2449,6 +2493,8 @@
2449
2493
  <span class="logo">\uD83E\uDDDD</span>
2450
2494
  </div>
2451
2495
  <div class="title">${PRODUCT_NAME}</div>
2496
+ <span class="session-badge" data-action="copy-session" title="Session: ${this.sessionToken}
2497
+ Click to copy session command">${this.sessionToken.slice(0, 8)}</span>
2452
2498
  <div class="controls">
2453
2499
  <button class="btn" data-action="select" title="Select elements (drag to select area)" aria-label="Select elements">\uD83D\uDC46</button>
2454
2500
  <button class="btn" data-action="record" title="Record test (click to start/stop)" aria-label="Record test">REC</button>
@@ -2535,6 +2581,8 @@
2535
2581
  this.startSelection();
2536
2582
  if (action2 === "stats")
2537
2583
  this.copyStatsToClipboard();
2584
+ if (action2 === "copy-session")
2585
+ this.copySessionToken(e.currentTarget);
2538
2586
  if (action2 === "close-modal")
2539
2587
  this.closeTestModal();
2540
2588
  if (action2 === "copy-test")
@@ -3160,6 +3208,28 @@
3160
3208
  });
3161
3209
  }
3162
3210
  }
3211
+ async copySessionToken(badge) {
3212
+ const cmd = `export HALTIJA_SESSION=${this.sessionToken}`;
3213
+ try {
3214
+ await navigator.clipboard.writeText(cmd);
3215
+ badge.textContent = "copied!";
3216
+ badge.classList.add("copied");
3217
+ setTimeout(() => {
3218
+ badge.textContent = this.sessionToken.slice(0, 8);
3219
+ badge.classList.remove("copied");
3220
+ }, 1500);
3221
+ } catch {
3222
+ try {
3223
+ await navigator.clipboard.writeText(this.sessionToken);
3224
+ badge.textContent = "copied!";
3225
+ badge.classList.add("copied");
3226
+ setTimeout(() => {
3227
+ badge.textContent = this.sessionToken.slice(0, 8);
3228
+ badge.classList.remove("copied");
3229
+ }, 1500);
3230
+ } catch {}
3231
+ }
3232
+ }
3163
3233
  async copyStatsToClipboard() {
3164
3234
  const refStats = refRegistry.getStats();
3165
3235
  stats.refsStale = refStats.stale;
@@ -4926,6 +4996,7 @@ ${elementSummary}${moreText}`;
4926
4996
  this.send("system", "connected", {
4927
4997
  windowId: this.windowId,
4928
4998
  browserId: this.browserId,
4999
+ session: this.sessionToken,
4929
5000
  version: VERSION2,
4930
5001
  serverSessionId: SERVER_SESSION_ID,
4931
5002
  url: location.href,
@@ -5351,7 +5422,7 @@ ${elementSummary}${moreText}`;
5351
5422
  const haltija = window.haltija;
5352
5423
  if (action2 === "open") {
5353
5424
  if (haltija?.openTab) {
5354
- haltija.openTab(payload2.url).then((opened) => {
5425
+ haltija.openTab(payload2.url, payload2.session).then((opened) => {
5355
5426
  this.respond(msg2.id, true, { opened });
5356
5427
  }).catch((err) => {
5357
5428
  this.respond(msg2.id, false, null, err.message);
@@ -5471,7 +5542,10 @@ ${elementSummary}${moreText}`;
5471
5542
  const summary = buildActionableSummary(el);
5472
5543
  this.respond(msg2.id, true, summary);
5473
5544
  } else {
5474
- const tree = buildDomTree(el, request);
5545
+ let tree = buildDomTree(el, request);
5546
+ if (request.interactiveOnly && tree) {
5547
+ tree = pruneNonInteractive(tree);
5548
+ }
5475
5549
  if (request.ancestors && tree) {
5476
5550
  const ancestors = [];
5477
5551
  let parent = el.parentElement;
@@ -7207,7 +7281,7 @@ ${elementSummary}${moreText}`;
7207
7281
  }
7208
7282
  registerDevChannel();
7209
7283
  var WIDGET_ID = "haltija-widget";
7210
- function inject(serverUrl2 = "wss://localhost:8700/ws/browser") {
7284
+ function inject(serverUrl2 = "wss://localhost:8700/ws/browser", options) {
7211
7285
  const existing = document.getElementById(WIDGET_ID);
7212
7286
  if (existing) {
7213
7287
  console.log(`${LOG_PREFIX} Already injected`);
@@ -7222,6 +7296,8 @@ ${elementSummary}${moreText}`;
7222
7296
  const el = DevChannel.elementCreator()();
7223
7297
  el.id = WIDGET_ID;
7224
7298
  el.setAttribute("server", serverUrl2);
7299
+ if (options?.session)
7300
+ el.setAttribute("session", options.session);
7225
7301
  el.setAttribute("data-version", VERSION2);
7226
7302
  document.body.appendChild(el);
7227
7303
  console.log(`${LOG_PREFIX} Injected`);
@@ -7233,7 +7309,7 @@ ${elementSummary}${moreText}`;
7233
7309
  const config = window.__haltija_config__;
7234
7310
  if (config?.autoInject !== false) {
7235
7311
  if (config) {
7236
- inject(config.serverUrl || config.wsUrl);
7312
+ inject(config.serverUrl || config.wsUrl, { session: config.session });
7237
7313
  return;
7238
7314
  }
7239
7315
  }
package/dist/hj.js CHANGED
@@ -10,13 +10,15 @@ import { fileURLToPath } from "url";
10
10
 
11
11
  // bin/format-tree.mjs
12
12
  var MAX_TEXT_LEN = 80;
13
- function formatTree(node, indent = 0) {
13
+ function formatTree(node, indent = 0, { depth } = {}) {
14
14
  if (!node)
15
15
  return "";
16
16
  const lines = [];
17
17
  formatNode(node, indent, lines);
18
+ const d = depth ?? -1;
19
+ const depthLabel = d === -1 ? "unlimited" : String(d);
18
20
  lines.push("---");
19
- lines.push("hj tree --json");
21
+ lines.push(`depth=${depthLabel} | -d N | -i (interactive) | --visible | --json`);
20
22
  return lines.join(`
21
23
  `);
22
24
  }
@@ -898,7 +900,11 @@ function parseTreeArgs(args) {
898
900
  body.compact = true;
899
901
  continue;
900
902
  }
901
- if (a === "--visible") {
903
+ if (a === "--interactive" || a === "-i") {
904
+ body.interactiveOnly = true;
905
+ continue;
906
+ }
907
+ if (a === "--visible" || a === "-v") {
902
908
  body.visibleOnly = true;
903
909
  continue;
904
910
  }
@@ -1140,6 +1146,13 @@ function clean(obj) {
1140
1146
  }
1141
1147
  return Object.keys(result).length ? result : undefined;
1142
1148
  }
1149
+ function getSessionId() {
1150
+ if (process.env.HALTIJA_SESSION)
1151
+ return process.env.HALTIJA_SESSION;
1152
+ const id = `hj_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
1153
+ process.env.HALTIJA_SESSION = id;
1154
+ return id;
1155
+ }
1143
1156
  async function isServerRunning(port) {
1144
1157
  try {
1145
1158
  const resp = await fetch(`http://localhost:${port}/status`, {
@@ -1198,9 +1211,75 @@ async function startServerInBackground(port) {
1198
1211
  }
1199
1212
  return false;
1200
1213
  }
1201
- async function runSubcommand(subcommand, subArgs, port = "8700") {
1214
+ async function launchElectronApp() {
1215
+ const { execSync, spawn: spawnChild } = await import("child_process");
1216
+ if (process.platform === "darwin") {
1217
+ const appPaths = [
1218
+ "/Applications/Haltija.app",
1219
+ `${process.env.HOME}/Applications/Haltija.app`
1220
+ ];
1221
+ for (const p of appPaths) {
1222
+ if (existsSync(p)) {
1223
+ spawnChild("open", ["-a", p], { stdio: "ignore", detached: true }).unref();
1224
+ return true;
1225
+ }
1226
+ }
1227
+ try {
1228
+ const result = execSync('mdfind "kMDItemCFBundleIdentifier == com.electron.haltija" | head -1', { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }).trim();
1229
+ if (result) {
1230
+ spawnChild("open", ["-a", result], { stdio: "ignore", detached: true }).unref();
1231
+ return true;
1232
+ }
1233
+ } catch {}
1234
+ return false;
1235
+ }
1236
+ return false;
1237
+ }
1238
+ async function ensureBrowserConnected(port) {
1239
+ try {
1240
+ const resp = await fetch(`http://localhost:${port}/status`, {
1241
+ signal: AbortSignal.timeout(2000)
1242
+ });
1243
+ const status = await resp.json();
1244
+ if (status.ok)
1245
+ return true;
1246
+ } catch {
1247
+ return false;
1248
+ }
1249
+ if (process.platform !== "darwin")
1250
+ return false;
1251
+ process.stderr.write("\x1B[2mLaunching Haltija browser...\x1B[0m");
1252
+ const launched = await launchElectronApp();
1253
+ if (!launched) {
1254
+ process.stderr.write(`\x1B[2m not found\x1B[0m
1255
+ `);
1256
+ return false;
1257
+ }
1258
+ const maxWait = 1e4;
1259
+ const start = Date.now();
1260
+ while (Date.now() - start < maxWait) {
1261
+ try {
1262
+ const resp = await fetch(`http://localhost:${port}/status`, {
1263
+ signal: AbortSignal.timeout(1000)
1264
+ });
1265
+ const status = await resp.json();
1266
+ if (status.ok) {
1267
+ process.stderr.write(`\x1B[2m ready\x1B[0m
1268
+ `);
1269
+ return true;
1270
+ }
1271
+ } catch {}
1272
+ await new Promise((r) => setTimeout(r, 500));
1273
+ }
1274
+ process.stderr.write(`\x1B[2m timeout\x1B[0m
1275
+ `);
1276
+ return false;
1277
+ }
1278
+ var INFO_COMMANDS = new Set(["status", "windows", "version", "help"]);
1279
+ async function runSubcommand(subcommand, subArgs, port = "8700", options = {}) {
1202
1280
  const baseUrl = `http://localhost:${port}`;
1203
1281
  const jsonOutput = subArgs.includes("--json");
1282
+ const noLaunch = options.noLaunch || false;
1204
1283
  let filteredArgs = subArgs.filter((a) => a !== "--json");
1205
1284
  let targetWindowId = undefined;
1206
1285
  const windowIdx = filteredArgs.indexOf("--window");
@@ -1208,6 +1287,11 @@ async function runSubcommand(subcommand, subArgs, port = "8700") {
1208
1287
  targetWindowId = filteredArgs[windowIdx + 1];
1209
1288
  filteredArgs = [...filteredArgs.slice(0, windowIdx), ...filteredArgs.slice(windowIdx + 2)];
1210
1289
  }
1290
+ const sessionIdx = filteredArgs.indexOf("--session");
1291
+ if (sessionIdx !== -1) {
1292
+ process.env.HALTIJA_SESSION = filteredArgs[sessionIdx + 1];
1293
+ filteredArgs = [...filteredArgs.slice(0, sessionIdx), ...filteredArgs.slice(sessionIdx + 2)];
1294
+ }
1211
1295
  if (!await isServerRunning(port)) {
1212
1296
  process.stderr.write("\x1B[2mStarting Haltija server...\x1B[0m");
1213
1297
  const started = await startServerInBackground(port);
@@ -1221,6 +1305,9 @@ async function runSubcommand(subcommand, subArgs, port = "8700") {
1221
1305
  process.exit(1);
1222
1306
  }
1223
1307
  }
1308
+ if (!noLaunch && !INFO_COMMANDS.has(subcommand)) {
1309
+ await ensureBrowserConnected(port);
1310
+ }
1224
1311
  if (subcommand === "send") {
1225
1312
  const firstArg = filteredArgs[0]?.toLocaleLowerCase();
1226
1313
  if (firstArg === "selection") {
@@ -1266,9 +1353,10 @@ async function runSubcommand(subcommand, subArgs, port = "8700") {
1266
1353
  async function doRequest(url, method, body, context = {}) {
1267
1354
  const { subcommand, jsonOutput } = context;
1268
1355
  try {
1269
- const opts = { method };
1356
+ const sessionId = getSessionId();
1357
+ const opts = { method, headers: { "X-Haltija-Session": sessionId } };
1270
1358
  if (body) {
1271
- opts.headers = { "Content-Type": "application/json" };
1359
+ opts.headers["Content-Type"] = "application/json";
1272
1360
  opts.body = JSON.stringify(body);
1273
1361
  }
1274
1362
  const resp = await fetch(url, opts);
@@ -1276,7 +1364,7 @@ async function doRequest(url, method, body, context = {}) {
1276
1364
  if (contentType.includes("application/json")) {
1277
1365
  const json = await resp.json();
1278
1366
  if (!jsonOutput && subcommand === "tree" && json.success && json.data) {
1279
- console.log(formatTree(json.data));
1367
+ console.log(formatTree(json.data, 0, { depth: body?.depth }));
1280
1368
  } else if (!jsonOutput && subcommand === "events" && (json.events || Array.isArray(json))) {
1281
1369
  console.log(formatEvents(json));
1282
1370
  } else if (!jsonOutput && subcommand === "test-run" && json.test) {
@@ -1428,7 +1516,7 @@ function listSubcommands() {
1428
1516
  return `
1429
1517
  Subcommands (replace curl with simple commands):
1430
1518
  ${bold("Inspect")}
1431
- tree [selector] [-d depth] DOM tree with ref IDs
1519
+ tree [selector] [-d N] [-i] [-v] DOM tree (full depth, -i=interactive, -v=visible)
1432
1520
  query <selector> Find elements matching selector
1433
1521
  inspect <@ref|selector> Detailed element info
1434
1522
  inspectAll <selector> Deep inspect all matches
@@ -1558,6 +1646,17 @@ if (portIdx !== -1 && args[portIdx + 1]) {
1558
1646
  port = args[portIdx + 1];
1559
1647
  args.splice(portIdx, 2);
1560
1648
  }
1649
+ var sessionIdx = args.indexOf("--session");
1650
+ if (sessionIdx !== -1 && args[sessionIdx + 1]) {
1651
+ process.env.HALTIJA_SESSION = args[sessionIdx + 1];
1652
+ args.splice(sessionIdx, 2);
1653
+ }
1654
+ var noLaunch = false;
1655
+ var noLaunchIdx = args.indexOf("--no-launch");
1656
+ if (noLaunchIdx !== -1) {
1657
+ noLaunch = true;
1658
+ args.splice(noLaunchIdx, 1);
1659
+ }
1561
1660
  var subcommand = args[0];
1562
1661
  var subArgs = args.slice(1).filter((a) => a !== "--window" || true);
1563
1662
  if (!isSubcommand(subcommand)) {
@@ -1581,7 +1680,7 @@ Examples: hj tree, hj navigate <url>, hj click @42`);
1581
1680
  console.error(`Run 'hj' for docs.`);
1582
1681
  process.exit(1);
1583
1682
  } else {
1584
- runSubcommand(subcommand, subArgs, port);
1683
+ runSubcommand(subcommand, subArgs, port, { noLaunch });
1585
1684
  }
1586
1685
  function filterHelp(topic) {
1587
1686
  const bold2 = (s) => `\x1B[1m${s}\x1B[0m`;