@lightworkai.official/debug-capture 0.15.0 → 0.16.0
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/README.md +28 -0
- package/dist/config.d.ts +13 -0
- package/dist/debug-capture.js +9 -7
- package/dist/index.mjs +15 -13
- package/dist/ui/callFrame.d.ts +63 -0
- package/dist/ui/ring.d.ts +6 -0
- package/package.json +1 -1
- package/src/config.ts +14 -0
- package/src/live.ts +71 -7
- package/src/ui/callFrame.ts +240 -0
- package/src/ui/ring.ts +10 -0
package/dist/ui/ring.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export declare function ringAbout(template: string, call: RingCall): string;
|
|
|
14
14
|
export declare function showRing(call: RingCall, handlers: RingHandlers): void;
|
|
15
15
|
/** Opened: the card says where to answer, and stops ringing. `blockedUrl` — the browser refused the tab: a link, which is a fresh press. */
|
|
16
16
|
export declare function ringOpened(blockedUrl?: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* The call's own frame is up (ui/callFrame, 0.16): its ring card is the one to press, so ours steps out of sight —
|
|
19
|
+
* the ring and the title stay, rung from this page, until the call stops ringing. `false` brings ours back (the frame
|
|
20
|
+
* failed: our card carries the call again).
|
|
21
|
+
*/
|
|
22
|
+
export declare function quietRing(id: string, quiet?: boolean): void;
|
|
17
23
|
/** The call is no longer ringing for this person — answered, declined, missed or ended. */
|
|
18
24
|
export declare function hideRing(id?: string): void;
|
|
19
25
|
export declare function ringShownFor(): string | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightworkai.official/debug-capture",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
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/config.ts
CHANGED
|
@@ -161,6 +161,20 @@ export interface DebugCaptureConfig {
|
|
|
161
161
|
*/
|
|
162
162
|
ring?: boolean;
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* The call itself, in this app. Default true (0.16).
|
|
166
|
+
*
|
|
167
|
+
* When a call rings, the support host's own call — its ring card, its call
|
|
168
|
+
* corner, its screen-sharing question, its voice settings — opens in a frame
|
|
169
|
+
* in this page, signed in with the same token the portal button uses: the
|
|
170
|
+
* reporter answers, talks and shows their screen without leaving the app.
|
|
171
|
+
* The frame needs nothing of the host but leave to frame the support host and
|
|
172
|
+
* to give it the microphone, camera and screen (no `frame-src` or
|
|
173
|
+
* `Permissions-Policy` that forbids them). Should the frame not come up, the
|
|
174
|
+
* card of 0.15 carries the call: open the portal to answer. `false`: always that.
|
|
175
|
+
*/
|
|
176
|
+
callFrame?: boolean;
|
|
177
|
+
|
|
164
178
|
/**
|
|
165
179
|
* Where this library's own notifications go.
|
|
166
180
|
*
|
package/src/live.ts
CHANGED
|
@@ -9,10 +9,14 @@
|
|
|
9
9
|
* for the host's signed token at `/ingest/live-ticket`, told about their own
|
|
10
10
|
* requests and calls and nothing else — and a call rings here as a card.
|
|
11
11
|
*
|
|
12
|
-
* Answering happens
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Answering happens in THIS app since 0.16 (`callFrame`, default on): the
|
|
13
|
+
* support host's own call — ring card, call corner, screen sharing, voice
|
|
14
|
+
* settings — opens in a frame here (ui/callFrame), signed in by the same
|
|
15
|
+
* token the portal button uses; our card steps aside once the frame is up,
|
|
16
|
+
* and comes back if it never does. Before 0.16, and with `callFrame: false`,
|
|
17
|
+
* the card's button opens the reporter's request on the support host — a tab
|
|
18
|
+
* opened INSIDE the press (a browser allows nothing later), its address set
|
|
19
|
+
* once the token is in hand.
|
|
16
20
|
*
|
|
17
21
|
* Quiet by design. No identity, calls off, a support host older than this
|
|
18
22
|
* package, a token refused: no socket, no card, nothing in the console — the
|
|
@@ -20,7 +24,8 @@
|
|
|
20
24
|
*/
|
|
21
25
|
import { getConfig } from "./config";
|
|
22
26
|
import { preparePortalUrl } from "./portal";
|
|
23
|
-
import { hideRing, ringOpened, ringShownFor, showRing } from "./ui/ring";
|
|
27
|
+
import { hideRing, quietRing, ringOpened, ringShownFor, showRing } from "./ui/ring";
|
|
28
|
+
import { callFrameFor, closeCallFrame, frameUrl, openCallFrame } from "./ui/callFrame";
|
|
24
29
|
|
|
25
30
|
export interface RingingCall {
|
|
26
31
|
id: string;
|
|
@@ -33,6 +38,7 @@ export interface RingingCall {
|
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
const RETRY_MS = [2000, 5000, 15_000, 30_000, 60_000];
|
|
41
|
+
const LIVE_STATES = new Set(["ringing", "active"]);
|
|
36
42
|
const PING_MS = 20_000;
|
|
37
43
|
|
|
38
44
|
let started = false;
|
|
@@ -109,6 +115,7 @@ export function stopLiveRing(): void {
|
|
|
109
115
|
}
|
|
110
116
|
socket = null;
|
|
111
117
|
hideRing();
|
|
118
|
+
closeCallFrame();
|
|
112
119
|
}
|
|
113
120
|
|
|
114
121
|
function schedule(slow = false): void {
|
|
@@ -178,10 +185,67 @@ async function connect(): Promise<void> {
|
|
|
178
185
|
};
|
|
179
186
|
}
|
|
180
187
|
|
|
181
|
-
/** The
|
|
188
|
+
/** The call this tab holds in its frame — taken back after a reload, so the call goes on. */
|
|
189
|
+
const FRAMED_KEY = "lw-call-framed";
|
|
190
|
+
function framedHere(): string | null {
|
|
191
|
+
try {
|
|
192
|
+
return sessionStorage.getItem(FRAMED_KEY);
|
|
193
|
+
} catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function markFramed(id: string | null): void {
|
|
198
|
+
try {
|
|
199
|
+
if (id) sessionStorage.setItem(FRAMED_KEY, id);
|
|
200
|
+
else sessionStorage.removeItem(FRAMED_KEY);
|
|
201
|
+
} catch {
|
|
202
|
+
/* this tab only, when it can */
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let framing: string | null = null;
|
|
207
|
+
/** The call in a frame here (0.16): the support host's own call UI, signed in with the host's token. */
|
|
208
|
+
async function frameFor(call: RingingCall): Promise<void> {
|
|
209
|
+
const config = getConfig();
|
|
210
|
+
if (config.callFrame === false || callFrameFor() === call.id || framing === call.id) return;
|
|
211
|
+
framing = call.id;
|
|
212
|
+
const token = await hostToken();
|
|
213
|
+
framing = null;
|
|
214
|
+
if (!token) return;
|
|
215
|
+
markFramed(call.id);
|
|
216
|
+
openCallFrame(frameUrl(config.host, config.realmKey, token, call.id), call.id, {
|
|
217
|
+
ready: () => quietRing(call.id),
|
|
218
|
+
done: () => {
|
|
219
|
+
markFramed(null);
|
|
220
|
+
hideRing(call.id);
|
|
221
|
+
},
|
|
222
|
+
// Our card carries the call again — still ringing, its button opens the portal to answer.
|
|
223
|
+
failed: () => {
|
|
224
|
+
markFramed(null);
|
|
225
|
+
quietRing(call.id, false);
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** The support host's word on a call: ringing here shows the card (and its frame); anything else takes the card away — the frame stays, it IS the call. */
|
|
182
231
|
function hear(call: RingingCall): void {
|
|
183
|
-
if (!ringsHere(call))
|
|
232
|
+
if (!ringsHere(call)) {
|
|
233
|
+
hideRing(call.id);
|
|
234
|
+
// Answered in this tab's frame, then the page reloaded: the frame comes back and takes the call back.
|
|
235
|
+
if (call.status === "active" && framedHere() === call.id && !callFrameFor()) void frameFor(call);
|
|
236
|
+
// Over: the frame says its last word and goes — and goes anyway if it cannot.
|
|
237
|
+
if (!LIVE_STATES.has(call.status) && callFrameFor() === call.id) {
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
if (callFrameFor() === call.id) {
|
|
240
|
+
closeCallFrame();
|
|
241
|
+
markFramed(null);
|
|
242
|
+
}
|
|
243
|
+
}, 12_000);
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
184
247
|
showRing(call, { open: () => answer(call), decline: () => void decline(call) });
|
|
248
|
+
void frameFor(call);
|
|
185
249
|
// Never longer than it rings: a socket that dropped mid-ring must not leave a card up for good.
|
|
186
250
|
clearTimeout(expiry);
|
|
187
251
|
const left = new Date(call.ringUntil).getTime() - Date.now();
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The call, in this app — the support host's own call UI in a frame (0.16; the support host's docs/CALLS-PLAN.md §15).
|
|
3
|
+
*
|
|
4
|
+
* A reporter used to answer in a new tab on the support host: a hand-off, a popup a browser might block, and the app
|
|
5
|
+
* with the problem left behind just when the team asks to see it. Now the support host's `/embed/call` opens here, in a
|
|
6
|
+
* frame of our own — the same ring card, call corner, screen-sharing question and voice settings as its portal, so
|
|
7
|
+
* they never drift apart — signed in by the token the portal button uses, given the microphone, camera and screen.
|
|
8
|
+
*
|
|
9
|
+
* The frame says how to hold it (its messages, below): ready, its size and whether a moment covers the page, a drag
|
|
10
|
+
* of its top line, done, failed. We listen only to that frame's own window, from the support host's origin.
|
|
11
|
+
*
|
|
12
|
+
* Its own shadow root, like the ring card: it lands on pages we do not control. Marked as ours, so a report filed
|
|
13
|
+
* during a call does not photograph it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const HOST_TAG = "lw-call-frame";
|
|
17
|
+
const SOURCE = "lw-call";
|
|
18
|
+
const MARGIN = 16;
|
|
19
|
+
/** The call corner's width — the support host's dock, plus the frame's room for its shadow. */
|
|
20
|
+
const CARD_WIDTH = 368;
|
|
21
|
+
|
|
22
|
+
export type FrameLayout = "card" | "full";
|
|
23
|
+
export interface FrameMessage {
|
|
24
|
+
source?: string;
|
|
25
|
+
type?: string;
|
|
26
|
+
width?: number;
|
|
27
|
+
height?: number;
|
|
28
|
+
layout?: FrameLayout;
|
|
29
|
+
phase?: "start" | "move" | "end";
|
|
30
|
+
x?: number;
|
|
31
|
+
y?: number;
|
|
32
|
+
reason?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface FrameHandlers {
|
|
35
|
+
/** The call is on screen in the frame. */
|
|
36
|
+
ready(): void;
|
|
37
|
+
/** Nothing left to show — the frame is gone already. */
|
|
38
|
+
done(): void;
|
|
39
|
+
/** It could not sign in or never came up — the frame is gone; the caller's own card carries the call. */
|
|
40
|
+
failed(reason: string): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let host: HTMLElement | null = null;
|
|
44
|
+
let frame: HTMLIFrameElement | null = null;
|
|
45
|
+
let callId: string | null = null;
|
|
46
|
+
let origin = "";
|
|
47
|
+
let handlers: FrameHandlers | null = null;
|
|
48
|
+
let layout: FrameLayout = "card";
|
|
49
|
+
let size = { width: CARD_WIDTH, height: 0 };
|
|
50
|
+
let readyTimer: ReturnType<typeof setTimeout> | undefined;
|
|
51
|
+
let isReady = false;
|
|
52
|
+
/** Where the corner was moved to (left, top), or null: bottom right. Kept for this tab. */
|
|
53
|
+
let placed: { x: number; y: number } | null = null;
|
|
54
|
+
let drag: { sx: number; sy: number; x: number; y: number } | null = null;
|
|
55
|
+
const PLACE_KEY = "lw-call-frame-place";
|
|
56
|
+
|
|
57
|
+
/** The support host's page for this call. Pure, for the tests. */
|
|
58
|
+
export function frameUrl(supportHost: string, realmKey: string, token: string, id: string): string {
|
|
59
|
+
const url = new URL("embed/call", supportHost.endsWith("/") ? supportHost : `${supportHost}/`);
|
|
60
|
+
url.searchParams.set("realm", realmKey);
|
|
61
|
+
url.searchParams.set("token", token);
|
|
62
|
+
url.searchParams.set("call", id);
|
|
63
|
+
return url.toString();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Within the window, by at least the margin — a corner dragged off screen comes back. Pure, for the tests. */
|
|
67
|
+
export function clampTo(p: { x: number; y: number }, box: { w: number; h: number }, view: { w: number; h: number }): { x: number; y: number } {
|
|
68
|
+
return {
|
|
69
|
+
x: Math.min(Math.max(p.x, MARGIN / 2), Math.max(MARGIN / 2, view.w - box.w - MARGIN / 2)),
|
|
70
|
+
y: Math.min(Math.max(p.y, MARGIN / 2), Math.max(MARGIN / 2, view.h - box.h - MARGIN / 2)),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Is this message from our frame, about the call? */
|
|
75
|
+
export function fromFrame(e: { origin: string; source: unknown; data: unknown }, expected: { origin: string; window: unknown }): FrameMessage | null {
|
|
76
|
+
if (e.origin !== expected.origin || e.source !== expected.window) return null;
|
|
77
|
+
const d = e.data as FrameMessage | null;
|
|
78
|
+
return d && typeof d === "object" && d.source === SOURCE && typeof d.type === "string" ? d : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function callFrameFor(): string | null {
|
|
82
|
+
return host?.isConnected ? callId : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Open the call's frame — once per call; the same call again changes nothing. Shown when it says it is ready. */
|
|
86
|
+
export function openCallFrame(src: string, id: string, h: FrameHandlers, readyWithinMs = 15_000): void {
|
|
87
|
+
if (typeof document === "undefined") return;
|
|
88
|
+
if (callId === id && host?.isConnected) return;
|
|
89
|
+
closeCallFrame();
|
|
90
|
+
callId = id;
|
|
91
|
+
handlers = h;
|
|
92
|
+
isReady = false;
|
|
93
|
+
layout = "card";
|
|
94
|
+
size = { width: CARD_WIDTH, height: 0 };
|
|
95
|
+
origin = new URL(src).origin;
|
|
96
|
+
try {
|
|
97
|
+
placed = JSON.parse(sessionStorage.getItem(PLACE_KEY) ?? "null");
|
|
98
|
+
} catch {
|
|
99
|
+
placed = null;
|
|
100
|
+
}
|
|
101
|
+
host = document.createElement(HOST_TAG);
|
|
102
|
+
host.dataset.debugReporter = "true";
|
|
103
|
+
const root = host.attachShadow({ mode: "open" });
|
|
104
|
+
const style = document.createElement("style");
|
|
105
|
+
style.textContent = `:host { all: initial; }
|
|
106
|
+
iframe { position: fixed; z-index: 2147483003; border: 0; background: transparent; color-scheme: normal; visibility: hidden; }
|
|
107
|
+
iframe.on { visibility: visible; }`;
|
|
108
|
+
frame = document.createElement("iframe");
|
|
109
|
+
// What a call needs: to hear and be heard, to show a face or a screen, to play the other side's voice.
|
|
110
|
+
frame.allow = "microphone; camera; display-capture; autoplay; clipboard-write; fullscreen";
|
|
111
|
+
frame.title = "สายจากทีมงาน";
|
|
112
|
+
frame.dataset.lwCallFrame = id;
|
|
113
|
+
frame.setAttribute("allowtransparency", "true");
|
|
114
|
+
frame.src = src;
|
|
115
|
+
root.append(style, frame);
|
|
116
|
+
document.body.append(host);
|
|
117
|
+
place();
|
|
118
|
+
window.addEventListener("message", onMessage);
|
|
119
|
+
window.addEventListener("resize", place);
|
|
120
|
+
// Never came up — a host that forbids the frame, a network that dropped it: the caller's card carries the call.
|
|
121
|
+
readyTimer = setTimeout(() => {
|
|
122
|
+
if (!isReady) fail("timeout");
|
|
123
|
+
}, readyWithinMs);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function closeCallFrame(): void {
|
|
127
|
+
clearTimeout(readyTimer);
|
|
128
|
+
window.removeEventListener("message", onMessage);
|
|
129
|
+
window.removeEventListener("resize", place);
|
|
130
|
+
host?.remove();
|
|
131
|
+
host = null;
|
|
132
|
+
frame = null;
|
|
133
|
+
callId = null;
|
|
134
|
+
handlers = null;
|
|
135
|
+
drag = null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function fail(reason: string): void {
|
|
139
|
+
const h = handlers;
|
|
140
|
+
closeCallFrame();
|
|
141
|
+
h?.failed(reason);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function onMessage(e: MessageEvent): void {
|
|
145
|
+
if (!frame) return;
|
|
146
|
+
const m = fromFrame(e, { origin, window: frame.contentWindow });
|
|
147
|
+
if (!m) return;
|
|
148
|
+
switch (m.type) {
|
|
149
|
+
case "ready":
|
|
150
|
+
isReady = true;
|
|
151
|
+
clearTimeout(readyTimer);
|
|
152
|
+
frame.classList.add("on");
|
|
153
|
+
handlers?.ready();
|
|
154
|
+
return;
|
|
155
|
+
case "size":
|
|
156
|
+
if (typeof m.width === "number" && typeof m.height === "number") size = { width: m.width, height: m.height };
|
|
157
|
+
if (m.layout === "card" || m.layout === "full") {
|
|
158
|
+
// About to cover the page: the call is told where its corner is, so it stays there while the frame grows.
|
|
159
|
+
if (m.layout === "full" && layout === "card") tellCorner();
|
|
160
|
+
layout = m.layout;
|
|
161
|
+
}
|
|
162
|
+
return place();
|
|
163
|
+
case "drag":
|
|
164
|
+
return onDrag(m);
|
|
165
|
+
case "home":
|
|
166
|
+
placed = null;
|
|
167
|
+
savePlace();
|
|
168
|
+
return place();
|
|
169
|
+
case "done": {
|
|
170
|
+
const h = handlers;
|
|
171
|
+
closeCallFrame();
|
|
172
|
+
h?.done();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
case "failed":
|
|
176
|
+
return fail(m.reason ?? "failed");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** The corner moved by its top line: from where the press began, by how far the pointer went on the screen. */
|
|
181
|
+
function onDrag(m: FrameMessage): void {
|
|
182
|
+
if (!frame || layout !== "card" || typeof m.x !== "number" || typeof m.y !== "number") return;
|
|
183
|
+
if (m.phase === "start") {
|
|
184
|
+
const r = frame.getBoundingClientRect();
|
|
185
|
+
drag = { sx: m.x, sy: m.y, x: r.left, y: r.top };
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!drag) return;
|
|
189
|
+
placed = clampTo({ x: drag.x + (m.x - drag.sx), y: drag.y + (m.y - drag.sy) }, boxOf(), viewOf());
|
|
190
|
+
place();
|
|
191
|
+
if (m.phase === "end") {
|
|
192
|
+
drag = null;
|
|
193
|
+
savePlace();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Where the corner is on the page — the frame asks nothing else of us, and is told nothing else. */
|
|
198
|
+
function tellCorner(): void {
|
|
199
|
+
if (!frame?.contentWindow) return;
|
|
200
|
+
const r = frame.getBoundingClientRect();
|
|
201
|
+
frame.contentWindow.postMessage({ source: "lw-call-host", type: "corner", x: Math.round(r.left), y: Math.round(r.top), width: Math.round(r.width) }, origin);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function savePlace(): void {
|
|
205
|
+
try {
|
|
206
|
+
if (placed) sessionStorage.setItem(PLACE_KEY, JSON.stringify(placed));
|
|
207
|
+
else sessionStorage.removeItem(PLACE_KEY);
|
|
208
|
+
} catch {
|
|
209
|
+
/* this tab only, when it can */
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const viewOf = () => ({ w: window.innerWidth, h: window.innerHeight });
|
|
214
|
+
/**
|
|
215
|
+
* The corner: always the dock's width — the call fills whatever width it is given, so a width it reported while it
|
|
216
|
+
* had the whole window must never come back as the corner's (measured: 1264 px after a screen share) — and the
|
|
217
|
+
* height it says it needs.
|
|
218
|
+
*/
|
|
219
|
+
function boxOf(): { w: number; h: number } {
|
|
220
|
+
const view = viewOf();
|
|
221
|
+
return { w: Math.min(CARD_WIDTH, view.w - MARGIN), h: Math.min(size.height || 0, view.h - MARGIN) };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** The whole window for a moment that covers it; otherwise the corner — bottom right, or where it was moved. */
|
|
225
|
+
function place(): void {
|
|
226
|
+
if (!frame) return;
|
|
227
|
+
const s = frame.style;
|
|
228
|
+
if (layout === "full") {
|
|
229
|
+
Object.assign(s, { left: "0px", top: "0px", right: "", bottom: "", width: "100vw", height: "100vh" });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const box = boxOf();
|
|
233
|
+
const narrow = viewOf().w < 480;
|
|
234
|
+
s.width = narrow ? `calc(100vw - ${MARGIN}px)` : `${box.w}px`;
|
|
235
|
+
s.height = `${box.h}px`;
|
|
236
|
+
if (placed && !narrow) {
|
|
237
|
+
const p = clampTo(placed, box, viewOf());
|
|
238
|
+
Object.assign(s, { left: `${p.x}px`, top: `${p.y}px`, right: "", bottom: "" });
|
|
239
|
+
} else Object.assign(s, { left: narrow ? `${MARGIN / 2}px` : "", top: "", right: narrow ? "" : `${MARGIN / 2}px`, bottom: `${MARGIN / 2}px` });
|
|
240
|
+
}
|
package/src/ui/ring.ts
CHANGED
|
@@ -185,6 +185,16 @@ export function ringOpened(blockedUrl?: string): void {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/**
|
|
189
|
+
* The call's own frame is up (ui/callFrame, 0.16): its ring card is the one to press, so ours steps out of sight —
|
|
190
|
+
* the ring and the title stay, rung from this page, until the call stops ringing. `false` brings ours back (the frame
|
|
191
|
+
* failed: our card carries the call again).
|
|
192
|
+
*/
|
|
193
|
+
export function quietRing(id: string, quiet = true): void {
|
|
194
|
+
if (!host?.isConnected || shownId !== id) return;
|
|
195
|
+
host.style.display = quiet ? "none" : "";
|
|
196
|
+
}
|
|
197
|
+
|
|
188
198
|
/** The call is no longer ringing for this person — answered, declined, missed or ended. */
|
|
189
199
|
export function hideRing(id?: string): void {
|
|
190
200
|
if (id && shownId && id !== shownId) return;
|