@lightworkai.official/debug-capture 0.19.10 → 0.19.12

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/live.d.ts CHANGED
@@ -9,6 +9,18 @@ export interface RingingCall {
9
9
  ringUntil: string;
10
10
  side?: string | null;
11
11
  }
12
+ /**
13
+ * The support host restarting (a deploy — it says 1012 as it stops): every widget on every page of every host app lost
14
+ * its socket in the same instant, and each came back after exactly 2 s — the whole fleet's tickets, sign-ins and
15
+ * sockets on the support host's one thread in the same second. Back somewhere in this window instead, at random.
16
+ */
17
+ export declare const RESTART_SPREAD_MS = 15000;
18
+ /**
19
+ * How long before trying again — each step ×0.5–1.5, never the same moment as every other widget's (the desk's own tabs
20
+ * do the same) — `restart`: the host said it restarts (1012); `slow`:
21
+ * refused for now (429), or nobody signed in yet; else the next step of the ladder. Pure but for the dice, for the tests.
22
+ */
23
+ export declare function retryWait(how: "restart" | "slow" | null, attempt: number, roll?: () => number): number;
12
24
  /** `wss://support.example/api/presence/ws?t=…` — the support host's scheme, the socket's own path. Pure, for the tests. */
13
25
  export declare function liveSocketUrl(host: string, ticket: string): string;
14
26
  /** Where the card sends them: their request, with the call — the support host rings it there. Pure, for the tests. */
@@ -23,7 +23,8 @@
23
23
  * STAY_MARGIN does not move it, so it does not jitter as the page settles.
24
24
 
25
25
  * A PAGE'S OWN PLACE. The desk's rules (ปุ่มแชท › ตำแหน่งปุ่ม): pages whose path starts so — hidden there, or
26
- * started in a corner at a height. The most specific rule wins; a person who moved it keeps their place.
26
+ * started on a side at x in from its edge and y up from the bottom, at its own z-index (0.19.11). The most specific
27
+ * rule wins; a person who moved it keeps their place (the rule's z-index still holds).
27
28
  *
28
29
  * The arithmetic is pure (bubblePlace.test.ts); `scanColumns` reads the page.
29
30
  */
@@ -80,6 +81,10 @@ export interface PlaceRule {
80
81
  show: "hide" | Side;
81
82
  /** Up from the bottom, px. */
82
83
  y: number;
84
+ /** In from its side's edge, px (0.19.11) — the host's own inset when the desk does not say. */
85
+ x?: number;
86
+ /** Its z-index on the page (0.19.11), the panel one above — null or absent: on top of everything. */
87
+ z?: number | null;
83
88
  }
84
89
  /** A rule's path as a test of a page's path: from its start, ending at a segment's end. Pure; null when malformed. */
85
90
  export declare function pathTest(pattern: string): RegExp | null;
@@ -24,11 +24,17 @@ export interface BubbleSpot {
24
24
  /** Up from the bottom, px — to the bubble's lower edge. */
25
25
  y: number;
26
26
  }
27
- /** Let go at (px, py), its centre: the nearer side, the height it was dropped at — inside the window. Pure. */
27
+ /**
28
+ * Let go at (px, py), its centre: just there (0.19.11, card f34152d3 — "move ได้เฉพาะ top/bottom left กับ top/bottom
29
+ * right อยากได้แบบ free hand"), measured from the nearer side so it keeps its place from that edge as the window
30
+ * changes; within SNAP of the edge it lines up at the inset. Inside the window. Pure.
31
+ */
28
32
  export declare function settleBubble(px: number, py: number, view: {
29
33
  w: number;
30
34
  h: number;
31
35
  }, inset: number, s?: number): BubbleSpot;
36
+ /** In from its edge, keeping the whole bubble on screen. Pure. */
37
+ export declare function clampBubbleX(x: number, viewW: number, s?: number): number;
32
38
  /** A height that keeps the whole bubble on screen. Pure. */
33
39
  export declare function clampBubbleY(y: number, viewH: number, s?: number): number;
34
40
  /** How far the call's card rises to clear the bubble: only a bubble on its side, low enough to meet it. Pure. */
@@ -42,6 +48,11 @@ export declare function anchorPanel(spot: BubbleSpot, viewH: number, s?: number)
42
48
  top?: number;
43
49
  height: number;
44
50
  };
51
+ /**
52
+ * The panel by the bubble, in from the same edge as the bubble — a bubble far in from it (0.19.11) still has its whole
53
+ * panel on screen. Pure.
54
+ */
55
+ export declare function panelInset(x: number, viewW: number): number;
45
56
  /** A dragged panel's top-left, inside the window by the margin. Pure. */
46
57
  export declare function clampPanel(p: {
47
58
  x: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightworkai.official/debug-capture",
3
- "version": "0.19.10",
3
+ "version": "0.19.12",
4
4
  "description": "Report-a-problem widget: rolling network/console/action capture, optional rrweb session replay, annotated screenshot, and submission to a Lightwork Support host",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",
package/src/live.ts CHANGED
@@ -42,6 +42,22 @@ export interface RingingCall {
42
42
  }
43
43
 
44
44
  const RETRY_MS = [2000, 5000, 15_000, 30_000, 60_000];
45
+ /**
46
+ * The support host restarting (a deploy — it says 1012 as it stops): every widget on every page of every host app lost
47
+ * its socket in the same instant, and each came back after exactly 2 s — the whole fleet's tickets, sign-ins and
48
+ * sockets on the support host's one thread in the same second. Back somewhere in this window instead, at random.
49
+ */
50
+ export const RESTART_SPREAD_MS = 15_000;
51
+ /**
52
+ * How long before trying again — each step ×0.5–1.5, never the same moment as every other widget's (the desk's own tabs
53
+ * do the same) — `restart`: the host said it restarts (1012); `slow`:
54
+ * refused for now (429), or nobody signed in yet; else the next step of the ladder. Pure but for the dice, for the tests.
55
+ */
56
+ export function retryWait(how: "restart" | "slow" | null, attempt: number, roll: () => number = Math.random): number {
57
+ if (how === "restart") return 1000 + Math.round(roll() * RESTART_SPREAD_MS);
58
+ const step = how === "slow" ? RETRY_MS[RETRY_MS.length - 1]! : RETRY_MS[Math.min(attempt, RETRY_MS.length - 1)]!;
59
+ return Math.round(step * (0.5 + roll()));
60
+ }
45
61
  const LIVE_STATES = new Set(["ringing", "active"]);
46
62
  const PING_MS = 20_000;
47
63
 
@@ -144,9 +160,9 @@ export function stopLiveRing(): void {
144
160
  closeCallFrame();
145
161
  }
146
162
 
147
- function schedule(slow = false): void {
163
+ function schedule(how: "restart" | "slow" | null = null): void {
148
164
  if (stopped) return;
149
- const wait = slow ? RETRY_MS[RETRY_MS.length - 1]! : RETRY_MS[Math.min(retry++, RETRY_MS.length - 1)]!;
165
+ const wait = retryWait(how, how ? retry : retry++);
150
166
  clearTimeout(reconnectTimer);
151
167
  reconnectTimer = setTimeout(() => void connect(), wait);
152
168
  }
@@ -160,7 +176,7 @@ async function connect(): Promise<void> {
160
176
  stopped = true;
161
177
  return;
162
178
  }
163
- return schedule(true);
179
+ return schedule("slow");
164
180
  }
165
181
  tokenless = 0;
166
182
  let res: Response;
@@ -174,7 +190,8 @@ async function connect(): Promise<void> {
174
190
  stopped = true;
175
191
  return;
176
192
  }
177
- if (!res.ok) return schedule(res.status === 429);
193
+ // Refused for now: slowly. The host restarting (its proxy answering for it): back at random within the window.
194
+ if (!res.ok) return schedule(res.status === 429 ? "slow" : res.status === 502 || res.status === 503 ? "restart" : null);
178
195
  let out: { calls?: boolean; ticket?: string | null; call?: RingingCall | null };
179
196
  try {
180
197
  out = await res.json();
@@ -221,10 +238,11 @@ async function connect(): Promise<void> {
221
238
  /* not ours to read */
222
239
  }
223
240
  };
224
- ws.onclose = () => {
241
+ ws.onclose = (e) => {
225
242
  clearInterval(pinger);
226
243
  if (socket === ws) socket = null;
227
- schedule();
244
+ // 1012: the support host is restarting — every widget heard it at once; each comes back at its own moment.
245
+ schedule(e.code === 1012 ? "restart" : null);
228
246
  };
229
247
  }
230
248
 
@@ -23,7 +23,8 @@
23
23
  * STAY_MARGIN does not move it, so it does not jitter as the page settles.
24
24
 
25
25
  * A PAGE'S OWN PLACE. The desk's rules (ปุ่มแชท › ตำแหน่งปุ่ม): pages whose path starts so — hidden there, or
26
- * started in a corner at a height. The most specific rule wins; a person who moved it keeps their place.
26
+ * started on a side at x in from its edge and y up from the bottom, at its own z-index (0.19.11). The most specific
27
+ * rule wins; a person who moved it keeps their place (the rule's z-index still holds).
27
28
  *
28
29
  * The arithmetic is pure (bubblePlace.test.ts); `scanColumns` reads the page.
29
30
  */
@@ -139,6 +140,10 @@ export interface PlaceRule {
139
140
  show: "hide" | Side;
140
141
  /** Up from the bottom, px. */
141
142
  y: number;
143
+ /** In from its side's edge, px (0.19.11) — the host's own inset when the desk does not say. */
144
+ x?: number;
145
+ /** Its z-index on the page (0.19.11), the panel one above — null or absent: on top of everything. */
146
+ z?: number | null;
142
147
  }
143
148
 
144
149
  /** A rule's path as a test of a page's path: from its start, ending at a segment's end. Pure; null when malformed. */
@@ -161,10 +161,21 @@ export interface BubbleSpot {
161
161
  const MARGIN = 8;
162
162
  /** The ring's card is ~180px tall at the corner: a bubble above that leaves it there. */
163
163
  const RING_ZONE = 200;
164
- /** Let go at (px, py), its centre: the nearer side, the height it was dropped at — inside the window. Pure. */
164
+ /** Let go this near its edge (px past the inset), it lines up with the corner at the inset. */
165
+ const SNAP = 24;
166
+ /**
167
+ * Let go at (px, py), its centre: just there (0.19.11, card f34152d3 — "move ได้เฉพาะ top/bottom left กับ top/bottom
168
+ * right อยากได้แบบ free hand"), measured from the nearer side so it keeps its place from that edge as the window
169
+ * changes; within SNAP of the edge it lines up at the inset. Inside the window. Pure.
170
+ */
165
171
  export function settleBubble(px: number, py: number, view: { w: number; h: number }, inset: number, s = SIZE): BubbleSpot {
166
172
  const side = px < view.w / 2 ? "left" : "right";
167
- return { side, x: inset, y: clampBubbleY(view.h - py - s / 2, view.h, s) };
173
+ const fromEdge = side === "left" ? px - s / 2 : view.w - px - s / 2;
174
+ return { side, x: fromEdge <= inset + SNAP ? inset : clampBubbleX(fromEdge, view.w, s), y: clampBubbleY(view.h - py - s / 2, view.h, s) };
175
+ }
176
+ /** In from its edge, keeping the whole bubble on screen. Pure. */
177
+ export function clampBubbleX(x: number, viewW: number, s = SIZE): number {
178
+ return Math.round(Math.min(Math.max(x, MARGIN), Math.max(MARGIN, viewW - s - MARGIN)));
168
179
  }
169
180
  /** A height that keeps the whole bubble on screen. Pure. */
170
181
  export function clampBubbleY(y: number, viewH: number, s = SIZE): number {
@@ -184,6 +195,13 @@ export function anchorPanel(spot: BubbleSpot, viewH: number, s = SIZE): { bottom
184
195
  if (above >= 420 || above >= below) return { bottom: spot.y + s + GAP, height: Math.max(240, Math.min(640, above)) };
185
196
  return { top: viewH - spot.y + GAP, height: Math.max(240, Math.min(640, below)) };
186
197
  }
198
+ /**
199
+ * The panel by the bubble, in from the same edge as the bubble — a bubble far in from it (0.19.11) still has its whole
200
+ * panel on screen. Pure.
201
+ */
202
+ export function panelInset(x: number, viewW: number): number {
203
+ return Math.round(Math.max(MARGIN, Math.min(x, viewW - Math.min(PANEL_W, viewW - 32) - MARGIN)));
204
+ }
187
205
  /** A dragged panel's top-left, inside the window by the margin. Pure. */
188
206
  export function clampPanel(p: { x: number; y: number }, box: { w: number; h: number }, view: { w: number; h: number }): { x: number; y: number } {
189
207
  return { x: Math.round(Math.min(Math.max(p.x, MARGIN), Math.max(MARGIN, view.w - box.w - MARGIN))), y: Math.round(Math.min(Math.max(p.y, MARGIN), Math.max(MARGIN, view.h - box.h - MARGIN))) };
@@ -351,10 +369,11 @@ function loadPlace(): void {
351
369
  const def = chatPlace(getConfig().chat);
352
370
  if (def) spot = { side: def.side, x: def.x, y: def.y };
353
371
  try {
354
- const kept = JSON.parse(localStorage.getItem(placeKey()) ?? "null") as { bubble?: { side?: unknown; y?: unknown }; panel?: { x?: unknown; y?: unknown; h?: unknown } | null; opened?: unknown } | null;
372
+ const kept = JSON.parse(localStorage.getItem(placeKey()) ?? "null") as { bubble?: { side?: unknown; x?: unknown; y?: unknown }; panel?: { x?: unknown; y?: unknown; h?: unknown } | null; opened?: unknown } | null;
355
373
  const b = kept?.bubble;
356
374
  movedByHand = !!b && (b.side === "left" || b.side === "right") && typeof b.y === "number" && Number.isFinite(b.y);
357
- if (movedByHand) spot = { ...spot, side: b!.side as BubbleSpot["side"], y: b!.y as number };
375
+ // Kept before 0.19.11 without its x: at the edge, where it was.
376
+ if (movedByHand) spot = { ...spot, side: b!.side as BubbleSpot["side"], y: b!.y as number, ...(typeof b!.x === "number" && Number.isFinite(b!.x) ? { x: b!.x } : {}) };
358
377
  const p = kept?.panel;
359
378
  panelAt = p && typeof p.x === "number" && typeof p.y === "number" && Number.isFinite(p.x) && Number.isFinite(p.y) ? { x: p.x, y: p.y, h: typeof p.h === "number" && p.h >= 240 ? p.h : 640 } : null;
360
379
  opened = kept?.opened === true;
@@ -364,7 +383,7 @@ function loadPlace(): void {
364
383
  }
365
384
  function savePlace(): void {
366
385
  try {
367
- localStorage.setItem(placeKey(), JSON.stringify({ ...(movedByHand ? { bubble: { side: spot.side, y: spot.y } } : {}), panel: panelAt, ...(opened ? { opened: true } : {}) }));
386
+ localStorage.setItem(placeKey(), JSON.stringify({ ...(movedByHand ? { bubble: { side: spot.side, x: spot.x, y: spot.y } } : {}), panel: panelAt, ...(opened ? { opened: true } : {}) }));
368
387
  } catch {
369
388
  /* this visit only */
370
389
  }
@@ -406,12 +425,18 @@ function avoidSetting(): { on: boolean; extra: string | null } {
406
425
  if (own === false || deskLook?.avoid === false) return { on: false, extra: null };
407
426
  return { on: true, extra: typeof own === "string" && own.trim() ? own.trim() : null };
408
427
  }
409
- /** Where it wants to be on this page: put by hand — kept; else the desk's rule for the page; else the config's place. */
428
+ /** The desk's z-index for this page (0.19.11) — null: the stylesheet's, on top of everything. */
429
+ let ruleZ: number | null = null;
430
+ /**
431
+ * Where it wants to be on this page: put by hand — kept; else the desk's rule for the page — its side, x in from that
432
+ * edge and y up (0.19.11: x the desk's too); else the config's place. The rule's z-index holds either way.
433
+ */
410
434
  function wanted(): BubbleSpot {
411
435
  const rule = placeFor(pagePath(), deskLook?.places);
412
436
  ruleHidden = rule?.show === "hide";
437
+ ruleZ = typeof rule?.z === "number" && Number.isFinite(rule.z) ? Math.round(Math.min(Math.max(rule.z, 0), 2147483000)) : null;
413
438
  if (movedByHand || !rule || rule.show === "hide") return spot;
414
- return { side: rule.show, x: spot.x, y: rule.y };
439
+ return { side: rule.show, x: typeof rule.x === "number" && Number.isFinite(rule.x) ? rule.x : spot.x, y: rule.y };
415
440
  }
416
441
  /** Look at the page afresh — its columns read again as placing asks for them — and place again. */
417
442
  function rescan(): void {
@@ -473,7 +498,8 @@ const onPageSettled = (e: Event) => {
473
498
  if (ours(e.target)) return;
474
499
  const r = e.target instanceof Element ? e.target.getBoundingClientRect() : null;
475
500
  const v = view();
476
- const x = spot.x;
501
+ // Its column where it stands — far in from the edge too (0.19.11).
502
+ const x = (shown ?? spot).x;
477
503
  const inColumn = !!r && r.width > 0 && (r.left < x + size || r.right > v.w - x - size);
478
504
  queueRescan(inColumn ? "soon" : "later");
479
505
  };
@@ -523,10 +549,11 @@ function layout(): void {
523
549
  const v = view();
524
550
  const want = wanted();
525
551
  const wantY = clampBubbleY(want.y, v.h, size);
552
+ const wantX = clampBubbleX(want.x, v.w, size);
526
553
  // While the panel is open it stays where it was opened from — never moved under the person's eyes.
527
- const wish = `${pagePath()}|${want.side}|${want.x}|${wantY}`;
554
+ const wish = `${pagePath()}|${want.side}|${wantX}|${wantY}`;
528
555
  const prev = shown && shownFor === wish ? shown : null;
529
- const home = { side: want.side, x: want.x, y: wantY };
556
+ const home = { side: want.side, x: wantX, y: wantY };
530
557
  const placing = !!columns && avoidSetting().on;
531
558
  const free = !isOpen || !shown ? (placing ? placeSpot(home, readColumn, v, size, MARGIN, Math.max(MARGIN, v.h - size - MARGIN), prev) : home) : shown;
532
559
  shown = { side: free.side, x: free.x, y: free.y };
@@ -537,6 +564,9 @@ function layout(): void {
537
564
  const other = at.side === "right" ? "left" : "right";
538
565
  bubble.style.display = ruleHidden && !isOpen ? "none" : "";
539
566
  Object.assign(bubble.style, { [at.side]: `${at.x}px`, [other]: "", bottom: `${y}px`, top: "" });
567
+ // The page's z-index (0.19.11): under the host's own layer when the desk says so — the panel just above its bubble.
568
+ bubble.style.zIndex = ruleZ === null ? "" : String(ruleZ);
569
+ panel.style.zIndex = ruleZ === null ? "" : String(ruleZ + 1);
540
570
  if (panelAt) {
541
571
  // The height it had when it was picked up — a drag moves it, never resizes it under the hand.
542
572
  const box = { w: Math.min(PANEL_W, v.w - 32), h: Math.min(panelAt.h, v.h - 2 * MARGIN) };
@@ -544,7 +574,7 @@ function layout(): void {
544
574
  Object.assign(panel.style, { left: `${p.x}px`, top: `${p.y}px`, right: "", bottom: "", height: `${box.h}px` });
545
575
  } else {
546
576
  const a = anchorPanel(at, v.h, size);
547
- Object.assign(panel.style, { [at.side]: `${at.x}px`, [other]: "", bottom: a.bottom !== undefined ? `${a.bottom}px` : "", top: a.top !== undefined ? `${a.top}px` : "", height: `${a.height}px` });
577
+ Object.assign(panel.style, { [at.side]: `${panelInset(at.x, v.w)}px`, [other]: "", bottom: a.bottom !== undefined ? `${a.bottom}px` : "", top: a.top !== undefined ? `${a.top}px` : "", height: `${a.height}px` });
548
578
  }
549
579
  // The call's card and frame sit above the bubble while it is there and low on their side (ring.ts, callFrame.ts).
550
580
  const lift = root && root.style.display !== "none" && bubble.style.display !== "none" ? liftFor(at, size) : 0;