whipdesk 0.0.9 → 0.1.2
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 +193 -0
- package/dist/agent.cjs +200 -27
- package/dist/mobile-web/assets/index-C_O8Rc7r.css +1 -0
- package/dist/mobile-web/assets/index-_PAhuG6R.js +2 -0
- package/dist/mobile-web/assets/loading-whip-anim-DmlnYIJ-.gif +0 -0
- package/dist/mobile-web/firebase-messaging-sw.js +34 -26
- package/dist/mobile-web/index.html +2 -2
- package/package.json +4 -3
- package/dist/mobile-web/assets/index-C1gDHBib.css +0 -1
- package/dist/mobile-web/assets/index-YSgNjLfG.js +0 -2
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# WhipDesk
|
|
2
|
+
|
|
3
|
+
> Control your desktop — and the AI coding agents running on it — from any phone browser.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/BinaryBananaLLC/WhipDesk/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/BinaryBananaLLC/WhipDesk/releases/latest)
|
|
7
|
+
[](https://scorecard.dev/viewer/?uri=github.com/BinaryBananaLLC/WhipDesk)
|
|
8
|
+
[](https://github.com/BinaryBananaLLC/WhipDesk/blob/main/LICENSE)
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
WhipDesk turns any mobile browser into a remote control for your dev machine. See the screen,
|
|
12
|
+
move the mouse, type, paste prompts into your AI tools, and get a push notification the moment a
|
|
13
|
+
long-running build or agent job finishes — no app store install, no kernel extension, no agent
|
|
14
|
+
running on your phone.
|
|
15
|
+
|
|
16
|
+
Because it's **screen-level**, it works with *every* AI agent — Claude Code and Codex in a
|
|
17
|
+
terminal, Copilot Chat inside VS Code, Cursor, a browser tab running tests — with **no wrappers
|
|
18
|
+
and no hooks required**: you never change how you launch your tools. CLI-wrapper apps only see
|
|
19
|
+
the one agent they wrap; WhipDesk sees your actual desktop.
|
|
20
|
+
|
|
21
|
+
It's peer-to-peer: an encrypted WebRTC connection that talks **directly** between phone and
|
|
22
|
+
desktop, on your LAN or across the internet. The cloud only brokers the initial handshake — your
|
|
23
|
+
screen and keystrokes never flow through anyone else's server.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- **Live screen, tuned for mobile data** — direct H.264, hardware-encoded. When you zoom, the
|
|
28
|
+
host **re-crops the encode to just your phone's viewport**, so a magnified terminal costs a
|
|
29
|
+
fraction of the bandwidth of streaming the whole desktop — full-desktop streamers (RustDesk,
|
|
30
|
+
Parsec, Chrome Remote Desktop) always ship every pixel. A quality ladder steps the bitrate
|
|
31
|
+
down automatically on a lossy cellular link, and encoding pauses entirely while your phone's
|
|
32
|
+
screen is off.
|
|
33
|
+
- **AI-agent monitoring (auto-whips)** — the host detects running agents (Claude Code, Codex,
|
|
34
|
+
Gemini CLI, Aider, Copilot — including Copilot Chat inside VS Code — opencode, Cursor, Amp) by
|
|
35
|
+
observing processes and transcripts, and pings your phone the moment one stops working: waiting
|
|
36
|
+
on you, finished, or crashed. Zero config; optional [agent-native hooks](https://github.com/BinaryBananaLLC/WhipDesk/blob/main/docs/HOOKS.md) make the
|
|
37
|
+
alert instant.
|
|
38
|
+
- **Full input** — mouse, touch, and keyboard injected into the real OS; built for poking at
|
|
39
|
+
AI agents and CLIs from your couch.
|
|
40
|
+
- **Job-done notifications** — a token-authenticated webhook (`POST /api/notify`) or an opt-in
|
|
41
|
+
file watcher fires a notification when a slow task completes; optional background push to a
|
|
42
|
+
closed PWA via FCM.
|
|
43
|
+
- **Private by design** — DTLS-encrypted P2P media, a PIN challenge on **every** connection (the
|
|
44
|
+
PIN itself never crosses the wire), a pairing token underneath, and persistent brute-force
|
|
45
|
+
lockout. Secrets stay on your machine. See [SECURITY.md](https://github.com/BinaryBananaLLC/WhipDesk/blob/main/SECURITY.md) for the threat model.
|
|
46
|
+
- **No account needed on LAN** — cloud (remote access, device dashboard) is strictly opt-in.
|
|
47
|
+
|
|
48
|
+
## Quick start (macOS)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
npm run dev # builds the web controller, then starts the desktop agent
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The agent prints a `http://<lan-ip>:8787/#t=<token>` URL and a QR code. Open it on your phone
|
|
56
|
+
(same Wi-Fi), then grant the launching terminal app **Screen Recording** and **Accessibility**
|
|
57
|
+
in System Settings → Privacy & Security, and restart it. Without those, frames show only the
|
|
58
|
+
wallpaper and input does nothing.
|
|
59
|
+
|
|
60
|
+
Fire a test notification:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run notify -- "Build done" "tsc finished with 0 errors"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> Tested on macOS (Apple Silicon). The capture/input stack is cross-platform, but Windows and
|
|
67
|
+
> Linux hosts aren't verified yet.
|
|
68
|
+
|
|
69
|
+
## Install a prebuilt agent
|
|
70
|
+
|
|
71
|
+
You don't have to build from source. Every [release](https://github.com/BinaryBananaLLC/WhipDesk/releases/latest)
|
|
72
|
+
ships two ways to run the agent — both built by GitHub Actions straight from the tagged source,
|
|
73
|
+
with [verifiable build provenance](https://github.com/BinaryBananaLLC/WhipDesk/blob/main/docs/VERIFYING-DOWNLOADS.md):
|
|
74
|
+
|
|
75
|
+
**Homebrew (macOS).**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
brew install --cask BinaryBananaLLC/whipdesk/whipdesk
|
|
79
|
+
whipdesk
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Self-contained download (no Node needed).** Grab the package for your OS:
|
|
83
|
+
|
|
84
|
+
| OS | Asset | Notes |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| macOS | `whipdesk-<ver>-macos-arm64.pkg` / `-x64.pkg` | Signed with a Developer ID & **notarized** — installs `whipdesk` to your PATH. |
|
|
87
|
+
| Windows | `whipdesk-<ver>-windows-x64.zip` | Unzip and run `whipdesk.exe`. SmartScreen: **More info → Run anyway** (see verification below). |
|
|
88
|
+
| Linux | `whipdesk-<ver>-linux-x64.tar.gz` | Extract and run `./whipdesk` (needs X11). |
|
|
89
|
+
|
|
90
|
+
**npm (if you already have Node ≥ 20):**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install -g whipdesk
|
|
94
|
+
whipdesk
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The npm package is published with [npm provenance](https://docs.npmjs.com/generating-provenance-statements)
|
|
98
|
+
(the verified badge on npmjs.com links back to the exact build).
|
|
99
|
+
|
|
100
|
+
Prebuilt agents keep their pairing/PIN state in `~/.whipdesk`, so updates don't re-pair you.
|
|
101
|
+
**Always verify a download before running it** — see [docs/VERIFYING-DOWNLOADS.md](https://github.com/BinaryBananaLLC/WhipDesk/blob/main/docs/VERIFYING-DOWNLOADS.md).
|
|
102
|
+
|
|
103
|
+
## Setup & permissions (troubleshooting)
|
|
104
|
+
|
|
105
|
+
The agent prints a short reminder for your OS at startup. If the screen shows only your wallpaper,
|
|
106
|
+
or the mouse/keyboard don't respond, it's almost always an OS permission — here's the fix per
|
|
107
|
+
platform:
|
|
108
|
+
|
|
109
|
+
**macOS** — grant the app that *launched* the agent (Terminal, iTerm, or VS Code), not "node":
|
|
110
|
+
|
|
111
|
+
1. **Screen Recording** — System Settings → Privacy & Security → Screen Recording → enable the app.
|
|
112
|
+
Without it, frames show only the wallpaper.
|
|
113
|
+
2. **Accessibility** — System Settings → Privacy & Security → Accessibility → enable the app.
|
|
114
|
+
Without it, mouse and keyboard input do nothing.
|
|
115
|
+
3. **Fully quit and reopen** that app (a plain window-close isn't enough), then run it again.
|
|
116
|
+
|
|
117
|
+
Shortcut to the right pane: `open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"`
|
|
118
|
+
|
|
119
|
+
**Windows** — capture and input work out of the box. If the screen is black or clicks are ignored
|
|
120
|
+
on an elevated window (a UAC prompt or an app "Run as administrator"), relaunch your terminal via
|
|
121
|
+
**Run as administrator** so the agent can see and drive those windows.
|
|
122
|
+
|
|
123
|
+
**Linux** — X11 sessions work out of the box. On **Wayland**, screen capture is gated behind the
|
|
124
|
+
desktop's screen-share portal: install/enable `xdg-desktop-portal` (plus your compositor's backend,
|
|
125
|
+
e.g. `xdg-desktop-portal-wlr` or `-gnome`), or log in under an **X11/Xorg** session instead of
|
|
126
|
+
Wayland. Input injection also needs access to `/dev/uinput` on some setups.
|
|
127
|
+
|
|
128
|
+
If capture is genuinely blocked, the agent also logs step-by-step help and pushes a
|
|
129
|
+
"Screen capture blocked" alert to your phone.
|
|
130
|
+
|
|
131
|
+
## How it works
|
|
132
|
+
|
|
133
|
+
A **desktop agent** (Node) captures the screen, injects input, and serves the **web controller**
|
|
134
|
+
(a framework-light vanilla-TS PWA). They speak one message contract
|
|
135
|
+
([`packages/protocol`](https://github.com/BinaryBananaLLC/WhipDesk/blob/main/packages/protocol)) over WebRTC — one DataChannel for control plus H.264
|
|
136
|
+
video tracks, all DTLS-encrypted — with two ways to broker the handshake:
|
|
137
|
+
|
|
138
|
+
- **LAN** — the agent's own WebSocket swaps the SDP offer/answer; the media flows host-to-host
|
|
139
|
+
on your network, touching nothing else.
|
|
140
|
+
- **Remote** — Firebase Realtime Database swaps the SDP; STUN connects directly, with
|
|
141
|
+
ephemeral-credential TURN as a last-resort relay.
|
|
142
|
+
|
|
143
|
+
Every session goes through the same gate: pairing token → PIN challenge → only then does the
|
|
144
|
+
screen start. Pointer coordinates travel normalized to `[0,1]`, so control is resolution- and
|
|
145
|
+
Retina-independent. See [docs/ARCHITECTURE.md](https://github.com/BinaryBananaLLC/WhipDesk/blob/main/docs/ARCHITECTURE.md) for the design seams.
|
|
146
|
+
|
|
147
|
+
## Cloud (optional)
|
|
148
|
+
|
|
149
|
+
LAN needs nothing. Remote access and the device dashboard run on the **hosted WhipDesk.com
|
|
150
|
+
backend, which is baked into this repo** (`apps/desktop-agent/src/cloud/config.ts`) — so it just
|
|
151
|
+
works out of the box. On startup the agent asks whether to enable secure cloud discovery; answer
|
|
152
|
+
**No** to stay strictly LAN-only, and nothing ever touches Firebase.
|
|
153
|
+
|
|
154
|
+
Those baked-in values are a Firebase **web** config — public by design, the same ones whipdesk.com
|
|
155
|
+
serves in its browser bundle. They are **not** secrets: there's no service-account key, cloud is
|
|
156
|
+
opt-in, and every read/write is locked to your own account by the Firestore/RTDB rules. The agent
|
|
157
|
+
signs in as the real user via passwordless email-link — the same account as the website.
|
|
158
|
+
|
|
159
|
+
**Prefer your own backend?** Point the agent at your own Firebase project + TURN by dropping a web
|
|
160
|
+
config in `.whipdesk/firebase.json` (gitignored) — that file is the only override, and it
|
|
161
|
+
replaces the baked-in default.
|
|
162
|
+
|
|
163
|
+
## Project layout
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
packages/protocol/ Types-only wire contract shared by both apps
|
|
167
|
+
apps/desktop-agent/ Node host: capture, input, server, watchers, cloud
|
|
168
|
+
apps/mobile-web/ Vite PWA controller, served by the agent
|
|
169
|
+
scripts/ notify.mjs + smoke-test harnesses
|
|
170
|
+
docs/ARCHITECTURE.md Design rationale
|
|
171
|
+
docs/HOOKS.md Optional agent-native hooks for instant monitoring alerts
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Contributing
|
|
175
|
+
|
|
176
|
+
PRs welcome. Read [AGENTS.md](https://github.com/BinaryBananaLLC/WhipDesk/blob/main/AGENTS.md) first — it's the operating contract for both humans and
|
|
177
|
+
AI coding agents (where each change goes, the wire-contract-first rule, how to verify). In short:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npm run typecheck # tsc --noEmit across workspaces
|
|
181
|
+
npm run test # node --test (auth handshake, pin, monitor states, crypto, protocol contract)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Security
|
|
185
|
+
|
|
186
|
+
Found a vulnerability? Please open a [security advisory](https://github.com/BinaryBananaLLC/WhipDesk/security/advisories/new)
|
|
187
|
+
rather than a public issue. Good-faith security research is welcome.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
[GNU AGPL-3.0](https://github.com/BinaryBananaLLC/WhipDesk/blob/main/LICENSE). You're free to run, study, modify, and share WhipDesk. If you offer a
|
|
192
|
+
modified version as a network service, the AGPL requires you to publish your source under the
|
|
193
|
+
same license. For commercial licensing, contact BinaryBanana LLC.
|
package/dist/agent.cjs
CHANGED
|
@@ -18909,7 +18909,7 @@ var require_view = __commonJS({
|
|
|
18909
18909
|
var dirname3 = path.dirname;
|
|
18910
18910
|
var basename2 = path.basename;
|
|
18911
18911
|
var extname = path.extname;
|
|
18912
|
-
var
|
|
18912
|
+
var join11 = path.join;
|
|
18913
18913
|
var resolve = path.resolve;
|
|
18914
18914
|
module2.exports = View;
|
|
18915
18915
|
function View(name, options) {
|
|
@@ -18971,12 +18971,12 @@ var require_view = __commonJS({
|
|
|
18971
18971
|
};
|
|
18972
18972
|
View.prototype.resolve = function resolve2(dir, file) {
|
|
18973
18973
|
var ext = this.ext;
|
|
18974
|
-
var path2 =
|
|
18974
|
+
var path2 = join11(dir, file);
|
|
18975
18975
|
var stat2 = tryStat(path2);
|
|
18976
18976
|
if (stat2 && stat2.isFile()) {
|
|
18977
18977
|
return path2;
|
|
18978
18978
|
}
|
|
18979
|
-
path2 =
|
|
18979
|
+
path2 = join11(dir, basename2(file, ext), "index" + ext);
|
|
18980
18980
|
stat2 = tryStat(path2);
|
|
18981
18981
|
if (stat2 && stat2.isFile()) {
|
|
18982
18982
|
return path2;
|
|
@@ -22802,7 +22802,7 @@ var require_send = __commonJS({
|
|
|
22802
22802
|
var Stream = require("stream");
|
|
22803
22803
|
var util = require("util");
|
|
22804
22804
|
var extname = path.extname;
|
|
22805
|
-
var
|
|
22805
|
+
var join11 = path.join;
|
|
22806
22806
|
var normalize = path.normalize;
|
|
22807
22807
|
var resolve = path.resolve;
|
|
22808
22808
|
var sep2 = path.sep;
|
|
@@ -22974,7 +22974,7 @@ var require_send = __commonJS({
|
|
|
22974
22974
|
return res;
|
|
22975
22975
|
}
|
|
22976
22976
|
parts = path2.split(sep2);
|
|
22977
|
-
path2 = normalize(
|
|
22977
|
+
path2 = normalize(join11(root, path2));
|
|
22978
22978
|
} else {
|
|
22979
22979
|
if (UP_PATH_REGEXP.test(path2)) {
|
|
22980
22980
|
debug('malicious path "%s"', path2);
|
|
@@ -23107,7 +23107,7 @@ var require_send = __commonJS({
|
|
|
23107
23107
|
if (err) return self.onStatError(err);
|
|
23108
23108
|
return self.error(404);
|
|
23109
23109
|
}
|
|
23110
|
-
var p =
|
|
23110
|
+
var p = join11(path2, self._index[i]);
|
|
23111
23111
|
debug('stat "%s"', p);
|
|
23112
23112
|
fs.stat(p, function(err2, stat2) {
|
|
23113
23113
|
if (err2) return next(err2);
|
|
@@ -27693,7 +27693,7 @@ function isPackaged() {
|
|
|
27693
27693
|
}
|
|
27694
27694
|
|
|
27695
27695
|
// src/version.ts
|
|
27696
|
-
var AGENT_VERSION = "0.
|
|
27696
|
+
var AGENT_VERSION = "0.1.2";
|
|
27697
27697
|
|
|
27698
27698
|
// src/config.ts
|
|
27699
27699
|
var here = (0, import_node_path2.dirname)((0, import_node_url2.fileURLToPath)(__whipdesk_meta_url));
|
|
@@ -28650,6 +28650,13 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28650
28650
|
spawnAt = 0;
|
|
28651
28651
|
lastPacketAt = 0;
|
|
28652
28652
|
everGotPacket = false;
|
|
28653
|
+
/** Whether the CURRENT spawn has delivered any packet — distinguishes a capture that "went
|
|
28654
|
+
* silent" from one that never started (the avfoundation zero-frame deadlock) in stall logs. */
|
|
28655
|
+
spawnGotPacket = false;
|
|
28656
|
+
/** Consecutive stall-restarts whose spawn never produced a frame (resets on any packet). */
|
|
28657
|
+
framelessRestarts = 0;
|
|
28658
|
+
/** Resolves the restart loop's bounded wait as soon as the current spawn's first packet lands. */
|
|
28659
|
+
firstPacketWaiter = null;
|
|
28653
28660
|
deadRestarts = 0;
|
|
28654
28661
|
erroredOut = false;
|
|
28655
28662
|
stopped = false;
|
|
@@ -28674,7 +28681,7 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28674
28681
|
this.activeListener = cb;
|
|
28675
28682
|
}
|
|
28676
28683
|
async start() {
|
|
28677
|
-
await this.
|
|
28684
|
+
await this.restart();
|
|
28678
28685
|
}
|
|
28679
28686
|
/** Change the zoom crop or target display. A no-op when nothing changed, so a redundant viewport
|
|
28680
28687
|
* echo never restarts ffmpeg. If a restart is already running (the controller settled a new zoom
|
|
@@ -28702,16 +28709,28 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28702
28709
|
return false;
|
|
28703
28710
|
}
|
|
28704
28711
|
const ov = !isFull(cfg.crop) && cfg.overview ? cfg.overview : null;
|
|
28705
|
-
let sock;
|
|
28712
|
+
let sock = null;
|
|
28706
28713
|
let sockOv = null;
|
|
28707
28714
|
try {
|
|
28708
28715
|
sock = await bindUdp();
|
|
28709
|
-
if (ov)
|
|
28716
|
+
if (ov) {
|
|
28717
|
+
sockOv = await bindUdp();
|
|
28718
|
+
for (let tries = 0; tries < 4; tries++) {
|
|
28719
|
+
const p = sock.address().port;
|
|
28720
|
+
const q = sockOv.address().port;
|
|
28721
|
+
if (Math.abs(p - q) > 1) break;
|
|
28722
|
+
const again = await bindUdp();
|
|
28723
|
+
closeQuietly(sockOv);
|
|
28724
|
+
sockOv = again;
|
|
28725
|
+
}
|
|
28726
|
+
}
|
|
28710
28727
|
} catch {
|
|
28728
|
+
if (sock) closeQuietly(sock);
|
|
28711
28729
|
if (sockOv) closeQuietly(sockOv);
|
|
28712
28730
|
this.fail();
|
|
28713
28731
|
return false;
|
|
28714
28732
|
}
|
|
28733
|
+
if (!sock) return false;
|
|
28715
28734
|
if (this.stopped) {
|
|
28716
28735
|
closeQuietly(sock);
|
|
28717
28736
|
if (sockOv) closeQuietly(sockOv);
|
|
@@ -28731,6 +28750,12 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28731
28750
|
"yuv420p",
|
|
28732
28751
|
"-g",
|
|
28733
28752
|
String(gop),
|
|
28753
|
+
// Also key on WALL-CLOCK time, not just frame count: with VFR a static screen yields few output
|
|
28754
|
+
// frames, so a frames-based GOP can leave a lost IDR unrepaired "until something moves". Forcing
|
|
28755
|
+
// a keyframe ~1s (by PTS, which advances via -use_wallclock_as_timestamps) guarantees the client
|
|
28756
|
+
// can always re-sync within ~1s even with no PLI and a frozen desktop.
|
|
28757
|
+
"-force_key_frames",
|
|
28758
|
+
"expr:gte(t,n_forced*1)",
|
|
28734
28759
|
"-b:v",
|
|
28735
28760
|
kbpsArg(kbps),
|
|
28736
28761
|
"-maxrate",
|
|
@@ -28774,14 +28799,18 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28774
28799
|
this.appliedCfg = cfg;
|
|
28775
28800
|
this.spawnAt = Date.now();
|
|
28776
28801
|
this.lastPacketAt = Date.now();
|
|
28802
|
+
this.spawnGotPacket = false;
|
|
28777
28803
|
const spawnCrop = cfg.crop;
|
|
28778
28804
|
let firstPacket = true;
|
|
28779
28805
|
sock.on("message", (msg) => {
|
|
28780
28806
|
this.lastPacketAt = Date.now();
|
|
28781
28807
|
this.everGotPacket = true;
|
|
28808
|
+
this.spawnGotPacket = true;
|
|
28782
28809
|
this.deadRestarts = 0;
|
|
28783
28810
|
if (firstPacket) {
|
|
28784
28811
|
firstPacket = false;
|
|
28812
|
+
this.framelessRestarts = 0;
|
|
28813
|
+
this.firstPacketWaiter?.();
|
|
28785
28814
|
this.activeListener?.(spawnCrop);
|
|
28786
28815
|
}
|
|
28787
28816
|
this.listener?.(msg);
|
|
@@ -28801,9 +28830,13 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28801
28830
|
}
|
|
28802
28831
|
});
|
|
28803
28832
|
proc.on("error", (e) => log.warn("video encoder process error:", e.message));
|
|
28833
|
+
proc.on("exit", (code, sig) => {
|
|
28834
|
+
if (this.proc === proc && !this.stopped) log.debug(`ffmpeg exited unexpectedly (${code ?? sig ?? "?"})`);
|
|
28835
|
+
});
|
|
28804
28836
|
this.startHealthMonitor();
|
|
28837
|
+
const cropDesc = isFull(cfg.crop) ? "full desktop" : `zoomed ${cfg.crop.x.toFixed(2)},${cfg.crop.y.toFixed(2)} ${cfg.crop.w.toFixed(2)}x${cfg.crop.h.toFixed(2)}`;
|
|
28805
28838
|
log.debug(
|
|
28806
|
-
`video: H.264 capture started (${enc}, ${kbpsArg(cfg.kbps)}@${cfg.fps}fps, ${
|
|
28839
|
+
`video: H.264 capture started (${enc}, ${kbpsArg(cfg.kbps)}@${cfg.fps}fps, ${cropDesc}${ov ? " + overview" : ""})`
|
|
28807
28840
|
);
|
|
28808
28841
|
return true;
|
|
28809
28842
|
}
|
|
@@ -28817,8 +28850,12 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28817
28850
|
if (this.stopped || this.restarting || !this.proc) return;
|
|
28818
28851
|
const now = Date.now();
|
|
28819
28852
|
if (this.everGotPacket) {
|
|
28820
|
-
|
|
28821
|
-
|
|
28853
|
+
const stallMs = this.spawnGotPacket ? _ScreenCaptureSession.STALL_MS : Math.min(4e3 + this.framelessRestarts * 1500, 1e4);
|
|
28854
|
+
if (now - this.lastPacketAt > stallMs) {
|
|
28855
|
+
if (!this.spawnGotPacket) this.framelessRestarts += 1;
|
|
28856
|
+
log.debug(
|
|
28857
|
+
`screen capture stalled \u2014 restarting (${this.spawnGotPacket ? "went silent" : `spawn never produced a frame after ${Math.round(stallMs / 1e3)}s`})`
|
|
28858
|
+
);
|
|
28822
28859
|
void this.restart();
|
|
28823
28860
|
}
|
|
28824
28861
|
return;
|
|
@@ -28838,13 +28875,58 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28838
28875
|
this.restarting = true;
|
|
28839
28876
|
try {
|
|
28840
28877
|
do {
|
|
28841
|
-
this.
|
|
28878
|
+
await this.killAndWait();
|
|
28842
28879
|
if (!await this.spawn()) break;
|
|
28880
|
+
await this.waitForFirstPacket(3e3);
|
|
28843
28881
|
} while (!this.stopped && this.appliedCfg !== null && !sameConfig(this.cfg, this.appliedCfg));
|
|
28844
28882
|
} finally {
|
|
28845
28883
|
this.restarting = false;
|
|
28846
28884
|
}
|
|
28847
28885
|
}
|
|
28886
|
+
/**
|
|
28887
|
+
* Kill the current ffmpeg and WAIT until it has actually EXITED — plus a short macOS beat for
|
|
28888
|
+
* WindowServer to release its screen-capture session — before the caller spawns the next one.
|
|
28889
|
+
*
|
|
28890
|
+
* kill() alone is fire-and-forget: signal delivery and the OS-side capture teardown are both
|
|
28891
|
+
* asynchronous, so under re-crop churn (pan swipe after swipe) the next ffmpeg starts while the
|
|
28892
|
+
* old capture session is still registered — the documented avfoundation deadlock where BOTH
|
|
28893
|
+
* captures yield zero frames. Worse, the health monitor's recovery restart repeats the same
|
|
28894
|
+
* unserialized kill→spawn every 5s, re-triggering the overlap each time: the observed
|
|
28895
|
+
* "stalled — restarting" loop with no "crop live" ever following, dead for tens of seconds.
|
|
28896
|
+
* Serializing exit→settle→spawn removes the overlap entirely, at ~150ms per re-crop.
|
|
28897
|
+
*/
|
|
28898
|
+
async killAndWait() {
|
|
28899
|
+
const proc = this.proc;
|
|
28900
|
+
const exited = proc && proc.exitCode === null && proc.signalCode === null ? new Promise((resolve) => {
|
|
28901
|
+
const cap = setTimeout(resolve, 2500);
|
|
28902
|
+
cap.unref?.();
|
|
28903
|
+
proc.once("exit", () => {
|
|
28904
|
+
clearTimeout(cap);
|
|
28905
|
+
resolve();
|
|
28906
|
+
});
|
|
28907
|
+
}) : null;
|
|
28908
|
+
this.kill();
|
|
28909
|
+
if (exited) {
|
|
28910
|
+
await exited;
|
|
28911
|
+
if (process.platform === "darwin") await new Promise((r) => setTimeout(r, 150));
|
|
28912
|
+
}
|
|
28913
|
+
}
|
|
28914
|
+
/** Bounded wait for the current spawn's first RTP packet (resolves immediately if it already
|
|
28915
|
+
* produced, on stop, or at the cap). See the restart loop for why replacing a capture that
|
|
28916
|
+
* hasn't produced yet is dangerous on macOS. */
|
|
28917
|
+
waitForFirstPacket(capMs) {
|
|
28918
|
+
if (this.spawnGotPacket || this.stopped) return Promise.resolve();
|
|
28919
|
+
return new Promise((resolve) => {
|
|
28920
|
+
const done = () => {
|
|
28921
|
+
clearTimeout(cap);
|
|
28922
|
+
if (this.firstPacketWaiter === done) this.firstPacketWaiter = null;
|
|
28923
|
+
resolve();
|
|
28924
|
+
};
|
|
28925
|
+
const cap = setTimeout(done, capMs);
|
|
28926
|
+
cap.unref?.();
|
|
28927
|
+
this.firstPacketWaiter = done;
|
|
28928
|
+
});
|
|
28929
|
+
}
|
|
28848
28930
|
fail() {
|
|
28849
28931
|
if (this.erroredOut) return;
|
|
28850
28932
|
this.erroredOut = true;
|
|
@@ -28858,11 +28940,22 @@ var ScreenCaptureSession = class _ScreenCaptureSession {
|
|
|
28858
28940
|
}
|
|
28859
28941
|
const proc = this.proc;
|
|
28860
28942
|
this.proc = null;
|
|
28861
|
-
if (proc) {
|
|
28943
|
+
if (proc && proc.exitCode === null && proc.signalCode === null) {
|
|
28862
28944
|
try {
|
|
28863
|
-
proc.kill("
|
|
28945
|
+
proc.kill("SIGTERM");
|
|
28864
28946
|
} catch {
|
|
28865
28947
|
}
|
|
28948
|
+
const hardKill = setTimeout(
|
|
28949
|
+
() => {
|
|
28950
|
+
try {
|
|
28951
|
+
proc.kill("SIGKILL");
|
|
28952
|
+
} catch {
|
|
28953
|
+
}
|
|
28954
|
+
},
|
|
28955
|
+
this.spawnGotPacket ? 1500 : 700
|
|
28956
|
+
);
|
|
28957
|
+
hardKill.unref?.();
|
|
28958
|
+
proc.once("exit", () => clearTimeout(hardKill));
|
|
28866
28959
|
}
|
|
28867
28960
|
if (this.sock) {
|
|
28868
28961
|
closeQuietly(this.sock);
|
|
@@ -28989,7 +29082,15 @@ var VideoHub = class {
|
|
|
28989
29082
|
if (this.opts.onCropActive) session.onActive((crop) => this.opts.onCropActive(crop));
|
|
28990
29083
|
await session.start();
|
|
28991
29084
|
if (this.paused || this.sinks.size === 0 && this.overviewSinks.size === 0) session.stop();
|
|
28992
|
-
else
|
|
29085
|
+
else {
|
|
29086
|
+
this.session = session;
|
|
29087
|
+
void session.reconfigure({
|
|
29088
|
+
displayIndex: this.opts.displayIndex(),
|
|
29089
|
+
crop: this.crop,
|
|
29090
|
+
kbps: this.kbps,
|
|
29091
|
+
fps: this.fps
|
|
29092
|
+
});
|
|
29093
|
+
}
|
|
28993
29094
|
})().finally(() => this.starting = null);
|
|
28994
29095
|
return this.starting;
|
|
28995
29096
|
}
|
|
@@ -29125,6 +29226,9 @@ async function answerWebRtcOffer(ctx, offerSdp, onClosed, opts = {}) {
|
|
|
29125
29226
|
pc.connectionStateChange.subscribe((s) => {
|
|
29126
29227
|
if (s === "failed" || s === "closed" || s === "disconnected") teardown();
|
|
29127
29228
|
});
|
|
29229
|
+
pc.iceConnectionStateChange.subscribe((s) => {
|
|
29230
|
+
if (s === "failed" || s === "closed" || s === "disconnected") teardown();
|
|
29231
|
+
});
|
|
29128
29232
|
await pc.setRemoteDescription({ type: "offer", sdp: offerSdp });
|
|
29129
29233
|
if (offerVideoCount > 0 && canSendVideo && ctx.video) {
|
|
29130
29234
|
for (let i = 0; i < offerVideoCount; i++) {
|
|
@@ -29345,8 +29449,8 @@ function printSetupReminder() {
|
|
|
29345
29449
|
|
|
29346
29450
|
// src/server.ts
|
|
29347
29451
|
var import_node_http = require("node:http");
|
|
29348
|
-
var
|
|
29349
|
-
var
|
|
29452
|
+
var import_node_fs10 = require("node:fs");
|
|
29453
|
+
var import_node_path12 = require("node:path");
|
|
29350
29454
|
var import_node_url3 = require("node:url");
|
|
29351
29455
|
var import_express = __toESM(require_express2(), 1);
|
|
29352
29456
|
|
|
@@ -30073,9 +30177,9 @@ var PinGuard = class _PinGuard {
|
|
|
30073
30177
|
get salt() {
|
|
30074
30178
|
return this.record?.salt ?? "";
|
|
30075
30179
|
}
|
|
30076
|
-
/** Persist a new PIN (>=
|
|
30180
|
+
/** Persist a new PIN (>= 6 chars). Stores only the stretched key. */
|
|
30077
30181
|
setPin(pin) {
|
|
30078
|
-
if (pin.length <
|
|
30182
|
+
if (pin.length < 6) throw new Error("PIN must be at least 6 characters");
|
|
30079
30183
|
const salt = (0, import_node_crypto5.randomBytes)(16).toString("hex");
|
|
30080
30184
|
const key = stretch(pin, salt, ITERATIONS);
|
|
30081
30185
|
this.record = { salt, iterations: ITERATIONS, key };
|
|
@@ -31036,6 +31140,30 @@ function saveAlwaysAgents(stateDir2, agents) {
|
|
|
31036
31140
|
}
|
|
31037
31141
|
}
|
|
31038
31142
|
|
|
31143
|
+
// src/timer-store.ts
|
|
31144
|
+
var import_node_fs9 = require("node:fs");
|
|
31145
|
+
var import_node_path11 = require("node:path");
|
|
31146
|
+
var FILE2 = "timers.json";
|
|
31147
|
+
function loadTimers(stateDir2) {
|
|
31148
|
+
try {
|
|
31149
|
+
const raw = (0, import_node_fs9.readFileSync)((0, import_node_path11.join)(stateDir2, FILE2), "utf8");
|
|
31150
|
+
const parsed = JSON.parse(raw);
|
|
31151
|
+
if (!Array.isArray(parsed.timers)) return [];
|
|
31152
|
+
return parsed.timers.filter(
|
|
31153
|
+
(t) => !!t && typeof t.id === "string" && typeof t.fireAtMs === "number"
|
|
31154
|
+
);
|
|
31155
|
+
} catch {
|
|
31156
|
+
return [];
|
|
31157
|
+
}
|
|
31158
|
+
}
|
|
31159
|
+
function saveTimers(stateDir2, timers) {
|
|
31160
|
+
try {
|
|
31161
|
+
(0, import_node_fs9.mkdirSync)(stateDir2, { recursive: true });
|
|
31162
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path11.join)(stateDir2, FILE2), JSON.stringify({ timers }), { mode: 384 });
|
|
31163
|
+
} catch {
|
|
31164
|
+
}
|
|
31165
|
+
}
|
|
31166
|
+
|
|
31039
31167
|
// src/util/update-check.ts
|
|
31040
31168
|
async function checkForUpdate(current) {
|
|
31041
31169
|
try {
|
|
@@ -31078,8 +31206,8 @@ function announceUpdate(latest, current, notify) {
|
|
|
31078
31206
|
}
|
|
31079
31207
|
|
|
31080
31208
|
// src/server.ts
|
|
31081
|
-
var here2 = (0,
|
|
31082
|
-
var webDist = isPackaged() ? (0,
|
|
31209
|
+
var here2 = (0, import_node_path12.dirname)((0, import_node_url3.fileURLToPath)(__whipdesk_meta_url));
|
|
31210
|
+
var webDist = isPackaged() ? (0, import_node_path12.join)(here2, "mobile-web") : (0, import_node_path12.join)(here2, "..", "..", "mobile-web", "dist");
|
|
31083
31211
|
async function startAgent() {
|
|
31084
31212
|
const config = loadConfig();
|
|
31085
31213
|
const capturer = new ScreenCapturer({ quality: config.quality, maxWidth: config.maxWidth });
|
|
@@ -31169,6 +31297,7 @@ async function startAgent() {
|
|
|
31169
31297
|
let captureFailing = false;
|
|
31170
31298
|
let viewport = { x: 0, y: 0, w: 1, h: 1 };
|
|
31171
31299
|
const timers = /* @__PURE__ */ new Map();
|
|
31300
|
+
const persistTimers = () => saveTimers(config.stateDir, [...timers.values()]);
|
|
31172
31301
|
const listTimers = () => [...timers.values()].map((t) => ({ id: t.id, label: t.label, fireAtMs: t.fireAtMs, hasAction: !!t.action }));
|
|
31173
31302
|
const broadcastTimers = () => {
|
|
31174
31303
|
const msg = { type: "timers", timers: listTimers() };
|
|
@@ -31190,13 +31319,38 @@ async function startAgent() {
|
|
|
31190
31319
|
const t = timers.get(id);
|
|
31191
31320
|
if (!t) return;
|
|
31192
31321
|
timers.delete(id);
|
|
31322
|
+
persistTimers();
|
|
31193
31323
|
broadcastTimers();
|
|
31194
31324
|
const a = t.action;
|
|
31195
31325
|
if (a) {
|
|
31196
31326
|
try {
|
|
31197
31327
|
log.info(`timer "${t.label || id}" firing${a.kind ? ` (${a.kind})` : ""}`);
|
|
31198
31328
|
if (typeof a.x === "number" && typeof a.y === "number") {
|
|
31199
|
-
|
|
31329
|
+
const pinnedId = t.displayId;
|
|
31330
|
+
let restoreInput = null;
|
|
31331
|
+
if (pinnedId !== void 0 && pinnedId !== activeDisplay) {
|
|
31332
|
+
const pinned = displays.find((d) => d.id === pinnedId);
|
|
31333
|
+
if (!pinned || pinned.width <= 0 || pinned.height <= 0) {
|
|
31334
|
+
throw new Error(`the display it was scheduled on ([${pinnedId}]) is no longer connected`);
|
|
31335
|
+
}
|
|
31336
|
+
input.setActiveDisplay({
|
|
31337
|
+
originX: pinned.originX,
|
|
31338
|
+
originY: pinned.originY,
|
|
31339
|
+
width: pinned.width,
|
|
31340
|
+
height: pinned.height
|
|
31341
|
+
});
|
|
31342
|
+
restoreInput = () => {
|
|
31343
|
+
const cur = displays.find((d) => d.id === activeDisplay);
|
|
31344
|
+
input.setActiveDisplay(
|
|
31345
|
+
cur && cur.width > 0 && cur.height > 0 ? { originX: cur.originX, originY: cur.originY, width: cur.width, height: cur.height } : null
|
|
31346
|
+
);
|
|
31347
|
+
};
|
|
31348
|
+
}
|
|
31349
|
+
try {
|
|
31350
|
+
await input.click(a.button ?? "left", false, a.x, a.y);
|
|
31351
|
+
} finally {
|
|
31352
|
+
restoreInput?.();
|
|
31353
|
+
}
|
|
31200
31354
|
if (a.kind === "key" || a.kind === "text") await delay(250);
|
|
31201
31355
|
}
|
|
31202
31356
|
if (a.kind === "key" && a.key) await input.keyTap(a.key);
|
|
@@ -31220,6 +31374,23 @@ async function startAgent() {
|
|
|
31220
31374
|
source: "timer"
|
|
31221
31375
|
});
|
|
31222
31376
|
};
|
|
31377
|
+
for (const t of loadTimers(config.stateDir)) {
|
|
31378
|
+
if (t.fireAtMs <= Date.now()) {
|
|
31379
|
+
hub.emit({
|
|
31380
|
+
title: t.label || "WhipDesk timer",
|
|
31381
|
+
body: "This timer came due while WhipDesk was not running \u2014 its action was NOT performed.",
|
|
31382
|
+
level: "error",
|
|
31383
|
+
source: "timer"
|
|
31384
|
+
});
|
|
31385
|
+
} else {
|
|
31386
|
+
timers.set(t.id, t);
|
|
31387
|
+
}
|
|
31388
|
+
}
|
|
31389
|
+
persistTimers();
|
|
31390
|
+
if (timers.size > 0) {
|
|
31391
|
+
ensureTimerTicker();
|
|
31392
|
+
log.info(`restored ${timers.size} pending timer${timers.size === 1 ? "" : "s"} from disk`);
|
|
31393
|
+
}
|
|
31223
31394
|
const captureOnce = async () => {
|
|
31224
31395
|
if (regionWatcher.count === 0) return "idle";
|
|
31225
31396
|
try {
|
|
@@ -31384,13 +31555,15 @@ async function startAgent() {
|
|
|
31384
31555
|
addTimer(msg) {
|
|
31385
31556
|
const fireInMs = Math.max(1e3, Math.min(msg.fireInMs, 7 * 24 * 36e5));
|
|
31386
31557
|
const fireAtMs = Date.now() + fireInMs;
|
|
31387
|
-
timers.set(msg.id, { id: msg.id, label: msg.label, fireAtMs, action: msg.action });
|
|
31558
|
+
timers.set(msg.id, { id: msg.id, label: msg.label, fireAtMs, action: msg.action, displayId: activeDisplay });
|
|
31559
|
+
persistTimers();
|
|
31388
31560
|
ensureTimerTicker();
|
|
31389
31561
|
broadcastTimers();
|
|
31390
31562
|
log.info(`timer "${msg.label}" in ${Math.round(fireInMs / 1e3)}s${msg.action ? ` (+${msg.action.kind})` : ""}`);
|
|
31391
31563
|
},
|
|
31392
31564
|
removeTimer(id) {
|
|
31393
31565
|
if (!timers.delete(id)) return;
|
|
31566
|
+
persistTimers();
|
|
31394
31567
|
broadcastTimers();
|
|
31395
31568
|
},
|
|
31396
31569
|
listTimers() {
|
|
@@ -31474,9 +31647,9 @@ async function startAgent() {
|
|
|
31474
31647
|
}
|
|
31475
31648
|
res.json({ ok: true });
|
|
31476
31649
|
});
|
|
31477
|
-
if ((0,
|
|
31650
|
+
if ((0, import_node_fs10.existsSync)(webDist)) {
|
|
31478
31651
|
app.use(import_express.default.static(webDist));
|
|
31479
|
-
app.get("/*splat", (_req, res) => res.sendFile((0,
|
|
31652
|
+
app.get("/*splat", (_req, res) => res.sendFile((0, import_node_path12.join)(webDist, "index.html")));
|
|
31480
31653
|
} else {
|
|
31481
31654
|
log.warn(`mobile-web build not found at ${webDist} \u2014 run "npm run build:web"`);
|
|
31482
31655
|
app.get(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg:#f4f6f9;--panel:#fff;--panel-2:#eef1f5;--border:#d6dce4;--text:#1a2330;--muted:#5f6b7a;--accent:#2f6fed;--accent-2:#1aa66b;--warn:#c77700;--error:#d83b3b;--shadow:#141e3229;--screen-bg:#1b2027}@media (prefers-color-scheme:dark){:root{--bg:#0b0e14;--panel:#151a23;--panel-2:#1d2430;--border:#2a3342;--text:#e6edf3;--muted:#8b98a9;--accent:#4ea1ff;--accent-2:#36d399;--warn:#f5a623;--error:#ff5c5c;--shadow:#00000080;--screen-bg:#000}}*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}html,body{background:var(--bg);height:100%;color:var(--text);overscroll-behavior:none;margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,system-ui,sans-serif;overflow:hidden}#app{-webkit-user-select:none;user-select:none;position:fixed;inset:0}input,textarea{-webkit-user-select:text;user-select:text}.wd-screen{background:var(--screen-bg);touch-action:none;width:100%;height:100%;display:block;position:absolute;inset:0}.wd-statusbar{top:calc(env(safe-area-inset-top,0px) + 8px);background:var(--panel);border:1px solid var(--border);max-width:calc(100% - 64px);min-height:38px;box-shadow:0 2px 10px var(--shadow);z-index:5;cursor:pointer;border-radius:10px;align-items:center;gap:8px;padding:6px 10px;font-size:12px;transition:max-width .25s,padding .25s;display:flex;position:absolute;left:8px}.wd-statusbar.collapsed{max-width:124px;padding:6px 8px;overflow:hidden}.wd-statusbar.collapsed .wd-status-text,.wd-statusbar.collapsed .wd-watchers{display:none}.wd-dot{background:var(--muted);border-radius:50%;flex:none;width:9px;height:9px}.wd-dot[data-status=connected]{background:var(--accent-2)}.wd-dot[data-status=connecting]{background:var(--warn)}.wd-dot[data-status=disconnected]{background:var(--error)}.wd-status-text{color:var(--text);white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;font-weight:600;overflow:hidden}.wd-watchers{color:var(--warn);white-space:nowrap;flex:none;font-weight:700}.wd-transport{letter-spacing:.4px;color:#fff;background:var(--accent-2);text-transform:uppercase;border-radius:6px;flex:none;padding:2px 6px;font-size:11px;font-weight:800}.wd-transport.hidden{display:none}.wd-transport[data-kind=turn]{color:#111;background:#e0a800}.wd-transport[data-kind=stun],.wd-transport[data-kind=direct]{background:var(--accent-2)}.wd-transport[data-kind=lan]{background:#3b82f6}.wd-bell{top:calc(env(safe-area-inset-top,0px) + 8px);z-index:6;border:1px solid var(--border);background:var(--panel);min-width:38px;min-height:38px;color:var(--text);box-shadow:0 2px 10px var(--shadow);cursor:pointer;border-radius:10px;place-items:center;padding:6px;display:grid;position:absolute;right:8px}.wd-badge{background:var(--accent);color:#fff;text-align:center;min-width:18px;height:18px;box-shadow:0 1px 4px var(--shadow);border-radius:9px;padding:0 5px;font-size:11px;font-weight:700;line-height:18px;position:absolute;top:-6px;right:-6px}.wd-ribbon{z-index:6;padding-bottom:env(safe-area-inset-bottom,0px);background:var(--panel);border-top:1px solid var(--border);box-shadow:0 -4px 16px var(--shadow);position:absolute;bottom:0;left:0;right:0}.wd-panel{flex-direction:column;display:flex}.wd-options{border-bottom:1px solid var(--border);padding:6px}.wd-pane{flex-wrap:wrap;justify-content:center;align-items:center;gap:5px;display:flex}.wd-pane-col{flex-direction:column;align-items:stretch;gap:8px}.wd-pane-single-row{scrollbar-width:none;flex-wrap:nowrap;justify-content:center;align-items:stretch;gap:4px;overflow-x:auto}.wd-pane-single-row::-webkit-scrollbar{display:none}.wd-pane-single-row .wd-group{flex:0 auto;gap:2px;min-width:0;padding:3px 4px}.wd-pane-single-row .wd-group-items{flex-wrap:nowrap;gap:3px}.wd-pane-single-row .wd-btn{min-width:0}.wd-pane-single-row .wd-btn.wd-icon-only{min-width:30px;padding:4px}.wd-pane-single-row .wd-btn.wd-icon-only svg{width:18px;height:18px}.wd-pane-single-row .wd-btn.wd-go{gap:4px;padding:6px 9px;font-size:12px}@media (min-width:390px){.wd-pane-single-row{gap:5px}.wd-pane-single-row .wd-group{padding:3px 6px}.wd-pane-single-row .wd-btn.wd-icon-only{min-width:36px;padding:5px}.wd-pane-single-row .wd-btn.wd-icon-only svg{width:20px;height:20px}.wd-pane-single-row .wd-btn.wd-go{padding:7px 12px;font-size:13px}}@media (min-width:560px){.wd-pane-single-row .wd-btn.wd-icon-only{min-width:42px;padding:6px}.wd-pane-single-row .wd-btn.wd-go{padding:8px 16px}}@media (min-width:820px){.wd-ribbon{border:1px solid var(--border);width:min(880px,94vw);box-shadow:0 10px 34px var(--shadow);border-radius:16px;padding-bottom:0;bottom:16px;left:50%;right:auto;transform:translate(-50%)}.wd-options{padding:5px 6px}.wd-btn{min-height:32px;font-size:12px}.wd-pane-single-row .wd-btn.wd-icon-only{min-width:32px;padding:4px}.wd-pane-single-row .wd-btn.wd-icon-only svg{width:17px;height:17px}.wd-pane-single-row .wd-btn.wd-go{padding:6px 12px;font-size:12px}.wd-group{padding:3px 6px}.wd-bell{min-width:32px;min-height:32px}.wd-statusbar{min-height:32px}}.wd-group{border:1px solid var(--border);background:var(--panel-2);border-radius:12px;flex-direction:column;align-items:stretch;gap:3px;padding:4px 6px;display:inline-flex}.wd-group-label{letter-spacing:.03em;text-transform:uppercase;color:var(--muted);white-space:nowrap;text-align:center;font-size:9px;font-weight:700}.wd-group-head{justify-content:space-between;align-items:center;gap:4px;display:flex}.wd-mode-toggle{border:1px solid var(--border);background:var(--panel);border-radius:999px;display:inline-flex;overflow:hidden}.wd-mode-btn{color:var(--muted);cursor:pointer;background:0 0;border:0;padding:4px 7px;font-size:10px;font-weight:700;line-height:1}.wd-mode-btn.on{background:var(--accent);color:#fff}.wd-group-items{flex-wrap:wrap;justify-content:center;align-items:center;gap:3px;display:flex}.wd-wrap{flex-wrap:wrap;justify-content:center;gap:6px;display:flex}.wd-btn-label{line-height:1}.wd-row{flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;display:flex}.wd-tabs{align-items:center;gap:4px;padding:5px 6px;display:flex}.wd-tab{min-height:48px;color:var(--muted);cursor:pointer;touch-action:manipulation;background:0 0;border:1px solid #0000;border-radius:10px;flex-direction:column;flex:1;align-items:center;gap:2px;padding:6px 4px;font-size:11px;font-weight:600;display:flex}.wd-tab .wd-tab-label{line-height:1}.wd-tab.on{background:var(--panel-2);color:var(--accent);border-color:var(--border)}.wd-collapse{border:1px solid var(--border);background:var(--panel);min-width:44px;min-height:48px;color:var(--text);cursor:pointer;border-radius:10px;flex:none;place-items:center;display:grid}.wd-btn{border:1px solid var(--border);background:var(--panel);min-width:40px;min-height:40px;color:var(--text);cursor:pointer;-webkit-user-select:none;user-select:none;touch-action:manipulation;white-space:nowrap;border-radius:10px;justify-content:center;align-items:center;gap:7px;padding:6px 10px;font-size:13px;font-weight:600;line-height:1;display:inline-flex}.wd-btn svg{flex:none}.wd-btn.wd-icon-only{min-width:40px;padding:6px}.wd-btn:active{background:var(--panel-2);transform:translateY(1px)}.wd-btn.on{border-color:var(--accent);color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}.wd-btn.wd-primary{background:var(--accent);border-color:var(--accent);color:#fff}.wd-btn.wd-go{background:var(--accent-2);border-color:var(--accent-2);color:#fff;padding:8px 14px}.wd-btn.wd-go:active{filter:brightness(.92);transform:translateY(1px)}.wd-seg{border:1px solid var(--border);border-radius:10px;display:inline-flex;overflow:hidden}.wd-seg-btn{background:var(--panel);min-height:44px;color:var(--muted);cursor:pointer;touch-action:manipulation;border:none;align-items:center;gap:6px;padding:8px 18px;font-size:15px;font-weight:600;display:inline-flex}.wd-seg-btn.on{background:var(--accent);color:#fff}.wd-zoom{text-align:center;font-variant-numeric:tabular-nums;min-width:52px;color:var(--text);font-weight:600}.wd-hint{text-align:center;color:var(--muted);flex-basis:100%;font-size:12px}.wd-type-input{resize:vertical;border:1px solid var(--border);background:var(--panel-2);width:100%;min-height:46px;color:var(--text);border-radius:10px;padding:10px;font-family:inherit;font-size:16px}.wd-keys-row{flex-wrap:wrap;justify-content:center;gap:6px;display:flex}.wd-type-actions{gap:6px;display:flex}.wd-type-actions .wd-btn{flex:1}.wd-monitor-list{flex-wrap:wrap;justify-content:center;gap:6px;display:flex}.wd-toasts{top:calc(env(safe-area-inset-top,0px) + 36px);z-index:9;pointer-events:none;flex-direction:column;gap:8px;display:flex;position:absolute;left:8px;right:8px}.wd-toast{background:var(--panel);border:1px solid var(--border);border-left-width:4px;border-radius:10px;flex-direction:column;gap:2px;padding:10px 12px;transition:opacity .3s,transform .3s;display:flex;box-shadow:0 6px 20px #0006}.wd-toast strong{font-size:14px}.wd-toast span{color:var(--muted);font-size:13px}.wd-toast.wd-success{border-left-color:var(--accent-2)}.wd-toast.wd-warning{border-left-color:var(--warn)}.wd-toast.wd-error{border-left-color:var(--error)}.wd-toast.wd-info{border-left-color:var(--accent)}.wd-toast.wd-hide{opacity:0;transform:translateY(-8px)}.hidden{display:none!important}.wd-sharpen{pointer-events:none;z-index:15;border:2.5px solid #ebf0f840;border-top-color:#ebf0f8e6;border-radius:50%;width:22px;height:22px;margin:-11px 0 0 -11px;animation:.9s linear infinite wd-sharpen-spin;position:fixed;top:50%;left:50%;box-shadow:0 0 0 4px #06090e59}@keyframes wd-sharpen-spin{to{transform:rotate(360deg)}}.wd-pin-overlay{z-index:20;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);background:#06090eeb;place-items:center;padding:20px;display:grid;position:absolute;inset:0}.wd-pin-card{background:var(--panel);border:1px solid var(--border);text-align:center;border-radius:14px;width:100%;max-width:320px;padding:24px}.wd-pin-whip{object-fit:contain;width:64px;height:64px;margin:0 auto 12px;display:block}.wd-pin-card h2{margin:0 0 8px;font-size:20px}.wd-pin-msg{color:var(--muted);margin:0 0 16px;font-size:14px}.wd-pin-msg.err{color:var(--error)}.wd-pin-input{border:1px solid var(--border);background:var(--panel-2);width:100%;color:var(--text);text-align:center;letter-spacing:6px;border-radius:10px;margin-bottom:14px;padding:14px;font-size:22px}.wd-pin-submit{background:var(--accent);color:#fff;cursor:pointer;touch-action:manipulation;border:none;border-radius:12px;width:100%;min-height:50px;font-size:17px;font-weight:700}.wd-pin-submit:active{filter:brightness(.92);transform:translateY(1px)}.wd-pin-back{color:var(--muted);cursor:pointer;background:0 0;border:none;margin:14px auto 0;padding:6px 10px;font-size:13px;font-weight:600;display:block}.wd-pin-back:active{color:var(--text)}.wd-connecting{z-index:18;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);background:#000000f5;place-items:center;padding:20px;display:grid;position:absolute;inset:0}.wd-connecting-card{text-align:center;width:100%;max-width:320px}.wd-connecting-anim{background:#000;border-radius:10px;width:180px;height:auto;margin:0 auto 16px;display:block}.wd-connecting-msg{color:#eef2f7;text-shadow:0 1px 2px #00000080;margin:0;font-size:15px;font-weight:600}.wd-dialog-overlay{z-index:18;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#06090e99;place-items:center;padding:16px;display:grid;position:absolute;inset:0}.wd-dialog{background:var(--panel);border:1px solid var(--border);border-radius:14px;width:100%;max-width:380px;padding:16px}.wd-dialog-head{justify-content:space-between;align-items:center;display:flex}.wd-dialog-head h2{margin:0;font-size:18px}.wd-dialog-x{border:1px solid var(--border);background:var(--panel-2);width:36px;height:36px;color:var(--text);cursor:pointer;border-radius:8px;place-items:center;display:grid}.wd-dialog-help{color:var(--muted);margin:8px 0 12px;font-size:13px}.wd-help-intro{margin:0 0 6px}.wd-help-list{flex-direction:column;gap:5px;margin:0 0 8px;padding-left:18px;display:flex}.wd-help-list strong{color:var(--text)}.wd-help-note{margin:0;font-style:italic}.wd-watch-list{flex-direction:column;gap:8px;margin-bottom:12px;display:flex}.wd-watch-row{border:1px solid var(--border);background:var(--panel-2);border-radius:10px;align-items:center;gap:8px;padding:8px 10px;display:flex}.wd-watch-name{text-align:left;color:var(--text);cursor:pointer;background:0 0;border:none;flex:1;padding:4px 0;font-family:inherit;font-size:14px;font-weight:600}.wd-watch-name:active{color:var(--accent)}.wd-timer-info{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.wd-timer-name{color:var(--text);align-items:center;gap:6px;font-size:14px;font-weight:600;display:flex}.wd-timer-name svg{color:var(--accent);flex:none}.wd-timer-remain{color:var(--muted);font-variant-numeric:tabular-nums;font-size:12px}.wd-timer-tag{letter-spacing:.5px;text-transform:uppercase;color:#fff;background:var(--accent);border-radius:6px;flex:none;padding:2px 6px;font-size:10px;font-weight:800}.wd-dialog-actions{gap:8px;margin-top:4px;display:flex}.wd-dialog-actions .wd-btn{flex:1;justify-content:center}.wd-dialog-actions.wd-actions-stack{flex-direction:column}.wd-actions-stack .wd-btn{width:100%}.wd-mon-state{letter-spacing:.4px;text-transform:uppercase;color:#fff;background:var(--muted);border-radius:6px;flex:none;padding:2px 6px;font-size:10px;font-weight:800}.wd-mon-state[data-state=working]{background:var(--accent-2)}.wd-mon-state[data-state=blocked]{background:var(--warn)}.wd-mon-state[data-state=finished]{background:var(--accent)}.wd-mon-state[data-state=crashed]{background:var(--error)}.wd-mon-pick-head{justify-content:space-between;align-items:center;gap:8px;margin-bottom:6px;display:flex}.wd-mon-pick{flex-direction:column;gap:6px;max-height:40vh;margin-bottom:12px;display:flex;overflow-y:auto}.wd-mon-item{border:1px solid var(--border);background:var(--panel);width:100%;color:var(--text);cursor:pointer;text-align:left;border-radius:10px;justify-content:space-between;align-items:center;gap:10px;padding:9px 11px;display:flex}.wd-mon-item.on{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}.wd-mon-item-main{align-items:center;gap:8px;min-width:0;display:flex}.wd-mon-item-main svg{color:var(--accent);flex:none}.wd-mon-item-title{white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:600;overflow:hidden}.wd-mon-always{flex-direction:column;gap:8px;display:flex}.wd-check{color:var(--text);cursor:pointer;align-items:flex-start;gap:8px;font-size:13px;display:flex}.wd-check input{width:16px;height:16px;accent-color:var(--accent);flex:none;margin-top:1px}.wd-check-text{flex-direction:column;gap:2px;display:flex}.wd-check-sub{color:var(--muted);font-size:11px}.wd-form-row{flex-direction:column;gap:5px;margin-bottom:12px;display:flex}.wd-form-label{color:var(--muted);font-size:12px;font-weight:700}.wd-input{border:1px solid var(--border);background:var(--panel-2);width:100%;color:var(--text);border-radius:10px;padding:10px 12px;font-family:inherit;font-size:15px}.wd-input-area{resize:vertical;min-height:64px}.wd-form-duration{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.wd-input-num{text-align:center;width:56px}.wd-form-unit{color:var(--muted);font-size:14px;font-weight:600}.wd-preset-row{flex-wrap:wrap;gap:6px;margin-bottom:10px;display:flex}.wd-preset{border:1px solid var(--border);background:var(--panel-2);min-width:48px;color:var(--text);cursor:pointer;border-radius:999px;flex:auto;padding:9px 10px;font-family:inherit;font-size:13px;font-weight:700}.wd-preset:active{background:var(--accent);color:#fff;border-color:#0000}.wd-stepper{align-items:center;gap:4px;display:flex}.wd-step-btn{border:1px solid var(--border);background:var(--panel-2);width:38px;height:38px;color:var(--text);cursor:pointer;touch-action:manipulation;border-radius:10px;flex:none;font-family:inherit;font-size:22px;font-weight:700;line-height:1}.wd-step-btn:active{background:var(--accent);color:#fff;border-color:#0000}.wd-form-target{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.wd-form-target-status{color:var(--muted);font-size:13px}.wd-form-target-status.set{color:var(--accent-2);font-weight:700}.wd-pick-banner{top:calc(env(safe-area-inset-top,0px) + 56px);z-index:30;background:var(--accent);color:#fff;box-shadow:0 6px 20px var(--shadow);pointer-events:none;text-align:center;border-radius:999px;max-width:90%;padding:10px 16px;font-size:13px;font-weight:700;position:absolute;left:50%;transform:translate(-50%)}.wd-conn-row{border-bottom:1px solid var(--border);align-items:flex-start;gap:10px;padding:10px 0;display:flex}.wd-conn-label{width:92px;color:var(--muted);flex:none;padding-top:2px;font-size:13px;font-weight:700}.wd-conn-value{color:var(--text);word-break:break-word;flex:1;font-size:15px;font-weight:600}.wd-conn-route{flex-direction:column;flex:1;gap:4px;display:flex}.wd-conn-route .wd-transport{align-self:flex-start;position:static}.wd-conn-desc{color:var(--muted);font-size:12.5px}.wd-conn-status{flex:1;align-items:center;gap:8px;display:flex}.wd-conn-error{border:1px solid var(--error);color:var(--error);word-break:break-word;background:#dc35451f;border-radius:10px;margin-top:12px;padding:9px 11px;font-size:12.5px;font-weight:600}.wd-disconnect{background:var(--error);color:#fff;border-color:#0000;justify-content:center;width:100%;min-height:46px;margin-top:14px;font-size:15px;font-weight:700}.wd-support-link{border:1px solid var(--border);color:var(--muted);cursor:pointer;background:0 0;border-radius:999px;align-self:flex-start;align-items:center;gap:6px;margin-top:8px;padding:5px 12px;font-size:12.5px;font-weight:600;transition:color .15s,border-color .15s;display:inline-flex}.wd-support-link:hover,.wd-support-link:active{color:var(--accent);border-color:var(--accent)}.wd-support-link svg{flex:none;width:14px;height:14px}.wd-conn-feedback{border-top:1px solid var(--border);text-align:center;margin-top:14px;padding-top:12px}.wd-conn-feedback-text{color:var(--muted);margin:0;font-size:12.5px}.wd-conn-feedback-links{justify-content:center;gap:10px;margin-top:9px;display:flex}.wd-conn-feedback-link{border:1px solid var(--border);color:var(--text);background:0 0;border-radius:999px;align-items:center;gap:7px;padding:7px 16px;font-size:13px;font-weight:600;text-decoration:none;transition:color .15s,border-color .15s;display:inline-flex}.wd-conn-feedback-link:hover,.wd-conn-feedback-link:active{color:var(--accent);border-color:var(--accent)}.wd-conn-feedback-link svg{flex:none;width:16px;height:16px}.wd-pin-support{margin-top:16px}.wd-placing .wd-ribbon,.wd-placing .wd-statusbar,.wd-placing .wd-bell{display:none}.wd-place-layer{z-index:40;touch-action:none;background:0 0;position:absolute;inset:0}.wd-place-marker{z-index:41;pointer-events:none;border:2px solid #4ea1ff;border-radius:50%;width:46px;height:46px;position:absolute;transform:translate(-50%,-50%);box-shadow:0 0 0 2px #00000080,0 0 14px #4ea1ffb3}.wd-place-marker:before,.wd-place-marker:after{content:"";background:#4ea1ff;position:absolute}.wd-place-marker:before{width:2px;top:-8px;bottom:-8px;left:50%;transform:translate(-50%)}.wd-place-marker:after{height:2px;top:50%;left:-8px;right:-8px;transform:translateY(-50%)}.wd-place-bar{z-index:42;padding:12px 14px calc(env(safe-area-inset-bottom,0px) + 12px);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);border-top:1px solid var(--border);background:#0a0e14f0;flex-direction:column;gap:10px;display:flex;position:absolute;bottom:0;left:0;right:0}.wd-place-hint{color:#eef2f7;text-align:center;margin:0;font-size:13px;font-weight:600}.wd-place-text{width:100%}.wd-place-buttons{gap:8px;display:flex}.wd-place-buttons .wd-btn{flex:1;justify-content:center}.wd-perm-row{border:1px solid var(--border);background:var(--panel-2);border-radius:10px;align-items:center;gap:8px;margin-bottom:12px;padding:8px 10px;display:flex}.wd-perm-dot{background:var(--muted);border-radius:50%;flex:none;width:9px;height:9px}.wd-perm-dot[data-state=on]{background:var(--accent-2)}.wd-perm-dot[data-state=warn]{background:var(--warn)}.wd-perm-dot[data-state=off]{background:var(--error)}.wd-perm-text{color:var(--muted);flex:1;font-size:12px}.wd-perm-enable{flex:none;min-width:auto;min-height:32px;padding:4px 12px;font-size:12px}.wd-dialog .wd-go{justify-content:center;width:100%}.wd-selector{z-index:19;border:2px solid var(--accent);touch-action:none;background:#2f6fed24;border-radius:6px;position:absolute;box-shadow:0 0 0 9999px #06090e59}.wd-selector-move{background:var(--accent);color:#fff;cursor:move;touch-action:none;border-radius:8px 0;place-items:center;width:38px;height:38px;display:grid;position:absolute;top:-1px;left:-1px}.wd-selector-handle{background:var(--accent);border:3px solid var(--panel);cursor:nwse-resize;touch-action:none;border-radius:50%;width:28px;height:28px;position:absolute;bottom:-12px;right:-12px}.wd-selector-bar{white-space:nowrap;gap:8px;display:flex;position:absolute;bottom:-56px;left:50%;transform:translate(-50%)}.wd-selector-info{top:calc(env(safe-area-inset-top,0px) + 52px);z-index:20;background:var(--panel);border:1px solid var(--border);border-left:4px solid var(--accent);max-width:430px;box-shadow:0 6px 20px var(--shadow);color:var(--text);text-align:center;pointer-events:none;border-radius:10px;margin:0 auto;padding:8px 12px;font-size:12.5px;line-height:1.35;position:absolute;left:12px;right:12px}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./index.esm-mXKu2C3o.js","./index.esm-B3ZTr43m.js","./index.esm-CmSagqpw.js","./index.esm-wogdVWd2.js","./index.esm-BgjPHsdM.js","./index.esm-Di8jSGaG.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();function e(e){return typeof e==`object`&&!!e&&typeof e.type==`string`}var t=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]);function n(e){return new TextEncoder().encode(e)}function r(e){let n=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),r=e.length*8,i=new Uint8Array((e.length+8>>6)+1<<6);i.set(e),i[e.length]=128;let a=new DataView(i.buffer);a.setUint32(i.length-4,r>>>0,!1),a.setUint32(i.length-8,Math.floor(r/4294967296),!1);let o=new Uint32Array(64);for(let e=0;e<i.length;e+=64){for(let t=0;t<16;t++)o[t]=a.getUint32(e+t*4,!1);for(let e=16;e<64;e++){let t=o[e-15],n=o[e-2],r=(t>>>7|t<<25)^(t>>>18|t<<14)^t>>>3,i=(n>>>17|n<<15)^(n>>>19|n<<13)^n>>>10;o[e]=o[e-16]+r+o[e-7]+i>>>0}let[r,i,s,c,l,u,d,f]=n;for(let e=0;e<64;e++){let n=(l>>>6|l<<26)^(l>>>11|l<<21)^(l>>>25|l<<7),a=l&u^~l&d,p=f+n+a+t[e]+o[e]>>>0,m=((r>>>2|r<<30)^(r>>>13|r<<19)^(r>>>22|r<<10))+(r&i^r&s^i&s)>>>0;f=d,d=u,u=l,l=c+p>>>0,c=s,s=i,i=r,r=p+m>>>0}n[0]=n[0]+r>>>0,n[1]=n[1]+i>>>0,n[2]=n[2]+s>>>0,n[3]=n[3]+c>>>0,n[4]=n[4]+l>>>0,n[5]=n[5]+u>>>0,n[6]=n[6]+d>>>0,n[7]=n[7]+f>>>0}let s=new Uint8Array(32);new DataView(s.buffer).setUint32(0,n[0],!1);for(let e=0;e<8;e++)new DataView(s.buffer).setUint32(e*4,n[e],!1);return s}var i=`0123456789abcdef`;function a(e){let t=``;for(let n of e)t+=i[n>>4]+i[n&15];return t}function o(e){return a(r(n(e)))}function s(e,t,n){let r=o(`${t}:${e}`);for(let e=1;e<n;e++)r=o(r);return r}function c(e,t){return o(`${e}:${t}`)}var l=class{token;handlers={status:new Set,welcome:new Set,screenMeta:new Set,screenRegion:new Set,videoTrack:new Set,overviewTrack:new Set,transport:new Set,notification:new Set,presence:new Set,pinRequired:new Set,watchers:new Set,timers:new Set,monitors:new Set,monitorAlways:new Set,monitorSessions:new Set,netStats:new Set,versionMismatch:new Set,error:new Set};sender=()=>{};challenge=null;wrongPin=!1;rememberedPin=null;constructor(e){this.token=e}on(e,t){this.handlers[e].add(t)}emit(e,t){for(let n of this.handlers[e])n(t)}setSender(e){this.sender=e}send(e){try{this.sender(JSON.stringify(e))}catch{}}sendHello(){this.send({type:`hello`,protocol:1,token:this.token,role:`controller`,client:{userAgent:navigator.userAgent}})}submitPin(e){if(this.rememberedPin=e,!this.challenge)return;let{salt:t,iterations:n,nonce:r}=this.challenge,i=s(e,t,n);this.send({type:`auth`,response:c(i,r)})}setVisible(e){this.send({type:`visibility`,visible:e})}handleText(t){let n;try{n=JSON.parse(t)}catch{return}if(!e(n))return;let r=n;switch(r.type){case`welcome`:this.challenge=null,r.protocol!==1&&this.emit(`versionMismatch`,{agentProtocol:r.protocol,clientProtocol:1,agentVersion:r.agent?.version}),this.emit(`welcome`,r),this.emit(`timers`,r.timers??[]),this.emit(`monitors`,r.monitors??[]),this.emit(`monitorAlways`,r.alwaysAgents??[]);break;case`auth-required`:this.challenge={salt:r.salt,iterations:r.iterations,nonce:r.nonce},this.rememberedPin&&!this.wrongPin?this.submitPin(this.rememberedPin):this.emit(`pinRequired`,{attemptsLeft:r.attemptsLeft,retry:this.wrongPin}),this.wrongPin=!1;break;case`screen-meta`:this.emit(`screenMeta`,{screen:r.screen,activeDisplay:r.activeDisplay});break;case`screen-region`:this.emit(`screenRegion`,{x:r.x,y:r.y,w:r.w,h:r.h,active:r.active});break;case`notification`:this.emit(`notification`,r);break;case`presence`:this.emit(`presence`,r.watchers);break;case`watchers`:this.emit(`watchers`,r.regions);break;case`timers`:this.emit(`timers`,r.timers);break;case`monitors`:this.emit(`monitors`,r.monitors);break;case`monitor-always-agents`:this.emit(`monitorAlways`,r.agents);break;case`monitor-sessions`:this.emit(`monitorSessions`,r.sessions);break;case`error`:r.code===`pin`?(this.wrongPin=!0,this.rememberedPin=null):this.emit(`error`,r.message);break;case`pong`:break}}},u=class{url;core;ws=null;pc=null;dc=null;mainXcv=null;overviewXcv=null;reconnectTimer=0;statsTimer=0;closedByUser=!1;constructor(e,t){this.url=e,this.core=new l(t),this.core.setSender(e=>{if(this.dc&&this.dc.readyState===`open`)try{this.dc.send(e)}catch{}})}on(e,t){this.core.on(e,t)}send(e){this.core.send(e)}submitPin(e){this.core.submitPin(e)}setVisible(e){this.core.setVisible(e)}connect(){this.closedByUser=!1,this.open()}close(){this.closedByUser=!0,window.clearTimeout(this.reconnectTimer),this.teardown()}isHealthy(){return!this.closedByUser&&this.dc?.readyState===`open`&&this.pc?.connectionState===`connected`}wake(){this.closedByUser||this.isHealthy()||(window.clearTimeout(this.reconnectTimer),this.reconnectTimer=0,this.open())}teardown(){if(window.clearInterval(this.statsTimer),this.statsTimer=0,this.core.emit(`videoTrack`,null),this.core.emit(`overviewTrack`,null),this.dc){this.dc.onopen=this.dc.onclose=this.dc.onmessage=null;try{this.dc.close()}catch{}this.dc=null}if(this.pc){this.pc.onconnectionstatechange=null,this.pc.ontrack=null,this.pc.onicecandidate=null;try{this.pc.close()}catch{}this.pc=null}if(this.ws){this.ws.onopen=this.ws.onclose=this.ws.onmessage=this.ws.onerror=null;try{this.ws.close()}catch{}this.ws=null}this.mainXcv=null,this.overviewXcv=null}scheduleReconnect(){this.closedByUser||(window.clearTimeout(this.reconnectTimer),this.reconnectTimer=window.setTimeout(()=>this.open(),1500))}open(){this.core.emit(`status`,`connecting`),this.teardown();let e=new WebSocket(this.url);this.ws=e;let t=t=>{if(e.readyState===WebSocket.OPEN)try{e.send(JSON.stringify(t))}catch{}},n=new RTCPeerConnection({iceServers:[]});this.pc=n;let r=n.createDataChannel(`whipdesk`);r.binaryType=`arraybuffer`,this.dc=r,r.onopen=()=>{this.core.sendHello(),this.core.emit(`status`,`connected`),this.core.emit(`transport`,`LAN`),this.startStatsPoll(n)},r.onclose=()=>this.core.emit(`status`,`disconnected`),r.onmessage=e=>{typeof e.data==`string`&&this.core.handleText(e.data)};try{this.mainXcv=n.addTransceiver(`video`,{direction:`recvonly`}),this.overviewXcv=n.addTransceiver(`video`,{direction:`recvonly`})}catch{}n.ontrack=e=>{let t=e.streams[0]??new MediaStream([e.track]);this.overviewXcv&&e.transceiver===this.overviewXcv?this.core.emit(`overviewTrack`,t):this.core.emit(`videoTrack`,t)},n.onicecandidate=e=>{e.candidate&&t({kind:`candidate`,candidate:e.candidate.toJSON()})},n.onconnectionstatechange=()=>{let e=n.connectionState;(e===`failed`||e===`disconnected`||e===`closed`)&&(this.core.emit(`status`,`disconnected`),this.scheduleReconnect())};let i=!1;e.onopen=async()=>{try{let e=await n.createOffer();await n.setLocalDescription(e),t({kind:`offer`,sdp:n.localDescription?.sdp??``})}catch{}},e.onmessage=e=>{let t;try{t=JSON.parse(typeof e.data==`string`?e.data:``)}catch{return}if(t.kind===`answer`&&t.sdp&&!i)i=!0,n.setRemoteDescription({type:`answer`,sdp:t.sdp});else if(t.kind===`candidate`&&t.candidate)try{n.addIceCandidate(t.candidate)}catch{}else t.kind===`error`&&this.core.emit(`error`,String(t.message??`host error`))},e.onclose=()=>{this.core.emit(`status`,`disconnected`),this.scheduleReconnect()},e.onerror=()=>{}}startStatsPoll(e){window.clearInterval(this.statsTimer),this.statsTimer=window.setInterval(async()=>{let t=null,n=0;try{(await e.getStats()).forEach(e=>{e.type===`inbound-rtp`&&e.kind===`video`?n=Math.max(n,Number(e.framesPerSecond??0)):e.type===`candidate-pair`&&e.nominated&&typeof e.currentRoundTripTime==`number`&&(t=e.currentRoundTripTime)})}catch{return}this.core.emit(`netStats`,{fps:Math.round(n),rtt:t==null?null:Math.round(t*1e3)})},1e3)}},d=``+new URL(`loading-whip-anim-DmlnYIJ-.gif`,import.meta.url).href,f=[`Rounding up your AI agents…`,`Connecting to your agent farm…`,`Uncoiling the whip…`,`Negotiating the fastest route…`,`Tightening the leash…`,`Almost in the saddle…`],p=class{overlay;msg;rotateTimer=0;msgIndex=0;constructor(e){this.overlay=document.createElement(`div`),this.overlay.className=`wd-connecting hidden`;let t=document.createElement(`div`);t.className=`wd-connecting-card`;let n=document.createElement(`img`);n.className=`wd-connecting-anim`,n.src=d,n.alt=`WhipDesk`,n.decoding=`async`,this.msg=document.createElement(`p`),this.msg.className=`wd-connecting-msg`,this.msg.textContent=f[0],t.append(n,this.msg),this.overlay.appendChild(t),e.appendChild(this.overlay)}show(e){this.msg.textContent=e??f[this.msgIndex],this.overlay.classList.contains(`hidden`)&&(this.overlay.classList.remove(`hidden`),!e&&(this.rotateTimer=window.setInterval(()=>{this.msgIndex=(this.msgIndex+1)%f.length,this.msg.textContent=f[this.msgIndex]},2200)))}hide(){this.overlay.classList.add(`hidden`),window.clearInterval(this.rotateTimer),this.rotateTimer=0}},m={eye:`<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>`,mouse:`<rect x="6" y="3" width="12" height="18" rx="6"/><path d="M12 7v4"/>`,keyboard:`<rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 10h.01M10 10h.01M14 10h.01M18 10h.01M7 14h10"/>`,monitor:`<rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/>`,hand:`<path d="M7 11V6a1.5 1.5 0 0 1 3 0v4m0-1V4.5a1.5 1.5 0 0 1 3 0V10m0-1.5a1.5 1.5 0 0 1 3 0V12c0 4-2.5 8-6.5 8S6 17 5 15l-1.5-3a1.4 1.4 0 0 1 2.3-1.5L7 12"/>`,bell:`<path d="M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6Z"/><path d="M10 19a2 2 0 0 0 4 0"/>`,plus:`<path d="M12 5v14M5 12h14"/>`,minus:`<path d="M5 12h14"/>`,"chevron-down":`<path d="m6 9 6 6 6-6"/>`,"chevron-up":`<path d="m6 15 6-6 6 6"/>`,pointer:`<path d="m4 4 6 16 2.5-6.5L19 11Z"/>`,"scroll-up":`<path d="m6 14 6-6 6 6"/><path d="M12 8v11"/>`,"scroll-down":`<path d="m6 10 6 6 6-6"/><path d="M12 16V5"/>`,"mouse-left":`<rect x="6" y="3" width="12" height="18" rx="6"/><path d="M12 3v8H6V8"/>`,"mouse-right":`<rect x="6" y="3" width="12" height="18" rx="6"/><path d="M12 3v8h6V8"/>`,"double-click":`<path d="m4 4 6 16 2.5-6.5L19 11Z"/><path d="M18 4v3M21 6h-3"/>`,drag:`<path d="M12 2v20M2 12h20" /><path d="m8 6 4-4 4 4M8 18l4 4 4-4M6 8l-4 4 4 4M18 8l4 4-4 4"/>`,send:`<path d="M22 2 11 13M22 2l-7 20-4-9-9-4Z"/>`,insert:`<path d="M12 5v14M5 12h7"/><rect x="16" y="4" width="4" height="16" rx="1"/>`,lock:`<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>`,clock:`<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>`,power:`<path d="M12 3v9"/><path d="M6.4 7.4a8 8 0 1 0 11.2 0"/>`,activity:`<path d="M3 12h4l2-7 4 14 2-7h6"/>`,x:`<path d="M6 6 18 18M18 6 6 18"/>`,heart:`<path d="M12 20s-7-4.4-9.3-8.5a4.5 4.5 0 0 1 8.1-3.9l1.2 1.6 1.2-1.6a4.5 4.5 0 0 1 8.1 3.9C19 15.6 12 20 12 20Z"/>`,trash:`<path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6M14 11v6"/>`,github:`<path fill="currentColor" stroke="none" d="M12 .5C5.37.5 0 5.87 0 12.5c0 5.3 3.44 9.8 8.21 11.39.6.11.82-.26.82-.58v-2.03c-3.34.73-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.74.08-.73.08-.73 1.2.09 1.84 1.24 1.84 1.24 1.07 1.83 2.81 1.3 3.5.99.11-.78.42-1.3.76-1.6-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.13-.3-.54-1.52.12-3.18 0 0 1.01-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.29-1.55 3.3-1.23 3.3-1.23.66 1.66.25 2.88.12 3.18.77.84 1.23 1.91 1.23 3.22 0 4.61-2.81 5.62-5.49 5.92.43.37.81 1.1.81 2.22v3.29c0 .32.22.7.83.58A12 12 0 0 0 24 12.5C24 5.87 18.63.5 12 .5Z"/>`,reddit:`<path fill="currentColor" stroke="none" d="M24 11.78a2.6 2.6 0 0 0-4.4-1.86 12.74 12.74 0 0 0-6.86-2.16l1.17-3.68 3.16.74a1.83 1.83 0 1 0 .2-1.18l-3.6-.85a.6.6 0 0 0-.72.43l-1.3 4.08a12.8 12.8 0 0 0-7 2.18 2.6 2.6 0 1 0-2.86 4.28 5.1 5.1 0 0 0-.06.79c0 4 4.66 7.24 10.42 7.24S20.42 17.83 20.42 12.95c0-.26-.02-.52-.06-.78A2.6 2.6 0 0 0 24 11.78ZM6.13 13.5a1.83 1.83 0 1 1 3.66 0 1.83 1.83 0 0 1-3.66 0Zm10.2 4.83c-1.25 1.25-3.64 1.34-4.33 1.34-.7 0-3.09-.09-4.33-1.34a.47.47 0 0 1 .67-.67c.79.79 2.47.99 3.66.99 1.2 0 2.88-.2 3.67-.99a.47.47 0 1 1 .66.67h.03Zm-.27-3a1.83 1.83 0 1 1 0-3.66 1.83 1.83 0 0 1 0 3.66Z"/>`},h=`http://www.w3.org/2000/svg`;function g(e,t=20){let n=document.createElementNS(h,`svg`);return n.setAttribute(`viewBox`,`0 0 24 24`),n.setAttribute(`width`,String(t)),n.setAttribute(`height`,String(t)),n.setAttribute(`fill`,`none`),n.setAttribute(`stroke`,`currentColor`),n.setAttribute(`stroke-width`,`2`),n.setAttribute(`stroke-linecap`,`round`),n.setAttribute(`stroke-linejoin`,`round`),n.setAttribute(`aria-hidden`,`true`),n.innerHTML=m[e],n}var _=[`en`],v=`en`;function y(){try{let e=localStorage.getItem(`wd-locale`);if(e&&_.includes(e))return e}catch{}let e=navigator.languages?.length?navigator.languages:[navigator.language];for(let t of e){let e=(t||``).toLowerCase().split(`-`)[0]??``;if(_.includes(e))return e}return v}function b(){return location.hostname.endsWith(`whipdesk.com`)?``:`https://whipdesk.com`}function x(){return`${b()}/${y()}/dashboard/`}function S(e){let t=e?`?next=${encodeURIComponent(e)}`:``;return`${b()}/${y()}/sign-in/${t}`}var C=`https://donate.stripe.com/6oU5kE19N5v35652n88so01`,w=`https://github.com/BinaryBananaLLC/WhipDesk/`,T=`https://www.reddit.com/r/WhipDesk/`,E=[[`Esc`,`Escape`],[`Tab`,`Tab`],[`⌫`,`Backspace`],[`⏎`,`Enter`],[`←`,`ArrowLeft`],[`↑`,`ArrowUp`],[`↓`,`ArrowDown`],[`→`,`ArrowRight`]];function D(e,t,n){let r=document.createElement(e);return t&&(r.className=t),n!==void 0&&(r.textContent=n),r}function O(e,...t){let n=D(`div`,`wd-group`);n.appendChild(D(`span`,`wd-group-label`,e));let r=D(`div`,`wd-group-items`);return r.append(...t),n.appendChild(r),n}function k(e,t){let n=0,r=0,i=()=>{window.clearTimeout(n),window.clearInterval(r)};e.addEventListener(`pointerdown`,e=>{e.preventDefault(),t(),n=window.setTimeout(()=>{r=window.setInterval(t,80)},350)});for(let t of[`pointerup`,`pointercancel`,`pointerleave`])e.addEventListener(t,i);return e}function A(e,t=`wd-btn`){return D(`button`,t,e)}function j(e,t=``,n=`wd-btn`){let r=D(`button`,n);if(r.appendChild(g(e)),t){let e=D(`span`,`wd-btn-label`,t);r.appendChild(e)}else r.classList.add(`wd-icon-only`),r.setAttribute(`aria-label`,e);return r}function ee(e,t,n){let r=D(`a`,`wd-conn-feedback-link`);return r.href=n,r.target=`_blank`,r.rel=`noopener noreferrer`,r.append(g(e,16),D(`span`,void 0,t)),r}function te(e){switch(e.toUpperCase()){case`LAN`:return`Direct on your local network — fastest, no relay.`;case`STUN`:return`Direct peer-to-peer across networks.`;case`TURN`:return`Your network blocked a direct P2P connection, so we're bouncing your stream through our encrypted relay. Yes, this costs us actual money to run. Consider supporting us.`;default:return``}}function M(e){return e.toUpperCase()===`TURN`?`$ ${e}`:e}var ne=class{root;deps;statusDot=D(`span`,`wd-dot`);statusText=D(`span`,`wd-status-text`,`Connecting…`);transportBadge=D(`span`,`wd-transport hidden`);watchersText=D(`span`,`wd-watchers`,``);alertBadge=D(`span`,`wd-badge hidden`);statusbar;statusCollapseTimer=0;connectionOverlay;connName;connRoute;connPresence;connSpeed;connStatusDot;connStatusText;connError;lastError=``;netFps=0;netRtt=null;panel;optionsArea;tabButtons=new Map;tabPanes=new Map;collapseBtn;interactHost;monitorList;promptInput;activeTab=null;interactMode=`mouse`;collapsed=!1;deviceName=``;transport=``;presenceCount=1;status=`connecting`;displays=[];activeDisplay=0;constructor(e,t){this.root=e,this.deps=t,this.build()}setStatus(e){this.status=e,this.statusDot.dataset.status=e,e===`connected`&&(this.lastError=``),this.renderStatusText(),this.updateStatusCollapse(),this.connectionOverlay&&!this.connectionOverlay.classList.contains(`hidden`)&&this.renderConnection()}setAlertCount(e){this.alertBadge.textContent=e>0?String(e):``,this.alertBadge.classList.toggle(`hidden`,e<=0)}setTransport(e){this.transport=e,this.transportBadge.textContent=M(e),this.transportBadge.dataset.kind=e.toLowerCase(),this.transportBadge.classList.toggle(`hidden`,!e),this.connectionOverlay&&!this.connectionOverlay.classList.contains(`hidden`)&&this.renderConnection(),this.peekStatus()}updateStatusCollapse(){window.clearTimeout(this.statusCollapseTimer),this.status===`connected`?this.scheduleStatusCollapse():this.statusbar?.classList.remove(`collapsed`)}scheduleStatusCollapse(){window.clearTimeout(this.statusCollapseTimer),this.statusCollapseTimer=window.setTimeout(()=>{this.status===`connected`&&this.statusbar?.classList.add(`collapsed`)},4e3)}peekStatus(){this.statusbar?.classList.remove(`collapsed`),this.scheduleStatusCollapse()}renderStatusText(){this.statusText.textContent=this.status===`connected`?this.deviceName?`Connected to ${this.deviceName}`:`Connected`:this.status===`connecting`?`Connecting…`:`Disconnected`}setWelcome(e){this.deviceName=e.agent.hostname,this.renderStatusText(),this.displays=e.displays??[],this.activeDisplay=e.activeDisplay??0,this.renderMonitors(),e.capabilities.mouse||this.deps.notifications.show({type:`notification`,id:`cap-${Date.now()}`,title:`View-only`,body:`Host mouse/keyboard unavailable — grant Accessibility on the host.`,level:`warning`,source:`client`,t:Date.now()})}setActiveDisplay(e){this.activeDisplay=e,this.renderMonitors()}setPresence(e){this.presenceCount=e,this.watchersText.textContent=e>1?`● ${e} watching`:``,this.connectionOverlay&&!this.connectionOverlay.classList.contains(`hidden`)&&this.renderConnection()}buildConnectionDialog(){let e=D(`div`,`wd-dialog-overlay hidden`);e.addEventListener(`pointerdown`,t=>{t.target===e&&e.classList.add(`hidden`)});let t=D(`div`,`wd-dialog`),n=D(`div`,`wd-dialog-head`);n.append(D(`h2`,``,`Connection`));let r=D(`button`,`wd-dialog-x`);r.appendChild(g(`x`)),r.onclick=()=>e.classList.add(`hidden`),n.appendChild(r);let i=D(`div`,`wd-conn-row`);i.append(D(`span`,`wd-conn-label`,`Status`));let a=D(`div`,`wd-conn-status`);this.connStatusDot=D(`span`,`wd-dot`),this.connStatusText=D(`span`,`wd-conn-value`,`—`),a.append(this.connStatusDot,this.connStatusText),i.appendChild(a);let o=D(`div`,`wd-conn-row`);o.append(D(`span`,`wd-conn-label`,`Machine`)),this.connName=D(`span`,`wd-conn-value`,`—`),o.appendChild(this.connName);let s=D(`div`,`wd-conn-row`);s.append(D(`span`,`wd-conn-label`,`Connection`)),this.connRoute=D(`div`,`wd-conn-route`),s.appendChild(this.connRoute);let c=D(`div`,`wd-conn-row`);c.append(D(`span`,`wd-conn-label`,`Viewers`)),this.connPresence=D(`span`,`wd-conn-value`,`1`),c.appendChild(this.connPresence);let l=D(`div`,`wd-conn-row`);l.append(D(`span`,`wd-conn-label`,`Speed (FPS/latency)`)),this.connSpeed=D(`span`,`wd-conn-value`,`—`),l.appendChild(this.connSpeed),this.connError=D(`div`,`wd-conn-error hidden`);let u=D(`button`,`wd-btn wd-disconnect`);u.append(g(`power`),D(`span`,`wd-btn-label`,`Disconnect`)),u.onclick=()=>this.disconnect();let d=D(`div`,`wd-conn-feedback`);d.append(D(`p`,`wd-conn-feedback-text`,`Noticed an issue or have an idea? Reach out:`));let f=D(`div`,`wd-conn-feedback-links`);f.append(ee(`reddit`,`Reddit`,T),ee(`github`,`GitHub`,w)),d.appendChild(f),t.append(n,i,s,l,o,c,this.connError,u,d),e.appendChild(t),this.root.appendChild(e),this.connectionOverlay=e}renderConnection(){if(this.connStatusDot.dataset.status=this.status,this.connStatusText.textContent=this.status===`connected`?`Connected`:this.status===`connecting`?`Connecting…`:`Disconnected`,this.lastError?(this.connError.textContent=this.lastError,this.connError.classList.remove(`hidden`)):this.connError.classList.add(`hidden`),this.connName.textContent=this.deviceName||`Connected device`,this.connPresence.textContent=String(Math.max(1,this.presenceCount)),this.connRoute.replaceChildren(),this.transport){let e=D(`span`,`wd-transport`);if(e.textContent=M(this.transport),e.dataset.kind=this.transport.toLowerCase(),this.connRoute.append(e,D(`span`,`wd-conn-desc`,te(this.transport))),this.transport.toLowerCase()===`turn`){let e=D(`button`,`wd-support-link`);e.append(g(`heart`,14),D(`span`,void 0,`Support WhipDesk`)),e.onclick=()=>window.open(C,`_blank`,`noopener`),this.connRoute.append(e)}}else this.connRoute.append(D(`span`,`wd-conn-desc`,this.status===`connected`?`Detecting route…`:`Connecting…`));this.renderSpeed()}renderSpeed(){let e=`${this.netFps} FPS`;this.connSpeed.textContent=this.netRtt==null?e:`${e} / ${this.netRtt} ms`}setNetStats(e,t){this.netFps=e,this.netRtt=t,this.connectionOverlay&&!this.connectionOverlay.classList.contains(`hidden`)&&this.renderSpeed()}openConnection(){this.renderConnection(),this.connectionOverlay.classList.remove(`hidden`)}disconnect(){this.deps.conn.close(),window.location.href=x()}flashError(e){this.lastError=e,this.connectionOverlay&&!this.connectionOverlay.classList.contains(`hidden`)&&this.renderConnection(),this.deps.notifications.show({type:`notification`,id:`err-${Date.now()}`,title:`WhipDesk`,body:e,level:`warning`,source:`client`,t:Date.now()})}build(){let e=D(`div`,`wd-statusbar`);e.append(this.statusDot,this.statusText,this.transportBadge,this.watchersText),e.onclick=()=>this.openConnection(),this.statusbar=e,this.buildConnectionDialog();let t=j(`bell`,``,`wd-bell`);t.setAttribute(`aria-label`,`Auto-Whips`),t.title=`Auto-Whips`,t.appendChild(this.alertBadge),t.onclick=()=>this.deps.watchers.open(),this.root.append(e,t);let n=D(`div`,`wd-ribbon`);this.panel=D(`div`,`wd-panel`),this.optionsArea=D(`div`,`wd-options`),this.tabPanes.set(`viewer`,this.buildViewerPane()),this.tabPanes.set(`interact`,this.buildInteractPane()),this.tabPanes.set(`type`,this.buildTypePane()),this.tabPanes.set(`monitor`,this.buildMonitorPane());for(let e of this.tabPanes.values())this.optionsArea.appendChild(e);let r=D(`div`,`wd-tabs`),i=(e,t,n)=>{let i=D(`button`,`wd-tab`);i.appendChild(g(t,18)),i.appendChild(D(`span`,`wd-tab-label`,n)),i.onclick=()=>this.selectTab(e),this.tabButtons.set(e,i),r.appendChild(i)};i(`viewer`,`eye`,`Viewer`),i(`interact`,`mouse`,`Interact`),i(`type`,`keyboard`,`Type`),i(`monitor`,`monitor`,`Monitor`),this.collapseBtn=j(`chevron-down`,``,`wd-collapse`),this.collapseBtn.onclick=()=>this.setCollapsed(!this.collapsed),r.appendChild(this.collapseBtn),this.panel.append(this.optionsArea,r),n.appendChild(this.panel),this.root.appendChild(n),this.selectTab(`viewer`)}buildViewerPane(){let{view:e,input:t}=this.deps,n=D(`div`,`wd-pane wd-pane-single-row`),r=k(j(`minus`,``,`wd-btn wd-icon-only`),()=>e.zoomBy(.9)),i=k(j(`plus`,``,`wd-btn wd-icon-only`),()=>e.zoomBy(1.11)),a=k(j(`scroll-up`,``,`wd-btn wd-icon-only`),()=>t.scrollStep(-6)),o=k(j(`scroll-down`,``,`wd-btn wd-icon-only`),()=>t.scrollStep(6)),s=j(`hand`,``,`wd-btn wd-icon-only`);s.setAttribute(`aria-label`,`Drag to scroll`),s.title=`Drag to scroll`;let c=j(`drag`,``,`wd-btn wd-icon-only`);c.setAttribute(`aria-label`,`Pan the zoomed screen with one finger`),c.title=`Pan the zoomed screen with one finger`,s.onclick=()=>{let e=!t.getDragScroll();t.setDragScroll(e),s.classList.toggle(`on`,e),c.classList.toggle(`on`,t.getPan())},c.onclick=()=>{let e=!t.getPan();t.setPan(e),c.classList.toggle(`on`,e),s.classList.toggle(`on`,t.getDragScroll())};let l=j(`pointer`,`Click`,`wd-btn wd-go`);return l.onclick=()=>t.click(`left`),n.append(O(`Zoom`,r,i),O(`Pan`,c),O(`Scroll`,a,o,s),O(`Pointer`,l)),n}buildInteractPane(){let e=D(`div`,`wd-pane`);return this.interactHost=D(`div`,`wd-pane`),e.appendChild(this.interactHost),this.renderInteract(),e}renderInteract(){let{input:e}=this.deps;this.interactHost.replaceChildren();let t=D(`div`,`wd-group`),n=D(`div`,`wd-group-head`),r=D(`span`,`wd-group-label`,`Mode`),i=D(`div`,`wd-mode-toggle`),a=D(`button`,`wd-mode-btn`,`Mouse`),o=D(`button`,`wd-mode-btn`,`Touch`);a.classList.toggle(`on`,this.interactMode===`mouse`),o.classList.toggle(`on`,this.interactMode===`touch`),a.onclick=()=>{this.interactMode=`mouse`,this.activeTab===`interact`&&this.deps.input.setInteraction(`mouse`),this.renderInteract()},o.onclick=()=>{this.interactMode=`touch`,this.activeTab===`interact`&&this.deps.input.setInteraction(`touch`),this.renderInteract()},i.append(a,o),n.append(r,i);let s=D(`div`,`wd-group-items`);if(this.interactMode===`mouse`){let t=j(`mouse-left`,`Left`);t.onclick=()=>e.click(`left`);let n=j(`mouse-right`,`Right`);n.onclick=()=>e.click(`right`);let r=j(`double-click`,`Double`);r.onclick=()=>e.multiClick(2);let i=j(`drag`,`Drag`);i.onclick=()=>{let t=!e.getDragLock();e.setDragLock(t),i.classList.toggle(`on`,t)},s.append(t,n,r,i)}else{let t=j(`pointer`,`Tap`);t.onclick=()=>e.click(`left`);let n=j(`hand`,`Hold`);n.onclick=()=>e.longPress();let r=k(j(`scroll-up`,`Up`,`wd-btn`),()=>e.swipe(0,-.25)),i=k(j(`scroll-down`,`Down`,`wd-btn`),()=>e.swipe(0,.25)),a=A(`←`);a.onclick=()=>e.swipe(-.25,0);let o=A(`→`);o.onclick=()=>e.swipe(.25,0);let c=A(`2 fingers`);c.onclick=()=>e.click(`right`),s.append(t,n,r,i,a,o,c)}t.append(n,s),this.interactHost.append(t)}buildTypePane(){let{conn:e,input:t}=this.deps,n=D(`div`,`wd-pane wd-pane-col`);this.promptInput=D(`textarea`,`wd-type-input`),this.promptInput.placeholder=`Type to send to the focused app (URL, command, message…)`,this.promptInput.rows=2;let r=D(`div`,`wd-wrap`);for(let[t,n]of E){let i=A(t);i.onclick=()=>e.send({type:`key`,key:n}),r.appendChild(i)}let i=j(`pointer`,`1x click`);i.onclick=()=>t.click(`left`);let a=j(`double-click`,`2x click`);a.onclick=()=>t.multiClick(2);let o=j(`double-click`,`3x click`);o.onclick=()=>t.multiClick(3);let s=j(`insert`,`Insert`);s.onclick=()=>this.sendText(!1);let c=j(`send`,`Send`,`wd-btn wd-go`);return c.onclick=()=>this.sendText(!0),r.append(i,a,o,s,c),n.append(this.promptInput,r),n}buildMonitorPane(){let e=D(`div`,`wd-pane`);return this.monitorList=D(`div`,`wd-monitor-list`),e.appendChild(this.monitorList),this.renderMonitors(),e}renderMonitors(){if(this.monitorList){if(this.monitorList.replaceChildren(),this.displays.length===0){this.monitorList.appendChild(D(`span`,`wd-hint`,`Single display`));return}this.displays.forEach((e,t)=>{let n=A(`${t+1}. ${e.name}${e.primary?` ★`:``}`);n.classList.toggle(`on`,e.id===this.activeDisplay),n.onclick=()=>{this.deps.conn.send({type:`select-display`,id:e.id}),this.activeDisplay=e.id,this.renderMonitors()},this.monitorList.appendChild(n)})}}selectTab(e){if(this.activeTab!==null&&e===this.activeTab){this.setCollapsed(!this.collapsed);return}this.activeTab=e,this.collapsed&&this.setCollapsed(!1);for(let[t,n]of this.tabButtons)n.classList.toggle(`on`,t===e);for(let[t,n]of this.tabPanes)n.classList.toggle(`hidden`,t!==e);this.deps.input.setInteraction(e===`interact`?this.interactMode:`viewer`),e===`type`&&window.setTimeout(()=>this.promptInput.focus(),50)}setCollapsed(e){this.collapsed=e,this.optionsArea.classList.toggle(`hidden`,e),this.collapseBtn.replaceChildren(g(e?`chevron-up`:`chevron-down`))}sendText(e){let t=this.promptInput.value;t&&(this.deps.conn.send({type:`type`,text:t,submit:e}),this.promptInput.value=``)}};function N(e){return e<0?0:e>1?1:e}var P=2.5,F=250,re=500,ie=8,ae=class{canvas;view;conn;cb;interaction=`viewer`;dragLock=!1;dragScroll=!1;pan=!1;holdingLeft=!1;cursor={nx:.5,ny:.5};pointers=new Map;longPressTimer=0;twoFinger=null;suppressTap=!1;constructor(e,t,n,r={}){this.canvas=e,this.view=t,this.conn=n,this.cb=r,e.style.touchAction=`none`,e.addEventListener(`pointerdown`,e=>this.onDown(e)),e.addEventListener(`pointermove`,e=>this.onMove(e)),e.addEventListener(`pointerup`,e=>this.onUp(e)),e.addEventListener(`pointercancel`,e=>this.onUp(e)),e.addEventListener(`contextmenu`,e=>e.preventDefault()),this.view.setCursor(this.cursor.nx,this.cursor.ny)}setInteraction(e){this.interaction=e,e===`viewer`&&(this.dragLock=!1)}getInteraction(){return this.interaction}setCallbacks(e){this.cb=e}setDragLock(e){this.dragLock=e}getDragLock(){return this.dragLock}setDragScroll(e){this.dragScroll=e,e&&(this.pan=!1)}getDragScroll(){return this.dragScroll}setPan(e){this.pan=e,e&&(this.dragScroll=!1)}getPan(){return this.pan}isPanning(){return this.pan&&this.interaction===`viewer`}click(e,t=!1){this.send({type:`pointer`,action:`click`,button:e,double:t,x:this.cursor.nx,y:this.cursor.ny}),navigator.vibrate?.(15)}multiClick(e){for(let t=0;t<e;t++)this.send({type:`pointer`,action:`click`,button:`left`,x:this.cursor.nx,y:this.cursor.ny});navigator.vibrate?.(15)}longPress(e=650){this.send({type:`pointer`,action:`down`,button:`left`,x:this.cursor.nx,y:this.cursor.ny}),navigator.vibrate?.(25),window.setTimeout(()=>this.send({type:`pointer`,action:`up`,button:`left`}),e)}swipe(e,t){let n=N(this.cursor.nx),r=N(this.cursor.ny),i=N(this.cursor.nx+e),a=N(this.cursor.ny+t);this.send({type:`pointer`,action:`down`,button:`left`,x:n,y:r});for(let e=1;e<=6;e++){let t=e/6;window.setTimeout(()=>{this.send({type:`pointer`,action:`move`,x:n+(i-n)*t,y:r+(a-r)*t}),e===6&&(this.moveCursor(i,a),this.send({type:`pointer`,action:`up`,button:`left`}))},e*16)}navigator.vibrate?.(15)}scrollStep(e){this.send({type:`scroll`,dx:0,dy:e})}send(e){this.conn.send(e)}positionOf(e){let t=this.canvas.getBoundingClientRect();return{x:e.clientX-t.left,y:e.clientY-t.top}}moveCursor(e,t){this.cursor.nx=N(e),this.cursor.ny=N(t),this.view.setCursor(this.cursor.nx,this.cursor.ny),this.cb.onCursor?.(this.cursor.nx,this.cursor.ny)}onDown(e){this.canvas.setPointerCapture?.(e.pointerId);let t=this.positionOf(e);if(this.pointers.set(e.pointerId,{x:t.x,y:t.y,startX:t.x,startY:t.y,startT:performance.now(),moved:!1,consumed:!1}),this.pointers.size===2){window.clearTimeout(this.longPressTimer),this.beginTwoFinger();return}if(this.pointers.size===1&&(this.view.beginViewGesture(),window.clearTimeout(this.longPressTimer),this.interaction===`mouse`&&!this.dragScroll&&(this.longPressTimer=window.setTimeout(()=>this.onLongPress(),re)),!this.dragScroll&&!this.isPanning()&&(this.interaction===`mouse`||this.interaction===`viewer`))){let e=this.view.canvasToNorm(t.x,t.y);this.moveCursor(e.nx,e.ny)}}onLongPress(){let e=[...this.pointers.values()][0];!e||e.moved||e.consumed||(e.consumed=!0,this.send({type:`pointer`,action:`click`,button:`right`,x:this.cursor.nx,y:this.cursor.ny}),navigator.vibrate?.(20))}beginTwoFinger(){let e=[...this.pointers.values()];if(e.length<2)return;let t=e[0],n=e[1];this.twoFinger={dist:Math.hypot(t.x-n.x,t.y-n.y),mx:(t.x+n.x)/2,my:(t.y+n.y)/2,start:performance.now(),moved:!1}}onMove(e){let t=this.pointers.get(e.pointerId);if(!t)return;let n=this.positionOf(e),r=t.x,i=t.y;if(t.x=n.x,t.y=n.y,Math.hypot(n.x-t.startX,n.y-t.startY)>ie&&(t.moved=!0),this.pointers.size>=2&&this.twoFinger){this.onTwoFingerMove();return}if(this.pointers.size!==1||t.consumed||!t.moved)return;window.clearTimeout(this.longPressTimer);let a=n.x-r,o=n.y-i;if(this.isPanning()){this.view.panByCanvasPixels(a,o);return}if(this.interaction===`touch`||this.dragScroll){this.send({type:`scroll`,dx:Math.round(-a/P),dy:Math.round(-o/P)});return}let s=this.view.canvasToNorm(n.x,n.y);this.moveCursor(s.nx,s.ny),this.interaction===`mouse`&&this.dragLock&&!this.holdingLeft&&(this.holdingLeft=!0,this.send({type:`pointer`,action:`down`,button:`left`,x:this.cursor.nx,y:this.cursor.ny})),this.send({type:`pointer`,action:`move`,x:this.cursor.nx,y:this.cursor.ny})}onTwoFingerMove(){let e=[...this.pointers.values()];if(e.length<2||!this.twoFinger)return;let t=e[0],n=e[1],r=Math.hypot(t.x-n.x,t.y-n.y),i=(t.x+n.x)/2,a=(t.y+n.y)/2,o=r-this.twoFinger.dist,s=i-this.twoFinger.mx,c=a-this.twoFinger.my;if(Math.abs(o)>6){this.twoFinger.moved=!0,this.view.zoomAround(1+o/200,i,a),this.twoFinger.dist=r;return}(Math.abs(c)>2||Math.abs(s)>2)&&(this.twoFinger.moved=!0,this.view.getZoom()>1?this.view.panByCanvasPixels(s,c):this.send({type:`scroll`,dx:Math.round(-s/P),dy:Math.round(-c/P)}),this.twoFinger.mx=i,this.twoFinger.my=a)}onUp(e){let t=this.pointers.size,n=this.pointers.get(e.pointerId);if(this.pointers.delete(e.pointerId),window.clearTimeout(this.longPressTimer),this.pointers.size===0&&this.view.endViewGesture(),t===2){let e=this.twoFinger;e&&!e.moved&&this.interaction===`mouse`&&performance.now()-e.start<F&&(this.send({type:`pointer`,action:`click`,button:`right`,x:this.cursor.nx,y:this.cursor.ny}),navigator.vibrate?.(20)),this.twoFinger=null,this.suppressTap=!0;for(let e of this.pointers.values())e.consumed=!0;return}if(this.holdingLeft&&this.pointers.size===0&&(this.holdingLeft=!1,this.send({type:`pointer`,action:`up`,button:`left`})),this.pointers.size<2&&(this.twoFinger=null),!n||n.consumed)return;if(this.suppressTap){this.pointers.size===0&&(this.suppressTap=!1);return}let r=performance.now()-n.startT;if(!(!(!n.moved&&r<F&&this.pointers.size===0)||this.dragScroll)&&(this.interaction===`mouse`||this.interaction===`touch`)){let e=this.view.canvasToNorm(n.startX,n.startY);this.moveCursor(e.nx,e.ny),this.send({type:`pointer`,action:`click`,button:`left`,x:e.nx,y:e.ny}),navigator.vibrate?.(12)}}},oe=class{container;constructor(e){this.container=e}async requestPermission(){try{`Notification`in window&&Notification.permission==="default"&&await Notification.requestPermission()}catch{}}get permission(){return`Notification`in window?Notification.permission:`unsupported`}flash(e,t,n=`info`){this.toast({type:`notification`,id:`flash-${Date.now()}`,title:e,body:t,level:n,source:`client`,t:Date.now()})}show(e){this.toast(e);try{`Notification`in window&&Notification.permission===`granted`&&new Notification(e.title,{body:e.body,tag:e.id})}catch{}navigator.vibrate?.(e.level===`error`?[60,40,60]:40)}toast(e){let t=document.createElement(`div`);t.className=`wd-toast wd-${e.level}`;let n=document.createElement(`strong`);if(n.textContent=e.title,t.appendChild(n),e.body){let n=document.createElement(`span`);n.textContent=e.body,t.appendChild(n)}this.container.appendChild(t),window.setTimeout(()=>{t.classList.add(`wd-hide`),window.setTimeout(()=>t.remove(),300)},5e3)}},se=``+new URL(`whip-CTqIatiK.png`,import.meta.url).href,ce=class{overlay;input;message;onSubmit=null;constructor(e){this.overlay=document.createElement(`div`),this.overlay.className=`wd-pin-overlay hidden`;let t=document.createElement(`div`);t.className=`wd-pin-card`;let n=document.createElement(`img`);n.className=`wd-pin-whip`,n.src=se,n.alt=`WhipDesk`,n.decoding=`async`;let r=document.createElement(`h2`);r.textContent=`Enter device PIN`,this.message=document.createElement(`p`),this.message.className=`wd-pin-msg`,this.message.textContent=`You're connected. Enter the PIN to unlock this device and start whipping.`,this.input=document.createElement(`input`),this.input.className=`wd-pin-input`,this.input.type=`password`,this.input.inputMode=`numeric`,this.input.autocomplete=`one-time-code`,this.input.name=`wd-device-pin`,this.input.setAttribute(`data-1p-ignore`,`true`),this.input.setAttribute(`data-lpignore`,`true`),this.input.setAttribute(`aria-label`,`Device PIN`),this.input.addEventListener(`keydown`,e=>{e.key===`Enter`&&this.submit()});let i=document.createElement(`button`);i.className=`wd-pin-submit`,i.textContent=`Unlock`,i.onclick=()=>this.submit();let a=document.createElement(`button`);a.type=`button`,a.className=`wd-support-link wd-pin-support`,a.append(g(`heart`,14));let o=document.createElement(`span`);o.textContent=`Support WhipDesk`,a.append(o),a.onclick=()=>window.open(C,`_blank`,`noopener`);let s=document.createElement(`button`);s.className=`wd-pin-back`,s.textContent=`← Back to dashboard`,s.onclick=()=>{window.location.href=x()},t.append(n,r,this.message,this.input,i,a,s),this.overlay.appendChild(t),e.appendChild(this.overlay)}show(e,t){this.onSubmit=t,this.input.value=``,e.retry?(this.message.textContent=e.attemptsLeft>0?`Wrong PIN — ${e.attemptsLeft} attempt(s) left.`:`Wrong PIN. Try again.`,this.message.classList.add(`err`),navigator.vibrate?.([40,40,40])):(this.message.textContent=`You're connected. Enter the PIN to unlock this device and start whipping.`,this.message.classList.remove(`err`)),this.overlay.classList.remove(`hidden`),window.setTimeout(()=>this.input.focus(),50)}hide(){this.overlay.classList.add(`hidden`)}submit(){let e=this.input.value.trim();if(e.length<4){this.message.textContent=`PIN is at least 4 characters.`,this.message.classList.add(`err`);return}this.onSubmit?.(e),this.message.textContent=`Checking…`,this.message.classList.remove(`err`)}},le=`modulepreload`,ue=function(e,t){return new URL(e,t).href},de={},I=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,new URL(`../../../src/node/plugins/importAnalysisBuild.ts`,import.meta.url)).href}r=o(t.map(t=>{if(t=ue(t,n),t=s(t),t in de)return;de[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:le,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};async function fe(e,t){if(e.vapidKey&&!(!(`serviceWorker`in navigator)||!(`Notification`in window)))try{let{initializeApp:n,getApps:r}=await I(async()=>{let{initializeApp:e,getApps:t}=await import(`./index.esm-mXKu2C3o.js`);return{initializeApp:e,getApps:t}},__vite__mapDeps([0,1]),import.meta.url),{getAuth:i}=await I(async()=>{let{getAuth:e}=await import(`./index.esm-CmSagqpw.js`);return{getAuth:e}},__vite__mapDeps([2,1]),import.meta.url),{getFirestore:a,doc:o,setDoc:s,serverTimestamp:c}=await I(async()=>{let{getFirestore:e,doc:t,setDoc:n,serverTimestamp:r}=await import(`./index.esm-wogdVWd2.js`);return{getFirestore:e,doc:t,setDoc:n,serverTimestamp:r}},__vite__mapDeps([3,1]),import.meta.url),{getMessaging:l,getToken:u,onMessage:d,isSupported:f}=await I(async()=>{let{getMessaging:e,getToken:t,onMessage:n,isSupported:r}=await import(`./index.esm-BgjPHsdM.js`);return{getMessaging:e,getToken:t,onMessage:n,isSupported:r}},__vite__mapDeps([4,1]),import.meta.url);if(!await f().catch(()=>!1))return;let p=r()[0]??n(e),m=i(p).currentUser;if(!m||Notification.permission==="default"&&await Notification.requestPermission()!==`granted`||Notification.permission!==`granted`)return;let h=await navigator.serviceWorker.register(`./firebase-messaging-sw.js`),g=l(p),_=await u(g,{vapidKey:e.vapidKey,serviceWorkerRegistration:h});if(!_)return;await s(o(a(p),`users`,m.uid,`fcmTokens`,_),{token:_,ua:navigator.userAgent,updatedAt:c()},{merge:!0}),d(g,e=>{let n=e.notification;t.show({type:`notification`,id:`push-${Date.now()}`,title:n?.title??`WhipDesk`,body:n?.body,level:`info`,source:`push`,t:Date.now()})})}catch{}}var pe=[{urls:`stun:turn-us1.whipdesk.com:3478`}];async function me(e){try{let t=await e.getStats(),n=``,r=``;t.forEach(e=>{e.type===`transport`&&e.selectedCandidatePairId&&(n=e.selectedCandidatePairId)}),t.forEach(e=>{e.type===`candidate-pair`&&!r&&(e.id===n||e.nominated&&e.state===`succeeded`)&&(r=e.localCandidateId)});let i=``;if(t.forEach(e=>{e.id===r&&e.type===`local-candidate`&&(i=e.candidateType)}),i===`relay`)return`TURN`;if(i===`srflx`||i===`prflx`)return`STUN`;if(i===`host`)return`LAN`}catch{}return null}var he=class{deviceId;config;core;pc=null;dc=null;mainXcv=null;overviewXcv=null;cleanupSignal=null;deleteSessionDoc=null;pushCandidate=null;appliedAnswer=!1;appliedCandidates=new Set;closed=!1;reconnectTimer=0;statsTimer=0;constructor(e,t,n){this.deviceId=e,this.config=n,this.core=new l(t),this.core.setSender(e=>{if(this.dc&&this.dc.readyState===`open`)try{this.dc.send(e)}catch{}})}on(e,t){this.core.on(e,t)}send(e){this.core.send(e)}submitPin(e){this.core.submitPin(e)}setVisible(e){this.core.setVisible(e)}connect(){this.closed=!1,this.start()}close(){this.closed=!0,window.clearTimeout(this.reconnectTimer),this.reconnectTimer=0,this.teardown()}isHealthy(){return!this.closed&&this.pc?.connectionState===`connected`&&this.dc?.readyState===`open`}wake(){this.closed||this.isHealthy()||(window.clearTimeout(this.reconnectTimer),this.reconnectTimer=0,this.teardown(),this.start())}start(){this.open().catch(e=>{this.core.emit(`error`,`Remote connect failed: ${e.message}`),this.core.emit(`status`,`disconnected`),this.scheduleReconnect()})}teardown(){if(window.clearInterval(this.statsTimer),this.statsTimer=0,this.cleanupSignal?.(),this.cleanupSignal=null,this.deleteSessionDoc?.(),this.deleteSessionDoc=null,this.pushCandidate=null,this.appliedAnswer=!1,this.appliedCandidates.clear(),this.core.emit(`videoTrack`,null),this.core.emit(`overviewTrack`,null),this.dc){this.dc.onopen=this.dc.onclose=this.dc.onmessage=null;try{this.dc.close()}catch{}this.dc=null}if(this.pc){this.pc.onconnectionstatechange=null,this.pc.ontrack=null;try{this.pc.close()}catch{}this.pc=null}this.mainXcv=null,this.overviewXcv=null}scheduleReconnect(){this.closed||this.reconnectTimer||(this.reconnectTimer=window.setTimeout(()=>{this.reconnectTimer=0,!this.closed&&(this.teardown(),this.start())},2e3))}async open(){this.core.emit(`status`,`connecting`);let{initializeApp:e,getApps:t}=await I(async()=>{let{initializeApp:e,getApps:t}=await import(`./index.esm-mXKu2C3o.js`);return{initializeApp:e,getApps:t}},__vite__mapDeps([0,1]),import.meta.url),{getAuth:n}=await I(async()=>{let{getAuth:e}=await import(`./index.esm-CmSagqpw.js`);return{getAuth:e}},__vite__mapDeps([2,1]),import.meta.url),{getDatabase:r,ref:i,child:a,push:o,set:s,onValue:c,remove:l,onDisconnect:u}=await I(async()=>{let{getDatabase:e,ref:t,child:n,push:r,set:i,onValue:a,remove:o,onDisconnect:s}=await import(`./index.esm-Di8jSGaG.js`);return{getDatabase:e,ref:t,child:n,push:r,set:i,onValue:a,remove:o,onDisconnect:s}},__vite__mapDeps([5,1]),import.meta.url),d=t()[0]??e(this.config),f=n(d);if(await new Promise(e=>{if(f.currentUser)return e();let t=f.onAuthStateChanged(()=>{t(),e()})}),!f.currentUser){window.location.assign(S(`/app/${window.location.hash}`));return}let p=f.currentUser.uid,m=r(d),h=await this.fetchIceServers(f.currentUser),g=new RTCPeerConnection({iceServers:h,iceTransportPolicy:`all`});this.pc=g;let _=g.createDataChannel(`whipdesk`);_.binaryType=`arraybuffer`,this.dc=_,this.wireDataChannel(_);try{this.mainXcv=g.addTransceiver(`video`,{direction:`recvonly`}),this.overviewXcv=g.addTransceiver(`video`,{direction:`recvonly`})}catch{}g.ontrack=e=>{let t=e.streams[0]??new MediaStream([e.track]);this.overviewXcv&&e.transceiver===this.overviewXcv?this.core.emit(`overviewTrack`,t):this.core.emit(`videoTrack`,t)};let v=!1,y=async()=>{if(v)return;let e=await me(g);if(!e)return;v=!0,this.core.emit(`transport`,e);let t=e===`TURN`?`turn`:e===`STUN`?`stun`:`lan`;try{let{getFirestore:e,doc:n,setDoc:r,increment:i}=await I(async()=>{let{getFirestore:e,doc:t,setDoc:n,increment:r}=await import(`./index.esm-wogdVWd2.js`);return{getFirestore:e,doc:t,setDoc:n,increment:r}},__vite__mapDeps([3,1]),import.meta.url);r(n(e(d),`users`,p),{stats:{[t]:i(1)}},{merge:!0}).catch(()=>{})}catch{}};g.onconnectionstatechange=()=>{let e=g.connectionState;e===`connected`?(this.core.emit(`status`,`connected`),y(),this.startStatsPoll(g)):(e===`failed`||e===`disconnected`||e===`closed`)&&(this.core.emit(`status`,`disconnected`),this.scheduleReconnect())};let b=o(i(m,`signaling/${p}/${this.deviceId}`));this.pushCandidate=e=>{try{s(o(a(b,`offerCandidates`)),e)}catch{}},g.onicecandidate=e=>{e.candidate&&this.pushCandidate?.(e.candidate.toJSON())};let x=await g.createOffer();await g.setLocalDescription(x),await s(b,{offer:{sdp:g.localDescription?.sdp??``},controllerUid:p,createdAtMs:Date.now()}),u(b).remove(),this.deleteSessionDoc=()=>void l(b).catch(()=>{}),this.cleanupSignal=c(b,e=>{let t=e.val();if(!(!t||this.closed)&&(t.answer?.sdp&&!this.appliedAnswer&&(this.appliedAnswer=!0,g.setRemoteDescription({type:`answer`,sdp:t.answer.sdp})),t.answerCandidates&&this.appliedAnswer)){for(let[e,n]of Object.entries(t.answerCandidates))if(!this.appliedCandidates.has(e)){this.appliedCandidates.add(e);try{g.addIceCandidate(n)}catch{}}}})}startStatsPoll(e){window.clearInterval(this.statsTimer),this.lossBase=null,this.statsPolls=0,this.statsTimer=window.setInterval(()=>void this.pollStats(e),1e3)}lossBase=null;statsPolls=0;async pollStats(e){let t=null,n=0,r=0,i=0;try{(await e.getStats()).forEach(e=>{e.type===`inbound-rtp`&&e.kind===`video`?(n=Math.max(n,Number(e.framesPerSecond??0)),r+=Number(e.packetsLost??0),i+=Number(e.packetsReceived??0)):e.type===`candidate-pair`&&e.nominated&&typeof e.currentRoundTripTime==`number`&&(t=e.currentRoundTripTime)})}catch{return}if(this.core.emit(`netStats`,{fps:Math.round(n),rtt:t==null?null:Math.round(t*1e3)}),this.statsPolls+=1,!this.lossBase)this.lossBase={lost:r,received:i};else if(this.statsPolls%5==0){let e=Math.max(0,r-this.lossBase.lost),n=Math.max(0,i-this.lossBase.received);this.lossBase={lost:r,received:i};let a=e+n;if(a>0){let n=e/a*100;this.core.send({type:`video-stats`,lossPct:n,rttMs:t==null?void 0:Math.round(t*1e3)})}}}wireDataChannel(e){e.onopen=()=>this.core.sendHello(),e.onclose=()=>{this.core.emit(`status`,`disconnected`),this.scheduleReconnect()},e.onmessage=e=>{typeof e.data==`string`&&this.core.handleText(e.data)}}async fetchIceServers(e){let t=[...pe];try{let e=sessionStorage.getItem(`wd-ice`);if(e){let{servers:t,exp:n}=JSON.parse(e);if(Array.isArray(t)&&t.length&&n>Date.now())return t}}catch{}try{let t=this.config.iceUrl||`https://us-central1-${this.config.projectId}.cloudfunctions.net/iceServers`,n=await e.getIdToken(),r=await fetch(t,{headers:{authorization:`Bearer ${n}`}});if(r.ok){let e=await r.json();if(Array.isArray(e.iceServers)&&e.iceServers.length){try{sessionStorage.setItem(`wd-ice`,JSON.stringify({servers:e.iceServers,exp:Date.now()+8*6e4}))}catch{}return e.iceServers}}}catch{}return t}};function L(e,t,n){return e<t?t:e>n?n:e}var ge=480,_e=600,R=-.6,z=1.6,ve=1500,ye=120,be=3e3;function B(e){return!e||e.x<=.001&&e.y<=.001&&e.w>=.999&&e.h>=.999}function V(e,t){if(B(e)&&B(t))return!0;if(!e||!t)return!1;let n=.005;return Math.abs(e.x-t.x)<n&&Math.abs(e.y-t.y)<n&&Math.abs(e.w-t.w)<n&&Math.abs(e.h-t.h)<n}var xe=class{canvas;ctx;mainEl=null;region=null;shownRegion=null;regionBridge=0;regionActiveHold=0;hasActiveEcho=!1;screen={width:0,height:0};viewMode=`fit`;zoom=1;center={nx:.5,ny:.5};cursor=null;onZoomCb;onViewCb;onGestureCb;gestureActive=!1;viewDirty=!1;videoActive=!1;videoRaf=0;cssW=1;cssH=1;dpr=1;layout={S:1,tx:0,ty:0};rafPending=!1;overlayBox=null;overlayView=null;overlayCanvas=null;overlayCtx=null;lastOverlayKey=``;overlayCanvasKey=``;minimapActiveAt=0;minimapShown=!0;overview=null;overviewCtx=null;overviewReady=!1;overviewDirty=!1;lastSnapAt=0;overviewEl=null;constructor(e){this.canvas=e;let t=e.getContext(`2d`);if(!t)throw Error(`2D canvas context unavailable`);this.ctx=t,this.createOverlay(),this.startOverlayLoop(),this.resize(),new ResizeObserver(()=>this.resize()).observe(e),window.addEventListener(`orientationchange`,()=>window.setTimeout(()=>this.resize(),200))}createOverlay(){let e=document.createElement(`div`);e.style.cssText=`position:fixed;top:calc(env(safe-area-inset-top, 0px) + 56px);right:10px;box-sizing:border-box;border:1.5px solid rgba(255,255,255,0.8);border-radius:6px;background:rgba(0,0,0,0.55);z-index:2147483600;pointer-events:none;display:none;box-shadow:0 1px 8px rgba(0,0,0,0.6);`;let t=document.createElement(`canvas`);t.style.cssText=`position:absolute;inset:0;width:100%;height:100%;border-radius:5px;display:block;`;let n=document.createElement(`div`);n.style.cssText=`position:absolute;box-sizing:border-box;border:2px solid #4ea1ff;background:rgba(78,161,255,0.18);border-radius:2px;`,e.appendChild(t),e.appendChild(n),document.body.appendChild(e),this.overlayBox=e,this.overlayView=n,this.overlayCanvas=t,this.overlayCtx=t.getContext(`2d`)}startOverlayLoop(){let e=()=>{this.renderOverlay(),requestAnimationFrame(e)};requestAnimationFrame(e)}renderOverlay(){let e=this.overlayBox,t=this.overlayView;if(!e||!t)return;let n=this.getVisibleRegion(),r=this.region,i=this.dispW(),a=this.dispH();if(!(r||n.w<.985||n.h<.985)){e.style.display!==`none`&&(e.style.display=`none`),this.lastOverlayKey=``,this.overlayCanvasKey=``;return}e.style.display===`none`&&(e.style.display=`block`,this.minimapActiveAt=performance.now(),this.minimapShown=!1);let o=Math.round(Math.min(132,Math.max(76,this.cssW*.32))),s=Math.round(o*(a/i||.625)),c=`${o}x${s}`;(c!==this.overlayCanvasKey||this.overviewDirty)&&(this.overlayCanvasKey=c,this.overviewDirty=!1,e.style.width=`${o}px`,e.style.height=`${s}px`,this.paintMinimap(o,s));let l=n,u=performance.now(),d=`${l.x.toFixed(3)},${l.y.toFixed(3)},${l.w.toFixed(3)},${l.h.toFixed(3)}`;d!==this.lastOverlayKey&&(this.lastOverlayKey=d,this.minimapActiveAt=u,t.style.left=`${(L(l.x,0,1)*100).toFixed(2)}%`,t.style.top=`${(L(l.y,0,1)*100).toFixed(2)}%`,t.style.width=`${(L(Math.max(.04,l.w),0,1)*100).toFixed(2)}%`,t.style.height=`${(L(Math.max(.04,l.h),0,1)*100).toFixed(2)}%`);let f=u-this.minimapActiveAt<be;f!==this.minimapShown&&(this.minimapShown=f,e.style.transition=`opacity ${f?.18:.5}s ease`,e.style.opacity=f?`1`:`0`)}paintMinimap(e,t){let n=this.overlayCanvas,r=this.overlayCtx;if(!n||!r)return;let i=Math.max(1,Math.round(e*this.dpr)),a=Math.max(1,Math.round(t*this.dpr));n.width!==i&&(n.width=i),n.height!==a&&(n.height=a),r.clearRect(0,0,i,a);let o=this.overviewImg();o&&(r.imageSmoothingEnabled=!0,r.imageSmoothingQuality=`low`,r.drawImage(o,0,0,i,a))}overviewImg(){return this.overviewReady?this.overview:null}maybeSnapshot(e,t,n){let r=performance.now();if(r-this.lastSnapAt<_e)return;this.lastSnapAt=r,this.overview||(this.overview=document.createElement(`canvas`),this.overviewCtx=this.overview.getContext(`2d`));let i=this.overview,a=this.overviewCtx;if(!i||!a)return;let o=ge,s=Math.max(1,Math.round(o*n/t));i.width!==o&&(i.width=o),i.height!==s&&(i.height=s),a.imageSmoothingEnabled=!0,a.imageSmoothingQuality=`medium`,a.drawImage(e,0,0,t,n,0,0,o,s),this.overviewReady=!0,this.overviewDirty=!0}getCanvas(){return this.canvas}resize(){let e=this.canvas.getBoundingClientRect();this.cssW=Math.max(1,e.width),this.cssH=Math.max(1,e.height),this.dpr=Math.min(window.devicePixelRatio||1,2),this.canvas.width=Math.round(this.cssW*this.dpr),this.canvas.height=Math.round(this.cssH*this.dpr),this.requestDraw(),this.emitView()}setScreen(e){let t=e.width!==this.screen.width||e.height!==this.screen.height;this.screen=e,t&&(this.overviewReady=!1,this.lastSnapAt=0,this.overviewDirty=!0),this.requestDraw()}getScreen(){return this.screen}dispW(){return this.screen.width||this.mainEl?.videoWidth||1}dispH(){return this.screen.height||this.mainEl?.videoHeight||1}setFrameRegion(e){let t=B(e)?null:e;this.region=t,window.clearTimeout(this.regionActiveHold),window.clearTimeout(this.regionBridge),this.hasActiveEcho||(this.regionBridge=window.setTimeout(()=>{this.shownRegion=t,this.videoActive||this.requestDraw()},ve)),this.videoActive||this.requestDraw()}setFrameRegionActive(e){this.hasActiveEcho=!0;let t=B(e)?null:e;if(!V(t,this.region)||V(t,this.shownRegion))return;window.clearTimeout(this.regionActiveHold);let n=()=>{V(t,this.region)&&(this.shownRegion=t,window.clearTimeout(this.regionBridge),this.videoActive||this.requestDraw())};this.regionActiveHold=window.setTimeout(()=>{if(!V(t,this.region))return;let e=this.mainEl;e&&typeof e.requestVideoFrameCallback==`function`?e.requestVideoFrameCallback(n):n()},ye)}setVideoSource(e){this.mainEl=e,this.videoActive=!!e,e?this.startVideoLoop():this.stopVideoLoop()}isVideoActive(){return this.videoActive}setOverviewSource(e){this.overviewEl=e}startVideoLoop(){if(this.videoRaf)return;let e=()=>{if(!this.videoActive){this.videoRaf=0;return}this.draw(),this.videoRaf=requestAnimationFrame(e)};this.videoRaf=requestAnimationFrame(e)}stopVideoLoop(){this.videoRaf&&cancelAnimationFrame(this.videoRaf),this.videoRaf=0,this.requestDraw()}setViewMode(e){this.viewMode=e,e===`fit`?(this.zoom=1,this.center={nx:.5,ny:.5}):this.zoom===1&&(this.zoom=2),this.emitView(),this.requestDraw()}getViewMode(){return this.viewMode}toggleViewMode(){return this.setViewMode(this.viewMode===`fit`?`magnify`:`fit`),this.viewMode}setZoom(e){this.zoom=L(e,1,8),this.viewMode=this.zoom===1?`fit`:`magnify`,this.zoom===1&&(this.center={nx:.5,ny:.5}),this.onZoomCb?.(this.zoom),this.emitView(),this.requestDraw()}zoomBy(e){this.setZoom(this.zoom*e)}getZoom(){return this.zoom}setOnZoom(e){this.onZoomCb=e}setOnView(e){this.onViewCb=e}setOnGesture(e){this.onGestureCb=e}beginViewGesture(){this.gestureActive=!0,this.onGestureCb?.(!0)}endViewGesture(){this.gestureActive&&(this.gestureActive=!1,this.viewDirty&&(this.viewDirty=!1,this.onViewCb?.(this.getVisibleRegion())),this.onGestureCb?.(!1))}emitView(){if(this.gestureActive){this.viewDirty=!0;return}this.onViewCb?.(this.getVisibleRegion())}zoomAround(e,t,n){let r=this.canvasToNorm(t,n),i=L(this.zoom*e,1,8);if(i===1){this.setZoom(1);return}this.zoom=i,this.viewMode=`magnify`;let a=this.dispW(),o=this.dispH(),s=Math.min(this.cssW/a,this.cssH/o)*this.zoom;this.center.nx=L(r.nx-(t-this.cssW/2)/(a*s||1),R,z),this.center.ny=L(r.ny-(n-this.cssH/2)/(o*s||1),R,z),this.onZoomCb?.(this.zoom),this.emitView(),this.requestDraw()}panByNorm(e,t){this.center.nx=L(this.center.nx+e,R,z),this.center.ny=L(this.center.ny+t,R,z),this.emitView(),this.requestDraw()}panByCanvasPixels(e,t){let{S:n}=this.computeLayout(),r=this.dispW(),i=this.dispH();!r||!i||!n||(this.center.nx=L(this.center.nx-e/(r*n),R,z),this.center.ny=L(this.center.ny-t/(i*n),R,z),this.emitView(),this.requestDraw())}setCursor(e,t=0){this.cursor=e===null?null:{nx:e,ny:t},this.videoActive||this.requestDraw()}computeLayout(){let{cssW:e,cssH:t}=this,n=this.dispW(),r=this.dispH();if(!n||!r)return this.layout={S:1,tx:0,ty:0},this.layout;let i=Math.min(e/n,t/r)*this.zoom,a,o;return this.viewMode===`fit`||this.zoom===1?(a=(e-n*i)/2,o=(t-r*i)/2):(a=e/2-this.center.nx*n*i,o=t/2-this.center.ny*r*i),this.layout={S:i,tx:a,ty:o},this.layout}canvasToNorm(e,t){let{S:n,tx:r,ty:i}=this.computeLayout();return{nx:L((e-r)/(this.dispW()*n||1),0,1),ny:L((t-i)/(this.dispH()*n||1),0,1)}}normToCanvas(e,t){let{S:n,tx:r,ty:i}=this.computeLayout();return{cx:r+e*this.dispW()*n,cy:i+t*this.dispH()*n}}getVisibleRegion(){let{S:e,tx:t,ty:n}=this.computeLayout(),r=this.dispW()*e,i=this.dispH()*e;if(!r||!i)return{x:0,y:0,w:1,h:1};let a=L(-t/r,0,1),o=L(-n/i,0,1),s=L((this.cssW-t)/r,0,1),c=L((this.cssH-n)/i,0,1);return{x:a,y:o,w:Math.max(.05,s-a),h:Math.max(.05,c-o)}}requestDraw(){this.videoActive||this.rafPending||(this.rafPending=!0,requestAnimationFrame(()=>{this.rafPending=!1,this.draw()}))}draw(){let e=this.ctx;e.setTransform(this.dpr,0,0,this.dpr,0,0),e.clearRect(0,0,this.cssW,this.cssH);let t=this.mainEl&&this.mainEl.videoWidth>0?this.mainEl:null;if(!t)return;let{S:n,tx:r,ty:i}=this.computeLayout(),a=this.dispW(),o=this.dispH(),s=a*n,c=o*n,l=t.videoWidth,u=t.videoHeight,d=l/u,f=e=>e?e.w*a/(e.h*o):a/o;if(this.region!==this.shownRegion){let e=Math.abs(d/f(this.region)-1)<.06,t=Math.abs(d/f(this.shownRegion)-1)<.06;e&&!t&&(this.shownRegion=this.region,window.clearTimeout(this.regionBridge))}let p=this.shownRegion,m=!!p&&Math.abs(d/f(p)-1)<.08,h=m?r+p.x*s:r,g=m?i+p.y*c:i,_=m?p.w*s:s,v=m?p.h*c:c,y=this.overviewImg();y&&(this.region||this.shownRegion)&&(e.imageSmoothingEnabled=!0,e.imageSmoothingQuality=`low`,e.drawImage(y,r,i,s,c));let b=Math.min(_/l,v/u),x=l*b,S=u*b;if(e.imageSmoothingEnabled=!0,e.imageSmoothingQuality=`high`,e.drawImage(t,0,0,l,u,h+(_-x)/2,g+(v-S)/2,x,S),this.cursor){let{cx:e,cy:t}=this.normToCanvas(this.cursor.nx,this.cursor.ny);this.drawCursor(e,t)}let C=this.overviewEl;if(this.region&&C&&C.videoWidth>0)this.maybeSnapshot(C,C.videoWidth,C.videoHeight);else if(!this.region&&!this.shownRegion&&o>0){let e=a/o;e>0&&Math.abs(d/e-1)<.06&&this.maybeSnapshot(t,l,u)}}drawCursor(e,t){let n=this.ctx;n.save(),n.beginPath(),n.arc(e,t,9,0,Math.PI*2),n.strokeStyle=`rgba(78,161,255,0.95)`,n.lineWidth=2,n.stroke(),n.beginPath(),n.arc(e,t,2.5,0,Math.PI*2),n.fillStyle=`rgba(78,161,255,0.95)`,n.fill(),n.restore()}};function Se(e,t,n,r){let i=document.createElement(`div`);i.className=`wd-place-layer`;let a=document.createElement(`div`);a.className=`wd-place-marker`;let o=document.createElement(`div`);o.className=`wd-place-bar`;let s=document.createElement(`p`);s.className=`wd-place-hint`,s.textContent=n.hint,o.appendChild(s);let c=null;n.withText&&(c=document.createElement(`textarea`),c.className=`wd-input wd-input-area wd-place-text`,c.placeholder=`Type the prompt to send…`,o.appendChild(c));let l=document.createElement(`div`);l.className=`wd-place-buttons`;let u=document.createElement(`button`);u.className=`wd-btn`,u.textContent=`Cancel`;let d=document.createElement(`button`);d.className=`wd-btn wd-go`,d.textContent=n.confirmLabel,l.append(u,d),o.appendChild(l),t.append(i,a,o),t.classList.add(`wd-placing`);let f={nx:.5,ny:.5},p=0,m=()=>{let{cx:t,cy:n}=e.normToCanvas(f.nx,f.ny);a.style.left=`${t}px`,a.style.top=`${n}px`,p=requestAnimationFrame(m)};p=requestAnimationFrame(m);let h=new Map,g=`idle`,_=null,v={x:0,y:0},y=!1,b=e=>{let t=i.getBoundingClientRect();return{x:e.clientX-t.left,y:e.clientY-t.top}},x=t=>{let{cx:n,cy:r}=e.normToCanvas(f.nx,f.ny);return Math.hypot(t.x-n,t.y-r)<38};i.addEventListener(`pointerdown`,t=>{i.setPointerCapture?.(t.pointerId);let n=b(t);if(h.set(t.pointerId,n),h.size===2){let e=[...h.values()],t=e[0],n=e[1];_={dist:Math.hypot(t.x-n.x,t.y-n.y),mx:(t.x+n.x)/2,my:(t.y+n.y)/2},g=`idle`}else h.size===1&&(v=n,y=!1,g=x(n)?`marker`:`pan`,g===`marker`&&(f=e.canvasToNorm(n.x,n.y)))}),i.addEventListener(`pointermove`,t=>{let n=h.get(t.pointerId);if(!n)return;let r=b(t);if(h.set(t.pointerId,r),h.size>=2&&_){let t=[...h.values()],n=t[0],r=t[1],i=Math.hypot(n.x-r.x,n.y-r.y),a=(n.x+r.x)/2,o=(n.y+r.y)/2;Math.abs(i-_.dist)>4&&(e.zoomAround(1+(i-_.dist)/200,a,o),_.dist=i),e.panByCanvasPixels(a-_.mx,o-_.my),_.mx=a,_.my=o;return}Math.hypot(r.x-v.x,r.y-v.y)>6&&(y=!0);let i=r.x-n.x,a=r.y-n.y;g===`marker`?f=e.canvasToNorm(r.x,r.y):g===`pan`&&e.panByCanvasPixels(i,a)});let S=t=>{let n=h.get(t.pointerId);h.delete(t.pointerId),n&&g===`pan`&&!y&&(f=e.canvasToNorm(n.x,n.y)),h.size<2&&(_=null),h.size===0&&(g=`idle`)};i.addEventListener(`pointerup`,S),i.addEventListener(`pointercancel`,S);let C=e=>{cancelAnimationFrame(p),t.classList.remove(`wd-placing`),i.remove(),a.remove(),o.remove(),r(e)};u.onclick=()=>C(null),d.onclick=()=>{let e=c?.value.trim()??``;if(n.withText&&!e){c?.focus();return}C({nx:f.nx,ny:f.ny,text:n.withText?e:void 0})}}var Ce=0;function H(){return`w${Date.now().toString(36)}${(Ce++).toString(36)}`}function we(){return new Date().toLocaleString(void 0,{month:`short`,day:`numeric`,hour:`2-digit`,minute:`2-digit`})}function Te(e){let t=Math.max(0,Math.round((e-Date.now())/1e3)),n=Math.floor(t/3600),r=Math.floor(t%3600/60),i=t%60;return n>0?`${n}h ${r}m`:r>0?`${r}m ${String(i).padStart(2,`0`)}s`:`${i}s`}function U(e,t,n){let r=document.createElement(e);return t&&(r.className=t),n!==void 0&&(r.textContent=n),r}var Ee={claude:`Claude Code`,codex:`Codex CLI`,gemini:`Gemini CLI`,aider:`Aider`,copilot:`Copilot CLI`,opencode:`opencode`,cursor:`Cursor Agent`,amp:`Amp`,unknown:`AI agent`};function W(e){return Ee[e]??`AI agent`}function De(e){switch(e){case`working`:return`working`;case`blocked`:return`needs you`;case`idle`:return`idle`;case`finished`:return`finished`;case`crashed`:return`crashed`;default:return`…`}}var Oe=class{root;conn;view;notifications;requestNotifications;regions=[];timers=[];monitors=[];monitorSessions=[];alwaysAgents=new Set;renderPicker=null;renderAlways=null;countdownTimer=0;overlay;list;permissionRow;selector=null;constructor(e,t,n,r,i=()=>{}){this.root=e,this.conn=t,this.view=n,this.notifications=r,this.requestNotifications=i,this.overlay=U(`div`,`wd-dialog-overlay hidden`),this.overlay.addEventListener(`pointerdown`,e=>{e.target===this.overlay&&this.close()});let a=U(`div`,`wd-dialog`),o=U(`div`,`wd-dialog-head`);o.append(U(`h2`,``,`Auto-Whips`));let s=U(`button`,`wd-dialog-x`);s.appendChild(g(`x`)),s.onclick=()=>this.close(),o.appendChild(s);let c=U(`div`,`wd-dialog-help`),l=U(`p`,`wd-help-intro`,`Auto-Whips are a set of features that can whip and monitor agents for you automatically:`),u=U(`ul`,`wd-help-list`),d=U(`li`);d.append(U(`strong`,void 0,`Alerts`),document.createTextNode(` — watch part of the screen and ping you when it changes (e.g. your agent finishes).`));let f=U(`li`);f.append(U(`strong`,void 0,`Timers`),document.createTextNode(` — ping you after a set time, and can auto-click or send a prompt when they fire.`));let p=U(`li`);p.append(U(`strong`,void 0,`Session Monitoring`),document.createTextNode(` — pick a running AI session and get pinged the moment the agent stops working (it's waiting on you or has gone idle).`)),u.append(d,f,p);let m=U(`p`,`wd-help-note`,`Enable browser notifications to be reminded even when the browser is closed.`);c.append(l,u,m),this.permissionRow=U(`div`,`wd-perm-row`),this.list=U(`div`,`wd-watch-list`);let h=U(`button`,`wd-btn wd-go`);h.append(g(`plus`),U(`span`,`wd-btn-label`,`Add alert`)),h.onclick=()=>this.beginSelection();let _=U(`button`,`wd-btn wd-go`);_.append(g(`clock`),U(`span`,`wd-btn-label`,`Add timer`)),_.onclick=()=>this.beginTimer();let v=U(`button`,`wd-btn wd-go`);v.append(g(`activity`),U(`span`,`wd-btn-label`,`Add Session Monitoring`)),v.onclick=()=>this.beginMonitor();let y=U(`div`,`wd-dialog-actions wd-actions-stack`);y.append(h,_,v),a.append(o,c,this.permissionRow,this.list,y),this.overlay.appendChild(a),this.root.appendChild(this.overlay),this.renderPermission(),this.conn.on(`monitorSessions`,e=>{this.monitorSessions=e,this.renderPicker?.()})}setRegions(e){this.regions=e,this.renderList()}setTimers(e){this.timers=e,this.renderList()}setMonitors(e){this.monitors=e,this.renderList()}setAlwaysAgents(e){this.alwaysAgents=new Set(e),this.renderAlways?.()}open(){this.renderPermission(),this.renderList(),this.overlay.classList.remove(`hidden`),window.clearInterval(this.countdownTimer),this.countdownTimer=window.setInterval(()=>{this.timers.length&&this.renderList()},1e3)}close(){this.overlay.classList.add(`hidden`),window.clearInterval(this.countdownTimer),this.countdownTimer=0}renderPermission(){this.permissionRow.replaceChildren();let e=U(`span`,`wd-perm-dot`),t=U(`span`,`wd-perm-text`),n=this.notifications.permission;if(n===`granted`)e.dataset.state=`on`,t.textContent=`Browser notifications are on.`,this.permissionRow.append(e,t);else if(n===`denied`)e.dataset.state=`off`,t.textContent=`Notifications are blocked — enable them in your browser settings.`,this.permissionRow.append(e,t);else if(n===`unsupported`)e.dataset.state=`off`,t.textContent=`This browser can't show notifications. Keep this page open for in-app alerts.`,this.permissionRow.append(e,t);else{e.dataset.state=`warn`,t.textContent=`Allow notifications to be alerted while this page is in the background.`;let n=U(`button`,`wd-btn wd-perm-enable`);n.append(U(`span`,`wd-btn-label`,`Enable`)),n.onclick=async()=>{await this.requestNotifications(),this.renderPermission()},this.permissionRow.append(e,t,n)}}renderList(){if(this.list.replaceChildren(),this.regions.length===0&&this.timers.length===0&&this.monitors.length===0){this.list.appendChild(U(`p`,`wd-dialog-help`,`No alerts, timers, or session monitors yet.`));return}for(let e of this.regions){let t=U(`div`,`wd-watch-row`),n=U(`button`,`wd-watch-name`,e.label);n.title=`Edit this alert`,n.onclick=()=>this.beginSelection(e),t.appendChild(n);let r=U(`button`,`wd-btn wd-icon-only`);r.appendChild(g(`trash`)),r.setAttribute(`aria-label`,`Remove ${e.label}`),r.onclick=()=>{this.conn.send({type:`watch-remove`,id:e.id}),this.regions=this.regions.filter(t=>t.id!==e.id),this.renderList()},t.appendChild(r),this.list.appendChild(t)}for(let e of this.timers){let t=U(`div`,`wd-watch-row`),n=U(`div`,`wd-timer-info`),r=U(`span`,`wd-timer-name`);r.append(g(`clock`,15),document.createTextNode(e.label));let i=U(`span`,`wd-timer-remain`,Te(e.fireAtMs));n.append(r,i),t.appendChild(n),e.hasAction&&t.appendChild(U(`span`,`wd-timer-tag`,`auto`));let a=U(`button`,`wd-btn wd-icon-only`);a.appendChild(g(`trash`)),a.setAttribute(`aria-label`,`Cancel ${e.label}`),a.onclick=()=>{this.conn.send({type:`timer-remove`,id:e.id}),this.timers=this.timers.filter(t=>t.id!==e.id),this.renderList()},t.appendChild(a),this.list.appendChild(t)}for(let e of this.monitors){let t=U(`div`,`wd-watch-row`),n=U(`div`,`wd-timer-info`),r=U(`span`,`wd-timer-name`);r.append(g(`activity`,15),document.createTextNode(`${W(e.agent)} · ${e.label}`));let i=U(`span`,`wd-mon-state`);i.dataset.state=e.live?e.state:`finished`,i.textContent=e.live?De(e.state):`ended`,n.append(r,i),t.appendChild(n);let a=U(`button`,`wd-btn wd-icon-only`);a.appendChild(g(`trash`)),a.setAttribute(`aria-label`,`Stop monitoring ${e.label}`),a.onclick=()=>{this.conn.send({type:`monitor-remove`,id:e.id}),this.monitors=this.monitors.filter(t=>t.id!==e.id),this.renderList()},t.appendChild(a),this.list.appendChild(t)}}beginSelection(e){this.close(),this.selector&&this.selector.remove();let t=this.root.getBoundingClientRect(),n=e?this.boxFromRegion(e,t):{x:t.width*.3,y:t.height*.3,w:t.width*.4,h:t.height*.25},r=U(`div`,`wd-selector-info`,e?`Move or resize the area to watch, then Save — you'll be alerted when its pixels change.`:`You'll get a browser notification when the pixels inside this box change. The agent watches this part of the screen.`),i=U(`div`,`wd-selector`),a=U(`div`,`wd-selector-move`);a.appendChild(g(`drag`));let o=U(`div`,`wd-selector-handle`),s=U(`div`,`wd-selector-bar`),c=U(`button`,`wd-btn wd-go`);c.append(U(`span`,`wd-btn-label`,e?`Save`:`Create`));let l=U(`button`,`wd-btn`);l.append(U(`span`,`wd-btn-label`,`Cancel`)),s.append(l,c),i.append(a,o,s),this.root.append(r,i),this.selector=i;let u=()=>{i.remove(),r.remove(),this.selector=null,this.open()},d=()=>{i.style.left=`${n.x}px`,i.style.top=`${n.y}px`,i.style.width=`${n.w}px`,i.style.height=`${n.h}px`};d();let f=(e,t)=>{e.addEventListener(`pointerdown`,n=>{n.preventDefault(),n.stopPropagation(),e.setPointerCapture(n.pointerId);let r=n.clientX,i=n.clientY,a=e=>{t(e.clientX-r,e.clientY-i),r=e.clientX,i=e.clientY,d()},o=()=>{e.removeEventListener(`pointermove`,a),e.removeEventListener(`pointerup`,o)};e.addEventListener(`pointermove`,a),e.addEventListener(`pointerup`,o)})};f(a,(e,r)=>{n.x=Math.max(0,Math.min(t.width-n.w,n.x+e)),n.y=Math.max(0,Math.min(t.height-n.h,n.y+r))}),f(o,(e,r)=>{n.w=Math.max(40,Math.min(t.width-n.x,n.w+e)),n.h=Math.max(40,Math.min(t.height-n.y,n.h+r))}),l.onclick=u,c.onclick=()=>{let t=this.toScreenRegion(n,e);t&&(this.conn.send({type:`watch-add`,region:t}),this.regions=e?this.regions.map(e=>e.id===t.id?t:e):[...this.regions,t],this.notifications.permission==="default"&&this.requestNotifications(),this.notifications.flash(e?`Alert updated`:`Alert created`,e?`"${t.label}" updated.`:`Monitoring "${t.label}". We'll notify you when it changes.`,`success`)),u()}}boxFromRegion(e,t){let n=this.view.normToCanvas(e.x,e.y),r=this.view.normToCanvas(e.x+e.w,e.y+e.h),i=Math.max(40,r.cx-n.cx),a=Math.max(40,r.cy-n.cy);return{x:Math.max(0,Math.min(t.width-i,n.cx)),y:Math.max(0,Math.min(t.height-a,n.cy)),w:i,h:a}}toScreenRegion(e,t){let n=this.view.canvasToNorm(e.x,e.y),r=this.view.canvasToNorm(e.x+e.w,e.y+e.h),i=Math.min(n.nx,r.nx),a=Math.min(n.ny,r.ny),o=Math.abs(r.nx-n.nx),s=Math.abs(r.ny-n.ny);return o<.005||s<.005?null:{id:t?.id??H(),label:t?.label??we(),x:i,y:a,w:o,h:s}}beginTimer(){this.close();let e=U(`div`,`wd-dialog-overlay`),t=()=>{e.remove(),this.open()};e.addEventListener(`pointerdown`,n=>{n.target===e&&t()});let n=U(`div`,`wd-dialog`),r=U(`div`,`wd-dialog-head`);r.append(U(`h2`,``,`Set timer`));let i=U(`button`,`wd-dialog-x`);i.appendChild(g(`x`)),i.onclick=t,r.appendChild(i);let a=U(`p`,`wd-dialog-help`,`Waiting for a session limit to reset? Get notified when the time's up. You can also schedule an action for the moment it hits zero — click Retry, or enter a new prompt.`),o=U(`div`,`wd-form-row`);o.appendChild(U(`label`,`wd-form-label`,`Remind me in`));let s=U(`input`,`wd-input wd-input-num`);s.type=`number`,s.min=`0`,s.max=`168`,s.value=`0`,s.inputMode=`numeric`;let c=U(`input`,`wd-input wd-input-num`);c.type=`number`,c.min=`0`,c.max=`59`,c.value=`30`,c.inputMode=`numeric`;let l=U(`div`,`wd-preset-row`);for(let[e,t,n]of[[`15m`,0,15],[`30m`,0,30],[`1h`,1,0],[`2h`,2,0],[`5h`,5,0]]){let r=U(`button`,`wd-preset`,e);r.type=`button`,r.onclick=()=>{s.value=String(t),c.value=String(n)},l.appendChild(r)}let u=(e,t,n,r)=>{let i=U(`div`,`wd-stepper`),a=U(`button`,`wd-step-btn`,`−`);a.type=`button`;let o=U(`button`,`wd-step-btn`,`+`);o.type=`button`;let s=e=>Math.max(0,Math.min(n,e));return a.onclick=()=>e.value=String(s((Number(e.value)||0)-t)),o.onclick=()=>e.value=String(s((Number(e.value)||0)+t)),i.append(a,e,U(`span`,`wd-form-unit`,r),o),i},d=U(`div`,`wd-form-duration`);d.append(u(s,1,168,`h`),u(c,5,59,`m`)),o.append(l,d);let f=U(`div`,`wd-form-row`);f.appendChild(U(`label`,`wd-form-label`,`Label`));let p=U(`input`,`wd-input`);p.placeholder=`e.g. Claude is back`,f.appendChild(p);let m=U(`div`,`wd-form-row`);m.appendChild(U(`label`,`wd-form-label`,`When it fires`));let h=U(`select`,`wd-input`);for(let[e,t]of[[`none`,`Just remind me`],[`click`,`Click a button`],[`key`,`Click & press Enter`],[`text`,`Click, type & send a prompt`]]){let n=document.createElement(`option`);n.value=e,n.textContent=t,h.appendChild(n)}m.appendChild(h);let _=U(`div`,`wd-dialog-actions`),v=U(`button`,`wd-btn`);v.append(U(`span`,`wd-btn-label`,`Cancel`)),v.onclick=t;let y=U(`button`,`wd-btn wd-go`),b=U(`span`,`wd-btn-label`,`Start timer`);y.append(g(`clock`),b);let x=()=>{b.textContent=h.value===`none`?`Start timer`:`Next: place target`};h.onchange=x,x();let S=()=>{let e=Math.max(0,Math.min(168,Math.round(Number(s.value)||0))),t=Math.max(0,Math.min(59,Math.round(Number(c.value)||0))),n=(e*60+t)*6e4;return n>=6e4?n:null},C=()=>{let e=Math.max(0,Math.round(Number(s.value)||0)),t=Math.max(0,Math.round(Number(c.value)||0));return`${e?`${e}h `:``}${t}m`},w=e=>{let n=H(),r=S(),i=p.value.trim()||`Timer (${C()})`;this.conn.send({type:`timer-add`,id:n,fireInMs:r,label:i,action:e}),this.timers=[...this.timers,{id:n,label:i,fireAtMs:Date.now()+r,hasAction:!!e}],this.notifications.permission==="default"&&this.requestNotifications(),this.notifications.flash(`Timer started`,`"${i}" — I'll ping you in ${C()}.`,`success`),t()};y.onclick=()=>{if(S()==null){this.notifications.flash(`Set a time`,`Choose at least 1 minute.`,`warning`);return}let t=h.value;if(t===`none`){w(void 0);return}e.style.display=`none`;let n=t===`text`?`Drag the crosshair onto the target input and type your prompt. The timer will click it to focus, insert the text and press Enter when time is up.`:t===`key`?`Drag the crosshair onto the field. The timer will click it to focus and press Enter when time is up.`:`Drag the crosshair onto the button or UI. The timer will click it when time is up.`;Se(this.view,this.root,{withText:t===`text`,hint:n,confirmLabel:`Confirm target`},n=>{if(!n){e.style.display=``;return}let r;r=t===`click`?{kind:`click`,x:n.nx,y:n.ny,button:`left`}:t===`key`?{kind:`key`,key:`Enter`,x:n.nx,y:n.ny}:{kind:`text`,text:n.text??``,x:n.nx,y:n.ny},w(r)})},_.append(v,y),n.append(r,a,o,f,m,_),e.appendChild(n),this.root.appendChild(e)}beginMonitor(){this.close();let e=U(`div`,`wd-dialog-overlay`),t=()=>{this.renderPicker=null,this.renderAlways=null,e.remove(),this.open()};e.addEventListener(`pointerdown`,n=>{n.target===e&&t()});let n=U(`div`,`wd-dialog`),r=U(`div`,`wd-dialog-head`);r.append(U(`h2`,``,`Monitor a session`));let i=U(`button`,`wd-dialog-x`);i.appendChild(g(`x`)),i.onclick=t,r.appendChild(i);let a=U(`p`,`wd-dialog-help`,`Pick a running AI session to watch. WhipDesk finds them automatically — no setup, no wrappers. You'll get one ping the moment the agent stops working (it's waiting on you or has gone idle).`),o=U(`div`,`wd-mon-pick-head`),s=U(`button`,`wd-btn`);s.append(U(`span`,`wd-btn-label`,`Rescan`)),s.onclick=()=>this.conn.send({type:`monitor-scan`}),o.append(U(`span`,`wd-form-label`,`Running sessions`),s);let c=U(`div`,`wd-mon-pick`),l=``,u=()=>{if(c.replaceChildren(),this.monitorSessions.length===0){l=``,v(),p(),c.appendChild(U(`p`,`wd-dialog-help`,`No AI sessions detected yet. Start Claude Code, Codex, Gemini, or Aider, then Rescan.`));return}(!l||!this.monitorSessions.some(e=>e.key===l))&&(l=this.monitorSessions[0].key);for(let e of this.monitorSessions){let t=U(`button`,`wd-mon-item`);t.type=`button`,e.key===l&&t.classList.add(`on`);let n=U(`div`,`wd-mon-item-main`);n.append(g(`activity`,15),U(`span`,`wd-mon-item-title`,`${W(e.agent)} · ${e.title}`));let r=U(`span`,`wd-mon-state`);r.dataset.state=e.state,r.textContent=De(e.state),t.append(n,r),t.onclick=()=>{l=e.key,u()},c.appendChild(t)}v(),p()};this.renderPicker=u;let d=U(`div`,`wd-form-row`);d.appendChild(U(`label`,`wd-form-label`,`Always alert me`));let f=U(`div`,`wd-mon-always`);d.appendChild(f);let p=()=>{f.replaceChildren();let e=this.monitorSessions.find(e=>e.key===l);if(!e){f.appendChild(U(`p`,`wd-dialog-help`,`Pick a session above to always alert for its agent.`));return}let t=e.agent,n=U(`label`,`wd-check`),r=U(`input`);r.type=`checkbox`,r.checked=this.alwaysAgents.has(t),r.onchange=()=>{r.checked?this.alwaysAgents.add(t):this.alwaysAgents.delete(t),this.conn.send({type:`monitor-always`,agent:t,enabled:r.checked}),r.checked&&this.notifications.permission==="default"&&this.requestNotifications(),p()};let i=U(`span`,`wd-check-text`);i.append(document.createTextNode(`Always monitor every ${W(t)} session`),U(`span`,`wd-check-sub`,`Keeps alerting across agent and WhipDesk restarts — no need to re-add it.`)),n.append(r,i),f.appendChild(n)};this.renderAlways=p;let m=U(`div`,`wd-dialog-actions`),h=U(`button`,`wd-btn`);h.append(U(`span`,`wd-btn-label`,`Cancel`)),h.onclick=t;let _=U(`button`,`wd-btn wd-go`);_.append(g(`activity`),U(`span`,`wd-btn-label`,`Add monitor`));let v=()=>_.disabled=!l;v(),_.onclick=()=>{let e=this.monitorSessions.find(e=>e.key===l);if(!e)return;let n=H(),r=e.title;this.conn.send({type:`monitor-add`,id:n,key:e.key,agent:e.agent,label:r}),this.monitors=[...this.monitors,{id:n,key:e.key,agent:e.agent,label:r,state:e.state,live:!0}],this.notifications.permission==="default"&&this.requestNotifications(),this.notifications.flash(`Monitoring started`,`Watching ${W(e.agent)} · ${r}.`,`success`),t()},m.append(h,_),n.append(r,a,o,c,d,m),e.appendChild(n),this.root.appendChild(e),u(),this.conn.send({type:`monitor-scan`})}};function ke(){let e=new URLSearchParams(location.hash.replace(/^#/,``));return{token:e.get(`t`)??e.get(`token`)??``,remote:e.get(`remote`)===`1`||e.has(`device`),device:e.get(`device`)??e.get(`d`)??``}}function Ae(){return`${location.protocol===`https:`?`wss`:`ws`}://${location.host}/ws`}async function je(){let e=window.__WHIPDESK_FB__;if(e?.apiKey)return e;try{let e=await fetch(`firebase.json`,{cache:`no-store`});if(e.ok)return await e.json()}catch{}return null}var G=document.getElementById(`app`);if(!G)throw Error(`#app not found`);var K=$(`canvas`,`wd-screen`);K.id=`wd-screen`;var Me=$(`div`,`wd-toasts`);G.append(K,Me);var{token:q,remote:J,device:Y}=ke(),X=new xe(K),Z=new oe(Me),Ne=new ce(G),Q=new p(G);async function Pe(e){if(J&&Y){if(e)return new he(Y,q,e);Z.show({type:`notification`,id:`no-fb`,title:`Remote unavailable`,body:`Firebase config not found for remote mode.`,level:`error`,source:`client`,t:Date.now()})}return new u(Ae(),q)}async function Fe(){let e=J&&Y?await je():null,t=await Pe(e),n=new ae(K,X,t),r=new Oe(G,t,X,Z,()=>e?fe(e,Z):Z.requestPermission()),i=new ne(G,{conn:t,view:X,input:n,notifications:Z,watchers:r}),a=document.createElement(`video`);a.muted=!0,a.autoplay=!0,a.playsInline=!0,a.setAttribute(`playsinline`,``),a.style.display=`none`,G.append(a);let o=document.createElement(`video`);o.muted=!0,o.autoplay=!0,o.playsInline=!0,o.setAttribute(`playsinline`,``),o.style.display=`none`,G.append(o);let s=!1,c=!1,l=null,u=0,d=e=>e.w>=.999&&e.h>=.999,f=null,p=0,m=0,h=!1,g=0,_=document.createElement(`div`);_.className=`wd-sharpen hidden`,_.setAttribute(`aria-label`,`updating view`),G.append(_);let v=()=>{h=!1,_.classList.add(`hidden`)},y=e=>{let n=e.w>=.92&&e.h>=.92?{x:0,y:0,w:1,h:1}:e,r=d(n);if(l){if(r&&d(l))return;if(!r){let e=.08;if(Math.abs(n.x-l.x)<n.w*e&&Math.abs(n.y-l.y)<n.h*e&&Math.abs(n.w-l.w)<n.w*e&&Math.abs(n.h-l.h)<n.h*e)return}}l=n,p=Date.now(),m=0,h=!f||!b(n,f),h&&(g=Date.now()),t.send({type:`set-viewport`,x:n.x,y:n.y,w:n.w,h:n.h})},b=(e,t)=>Math.abs(e.x-t.x)<.005&&Math.abs(e.y-t.y)<.005&&Math.abs(e.w-t.w)<.005&&Math.abs(e.h-t.h)<.005;window.setInterval(()=>{let e=Date.now()-g;if(_.classList.toggle(`hidden`,!(h&&e>1200&&e<3e4)),!(!l||!f)){if(b(f,l)){m=0;return}Date.now()-p<1200||m>=3||(m+=1,p=Date.now(),t.send({type:`set-viewport`,x:l.x,y:l.y,w:l.w,h:l.h}))}},1e3);let x=!1,S=!1,C=()=>{let e=X.getZoom()>1.01;window.clearTimeout(u),u=window.setTimeout(()=>{u=0,y(X.getZoom()>1.01?X.getVisibleRegion():{x:0,y:0,w:1,h:1})},e?400:0)};X.setOnView(()=>{x=!0,C()}),X.setOnGesture(e=>{e?(S=u!==0,S&&(window.clearTimeout(u),u=0),x=!1):!x&&S&&(S=!1,C())}),t.on(`status`,e=>{i.setStatus(e),e===`connected`?(c=!1,Q.hide()):(!s||c)&&Q.show(s?`Reconnecting…`:void 0),e===`disconnected`&&(l=null,f=null,v())}),t.on(`transport`,e=>i.setTransport(e)),t.on(`welcome`,e=>{let t=!s;s=!0,Q.hide(),Ne.hide(),X.setScreen(e.screen),i.setWelcome(e),t?(X.setZoom(1),l=null,y({x:0,y:0,w:1,h:1})):(l=null,window.clearTimeout(u),u=0,y(X.getZoom()>1.01?X.getVisibleRegion():{x:0,y:0,w:1,h:1}))}),t.on(`versionMismatch`,e=>Ie(e.agentVersion)),t.on(`pinRequired`,e=>{Q.hide(),Ne.show(e,e=>t.submitPin(e))}),t.on(`presence`,e=>i.setPresence(e));let w=0,T=0,E=0,D=()=>i.setAlertCount(w+T+E);if(t.on(`watchers`,e=>{w=e.length,r.setRegions(e),D()}),t.on(`timers`,e=>{T=e.length,r.setTimers(e),D()}),t.on(`monitors`,e=>{E=e.length,r.setMonitors(e),D()}),t.on(`monitorAlways`,e=>{r.setAlwaysAgents(e)}),t.on(`screenRegion`,e=>{f={x:e.x,y:e.y,w:e.w,h:e.h},e.active&&v();let t=d(e)?null:e;e.active?X.setFrameRegionActive(t):X.setFrameRegion(t)}),t.on(`videoTrack`,e=>{if(!e){a.srcObject=null,X.setVideoSource(null);return}a.srcObject=e,a.play().catch(()=>{}),X.setVideoSource(a)}),t.on(`overviewTrack`,e=>{if(!e){o.srcObject=null,X.setOverviewSource(null);return}o.srcObject=e,o.play().catch(()=>{}),X.setOverviewSource(o)}),t.on(`netStats`,({fps:e,rtt:t})=>i.setNetStats(e,t)),t.on(`screenMeta`,({screen:e,activeDisplay:t})=>{X.setScreen(e),t!==void 0&&i.setActiveDisplay(t)}),t.on(`notification`,e=>Z.show(e)),t.on(`error`,e=>i.flashError(e)),e){let n=!1;t.on(`welcome`,()=>{n||Z.permission!==`granted`||(n=!0,fe(e,Z))})}let O=()=>{document.hidden||t.isHealthy()||(c=!0,Q.show(`Reconnecting…`),t.wake())};document.addEventListener(`visibilitychange`,()=>{t.setVisible(!document.hidden),document.hidden||O()}),window.addEventListener(`pageshow`,O),window.addEventListener(`pagehide`,e=>{e.persisted||t.close()}),q||i.flashError(`No pairing token in the URL (#t=…). Re-open the link/QR from the agent.`),t.connect()}Fe();function $(e,t){let n=document.createElement(e);return n.className=t,n}function Ie(e){let t=`wd-outdated-banner`;if(document.getElementById(t))return;let n=document.createElement(`div`);n.id=t,n.style.cssText=`position:fixed;top:0;left:0;right:0;z-index:9999;background:#7a2e00;color:#fff;font:14px/1.4 system-ui,sans-serif;padding:10px 40px 10px 14px;text-align:center;box-shadow:0 2px 8px rgba(0,0,0,.35)`,n.innerHTML=`⚠️ This WhipDesk agent${e?` (agent ${e})`:``} is out of date. Update from <a style="color:#ffd9a6" href="https://github.com/BinaryBananaLLC/WhipDesk/releases/latest" target="_blank" rel="noreferrer noopener">the latest release</a> or run <code>npm i -g whipdesk@latest</code>.`;let r=document.createElement(`button`);r.textContent=`✕`,r.setAttribute(`aria-label`,`Dismiss`),r.style.cssText=`position:absolute;right:8px;top:6px;background:none;border:none;color:#fff;font-size:16px;cursor:pointer`,r.onclick=()=>n.remove(),n.appendChild(r),document.body.appendChild(n)}
|
|
Binary file
|
|
@@ -2,13 +2,41 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* WhipDesk FCM background handler (service worker).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Shows a notification for every incoming web push even when the controller PWA is closed.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: the `push` listener is registered SYNCHRONOUSLY at script evaluation and displays
|
|
8
|
+
* the notification itself — no Firebase SDK. The previous version initialized the FCM compat SDK
|
|
9
|
+
* asynchronously (after fetching ./firebase.json), but a push that WAKES a cold service worker
|
|
10
|
+
* dispatches before any late-registered handler exists, so nothing called showNotification and
|
|
11
|
+
* Chrome displayed its generic "This site has been updated in the background" fallback instead
|
|
12
|
+
* of the real alert. Token registration lives page-side (src/push.ts); the worker only needs to
|
|
13
|
+
* parse the FCM webpush payload ({ notification: { title, body, ... }, data: {...} }) and render
|
|
14
|
+
* it, which needs no SDK at all.
|
|
9
15
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
self.addEventListener("push", (event) => {
|
|
17
|
+
let payload = {};
|
|
18
|
+
try {
|
|
19
|
+
payload = event.data ? event.data.json() : {};
|
|
20
|
+
} catch (e) {
|
|
21
|
+
/* non-JSON push — fall through to the generic title below */
|
|
22
|
+
}
|
|
23
|
+
const n = payload.notification || {};
|
|
24
|
+
const d = payload.data || {};
|
|
25
|
+
const title = n.title || d.title || "WhipDesk";
|
|
26
|
+
const body = n.body || d.body || "";
|
|
27
|
+
event.waitUntil(
|
|
28
|
+
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((wins) => {
|
|
29
|
+
// App open and visible => its own in-app alerts cover this; skip the OS notification
|
|
30
|
+
// (same policy the FCM SDK applied).
|
|
31
|
+
if (wins.some((w) => w.visibilityState === "visible")) return;
|
|
32
|
+
return self.registration.showNotification(title, {
|
|
33
|
+
body,
|
|
34
|
+
tag: n.tag || d.tag || "whipdesk-alert",
|
|
35
|
+
renotify: true,
|
|
36
|
+
});
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
12
40
|
|
|
13
41
|
self.addEventListener("notificationclick", (event) => {
|
|
14
42
|
event.notification.close();
|
|
@@ -21,23 +49,3 @@ self.addEventListener("notificationclick", (event) => {
|
|
|
21
49
|
}),
|
|
22
50
|
);
|
|
23
51
|
});
|
|
24
|
-
|
|
25
|
-
(async () => {
|
|
26
|
-
try {
|
|
27
|
-
const res = await fetch("./firebase.json", { cache: "no-store" });
|
|
28
|
-
if (!res.ok) return;
|
|
29
|
-
const config = await res.json();
|
|
30
|
-
firebase.initializeApp(config);
|
|
31
|
-
const messaging = firebase.messaging();
|
|
32
|
-
messaging.onBackgroundMessage((payload) => {
|
|
33
|
-
const n = (payload && payload.notification) || {};
|
|
34
|
-
self.registration.showNotification(n.title || "WhipDesk", {
|
|
35
|
-
body: n.body || "",
|
|
36
|
-
tag: (payload && payload.data && payload.data.tag) || "whipdesk-alert",
|
|
37
|
-
renotify: true,
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
} catch (e) {
|
|
41
|
-
/* no config / unsupported browser — background push stays disabled */
|
|
42
|
-
}
|
|
43
|
-
})();
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<meta name="apple-mobile-web-app-title" content="WhipDesk" />
|
|
19
19
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
20
20
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
21
|
-
<script type="module" crossorigin src="./assets/index-
|
|
22
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
21
|
+
<script type="module" crossorigin src="./assets/index-_PAhuG6R.js"></script>
|
|
22
|
+
<link rel="stylesheet" crossorigin href="./assets/index-C_O8Rc7r.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body>
|
|
25
25
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whipdesk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WhipDesk desktop host: screen capture, input injection, notification hub, and the HTTP/WebSocket server that serves the mobile client.",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
7
|
-
"homepage": "https://
|
|
7
|
+
"homepage": "https://whipdesk.com",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/BinaryBananaLLC/WhipDesk.git",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist/agent.cjs",
|
|
18
|
-
"dist/mobile-web"
|
|
18
|
+
"dist/mobile-web",
|
|
19
|
+
"README.md"
|
|
19
20
|
],
|
|
20
21
|
"engines": {
|
|
21
22
|
"node": ">=20"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--bg:#f4f6f9;--panel:#fff;--panel-2:#eef1f5;--border:#d6dce4;--text:#1a2330;--muted:#5f6b7a;--accent:#2f6fed;--accent-2:#1aa66b;--warn:#c77700;--error:#d83b3b;--shadow:#141e3229;--screen-bg:#1b2027}@media (prefers-color-scheme:dark){:root{--bg:#0b0e14;--panel:#151a23;--panel-2:#1d2430;--border:#2a3342;--text:#e6edf3;--muted:#8b98a9;--accent:#4ea1ff;--accent-2:#36d399;--warn:#f5a623;--error:#ff5c5c;--shadow:#00000080;--screen-bg:#000}}*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}html,body{background:var(--bg);height:100%;color:var(--text);overscroll-behavior:none;margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,system-ui,sans-serif;overflow:hidden}#app{-webkit-user-select:none;user-select:none;position:fixed;inset:0}input,textarea{-webkit-user-select:text;user-select:text}.wd-screen{background:var(--screen-bg);touch-action:none;width:100%;height:100%;display:block;position:absolute;inset:0}.wd-statusbar{top:calc(env(safe-area-inset-top,0px) + 8px);background:var(--panel);border:1px solid var(--border);max-width:calc(100% - 64px);min-height:38px;box-shadow:0 2px 10px var(--shadow);z-index:5;cursor:pointer;border-radius:10px;align-items:center;gap:8px;padding:6px 10px;font-size:12px;transition:max-width .25s,padding .25s;display:flex;position:absolute;left:8px}.wd-statusbar.collapsed{max-width:124px;padding:6px 8px;overflow:hidden}.wd-statusbar.collapsed .wd-status-text,.wd-statusbar.collapsed .wd-watchers{display:none}.wd-dot{background:var(--muted);border-radius:50%;flex:none;width:9px;height:9px}.wd-dot[data-status=connected]{background:var(--accent-2)}.wd-dot[data-status=connecting]{background:var(--warn)}.wd-dot[data-status=disconnected]{background:var(--error)}.wd-status-text{color:var(--text);white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;font-weight:600;overflow:hidden}.wd-watchers{color:var(--warn);white-space:nowrap;flex:none;font-weight:700}.wd-transport{letter-spacing:.4px;color:#fff;background:var(--accent-2);text-transform:uppercase;border-radius:6px;flex:none;padding:2px 6px;font-size:11px;font-weight:800}.wd-transport.hidden{display:none}.wd-transport[data-kind=turn]{color:#111;background:#e0a800}.wd-transport[data-kind=stun],.wd-transport[data-kind=direct]{background:var(--accent-2)}.wd-transport[data-kind=lan]{background:#3b82f6}.wd-bell{top:calc(env(safe-area-inset-top,0px) + 8px);z-index:6;border:1px solid var(--border);background:var(--panel);min-width:38px;min-height:38px;color:var(--text);box-shadow:0 2px 10px var(--shadow);cursor:pointer;border-radius:10px;place-items:center;padding:6px;display:grid;position:absolute;right:8px}.wd-badge{background:var(--accent);color:#fff;text-align:center;min-width:18px;height:18px;box-shadow:0 1px 4px var(--shadow);border-radius:9px;padding:0 5px;font-size:11px;font-weight:700;line-height:18px;position:absolute;top:-6px;right:-6px}.wd-ribbon{z-index:6;padding-bottom:env(safe-area-inset-bottom,0px);background:var(--panel);border-top:1px solid var(--border);box-shadow:0 -4px 16px var(--shadow);position:absolute;bottom:0;left:0;right:0}.wd-panel{flex-direction:column;display:flex}.wd-options{border-bottom:1px solid var(--border);padding:6px}.wd-pane{flex-wrap:wrap;justify-content:center;align-items:center;gap:5px;display:flex}.wd-pane-col{flex-direction:column;align-items:stretch;gap:8px}.wd-pane-single-row{scrollbar-width:none;flex-wrap:nowrap;justify-content:center;align-items:stretch;gap:4px;overflow-x:auto}.wd-pane-single-row::-webkit-scrollbar{display:none}.wd-pane-single-row .wd-group{flex:0 auto;gap:2px;min-width:0;padding:3px 4px}.wd-pane-single-row .wd-group-items{flex-wrap:nowrap;gap:3px}.wd-pane-single-row .wd-btn{min-width:0}.wd-pane-single-row .wd-btn.wd-icon-only{min-width:30px;padding:4px}.wd-pane-single-row .wd-btn.wd-icon-only svg{width:18px;height:18px}.wd-pane-single-row .wd-btn.wd-go{gap:4px;padding:6px 9px;font-size:12px}@media (min-width:390px){.wd-pane-single-row{gap:5px}.wd-pane-single-row .wd-group{padding:3px 6px}.wd-pane-single-row .wd-btn.wd-icon-only{min-width:36px;padding:5px}.wd-pane-single-row .wd-btn.wd-icon-only svg{width:20px;height:20px}.wd-pane-single-row .wd-btn.wd-go{padding:7px 12px;font-size:13px}}@media (min-width:560px){.wd-pane-single-row .wd-btn.wd-icon-only{min-width:42px;padding:6px}.wd-pane-single-row .wd-btn.wd-go{padding:8px 16px}}@media (min-width:820px){.wd-ribbon{border:1px solid var(--border);width:min(880px,94vw);box-shadow:0 10px 34px var(--shadow);border-radius:16px;padding-bottom:0;bottom:16px;left:50%;right:auto;transform:translate(-50%)}.wd-options{padding:5px 6px}.wd-btn{min-height:32px;font-size:12px}.wd-pane-single-row .wd-btn.wd-icon-only{min-width:32px;padding:4px}.wd-pane-single-row .wd-btn.wd-icon-only svg{width:17px;height:17px}.wd-pane-single-row .wd-btn.wd-go{padding:6px 12px;font-size:12px}.wd-group{padding:3px 6px}.wd-bell{min-width:32px;min-height:32px}.wd-statusbar{min-height:32px}}.wd-group{border:1px solid var(--border);background:var(--panel-2);border-radius:12px;flex-direction:column;align-items:stretch;gap:3px;padding:4px 6px;display:inline-flex}.wd-group-label{letter-spacing:.03em;text-transform:uppercase;color:var(--muted);white-space:nowrap;text-align:center;font-size:9px;font-weight:700}.wd-group-head{justify-content:space-between;align-items:center;gap:4px;display:flex}.wd-mode-toggle{border:1px solid var(--border);background:var(--panel);border-radius:999px;display:inline-flex;overflow:hidden}.wd-mode-btn{color:var(--muted);cursor:pointer;background:0 0;border:0;padding:4px 7px;font-size:10px;font-weight:700;line-height:1}.wd-mode-btn.on{background:var(--accent);color:#fff}.wd-group-items{flex-wrap:wrap;justify-content:center;align-items:center;gap:3px;display:flex}.wd-wrap{flex-wrap:wrap;justify-content:center;gap:6px;display:flex}.wd-btn-label{line-height:1}.wd-row{flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;display:flex}.wd-tabs{align-items:center;gap:4px;padding:5px 6px;display:flex}.wd-tab{min-height:48px;color:var(--muted);cursor:pointer;touch-action:manipulation;background:0 0;border:1px solid #0000;border-radius:10px;flex-direction:column;flex:1;align-items:center;gap:2px;padding:6px 4px;font-size:11px;font-weight:600;display:flex}.wd-tab .wd-tab-label{line-height:1}.wd-tab.on{background:var(--panel-2);color:var(--accent);border-color:var(--border)}.wd-collapse{border:1px solid var(--border);background:var(--panel);min-width:44px;min-height:48px;color:var(--text);cursor:pointer;border-radius:10px;flex:none;place-items:center;display:grid}.wd-btn{border:1px solid var(--border);background:var(--panel);min-width:40px;min-height:40px;color:var(--text);cursor:pointer;-webkit-user-select:none;user-select:none;touch-action:manipulation;white-space:nowrap;border-radius:10px;justify-content:center;align-items:center;gap:7px;padding:6px 10px;font-size:13px;font-weight:600;line-height:1;display:inline-flex}.wd-btn svg{flex:none}.wd-btn.wd-icon-only{min-width:40px;padding:6px}.wd-btn:active{background:var(--panel-2);transform:translateY(1px)}.wd-btn.on{border-color:var(--accent);color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}.wd-btn.wd-primary{background:var(--accent);border-color:var(--accent);color:#fff}.wd-btn.wd-go{background:var(--accent-2);border-color:var(--accent-2);color:#fff;padding:8px 14px}.wd-btn.wd-go:active{filter:brightness(.92);transform:translateY(1px)}.wd-seg{border:1px solid var(--border);border-radius:10px;display:inline-flex;overflow:hidden}.wd-seg-btn{background:var(--panel);min-height:44px;color:var(--muted);cursor:pointer;touch-action:manipulation;border:none;align-items:center;gap:6px;padding:8px 18px;font-size:15px;font-weight:600;display:inline-flex}.wd-seg-btn.on{background:var(--accent);color:#fff}.wd-zoom{text-align:center;font-variant-numeric:tabular-nums;min-width:52px;color:var(--text);font-weight:600}.wd-hint{text-align:center;color:var(--muted);flex-basis:100%;font-size:12px}.wd-type-input{resize:vertical;border:1px solid var(--border);background:var(--panel-2);width:100%;min-height:46px;color:var(--text);border-radius:10px;padding:10px;font-family:inherit;font-size:16px}.wd-keys-row{flex-wrap:wrap;justify-content:center;gap:6px;display:flex}.wd-type-actions{gap:6px;display:flex}.wd-type-actions .wd-btn{flex:1}.wd-monitor-list{flex-wrap:wrap;justify-content:center;gap:6px;display:flex}.wd-toasts{top:calc(env(safe-area-inset-top,0px) + 36px);z-index:9;pointer-events:none;flex-direction:column;gap:8px;display:flex;position:absolute;left:8px;right:8px}.wd-toast{background:var(--panel);border:1px solid var(--border);border-left-width:4px;border-radius:10px;flex-direction:column;gap:2px;padding:10px 12px;transition:opacity .3s,transform .3s;display:flex;box-shadow:0 6px 20px #0006}.wd-toast strong{font-size:14px}.wd-toast span{color:var(--muted);font-size:13px}.wd-toast.wd-success{border-left-color:var(--accent-2)}.wd-toast.wd-warning{border-left-color:var(--warn)}.wd-toast.wd-error{border-left-color:var(--error)}.wd-toast.wd-info{border-left-color:var(--accent)}.wd-toast.wd-hide{opacity:0;transform:translateY(-8px)}.hidden{display:none!important}.wd-pin-overlay{z-index:20;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);background:#06090eeb;place-items:center;padding:20px;display:grid;position:absolute;inset:0}.wd-pin-card{background:var(--panel);border:1px solid var(--border);text-align:center;border-radius:14px;width:100%;max-width:320px;padding:24px}.wd-pin-whip{object-fit:contain;width:64px;height:64px;margin:0 auto 12px;display:block}.wd-pin-card h2{margin:0 0 8px;font-size:20px}.wd-pin-msg{color:var(--muted);margin:0 0 16px;font-size:14px}.wd-pin-msg.err{color:var(--error)}.wd-pin-input{border:1px solid var(--border);background:var(--panel-2);width:100%;color:var(--text);text-align:center;letter-spacing:6px;border-radius:10px;margin-bottom:14px;padding:14px;font-size:22px}.wd-pin-submit{background:var(--accent);color:#fff;cursor:pointer;touch-action:manipulation;border:none;border-radius:12px;width:100%;min-height:50px;font-size:17px;font-weight:700}.wd-pin-submit:active{filter:brightness(.92);transform:translateY(1px)}.wd-pin-back{color:var(--muted);cursor:pointer;background:0 0;border:none;margin:14px auto 0;padding:6px 10px;font-size:13px;font-weight:600;display:block}.wd-pin-back:active{color:var(--text)}.wd-connecting{z-index:18;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);background:#06090eeb;place-items:center;padding:20px;display:grid;position:absolute;inset:0}.wd-connecting-card{text-align:center;width:100%;max-width:320px}.wd-connecting-whip{object-fit:contain;transform-origin:0 100%;width:56px;height:56px;margin:0 auto 18px;animation:1.4s ease-in-out infinite wd-whip-crack;display:block}.wd-connecting-spinner{border:3px solid #ffffff38;border-top-color:#4ea1ff;border-radius:50%;width:34px;height:34px;margin:0 auto 16px;animation:.8s linear infinite wd-spin}.wd-connecting-msg{color:#eef2f7;text-shadow:0 1px 2px #00000080;margin:0;font-size:15px;font-weight:600}@keyframes wd-spin{to{transform:rotate(360deg)}}@keyframes wd-whip-crack{0%,to{transform:rotate(-16deg)}40%{transform:rotate(14deg)}55%{transform:rotate(5deg)}70%{transform:rotate(11deg)}}.wd-dialog-overlay{z-index:18;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#06090e99;place-items:center;padding:16px;display:grid;position:absolute;inset:0}.wd-dialog{background:var(--panel);border:1px solid var(--border);border-radius:14px;width:100%;max-width:380px;padding:16px}.wd-dialog-head{justify-content:space-between;align-items:center;display:flex}.wd-dialog-head h2{margin:0;font-size:18px}.wd-dialog-x{border:1px solid var(--border);background:var(--panel-2);width:36px;height:36px;color:var(--text);cursor:pointer;border-radius:8px;place-items:center;display:grid}.wd-dialog-help{color:var(--muted);margin:8px 0 12px;font-size:13px}.wd-help-intro{margin:0 0 6px}.wd-help-list{flex-direction:column;gap:5px;margin:0 0 8px;padding-left:18px;display:flex}.wd-help-list strong{color:var(--text)}.wd-help-note{margin:0;font-style:italic}.wd-watch-list{flex-direction:column;gap:8px;margin-bottom:12px;display:flex}.wd-watch-row{border:1px solid var(--border);background:var(--panel-2);border-radius:10px;align-items:center;gap:8px;padding:8px 10px;display:flex}.wd-watch-name{text-align:left;color:var(--text);cursor:pointer;background:0 0;border:none;flex:1;padding:4px 0;font-family:inherit;font-size:14px;font-weight:600}.wd-watch-name:active{color:var(--accent)}.wd-timer-info{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.wd-timer-name{color:var(--text);align-items:center;gap:6px;font-size:14px;font-weight:600;display:flex}.wd-timer-name svg{color:var(--accent);flex:none}.wd-timer-remain{color:var(--muted);font-variant-numeric:tabular-nums;font-size:12px}.wd-timer-tag{letter-spacing:.5px;text-transform:uppercase;color:#fff;background:var(--accent);border-radius:6px;flex:none;padding:2px 6px;font-size:10px;font-weight:800}.wd-dialog-actions{gap:8px;margin-top:4px;display:flex}.wd-dialog-actions .wd-btn{flex:1;justify-content:center}.wd-dialog-actions.wd-actions-stack{flex-direction:column}.wd-actions-stack .wd-btn{width:100%}.wd-mon-state{letter-spacing:.4px;text-transform:uppercase;color:#fff;background:var(--muted);border-radius:6px;flex:none;padding:2px 6px;font-size:10px;font-weight:800}.wd-mon-state[data-state=working]{background:var(--accent-2)}.wd-mon-state[data-state=blocked]{background:var(--warn)}.wd-mon-state[data-state=finished]{background:var(--accent)}.wd-mon-state[data-state=crashed]{background:var(--error)}.wd-mon-pick-head{justify-content:space-between;align-items:center;gap:8px;margin-bottom:6px;display:flex}.wd-mon-pick{flex-direction:column;gap:6px;max-height:40vh;margin-bottom:12px;display:flex;overflow-y:auto}.wd-mon-item{border:1px solid var(--border);background:var(--panel);width:100%;color:var(--text);cursor:pointer;text-align:left;border-radius:10px;justify-content:space-between;align-items:center;gap:10px;padding:9px 11px;display:flex}.wd-mon-item.on{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}.wd-mon-item-main{align-items:center;gap:8px;min-width:0;display:flex}.wd-mon-item-main svg{color:var(--accent);flex:none}.wd-mon-item-title{white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:600;overflow:hidden}.wd-mon-always{flex-direction:column;gap:8px;display:flex}.wd-check{color:var(--text);cursor:pointer;align-items:flex-start;gap:8px;font-size:13px;display:flex}.wd-check input{width:16px;height:16px;accent-color:var(--accent);flex:none;margin-top:1px}.wd-check-text{flex-direction:column;gap:2px;display:flex}.wd-check-sub{color:var(--muted);font-size:11px}.wd-form-row{flex-direction:column;gap:5px;margin-bottom:12px;display:flex}.wd-form-label{color:var(--muted);font-size:12px;font-weight:700}.wd-input{border:1px solid var(--border);background:var(--panel-2);width:100%;color:var(--text);border-radius:10px;padding:10px 12px;font-family:inherit;font-size:15px}.wd-input-area{resize:vertical;min-height:64px}.wd-form-duration{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.wd-input-num{text-align:center;width:56px}.wd-form-unit{color:var(--muted);font-size:14px;font-weight:600}.wd-preset-row{flex-wrap:wrap;gap:6px;margin-bottom:10px;display:flex}.wd-preset{border:1px solid var(--border);background:var(--panel-2);min-width:48px;color:var(--text);cursor:pointer;border-radius:999px;flex:auto;padding:9px 10px;font-family:inherit;font-size:13px;font-weight:700}.wd-preset:active{background:var(--accent);color:#fff;border-color:#0000}.wd-stepper{align-items:center;gap:4px;display:flex}.wd-step-btn{border:1px solid var(--border);background:var(--panel-2);width:38px;height:38px;color:var(--text);cursor:pointer;touch-action:manipulation;border-radius:10px;flex:none;font-family:inherit;font-size:22px;font-weight:700;line-height:1}.wd-step-btn:active{background:var(--accent);color:#fff;border-color:#0000}.wd-form-target{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.wd-form-target-status{color:var(--muted);font-size:13px}.wd-form-target-status.set{color:var(--accent-2);font-weight:700}.wd-pick-banner{top:calc(env(safe-area-inset-top,0px) + 56px);z-index:30;background:var(--accent);color:#fff;box-shadow:0 6px 20px var(--shadow);pointer-events:none;text-align:center;border-radius:999px;max-width:90%;padding:10px 16px;font-size:13px;font-weight:700;position:absolute;left:50%;transform:translate(-50%)}.wd-conn-row{border-bottom:1px solid var(--border);align-items:flex-start;gap:10px;padding:10px 0;display:flex}.wd-conn-label{width:92px;color:var(--muted);flex:none;padding-top:2px;font-size:13px;font-weight:700}.wd-conn-value{color:var(--text);word-break:break-word;flex:1;font-size:15px;font-weight:600}.wd-conn-route{flex-direction:column;flex:1;gap:4px;display:flex}.wd-conn-route .wd-transport{align-self:flex-start;position:static}.wd-conn-desc{color:var(--muted);font-size:12.5px}.wd-conn-status{flex:1;align-items:center;gap:8px;display:flex}.wd-conn-error{border:1px solid var(--error);color:var(--error);word-break:break-word;background:#dc35451f;border-radius:10px;margin-top:12px;padding:9px 11px;font-size:12.5px;font-weight:600}.wd-disconnect{background:var(--error);color:#fff;border-color:#0000;justify-content:center;width:100%;min-height:46px;margin-top:14px;font-size:15px;font-weight:700}.wd-support-link{border:1px solid var(--border);color:var(--muted);cursor:pointer;background:0 0;border-radius:999px;align-self:flex-start;align-items:center;gap:6px;margin-top:8px;padding:5px 12px;font-size:12.5px;font-weight:600;transition:color .15s,border-color .15s;display:inline-flex}.wd-support-link:hover,.wd-support-link:active{color:var(--accent);border-color:var(--accent)}.wd-support-link svg{flex:none;width:14px;height:14px}.wd-conn-feedback{border-top:1px solid var(--border);text-align:center;margin-top:14px;padding-top:12px}.wd-conn-feedback-text{color:var(--muted);margin:0;font-size:12.5px}.wd-conn-feedback-links{justify-content:center;gap:10px;margin-top:9px;display:flex}.wd-conn-feedback-link{border:1px solid var(--border);color:var(--text);background:0 0;border-radius:999px;align-items:center;gap:7px;padding:7px 16px;font-size:13px;font-weight:600;text-decoration:none;transition:color .15s,border-color .15s;display:inline-flex}.wd-conn-feedback-link:hover,.wd-conn-feedback-link:active{color:var(--accent);border-color:var(--accent)}.wd-conn-feedback-link svg{flex:none;width:16px;height:16px}.wd-pin-support{margin-top:16px}.wd-placing .wd-ribbon,.wd-placing .wd-statusbar,.wd-placing .wd-bell{display:none}.wd-place-layer{z-index:40;touch-action:none;background:0 0;position:absolute;inset:0}.wd-place-marker{z-index:41;pointer-events:none;border:2px solid #4ea1ff;border-radius:50%;width:46px;height:46px;position:absolute;transform:translate(-50%,-50%);box-shadow:0 0 0 2px #00000080,0 0 14px #4ea1ffb3}.wd-place-marker:before,.wd-place-marker:after{content:"";background:#4ea1ff;position:absolute}.wd-place-marker:before{width:2px;top:-8px;bottom:-8px;left:50%;transform:translate(-50%)}.wd-place-marker:after{height:2px;top:50%;left:-8px;right:-8px;transform:translateY(-50%)}.wd-place-bar{z-index:42;padding:12px 14px calc(env(safe-area-inset-bottom,0px) + 12px);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);border-top:1px solid var(--border);background:#0a0e14f0;flex-direction:column;gap:10px;display:flex;position:absolute;bottom:0;left:0;right:0}.wd-place-hint{color:#eef2f7;text-align:center;margin:0;font-size:13px;font-weight:600}.wd-place-text{width:100%}.wd-place-buttons{gap:8px;display:flex}.wd-place-buttons .wd-btn{flex:1;justify-content:center}.wd-perm-row{border:1px solid var(--border);background:var(--panel-2);border-radius:10px;align-items:center;gap:8px;margin-bottom:12px;padding:8px 10px;display:flex}.wd-perm-dot{background:var(--muted);border-radius:50%;flex:none;width:9px;height:9px}.wd-perm-dot[data-state=on]{background:var(--accent-2)}.wd-perm-dot[data-state=warn]{background:var(--warn)}.wd-perm-dot[data-state=off]{background:var(--error)}.wd-perm-text{color:var(--muted);flex:1;font-size:12px}.wd-perm-enable{flex:none;min-width:auto;min-height:32px;padding:4px 12px;font-size:12px}.wd-dialog .wd-go{justify-content:center;width:100%}.wd-selector{z-index:19;border:2px solid var(--accent);touch-action:none;background:#2f6fed24;border-radius:6px;position:absolute;box-shadow:0 0 0 9999px #06090e59}.wd-selector-move{background:var(--accent);color:#fff;cursor:move;touch-action:none;border-radius:8px 0;place-items:center;width:38px;height:38px;display:grid;position:absolute;top:-1px;left:-1px}.wd-selector-handle{background:var(--accent);border:3px solid var(--panel);cursor:nwse-resize;touch-action:none;border-radius:50%;width:28px;height:28px;position:absolute;bottom:-12px;right:-12px}.wd-selector-bar{white-space:nowrap;gap:8px;display:flex;position:absolute;bottom:-56px;left:50%;transform:translate(-50%)}.wd-selector-info{top:calc(env(safe-area-inset-top,0px) + 52px);z-index:20;background:var(--panel);border:1px solid var(--border);border-left:4px solid var(--accent);max-width:430px;box-shadow:0 6px 20px var(--shadow);color:var(--text);text-align:center;pointer-events:none;border-radius:10px;margin:0 auto;padding:8px 12px;font-size:12.5px;line-height:1.35;position:absolute;left:12px;right:12px}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./index.esm-mXKu2C3o.js","./index.esm-B3ZTr43m.js","./index.esm-CmSagqpw.js","./index.esm-wogdVWd2.js","./index.esm-BgjPHsdM.js","./index.esm-Di8jSGaG.js"])))=>i.map(i=>d[i]);
|
|
2
|
-
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();function e(e){return typeof e==`object`&&!!e&&typeof e.type==`string`}var t=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]);function n(e){return new TextEncoder().encode(e)}function r(e){let n=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),r=e.length*8,i=new Uint8Array((e.length+8>>6)+1<<6);i.set(e),i[e.length]=128;let a=new DataView(i.buffer);a.setUint32(i.length-4,r>>>0,!1),a.setUint32(i.length-8,Math.floor(r/4294967296),!1);let o=new Uint32Array(64);for(let e=0;e<i.length;e+=64){for(let t=0;t<16;t++)o[t]=a.getUint32(e+t*4,!1);for(let e=16;e<64;e++){let t=o[e-15],n=o[e-2],r=(t>>>7|t<<25)^(t>>>18|t<<14)^t>>>3,i=(n>>>17|n<<15)^(n>>>19|n<<13)^n>>>10;o[e]=o[e-16]+r+o[e-7]+i>>>0}let[r,i,s,c,l,u,d,f]=n;for(let e=0;e<64;e++){let n=(l>>>6|l<<26)^(l>>>11|l<<21)^(l>>>25|l<<7),a=l&u^~l&d,p=f+n+a+t[e]+o[e]>>>0,m=((r>>>2|r<<30)^(r>>>13|r<<19)^(r>>>22|r<<10))+(r&i^r&s^i&s)>>>0;f=d,d=u,u=l,l=c+p>>>0,c=s,s=i,i=r,r=p+m>>>0}n[0]=n[0]+r>>>0,n[1]=n[1]+i>>>0,n[2]=n[2]+s>>>0,n[3]=n[3]+c>>>0,n[4]=n[4]+l>>>0,n[5]=n[5]+u>>>0,n[6]=n[6]+d>>>0,n[7]=n[7]+f>>>0}let s=new Uint8Array(32);new DataView(s.buffer).setUint32(0,n[0],!1);for(let e=0;e<8;e++)new DataView(s.buffer).setUint32(e*4,n[e],!1);return s}var i=`0123456789abcdef`;function a(e){let t=``;for(let n of e)t+=i[n>>4]+i[n&15];return t}function o(e){return a(r(n(e)))}function s(e,t,n){let r=o(`${t}:${e}`);for(let e=1;e<n;e++)r=o(r);return r}function c(e,t){return o(`${e}:${t}`)}var l=class{token;handlers={status:new Set,welcome:new Set,screenMeta:new Set,screenRegion:new Set,videoTrack:new Set,overviewTrack:new Set,transport:new Set,notification:new Set,presence:new Set,pinRequired:new Set,watchers:new Set,timers:new Set,monitors:new Set,monitorAlways:new Set,monitorSessions:new Set,netStats:new Set,versionMismatch:new Set,error:new Set};sender=()=>{};challenge=null;wrongPin=!1;rememberedPin=null;constructor(e){this.token=e}on(e,t){this.handlers[e].add(t)}emit(e,t){for(let n of this.handlers[e])n(t)}setSender(e){this.sender=e}send(e){try{this.sender(JSON.stringify(e))}catch{}}sendHello(){this.send({type:`hello`,protocol:1,token:this.token,role:`controller`,client:{userAgent:navigator.userAgent}})}submitPin(e){if(this.rememberedPin=e,!this.challenge)return;let{salt:t,iterations:n,nonce:r}=this.challenge,i=s(e,t,n);this.send({type:`auth`,response:c(i,r)})}setVisible(e){this.send({type:`visibility`,visible:e})}handleText(t){let n;try{n=JSON.parse(t)}catch{return}if(!e(n))return;let r=n;switch(r.type){case`welcome`:this.challenge=null,r.protocol!==1&&this.emit(`versionMismatch`,{agentProtocol:r.protocol,clientProtocol:1,agentVersion:r.agent?.version}),this.emit(`welcome`,r),this.emit(`timers`,r.timers??[]),this.emit(`monitors`,r.monitors??[]),this.emit(`monitorAlways`,r.alwaysAgents??[]);break;case`auth-required`:this.challenge={salt:r.salt,iterations:r.iterations,nonce:r.nonce},this.rememberedPin&&!this.wrongPin?this.submitPin(this.rememberedPin):this.emit(`pinRequired`,{attemptsLeft:r.attemptsLeft,retry:this.wrongPin}),this.wrongPin=!1;break;case`screen-meta`:this.emit(`screenMeta`,{screen:r.screen,activeDisplay:r.activeDisplay});break;case`screen-region`:this.emit(`screenRegion`,{x:r.x,y:r.y,w:r.w,h:r.h,active:r.active});break;case`notification`:this.emit(`notification`,r);break;case`presence`:this.emit(`presence`,r.watchers);break;case`watchers`:this.emit(`watchers`,r.regions);break;case`timers`:this.emit(`timers`,r.timers);break;case`monitors`:this.emit(`monitors`,r.monitors);break;case`monitor-always-agents`:this.emit(`monitorAlways`,r.agents);break;case`monitor-sessions`:this.emit(`monitorSessions`,r.sessions);break;case`error`:r.code===`pin`?(this.wrongPin=!0,this.rememberedPin=null):this.emit(`error`,r.message);break;case`pong`:break}}},u=class{url;core;ws=null;pc=null;dc=null;mainXcv=null;overviewXcv=null;reconnectTimer=0;statsTimer=0;closedByUser=!1;constructor(e,t){this.url=e,this.core=new l(t),this.core.setSender(e=>{if(this.dc&&this.dc.readyState===`open`)try{this.dc.send(e)}catch{}})}on(e,t){this.core.on(e,t)}send(e){this.core.send(e)}submitPin(e){this.core.submitPin(e)}setVisible(e){this.core.setVisible(e)}connect(){this.closedByUser=!1,this.open()}close(){this.closedByUser=!0,window.clearTimeout(this.reconnectTimer),this.teardown()}isHealthy(){return!this.closedByUser&&this.dc?.readyState===`open`&&this.pc?.connectionState===`connected`}wake(){this.closedByUser||this.isHealthy()||(window.clearTimeout(this.reconnectTimer),this.reconnectTimer=0,this.open())}teardown(){if(window.clearInterval(this.statsTimer),this.statsTimer=0,this.core.emit(`videoTrack`,null),this.core.emit(`overviewTrack`,null),this.dc){this.dc.onopen=this.dc.onclose=this.dc.onmessage=null;try{this.dc.close()}catch{}this.dc=null}if(this.pc){this.pc.onconnectionstatechange=null,this.pc.ontrack=null,this.pc.onicecandidate=null;try{this.pc.close()}catch{}this.pc=null}if(this.ws){this.ws.onopen=this.ws.onclose=this.ws.onmessage=this.ws.onerror=null;try{this.ws.close()}catch{}this.ws=null}this.mainXcv=null,this.overviewXcv=null}scheduleReconnect(){this.closedByUser||(window.clearTimeout(this.reconnectTimer),this.reconnectTimer=window.setTimeout(()=>this.open(),1500))}open(){this.core.emit(`status`,`connecting`),this.teardown();let e=new WebSocket(this.url);this.ws=e;let t=t=>{if(e.readyState===WebSocket.OPEN)try{e.send(JSON.stringify(t))}catch{}},n=new RTCPeerConnection({iceServers:[]});this.pc=n;let r=n.createDataChannel(`whipdesk`);r.binaryType=`arraybuffer`,this.dc=r,r.onopen=()=>{this.core.sendHello(),this.core.emit(`status`,`connected`),this.core.emit(`transport`,`LAN`),this.startStatsPoll(n)},r.onclose=()=>this.core.emit(`status`,`disconnected`),r.onmessage=e=>{typeof e.data==`string`&&this.core.handleText(e.data)};try{this.mainXcv=n.addTransceiver(`video`,{direction:`recvonly`}),this.overviewXcv=n.addTransceiver(`video`,{direction:`recvonly`})}catch{}n.ontrack=e=>{let t=e.streams[0]??new MediaStream([e.track]);this.overviewXcv&&e.transceiver===this.overviewXcv?this.core.emit(`overviewTrack`,t):this.core.emit(`videoTrack`,t)},n.onicecandidate=e=>{e.candidate&&t({kind:`candidate`,candidate:e.candidate.toJSON()})},n.onconnectionstatechange=()=>{let e=n.connectionState;(e===`failed`||e===`disconnected`||e===`closed`)&&(this.core.emit(`status`,`disconnected`),this.scheduleReconnect())};let i=!1;e.onopen=async()=>{try{let e=await n.createOffer();await n.setLocalDescription(e),t({kind:`offer`,sdp:n.localDescription?.sdp??``})}catch{}},e.onmessage=e=>{let t;try{t=JSON.parse(typeof e.data==`string`?e.data:``)}catch{return}if(t.kind===`answer`&&t.sdp&&!i)i=!0,n.setRemoteDescription({type:`answer`,sdp:t.sdp});else if(t.kind===`candidate`&&t.candidate)try{n.addIceCandidate(t.candidate)}catch{}else t.kind===`error`&&this.core.emit(`error`,String(t.message??`host error`))},e.onclose=()=>{this.core.emit(`status`,`disconnected`),this.scheduleReconnect()},e.onerror=()=>{}}startStatsPoll(e){window.clearInterval(this.statsTimer),this.statsTimer=window.setInterval(async()=>{let t=null,n=0;try{(await e.getStats()).forEach(e=>{e.type===`inbound-rtp`&&e.kind===`video`?n=Math.max(n,Number(e.framesPerSecond??0)):e.type===`candidate-pair`&&e.nominated&&typeof e.currentRoundTripTime==`number`&&(t=e.currentRoundTripTime)})}catch{return}this.core.emit(`netStats`,{fps:Math.round(n),rtt:t==null?null:Math.round(t*1e3)})},1e3)}},d=``+new URL(`whip-CTqIatiK.png`,import.meta.url).href,f=[`Rounding up your AI agents…`,`Connecting to your agent farm…`,`Uncoiling the whip…`,`Negotiating the fastest route…`,`Tightening the leash…`,`Almost in the saddle…`],p=class{overlay;msg;rotateTimer=0;msgIndex=0;constructor(e){this.overlay=document.createElement(`div`),this.overlay.className=`wd-connecting hidden`;let t=document.createElement(`div`);t.className=`wd-connecting-card`;let n=document.createElement(`img`);n.className=`wd-connecting-whip`,n.src=d,n.alt=`WhipDesk`,n.decoding=`async`;let r=document.createElement(`div`);r.className=`wd-connecting-spinner`,this.msg=document.createElement(`p`),this.msg.className=`wd-connecting-msg`,this.msg.textContent=f[0],t.append(n,r,this.msg),this.overlay.appendChild(t),e.appendChild(this.overlay)}show(e){this.msg.textContent=e??f[this.msgIndex],this.overlay.classList.contains(`hidden`)&&(this.overlay.classList.remove(`hidden`),!e&&(this.rotateTimer=window.setInterval(()=>{this.msgIndex=(this.msgIndex+1)%f.length,this.msg.textContent=f[this.msgIndex]},2200)))}hide(){this.overlay.classList.add(`hidden`),window.clearInterval(this.rotateTimer),this.rotateTimer=0}},m={eye:`<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>`,mouse:`<rect x="6" y="3" width="12" height="18" rx="6"/><path d="M12 7v4"/>`,keyboard:`<rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 10h.01M10 10h.01M14 10h.01M18 10h.01M7 14h10"/>`,monitor:`<rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/>`,hand:`<path d="M7 11V6a1.5 1.5 0 0 1 3 0v4m0-1V4.5a1.5 1.5 0 0 1 3 0V10m0-1.5a1.5 1.5 0 0 1 3 0V12c0 4-2.5 8-6.5 8S6 17 5 15l-1.5-3a1.4 1.4 0 0 1 2.3-1.5L7 12"/>`,bell:`<path d="M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6Z"/><path d="M10 19a2 2 0 0 0 4 0"/>`,plus:`<path d="M12 5v14M5 12h14"/>`,minus:`<path d="M5 12h14"/>`,"chevron-down":`<path d="m6 9 6 6 6-6"/>`,"chevron-up":`<path d="m6 15 6-6 6 6"/>`,pointer:`<path d="m4 4 6 16 2.5-6.5L19 11Z"/>`,"scroll-up":`<path d="m6 14 6-6 6 6"/><path d="M12 8v11"/>`,"scroll-down":`<path d="m6 10 6 6 6-6"/><path d="M12 16V5"/>`,"mouse-left":`<rect x="6" y="3" width="12" height="18" rx="6"/><path d="M12 3v8H6V8"/>`,"mouse-right":`<rect x="6" y="3" width="12" height="18" rx="6"/><path d="M12 3v8h6V8"/>`,"double-click":`<path d="m4 4 6 16 2.5-6.5L19 11Z"/><path d="M18 4v3M21 6h-3"/>`,drag:`<path d="M12 2v20M2 12h20" /><path d="m8 6 4-4 4 4M8 18l4 4 4-4M6 8l-4 4 4 4M18 8l4 4-4 4"/>`,send:`<path d="M22 2 11 13M22 2l-7 20-4-9-9-4Z"/>`,insert:`<path d="M12 5v14M5 12h7"/><rect x="16" y="4" width="4" height="16" rx="1"/>`,lock:`<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>`,clock:`<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>`,power:`<path d="M12 3v9"/><path d="M6.4 7.4a8 8 0 1 0 11.2 0"/>`,activity:`<path d="M3 12h4l2-7 4 14 2-7h6"/>`,x:`<path d="M6 6 18 18M18 6 6 18"/>`,heart:`<path d="M12 20s-7-4.4-9.3-8.5a4.5 4.5 0 0 1 8.1-3.9l1.2 1.6 1.2-1.6a4.5 4.5 0 0 1 8.1 3.9C19 15.6 12 20 12 20Z"/>`,trash:`<path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6M14 11v6"/>`,github:`<path fill="currentColor" stroke="none" d="M12 .5C5.37.5 0 5.87 0 12.5c0 5.3 3.44 9.8 8.21 11.39.6.11.82-.26.82-.58v-2.03c-3.34.73-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.74.08-.73.08-.73 1.2.09 1.84 1.24 1.84 1.24 1.07 1.83 2.81 1.3 3.5.99.11-.78.42-1.3.76-1.6-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.13-.3-.54-1.52.12-3.18 0 0 1.01-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.29-1.55 3.3-1.23 3.3-1.23.66 1.66.25 2.88.12 3.18.77.84 1.23 1.91 1.23 3.22 0 4.61-2.81 5.62-5.49 5.92.43.37.81 1.1.81 2.22v3.29c0 .32.22.7.83.58A12 12 0 0 0 24 12.5C24 5.87 18.63.5 12 .5Z"/>`,reddit:`<path fill="currentColor" stroke="none" d="M24 11.78a2.6 2.6 0 0 0-4.4-1.86 12.74 12.74 0 0 0-6.86-2.16l1.17-3.68 3.16.74a1.83 1.83 0 1 0 .2-1.18l-3.6-.85a.6.6 0 0 0-.72.43l-1.3 4.08a12.8 12.8 0 0 0-7 2.18 2.6 2.6 0 1 0-2.86 4.28 5.1 5.1 0 0 0-.06.79c0 4 4.66 7.24 10.42 7.24S20.42 17.83 20.42 12.95c0-.26-.02-.52-.06-.78A2.6 2.6 0 0 0 24 11.78ZM6.13 13.5a1.83 1.83 0 1 1 3.66 0 1.83 1.83 0 0 1-3.66 0Zm10.2 4.83c-1.25 1.25-3.64 1.34-4.33 1.34-.7 0-3.09-.09-4.33-1.34a.47.47 0 0 1 .67-.67c.79.79 2.47.99 3.66.99 1.2 0 2.88-.2 3.67-.99a.47.47 0 1 1 .66.67h.03Zm-.27-3a1.83 1.83 0 1 1 0-3.66 1.83 1.83 0 0 1 0 3.66Z"/>`},h=`http://www.w3.org/2000/svg`;function g(e,t=20){let n=document.createElementNS(h,`svg`);return n.setAttribute(`viewBox`,`0 0 24 24`),n.setAttribute(`width`,String(t)),n.setAttribute(`height`,String(t)),n.setAttribute(`fill`,`none`),n.setAttribute(`stroke`,`currentColor`),n.setAttribute(`stroke-width`,`2`),n.setAttribute(`stroke-linecap`,`round`),n.setAttribute(`stroke-linejoin`,`round`),n.setAttribute(`aria-hidden`,`true`),n.innerHTML=m[e],n}var _=[`en`],v=`en`;function y(){try{let e=localStorage.getItem(`wd-locale`);if(e&&_.includes(e))return e}catch{}let e=navigator.languages?.length?navigator.languages:[navigator.language];for(let t of e){let e=(t||``).toLowerCase().split(`-`)[0]??``;if(_.includes(e))return e}return v}function b(){return location.hostname.endsWith(`whipdesk.com`)?``:`https://whipdesk.com`}function x(){return`${b()}/${y()}/dashboard/`}function S(e){let t=e?`?next=${encodeURIComponent(e)}`:``;return`${b()}/${y()}/sign-in/${t}`}var C=`https://donate.stripe.com/6oU5kE19N5v35652n88so01`,w=`https://github.com/BinaryBananaLLC/WhipDesk/`,ee=`https://www.reddit.com/r/WhipDesk/`,te=[[`Esc`,`Escape`],[`Tab`,`Tab`],[`⌫`,`Backspace`],[`⏎`,`Enter`],[`←`,`ArrowLeft`],[`↑`,`ArrowUp`],[`↓`,`ArrowDown`],[`→`,`ArrowRight`]];function T(e,t,n){let r=document.createElement(e);return t&&(r.className=t),n!==void 0&&(r.textContent=n),r}function E(e,...t){let n=T(`div`,`wd-group`);n.appendChild(T(`span`,`wd-group-label`,e));let r=T(`div`,`wd-group-items`);return r.append(...t),n.appendChild(r),n}function D(e,t){let n=0,r=0,i=()=>{window.clearTimeout(n),window.clearInterval(r)};e.addEventListener(`pointerdown`,e=>{e.preventDefault(),t(),n=window.setTimeout(()=>{r=window.setInterval(t,80)},350)});for(let t of[`pointerup`,`pointercancel`,`pointerleave`])e.addEventListener(t,i);return e}function O(e,t=`wd-btn`){return T(`button`,t,e)}function k(e,t=``,n=`wd-btn`){let r=T(`button`,n);if(r.appendChild(g(e)),t){let e=T(`span`,`wd-btn-label`,t);r.appendChild(e)}else r.classList.add(`wd-icon-only`),r.setAttribute(`aria-label`,e);return r}function A(e,t,n){let r=T(`a`,`wd-conn-feedback-link`);return r.href=n,r.target=`_blank`,r.rel=`noopener noreferrer`,r.append(g(e,16),T(`span`,void 0,t)),r}function ne(e){switch(e.toUpperCase()){case`LAN`:return`Direct on your local network — fastest, no relay.`;case`STUN`:return`Direct peer-to-peer across networks.`;case`TURN`:return`Your network blocked a direct P2P connection, so we're bouncing your stream through our encrypted relay. Yes, this costs us actual money to run. Consider supporting us.`;default:return``}}function j(e){return e.toUpperCase()===`TURN`?`$ ${e}`:e}var re=class{root;deps;statusDot=T(`span`,`wd-dot`);statusText=T(`span`,`wd-status-text`,`Connecting…`);transportBadge=T(`span`,`wd-transport hidden`);watchersText=T(`span`,`wd-watchers`,``);alertBadge=T(`span`,`wd-badge hidden`);statusbar;statusCollapseTimer=0;connectionOverlay;connName;connRoute;connPresence;connSpeed;connStatusDot;connStatusText;connError;lastError=``;netFps=0;netRtt=null;panel;optionsArea;tabButtons=new Map;tabPanes=new Map;collapseBtn;interactHost;monitorList;promptInput;activeTab=null;interactMode=`mouse`;collapsed=!1;deviceName=``;transport=``;presenceCount=1;status=`connecting`;displays=[];activeDisplay=0;constructor(e,t){this.root=e,this.deps=t,this.build()}setStatus(e){this.status=e,this.statusDot.dataset.status=e,e===`connected`&&(this.lastError=``),this.renderStatusText(),this.updateStatusCollapse(),this.connectionOverlay&&!this.connectionOverlay.classList.contains(`hidden`)&&this.renderConnection()}setAlertCount(e){this.alertBadge.textContent=e>0?String(e):``,this.alertBadge.classList.toggle(`hidden`,e<=0)}setTransport(e){this.transport=e,this.transportBadge.textContent=j(e),this.transportBadge.dataset.kind=e.toLowerCase(),this.transportBadge.classList.toggle(`hidden`,!e),this.connectionOverlay&&!this.connectionOverlay.classList.contains(`hidden`)&&this.renderConnection(),this.peekStatus()}updateStatusCollapse(){window.clearTimeout(this.statusCollapseTimer),this.status===`connected`?this.scheduleStatusCollapse():this.statusbar?.classList.remove(`collapsed`)}scheduleStatusCollapse(){window.clearTimeout(this.statusCollapseTimer),this.statusCollapseTimer=window.setTimeout(()=>{this.status===`connected`&&this.statusbar?.classList.add(`collapsed`)},4e3)}peekStatus(){this.statusbar?.classList.remove(`collapsed`),this.scheduleStatusCollapse()}renderStatusText(){this.statusText.textContent=this.status===`connected`?this.deviceName?`Connected to ${this.deviceName}`:`Connected`:this.status===`connecting`?`Connecting…`:`Disconnected`}setWelcome(e){this.deviceName=e.agent.hostname,this.renderStatusText(),this.displays=e.displays??[],this.activeDisplay=e.activeDisplay??0,this.renderMonitors(),e.capabilities.mouse||this.deps.notifications.show({type:`notification`,id:`cap-${Date.now()}`,title:`View-only`,body:`Host mouse/keyboard unavailable — grant Accessibility on the host.`,level:`warning`,source:`client`,t:Date.now()})}setActiveDisplay(e){this.activeDisplay=e,this.renderMonitors()}setPresence(e){this.presenceCount=e,this.watchersText.textContent=e>1?`● ${e} watching`:``,this.connectionOverlay&&!this.connectionOverlay.classList.contains(`hidden`)&&this.renderConnection()}buildConnectionDialog(){let e=T(`div`,`wd-dialog-overlay hidden`);e.addEventListener(`pointerdown`,t=>{t.target===e&&e.classList.add(`hidden`)});let t=T(`div`,`wd-dialog`),n=T(`div`,`wd-dialog-head`);n.append(T(`h2`,``,`Connection`));let r=T(`button`,`wd-dialog-x`);r.appendChild(g(`x`)),r.onclick=()=>e.classList.add(`hidden`),n.appendChild(r);let i=T(`div`,`wd-conn-row`);i.append(T(`span`,`wd-conn-label`,`Status`));let a=T(`div`,`wd-conn-status`);this.connStatusDot=T(`span`,`wd-dot`),this.connStatusText=T(`span`,`wd-conn-value`,`—`),a.append(this.connStatusDot,this.connStatusText),i.appendChild(a);let o=T(`div`,`wd-conn-row`);o.append(T(`span`,`wd-conn-label`,`Machine`)),this.connName=T(`span`,`wd-conn-value`,`—`),o.appendChild(this.connName);let s=T(`div`,`wd-conn-row`);s.append(T(`span`,`wd-conn-label`,`Connection`)),this.connRoute=T(`div`,`wd-conn-route`),s.appendChild(this.connRoute);let c=T(`div`,`wd-conn-row`);c.append(T(`span`,`wd-conn-label`,`Viewers`)),this.connPresence=T(`span`,`wd-conn-value`,`1`),c.appendChild(this.connPresence);let l=T(`div`,`wd-conn-row`);l.append(T(`span`,`wd-conn-label`,`Speed (FPS/latency)`)),this.connSpeed=T(`span`,`wd-conn-value`,`—`),l.appendChild(this.connSpeed),this.connError=T(`div`,`wd-conn-error hidden`);let u=T(`button`,`wd-btn wd-disconnect`);u.append(g(`power`),T(`span`,`wd-btn-label`,`Disconnect`)),u.onclick=()=>this.disconnect();let d=T(`div`,`wd-conn-feedback`);d.append(T(`p`,`wd-conn-feedback-text`,`Noticed an issue or have an idea? Reach out:`));let f=T(`div`,`wd-conn-feedback-links`);f.append(A(`reddit`,`Reddit`,ee),A(`github`,`GitHub`,w)),d.appendChild(f),t.append(n,i,s,l,o,c,this.connError,u,d),e.appendChild(t),this.root.appendChild(e),this.connectionOverlay=e}renderConnection(){if(this.connStatusDot.dataset.status=this.status,this.connStatusText.textContent=this.status===`connected`?`Connected`:this.status===`connecting`?`Connecting…`:`Disconnected`,this.lastError?(this.connError.textContent=this.lastError,this.connError.classList.remove(`hidden`)):this.connError.classList.add(`hidden`),this.connName.textContent=this.deviceName||`Connected device`,this.connPresence.textContent=String(Math.max(1,this.presenceCount)),this.connRoute.replaceChildren(),this.transport){let e=T(`span`,`wd-transport`);if(e.textContent=j(this.transport),e.dataset.kind=this.transport.toLowerCase(),this.connRoute.append(e,T(`span`,`wd-conn-desc`,ne(this.transport))),this.transport.toLowerCase()===`turn`){let e=T(`button`,`wd-support-link`);e.append(g(`heart`,14),T(`span`,void 0,`Support WhipDesk`)),e.onclick=()=>window.open(C,`_blank`,`noopener`),this.connRoute.append(e)}}else this.connRoute.append(T(`span`,`wd-conn-desc`,this.status===`connected`?`Detecting route…`:`Connecting…`));this.renderSpeed()}renderSpeed(){let e=`${this.netFps} FPS`;this.connSpeed.textContent=this.netRtt==null?e:`${e} / ${this.netRtt} ms`}setNetStats(e,t){this.netFps=e,this.netRtt=t,this.connectionOverlay&&!this.connectionOverlay.classList.contains(`hidden`)&&this.renderSpeed()}openConnection(){this.renderConnection(),this.connectionOverlay.classList.remove(`hidden`)}disconnect(){this.deps.conn.close(),window.location.href=x()}flashError(e){this.lastError=e,this.connectionOverlay&&!this.connectionOverlay.classList.contains(`hidden`)&&this.renderConnection(),this.deps.notifications.show({type:`notification`,id:`err-${Date.now()}`,title:`WhipDesk`,body:e,level:`warning`,source:`client`,t:Date.now()})}build(){let e=T(`div`,`wd-statusbar`);e.append(this.statusDot,this.statusText,this.transportBadge,this.watchersText),e.onclick=()=>this.openConnection(),this.statusbar=e,this.buildConnectionDialog();let t=k(`bell`,``,`wd-bell`);t.setAttribute(`aria-label`,`Auto-Whips`),t.title=`Auto-Whips`,t.appendChild(this.alertBadge),t.onclick=()=>this.deps.watchers.open(),this.root.append(e,t);let n=T(`div`,`wd-ribbon`);this.panel=T(`div`,`wd-panel`),this.optionsArea=T(`div`,`wd-options`),this.tabPanes.set(`viewer`,this.buildViewerPane()),this.tabPanes.set(`interact`,this.buildInteractPane()),this.tabPanes.set(`type`,this.buildTypePane()),this.tabPanes.set(`monitor`,this.buildMonitorPane());for(let e of this.tabPanes.values())this.optionsArea.appendChild(e);let r=T(`div`,`wd-tabs`),i=(e,t,n)=>{let i=T(`button`,`wd-tab`);i.appendChild(g(t,18)),i.appendChild(T(`span`,`wd-tab-label`,n)),i.onclick=()=>this.selectTab(e),this.tabButtons.set(e,i),r.appendChild(i)};i(`viewer`,`eye`,`Viewer`),i(`interact`,`mouse`,`Interact`),i(`type`,`keyboard`,`Type`),i(`monitor`,`monitor`,`Monitor`),this.collapseBtn=k(`chevron-down`,``,`wd-collapse`),this.collapseBtn.onclick=()=>this.setCollapsed(!this.collapsed),r.appendChild(this.collapseBtn),this.panel.append(this.optionsArea,r),n.appendChild(this.panel),this.root.appendChild(n),this.selectTab(`viewer`)}buildViewerPane(){let{view:e,input:t}=this.deps,n=T(`div`,`wd-pane wd-pane-single-row`),r=D(k(`minus`,``,`wd-btn wd-icon-only`),()=>e.zoomBy(.9)),i=D(k(`plus`,``,`wd-btn wd-icon-only`),()=>e.zoomBy(1.11)),a=D(k(`scroll-up`,``,`wd-btn wd-icon-only`),()=>t.scrollStep(-6)),o=D(k(`scroll-down`,``,`wd-btn wd-icon-only`),()=>t.scrollStep(6)),s=k(`hand`,``,`wd-btn wd-icon-only`);s.setAttribute(`aria-label`,`Drag to scroll`),s.title=`Drag to scroll`;let c=k(`drag`,``,`wd-btn wd-icon-only`);c.setAttribute(`aria-label`,`Pan the zoomed screen with one finger`),c.title=`Pan the zoomed screen with one finger`,s.onclick=()=>{let e=!t.getDragScroll();t.setDragScroll(e),s.classList.toggle(`on`,e),c.classList.toggle(`on`,t.getPan())},c.onclick=()=>{let e=!t.getPan();t.setPan(e),c.classList.toggle(`on`,e),s.classList.toggle(`on`,t.getDragScroll())};let l=k(`pointer`,`Click`,`wd-btn wd-go`);return l.onclick=()=>t.click(`left`),n.append(E(`Zoom`,r,i),E(`Pan`,c),E(`Scroll`,a,o,s),E(`Pointer`,l)),n}buildInteractPane(){let e=T(`div`,`wd-pane`);return this.interactHost=T(`div`,`wd-pane`),e.appendChild(this.interactHost),this.renderInteract(),e}renderInteract(){let{input:e}=this.deps;this.interactHost.replaceChildren();let t=T(`div`,`wd-group`),n=T(`div`,`wd-group-head`),r=T(`span`,`wd-group-label`,`Mode`),i=T(`div`,`wd-mode-toggle`),a=T(`button`,`wd-mode-btn`,`Mouse`),o=T(`button`,`wd-mode-btn`,`Touch`);a.classList.toggle(`on`,this.interactMode===`mouse`),o.classList.toggle(`on`,this.interactMode===`touch`),a.onclick=()=>{this.interactMode=`mouse`,this.activeTab===`interact`&&this.deps.input.setInteraction(`mouse`),this.renderInteract()},o.onclick=()=>{this.interactMode=`touch`,this.activeTab===`interact`&&this.deps.input.setInteraction(`touch`),this.renderInteract()},i.append(a,o),n.append(r,i);let s=T(`div`,`wd-group-items`);if(this.interactMode===`mouse`){let t=k(`mouse-left`,`Left`);t.onclick=()=>e.click(`left`);let n=k(`mouse-right`,`Right`);n.onclick=()=>e.click(`right`);let r=k(`double-click`,`Double`);r.onclick=()=>e.multiClick(2);let i=k(`drag`,`Drag`);i.onclick=()=>{let t=!e.getDragLock();e.setDragLock(t),i.classList.toggle(`on`,t)},s.append(t,n,r,i)}else{let t=k(`pointer`,`Tap`);t.onclick=()=>e.click(`left`);let n=k(`hand`,`Hold`);n.onclick=()=>e.longPress();let r=D(k(`scroll-up`,`Up`,`wd-btn`),()=>e.swipe(0,-.25)),i=D(k(`scroll-down`,`Down`,`wd-btn`),()=>e.swipe(0,.25)),a=O(`←`);a.onclick=()=>e.swipe(-.25,0);let o=O(`→`);o.onclick=()=>e.swipe(.25,0);let c=O(`2 fingers`);c.onclick=()=>e.click(`right`),s.append(t,n,r,i,a,o,c)}t.append(n,s),this.interactHost.append(t)}buildTypePane(){let{conn:e,input:t}=this.deps,n=T(`div`,`wd-pane wd-pane-col`);this.promptInput=T(`textarea`,`wd-type-input`),this.promptInput.placeholder=`Type to send to the focused app (URL, command, message…)`,this.promptInput.rows=2;let r=T(`div`,`wd-wrap`);for(let[t,n]of te){let i=O(t);i.onclick=()=>e.send({type:`key`,key:n}),r.appendChild(i)}let i=O(`Double-click`);i.onclick=()=>t.multiClick(2);let a=O(`Triple-click`);a.onclick=()=>t.multiClick(3);let o=k(`insert`,`Insert`);o.onclick=()=>this.sendText(!1);let s=k(`send`,`Send`,`wd-btn wd-go`);return s.onclick=()=>this.sendText(!0),r.append(i,a,o,s),n.append(this.promptInput,r),n}buildMonitorPane(){let e=T(`div`,`wd-pane`);return this.monitorList=T(`div`,`wd-monitor-list`),e.appendChild(this.monitorList),this.renderMonitors(),e}renderMonitors(){if(this.monitorList){if(this.monitorList.replaceChildren(),this.displays.length===0){this.monitorList.appendChild(T(`span`,`wd-hint`,`Single display`));return}this.displays.forEach((e,t)=>{let n=O(`${t+1}. ${e.name}${e.primary?` ★`:``}`);n.classList.toggle(`on`,e.id===this.activeDisplay),n.onclick=()=>{this.deps.conn.send({type:`select-display`,id:e.id}),this.activeDisplay=e.id,this.renderMonitors()},this.monitorList.appendChild(n)})}}selectTab(e){if(this.activeTab!==null&&e===this.activeTab){this.setCollapsed(!this.collapsed);return}this.activeTab=e,this.collapsed&&this.setCollapsed(!1);for(let[t,n]of this.tabButtons)n.classList.toggle(`on`,t===e);for(let[t,n]of this.tabPanes)n.classList.toggle(`hidden`,t!==e);this.deps.input.setInteraction(e===`interact`?this.interactMode:`viewer`),e===`type`&&window.setTimeout(()=>this.promptInput.focus(),50)}setCollapsed(e){this.collapsed=e,this.optionsArea.classList.toggle(`hidden`,e),this.collapseBtn.replaceChildren(g(e?`chevron-up`:`chevron-down`))}sendText(e){let t=this.promptInput.value;t&&(this.deps.conn.send({type:`type`,text:t,submit:e}),this.promptInput.value=``)}};function M(e){return e<0?0:e>1?1:e}var N=2.5,ie=250,ae=500,oe=8,se=class{canvas;view;conn;cb;interaction=`viewer`;dragLock=!1;dragScroll=!1;pan=!1;holdingLeft=!1;cursor={nx:.5,ny:.5};pointers=new Map;longPressTimer=0;twoFinger=null;suppressTap=!1;constructor(e,t,n,r={}){this.canvas=e,this.view=t,this.conn=n,this.cb=r,e.style.touchAction=`none`,e.addEventListener(`pointerdown`,e=>this.onDown(e)),e.addEventListener(`pointermove`,e=>this.onMove(e)),e.addEventListener(`pointerup`,e=>this.onUp(e)),e.addEventListener(`pointercancel`,e=>this.onUp(e)),e.addEventListener(`contextmenu`,e=>e.preventDefault()),this.view.setCursor(this.cursor.nx,this.cursor.ny)}setInteraction(e){this.interaction=e,e===`viewer`&&(this.dragLock=!1)}getInteraction(){return this.interaction}setCallbacks(e){this.cb=e}setDragLock(e){this.dragLock=e}getDragLock(){return this.dragLock}setDragScroll(e){this.dragScroll=e,e&&(this.pan=!1)}getDragScroll(){return this.dragScroll}setPan(e){this.pan=e,e&&(this.dragScroll=!1)}getPan(){return this.pan}isPanning(){return this.pan&&this.interaction===`viewer`}click(e,t=!1){this.send({type:`pointer`,action:`click`,button:e,double:t,x:this.cursor.nx,y:this.cursor.ny}),navigator.vibrate?.(15)}multiClick(e){for(let t=0;t<e;t++)this.send({type:`pointer`,action:`click`,button:`left`,x:this.cursor.nx,y:this.cursor.ny});navigator.vibrate?.(15)}longPress(e=650){this.send({type:`pointer`,action:`down`,button:`left`,x:this.cursor.nx,y:this.cursor.ny}),navigator.vibrate?.(25),window.setTimeout(()=>this.send({type:`pointer`,action:`up`,button:`left`}),e)}swipe(e,t){let n=M(this.cursor.nx),r=M(this.cursor.ny),i=M(this.cursor.nx+e),a=M(this.cursor.ny+t);this.send({type:`pointer`,action:`down`,button:`left`,x:n,y:r});for(let e=1;e<=6;e++){let t=e/6;window.setTimeout(()=>{this.send({type:`pointer`,action:`move`,x:n+(i-n)*t,y:r+(a-r)*t}),e===6&&(this.moveCursor(i,a),this.send({type:`pointer`,action:`up`,button:`left`}))},e*16)}navigator.vibrate?.(15)}scrollStep(e){this.send({type:`scroll`,dx:0,dy:e})}send(e){this.conn.send(e)}positionOf(e){let t=this.canvas.getBoundingClientRect();return{x:e.clientX-t.left,y:e.clientY-t.top}}moveCursor(e,t){this.cursor.nx=M(e),this.cursor.ny=M(t),this.view.setCursor(this.cursor.nx,this.cursor.ny),this.cb.onCursor?.(this.cursor.nx,this.cursor.ny)}onDown(e){this.canvas.setPointerCapture?.(e.pointerId);let t=this.positionOf(e);if(this.pointers.set(e.pointerId,{x:t.x,y:t.y,startX:t.x,startY:t.y,startT:performance.now(),moved:!1,consumed:!1}),this.pointers.size===2){window.clearTimeout(this.longPressTimer),this.beginTwoFinger();return}if(this.pointers.size===1&&(this.view.beginViewGesture(),window.clearTimeout(this.longPressTimer),this.interaction===`mouse`&&!this.dragScroll&&(this.longPressTimer=window.setTimeout(()=>this.onLongPress(),ae)),!this.dragScroll&&!this.isPanning()&&(this.interaction===`mouse`||this.interaction===`viewer`))){let e=this.view.canvasToNorm(t.x,t.y);this.moveCursor(e.nx,e.ny)}}onLongPress(){let e=[...this.pointers.values()][0];!e||e.moved||e.consumed||(e.consumed=!0,this.send({type:`pointer`,action:`click`,button:`right`,x:this.cursor.nx,y:this.cursor.ny}),navigator.vibrate?.(20))}beginTwoFinger(){let e=[...this.pointers.values()];if(e.length<2)return;let t=e[0],n=e[1];this.twoFinger={dist:Math.hypot(t.x-n.x,t.y-n.y),mx:(t.x+n.x)/2,my:(t.y+n.y)/2,start:performance.now(),moved:!1}}onMove(e){let t=this.pointers.get(e.pointerId);if(!t)return;let n=this.positionOf(e),r=t.x,i=t.y;if(t.x=n.x,t.y=n.y,Math.hypot(n.x-t.startX,n.y-t.startY)>oe&&(t.moved=!0),this.pointers.size>=2&&this.twoFinger){this.onTwoFingerMove();return}if(this.pointers.size!==1||t.consumed||!t.moved)return;window.clearTimeout(this.longPressTimer);let a=n.x-r,o=n.y-i;if(this.isPanning()){this.view.panByCanvasPixels(a,o);return}if(this.interaction===`touch`||this.dragScroll){this.send({type:`scroll`,dx:Math.round(-a/N),dy:Math.round(-o/N)});return}let s=this.view.canvasToNorm(n.x,n.y);this.moveCursor(s.nx,s.ny),this.interaction===`mouse`&&this.dragLock&&!this.holdingLeft&&(this.holdingLeft=!0,this.send({type:`pointer`,action:`down`,button:`left`,x:this.cursor.nx,y:this.cursor.ny})),this.send({type:`pointer`,action:`move`,x:this.cursor.nx,y:this.cursor.ny})}onTwoFingerMove(){let e=[...this.pointers.values()];if(e.length<2||!this.twoFinger)return;let t=e[0],n=e[1],r=Math.hypot(t.x-n.x,t.y-n.y),i=(t.x+n.x)/2,a=(t.y+n.y)/2,o=r-this.twoFinger.dist,s=i-this.twoFinger.mx,c=a-this.twoFinger.my;if(Math.abs(o)>6){this.twoFinger.moved=!0,this.view.zoomAround(1+o/200,i,a),this.twoFinger.dist=r;return}(Math.abs(c)>2||Math.abs(s)>2)&&(this.twoFinger.moved=!0,this.view.getZoom()>1?this.view.panByCanvasPixels(s,c):this.send({type:`scroll`,dx:Math.round(-s/N),dy:Math.round(-c/N)}),this.twoFinger.mx=i,this.twoFinger.my=a)}onUp(e){let t=this.pointers.size,n=this.pointers.get(e.pointerId);if(this.pointers.delete(e.pointerId),window.clearTimeout(this.longPressTimer),this.pointers.size===0&&this.view.endViewGesture(),t===2){let e=this.twoFinger;e&&!e.moved&&this.interaction===`mouse`&&performance.now()-e.start<ie&&(this.send({type:`pointer`,action:`click`,button:`right`,x:this.cursor.nx,y:this.cursor.ny}),navigator.vibrate?.(20)),this.twoFinger=null,this.suppressTap=!0;for(let e of this.pointers.values())e.consumed=!0;return}if(this.holdingLeft&&this.pointers.size===0&&(this.holdingLeft=!1,this.send({type:`pointer`,action:`up`,button:`left`})),this.pointers.size<2&&(this.twoFinger=null),!n||n.consumed)return;if(this.suppressTap){this.pointers.size===0&&(this.suppressTap=!1);return}let r=performance.now()-n.startT;if(!(!(!n.moved&&r<ie&&this.pointers.size===0)||this.dragScroll)&&(this.interaction===`mouse`||this.interaction===`touch`)){let e=this.view.canvasToNorm(n.startX,n.startY);this.moveCursor(e.nx,e.ny),this.send({type:`pointer`,action:`click`,button:`left`,x:e.nx,y:e.ny}),navigator.vibrate?.(12)}}},ce=class{container;constructor(e){this.container=e}async requestPermission(){try{`Notification`in window&&Notification.permission==="default"&&await Notification.requestPermission()}catch{}}get permission(){return`Notification`in window?Notification.permission:`unsupported`}flash(e,t,n=`info`){this.toast({type:`notification`,id:`flash-${Date.now()}`,title:e,body:t,level:n,source:`client`,t:Date.now()})}show(e){this.toast(e);try{`Notification`in window&&Notification.permission===`granted`&&new Notification(e.title,{body:e.body,tag:e.id})}catch{}navigator.vibrate?.(e.level===`error`?[60,40,60]:40)}toast(e){let t=document.createElement(`div`);t.className=`wd-toast wd-${e.level}`;let n=document.createElement(`strong`);if(n.textContent=e.title,t.appendChild(n),e.body){let n=document.createElement(`span`);n.textContent=e.body,t.appendChild(n)}this.container.appendChild(t),window.setTimeout(()=>{t.classList.add(`wd-hide`),window.setTimeout(()=>t.remove(),300)},5e3)}},le=class{overlay;input;message;onSubmit=null;constructor(e){this.overlay=document.createElement(`div`),this.overlay.className=`wd-pin-overlay hidden`;let t=document.createElement(`div`);t.className=`wd-pin-card`;let n=document.createElement(`img`);n.className=`wd-pin-whip`,n.src=d,n.alt=`WhipDesk`,n.decoding=`async`;let r=document.createElement(`h2`);r.textContent=`Enter device PIN`,this.message=document.createElement(`p`),this.message.className=`wd-pin-msg`,this.message.textContent=`You're connected. Enter the PIN to unlock this device and start whipping.`,this.input=document.createElement(`input`),this.input.className=`wd-pin-input`,this.input.type=`password`,this.input.inputMode=`numeric`,this.input.autocomplete=`one-time-code`,this.input.name=`wd-device-pin`,this.input.setAttribute(`data-1p-ignore`,`true`),this.input.setAttribute(`data-lpignore`,`true`),this.input.setAttribute(`aria-label`,`Device PIN`),this.input.addEventListener(`keydown`,e=>{e.key===`Enter`&&this.submit()});let i=document.createElement(`button`);i.className=`wd-pin-submit`,i.textContent=`Unlock`,i.onclick=()=>this.submit();let a=document.createElement(`button`);a.type=`button`,a.className=`wd-support-link wd-pin-support`,a.append(g(`heart`,14));let o=document.createElement(`span`);o.textContent=`Support WhipDesk`,a.append(o),a.onclick=()=>window.open(C,`_blank`,`noopener`);let s=document.createElement(`button`);s.className=`wd-pin-back`,s.textContent=`← Back to dashboard`,s.onclick=()=>{window.location.href=x()},t.append(n,r,this.message,this.input,i,a,s),this.overlay.appendChild(t),e.appendChild(this.overlay)}show(e,t){this.onSubmit=t,this.input.value=``,e.retry?(this.message.textContent=e.attemptsLeft>0?`Wrong PIN — ${e.attemptsLeft} attempt(s) left.`:`Wrong PIN. Try again.`,this.message.classList.add(`err`),navigator.vibrate?.([40,40,40])):(this.message.textContent=`You're connected. Enter the PIN to unlock this device and start whipping.`,this.message.classList.remove(`err`)),this.overlay.classList.remove(`hidden`),window.setTimeout(()=>this.input.focus(),50)}hide(){this.overlay.classList.add(`hidden`)}submit(){let e=this.input.value.trim();if(e.length<4){this.message.textContent=`PIN is at least 4 characters.`,this.message.classList.add(`err`);return}this.onSubmit?.(e),this.message.textContent=`Checking…`,this.message.classList.remove(`err`)}},ue=`modulepreload`,de=function(e,t){return new URL(e,t).href},P={},F=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,new URL(`../../../src/node/plugins/importAnalysisBuild.ts`,import.meta.url)).href}r=o(t.map(t=>{if(t=de(t,n),t=s(t),t in P)return;P[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:ue,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};async function I(e,t){if(e.vapidKey&&!(!(`serviceWorker`in navigator)||!(`Notification`in window)))try{let{initializeApp:n,getApps:r}=await F(async()=>{let{initializeApp:e,getApps:t}=await import(`./index.esm-mXKu2C3o.js`);return{initializeApp:e,getApps:t}},__vite__mapDeps([0,1]),import.meta.url),{getAuth:i}=await F(async()=>{let{getAuth:e}=await import(`./index.esm-CmSagqpw.js`);return{getAuth:e}},__vite__mapDeps([2,1]),import.meta.url),{getFirestore:a,doc:o,setDoc:s,serverTimestamp:c}=await F(async()=>{let{getFirestore:e,doc:t,setDoc:n,serverTimestamp:r}=await import(`./index.esm-wogdVWd2.js`);return{getFirestore:e,doc:t,setDoc:n,serverTimestamp:r}},__vite__mapDeps([3,1]),import.meta.url),{getMessaging:l,getToken:u,onMessage:d,isSupported:f}=await F(async()=>{let{getMessaging:e,getToken:t,onMessage:n,isSupported:r}=await import(`./index.esm-BgjPHsdM.js`);return{getMessaging:e,getToken:t,onMessage:n,isSupported:r}},__vite__mapDeps([4,1]),import.meta.url);if(!await f().catch(()=>!1))return;let p=r()[0]??n(e),m=i(p).currentUser;if(!m||Notification.permission==="default"&&await Notification.requestPermission()!==`granted`||Notification.permission!==`granted`)return;let h=await navigator.serviceWorker.register(`./firebase-messaging-sw.js`),g=l(p),_=await u(g,{vapidKey:e.vapidKey,serviceWorkerRegistration:h});if(!_)return;await s(o(a(p),`users`,m.uid,`fcmTokens`,_),{token:_,ua:navigator.userAgent,updatedAt:c()},{merge:!0}),d(g,e=>{let n=e.notification;t.show({type:`notification`,id:`push-${Date.now()}`,title:n?.title??`WhipDesk`,body:n?.body,level:`info`,source:`push`,t:Date.now()})})}catch{}}var fe=[{urls:`stun:turn-us1.whipdesk.com:3478`}];async function pe(e){try{let t=await e.getStats(),n=``,r=``;t.forEach(e=>{e.type===`transport`&&e.selectedCandidatePairId&&(n=e.selectedCandidatePairId)}),t.forEach(e=>{e.type===`candidate-pair`&&!r&&(e.id===n||e.nominated&&e.state===`succeeded`)&&(r=e.localCandidateId)});let i=``;if(t.forEach(e=>{e.id===r&&e.type===`local-candidate`&&(i=e.candidateType)}),i===`relay`)return`TURN`;if(i===`srflx`||i===`prflx`)return`STUN`;if(i===`host`)return`LAN`}catch{}return null}var me=class{deviceId;config;core;pc=null;dc=null;mainXcv=null;overviewXcv=null;cleanupSignal=null;deleteSessionDoc=null;pushCandidate=null;appliedAnswer=!1;appliedCandidates=new Set;closed=!1;reconnectTimer=0;statsTimer=0;constructor(e,t,n){this.deviceId=e,this.config=n,this.core=new l(t),this.core.setSender(e=>{if(this.dc&&this.dc.readyState===`open`)try{this.dc.send(e)}catch{}})}on(e,t){this.core.on(e,t)}send(e){this.core.send(e)}submitPin(e){this.core.submitPin(e)}setVisible(e){this.core.setVisible(e)}connect(){this.closed=!1,this.start()}close(){this.closed=!0,window.clearTimeout(this.reconnectTimer),this.reconnectTimer=0,this.teardown()}isHealthy(){return!this.closed&&this.pc?.connectionState===`connected`&&this.dc?.readyState===`open`}wake(){this.closed||this.isHealthy()||(window.clearTimeout(this.reconnectTimer),this.reconnectTimer=0,this.teardown(),this.start())}start(){this.open().catch(e=>{this.core.emit(`error`,`Remote connect failed: ${e.message}`),this.core.emit(`status`,`disconnected`),this.scheduleReconnect()})}teardown(){if(window.clearInterval(this.statsTimer),this.statsTimer=0,this.cleanupSignal?.(),this.cleanupSignal=null,this.deleteSessionDoc?.(),this.deleteSessionDoc=null,this.pushCandidate=null,this.appliedAnswer=!1,this.appliedCandidates.clear(),this.core.emit(`videoTrack`,null),this.core.emit(`overviewTrack`,null),this.dc){this.dc.onopen=this.dc.onclose=this.dc.onmessage=null;try{this.dc.close()}catch{}this.dc=null}if(this.pc){this.pc.onconnectionstatechange=null,this.pc.ontrack=null;try{this.pc.close()}catch{}this.pc=null}this.mainXcv=null,this.overviewXcv=null}scheduleReconnect(){this.closed||this.reconnectTimer||(this.reconnectTimer=window.setTimeout(()=>{this.reconnectTimer=0,!this.closed&&(this.teardown(),this.start())},2e3))}async open(){this.core.emit(`status`,`connecting`);let{initializeApp:e,getApps:t}=await F(async()=>{let{initializeApp:e,getApps:t}=await import(`./index.esm-mXKu2C3o.js`);return{initializeApp:e,getApps:t}},__vite__mapDeps([0,1]),import.meta.url),{getAuth:n}=await F(async()=>{let{getAuth:e}=await import(`./index.esm-CmSagqpw.js`);return{getAuth:e}},__vite__mapDeps([2,1]),import.meta.url),{getDatabase:r,ref:i,child:a,push:o,set:s,onValue:c,remove:l,onDisconnect:u}=await F(async()=>{let{getDatabase:e,ref:t,child:n,push:r,set:i,onValue:a,remove:o,onDisconnect:s}=await import(`./index.esm-Di8jSGaG.js`);return{getDatabase:e,ref:t,child:n,push:r,set:i,onValue:a,remove:o,onDisconnect:s}},__vite__mapDeps([5,1]),import.meta.url),d=t()[0]??e(this.config),f=n(d);if(await new Promise(e=>{if(f.currentUser)return e();let t=f.onAuthStateChanged(()=>{t(),e()})}),!f.currentUser){window.location.assign(S(`/app/${window.location.hash}`));return}let p=f.currentUser.uid,m=r(d),h=await this.fetchIceServers(f.currentUser),g=new RTCPeerConnection({iceServers:h,iceTransportPolicy:`all`});this.pc=g;let _=g.createDataChannel(`whipdesk`);_.binaryType=`arraybuffer`,this.dc=_,this.wireDataChannel(_);try{this.mainXcv=g.addTransceiver(`video`,{direction:`recvonly`}),this.overviewXcv=g.addTransceiver(`video`,{direction:`recvonly`})}catch{}g.ontrack=e=>{let t=e.streams[0]??new MediaStream([e.track]);this.overviewXcv&&e.transceiver===this.overviewXcv?this.core.emit(`overviewTrack`,t):this.core.emit(`videoTrack`,t)};let v=!1,y=async()=>{if(v)return;let e=await pe(g);if(!e)return;v=!0,this.core.emit(`transport`,e);let t=e===`TURN`?`turn`:e===`STUN`?`stun`:`lan`;try{let{getFirestore:e,doc:n,setDoc:r,increment:i}=await F(async()=>{let{getFirestore:e,doc:t,setDoc:n,increment:r}=await import(`./index.esm-wogdVWd2.js`);return{getFirestore:e,doc:t,setDoc:n,increment:r}},__vite__mapDeps([3,1]),import.meta.url);r(n(e(d),`users`,p),{stats:{[t]:i(1)}},{merge:!0}).catch(()=>{})}catch{}};g.onconnectionstatechange=()=>{let e=g.connectionState;e===`connected`?(this.core.emit(`status`,`connected`),y(),this.startStatsPoll(g)):(e===`failed`||e===`disconnected`||e===`closed`)&&(this.core.emit(`status`,`disconnected`),this.scheduleReconnect())};let b=o(i(m,`signaling/${p}/${this.deviceId}`));this.pushCandidate=e=>{try{s(o(a(b,`offerCandidates`)),e)}catch{}},g.onicecandidate=e=>{e.candidate&&this.pushCandidate?.(e.candidate.toJSON())};let x=await g.createOffer();await g.setLocalDescription(x),await s(b,{offer:{sdp:g.localDescription?.sdp??``},controllerUid:p,createdAtMs:Date.now()}),u(b).remove(),this.deleteSessionDoc=()=>void l(b).catch(()=>{}),this.cleanupSignal=c(b,e=>{let t=e.val();if(!(!t||this.closed)&&(t.answer?.sdp&&!this.appliedAnswer&&(this.appliedAnswer=!0,g.setRemoteDescription({type:`answer`,sdp:t.answer.sdp})),t.answerCandidates&&this.appliedAnswer)){for(let[e,n]of Object.entries(t.answerCandidates))if(!this.appliedCandidates.has(e)){this.appliedCandidates.add(e);try{g.addIceCandidate(n)}catch{}}}})}startStatsPoll(e){window.clearInterval(this.statsTimer),this.lossBase=null,this.statsPolls=0,this.statsTimer=window.setInterval(()=>void this.pollStats(e),1e3)}lossBase=null;statsPolls=0;async pollStats(e){let t=null,n=0,r=0,i=0;try{(await e.getStats()).forEach(e=>{e.type===`inbound-rtp`&&e.kind===`video`?(n=Math.max(n,Number(e.framesPerSecond??0)),r+=Number(e.packetsLost??0),i+=Number(e.packetsReceived??0)):e.type===`candidate-pair`&&e.nominated&&typeof e.currentRoundTripTime==`number`&&(t=e.currentRoundTripTime)})}catch{return}if(this.core.emit(`netStats`,{fps:Math.round(n),rtt:t==null?null:Math.round(t*1e3)}),this.statsPolls+=1,!this.lossBase)this.lossBase={lost:r,received:i};else if(this.statsPolls%5==0){let e=Math.max(0,r-this.lossBase.lost),n=Math.max(0,i-this.lossBase.received);this.lossBase={lost:r,received:i};let a=e+n;if(a>0){let n=e/a*100;this.core.send({type:`video-stats`,lossPct:n,rttMs:t==null?void 0:Math.round(t*1e3)})}}}wireDataChannel(e){e.onopen=()=>this.core.sendHello(),e.onclose=()=>{this.core.emit(`status`,`disconnected`),this.scheduleReconnect()},e.onmessage=e=>{typeof e.data==`string`&&this.core.handleText(e.data)}}async fetchIceServers(e){let t=[...fe];try{let e=sessionStorage.getItem(`wd-ice`);if(e){let{servers:t,exp:n}=JSON.parse(e);if(Array.isArray(t)&&t.length&&n>Date.now())return t}}catch{}try{let t=this.config.iceUrl||`https://us-central1-${this.config.projectId}.cloudfunctions.net/iceServers`,n=await e.getIdToken(),r=await fetch(t,{headers:{authorization:`Bearer ${n}`}});if(r.ok){let e=await r.json();if(Array.isArray(e.iceServers)&&e.iceServers.length){try{sessionStorage.setItem(`wd-ice`,JSON.stringify({servers:e.iceServers,exp:Date.now()+8*6e4}))}catch{}return e.iceServers}}}catch{}return t}};function L(e,t,n){return e<t?t:e>n?n:e}var he=480,ge=600,R=-.6,z=1.6,_e=1500,ve=120,ye=3e3;function B(e){return!e||e.x<=.001&&e.y<=.001&&e.w>=.999&&e.h>=.999}function V(e,t){if(B(e)&&B(t))return!0;if(!e||!t)return!1;let n=.005;return Math.abs(e.x-t.x)<n&&Math.abs(e.y-t.y)<n&&Math.abs(e.w-t.w)<n&&Math.abs(e.h-t.h)<n}var be=class{canvas;ctx;mainEl=null;region=null;shownRegion=null;regionBridge=0;regionActiveHold=0;screen={width:0,height:0};viewMode=`fit`;zoom=1;center={nx:.5,ny:.5};cursor=null;onZoomCb;onViewCb;gestureActive=!1;viewDirty=!1;videoActive=!1;videoRaf=0;cssW=1;cssH=1;dpr=1;layout={S:1,tx:0,ty:0};rafPending=!1;overlayBox=null;overlayView=null;overlayCanvas=null;overlayCtx=null;lastOverlayKey=``;overlayCanvasKey=``;minimapActiveAt=0;minimapShown=!0;overview=null;overviewCtx=null;overviewReady=!1;overviewDirty=!1;lastSnapAt=0;overviewEl=null;constructor(e){this.canvas=e;let t=e.getContext(`2d`);if(!t)throw Error(`2D canvas context unavailable`);this.ctx=t,this.createOverlay(),this.startOverlayLoop(),this.resize(),new ResizeObserver(()=>this.resize()).observe(e),window.addEventListener(`orientationchange`,()=>window.setTimeout(()=>this.resize(),200))}createOverlay(){let e=document.createElement(`div`);e.style.cssText=`position:fixed;top:calc(env(safe-area-inset-top, 0px) + 56px);right:10px;box-sizing:border-box;border:1.5px solid rgba(255,255,255,0.8);border-radius:6px;background:rgba(0,0,0,0.55);z-index:2147483600;pointer-events:none;display:none;box-shadow:0 1px 8px rgba(0,0,0,0.6);`;let t=document.createElement(`canvas`);t.style.cssText=`position:absolute;inset:0;width:100%;height:100%;border-radius:5px;display:block;`;let n=document.createElement(`div`);n.style.cssText=`position:absolute;box-sizing:border-box;border:2px solid #4ea1ff;background:rgba(78,161,255,0.18);border-radius:2px;`,e.appendChild(t),e.appendChild(n),document.body.appendChild(e),this.overlayBox=e,this.overlayView=n,this.overlayCanvas=t,this.overlayCtx=t.getContext(`2d`)}startOverlayLoop(){let e=()=>{this.renderOverlay(),requestAnimationFrame(e)};requestAnimationFrame(e)}renderOverlay(){let e=this.overlayBox,t=this.overlayView;if(!e||!t)return;let n=this.getVisibleRegion(),r=this.region,i=this.dispW(),a=this.dispH();if(!(r||n.w<.985||n.h<.985)){e.style.display!==`none`&&(e.style.display=`none`),this.lastOverlayKey=``,this.overlayCanvasKey=``;return}e.style.display===`none`&&(e.style.display=`block`,this.minimapActiveAt=performance.now(),this.minimapShown=!1);let o=Math.round(Math.min(132,Math.max(76,this.cssW*.32))),s=Math.round(o*(a/i||.625)),c=`${o}x${s}`;(c!==this.overlayCanvasKey||this.overviewDirty)&&(this.overlayCanvasKey=c,this.overviewDirty=!1,e.style.width=`${o}px`,e.style.height=`${s}px`,this.paintMinimap(o,s));let l=n,u=performance.now(),d=`${l.x.toFixed(3)},${l.y.toFixed(3)},${l.w.toFixed(3)},${l.h.toFixed(3)}`;d!==this.lastOverlayKey&&(this.lastOverlayKey=d,this.minimapActiveAt=u,t.style.left=`${(L(l.x,0,1)*100).toFixed(2)}%`,t.style.top=`${(L(l.y,0,1)*100).toFixed(2)}%`,t.style.width=`${(L(Math.max(.04,l.w),0,1)*100).toFixed(2)}%`,t.style.height=`${(L(Math.max(.04,l.h),0,1)*100).toFixed(2)}%`);let f=u-this.minimapActiveAt<ye;f!==this.minimapShown&&(this.minimapShown=f,e.style.transition=`opacity ${f?.18:.5}s ease`,e.style.opacity=f?`1`:`0`)}paintMinimap(e,t){let n=this.overlayCanvas,r=this.overlayCtx;if(!n||!r)return;let i=Math.max(1,Math.round(e*this.dpr)),a=Math.max(1,Math.round(t*this.dpr));n.width!==i&&(n.width=i),n.height!==a&&(n.height=a),r.clearRect(0,0,i,a);let o=this.overviewImg();o&&(r.imageSmoothingEnabled=!0,r.imageSmoothingQuality=`low`,r.drawImage(o,0,0,i,a))}overviewImg(){return this.overviewReady?this.overview:null}maybeSnapshot(e,t,n){let r=performance.now();if(r-this.lastSnapAt<ge)return;this.lastSnapAt=r,this.overview||(this.overview=document.createElement(`canvas`),this.overviewCtx=this.overview.getContext(`2d`));let i=this.overview,a=this.overviewCtx;if(!i||!a)return;let o=he,s=Math.max(1,Math.round(o*n/t));i.width!==o&&(i.width=o),i.height!==s&&(i.height=s),a.imageSmoothingEnabled=!0,a.imageSmoothingQuality=`medium`,a.drawImage(e,0,0,t,n,0,0,o,s),this.overviewReady=!0,this.overviewDirty=!0}getCanvas(){return this.canvas}resize(){let e=this.canvas.getBoundingClientRect();this.cssW=Math.max(1,e.width),this.cssH=Math.max(1,e.height),this.dpr=Math.min(window.devicePixelRatio||1,2),this.canvas.width=Math.round(this.cssW*this.dpr),this.canvas.height=Math.round(this.cssH*this.dpr),this.requestDraw(),this.emitView()}setScreen(e){let t=e.width!==this.screen.width||e.height!==this.screen.height;this.screen=e,t&&(this.overviewReady=!1,this.lastSnapAt=0,this.overviewDirty=!0),this.requestDraw()}getScreen(){return this.screen}dispW(){return this.screen.width||this.mainEl?.videoWidth||1}dispH(){return this.screen.height||this.mainEl?.videoHeight||1}setFrameRegion(e){let t=B(e)?null:e;this.region=t,window.clearTimeout(this.regionActiveHold),window.clearTimeout(this.regionBridge),this.regionBridge=window.setTimeout(()=>{this.shownRegion=t,this.videoActive||this.requestDraw()},_e),this.videoActive||this.requestDraw()}setFrameRegionActive(e){let t=B(e)?null:e;V(t,this.region)&&(V(t,this.shownRegion)||(window.clearTimeout(this.regionActiveHold),this.regionActiveHold=window.setTimeout(()=>{V(t,this.region)&&(this.shownRegion=t,window.clearTimeout(this.regionBridge),this.videoActive||this.requestDraw())},ve)))}setVideoSource(e){this.mainEl=e,this.videoActive=!!e,e?this.startVideoLoop():this.stopVideoLoop()}isVideoActive(){return this.videoActive}setOverviewSource(e){this.overviewEl=e}startVideoLoop(){if(this.videoRaf)return;let e=()=>{if(!this.videoActive){this.videoRaf=0;return}this.draw(),this.videoRaf=requestAnimationFrame(e)};this.videoRaf=requestAnimationFrame(e)}stopVideoLoop(){this.videoRaf&&cancelAnimationFrame(this.videoRaf),this.videoRaf=0,this.requestDraw()}setViewMode(e){this.viewMode=e,e===`fit`?(this.zoom=1,this.center={nx:.5,ny:.5}):this.zoom===1&&(this.zoom=2),this.emitView(),this.requestDraw()}getViewMode(){return this.viewMode}toggleViewMode(){return this.setViewMode(this.viewMode===`fit`?`magnify`:`fit`),this.viewMode}setZoom(e){this.zoom=L(e,1,8),this.viewMode=this.zoom===1?`fit`:`magnify`,this.zoom===1&&(this.center={nx:.5,ny:.5}),this.onZoomCb?.(this.zoom),this.emitView(),this.requestDraw()}zoomBy(e){this.setZoom(this.zoom*e)}getZoom(){return this.zoom}setOnZoom(e){this.onZoomCb=e}setOnView(e){this.onViewCb=e}beginViewGesture(){this.gestureActive=!0}endViewGesture(){this.gestureActive&&(this.gestureActive=!1,this.viewDirty&&(this.viewDirty=!1,this.onViewCb?.(this.getVisibleRegion())))}emitView(){if(this.gestureActive){this.viewDirty=!0;return}this.onViewCb?.(this.getVisibleRegion())}zoomAround(e,t,n){let r=this.canvasToNorm(t,n),i=L(this.zoom*e,1,8);if(i===1){this.setZoom(1);return}this.zoom=i,this.viewMode=`magnify`;let a=this.dispW(),o=this.dispH(),s=Math.min(this.cssW/a,this.cssH/o)*this.zoom;this.center.nx=L(r.nx-(t-this.cssW/2)/(a*s||1),R,z),this.center.ny=L(r.ny-(n-this.cssH/2)/(o*s||1),R,z),this.onZoomCb?.(this.zoom),this.emitView(),this.requestDraw()}panByNorm(e,t){this.center.nx=L(this.center.nx+e,R,z),this.center.ny=L(this.center.ny+t,R,z),this.emitView(),this.requestDraw()}panByCanvasPixels(e,t){let{S:n}=this.computeLayout(),r=this.dispW(),i=this.dispH();!r||!i||!n||(this.center.nx=L(this.center.nx-e/(r*n),R,z),this.center.ny=L(this.center.ny-t/(i*n),R,z),this.emitView(),this.requestDraw())}setCursor(e,t=0){this.cursor=e===null?null:{nx:e,ny:t},this.videoActive||this.requestDraw()}computeLayout(){let{cssW:e,cssH:t}=this,n=this.dispW(),r=this.dispH();if(!n||!r)return this.layout={S:1,tx:0,ty:0},this.layout;let i=Math.min(e/n,t/r)*this.zoom,a,o;return this.viewMode===`fit`||this.zoom===1?(a=(e-n*i)/2,o=(t-r*i)/2):(a=e/2-this.center.nx*n*i,o=t/2-this.center.ny*r*i),this.layout={S:i,tx:a,ty:o},this.layout}canvasToNorm(e,t){let{S:n,tx:r,ty:i}=this.computeLayout();return{nx:L((e-r)/(this.dispW()*n||1),0,1),ny:L((t-i)/(this.dispH()*n||1),0,1)}}normToCanvas(e,t){let{S:n,tx:r,ty:i}=this.computeLayout();return{cx:r+e*this.dispW()*n,cy:i+t*this.dispH()*n}}getVisibleRegion(){let{S:e,tx:t,ty:n}=this.computeLayout(),r=this.dispW()*e,i=this.dispH()*e;if(!r||!i)return{x:0,y:0,w:1,h:1};let a=L(-t/r,0,1),o=L(-n/i,0,1),s=L((this.cssW-t)/r,0,1),c=L((this.cssH-n)/i,0,1);return{x:a,y:o,w:Math.max(.05,s-a),h:Math.max(.05,c-o)}}requestDraw(){this.videoActive||this.rafPending||(this.rafPending=!0,requestAnimationFrame(()=>{this.rafPending=!1,this.draw()}))}draw(){let e=this.ctx;e.setTransform(this.dpr,0,0,this.dpr,0,0),e.clearRect(0,0,this.cssW,this.cssH);let t=this.mainEl&&this.mainEl.videoWidth>0?this.mainEl:null;if(!t)return;let{S:n,tx:r,ty:i}=this.computeLayout(),a=this.dispW(),o=this.dispH(),s=a*n,c=o*n,l=t.videoWidth,u=t.videoHeight,d=l/u,f=e=>e?e.w*a/(e.h*o):a/o;if(this.region!==this.shownRegion){let e=Math.abs(d/f(this.region)-1)<.06,t=Math.abs(d/f(this.shownRegion)-1)<.06;e&&!t&&(this.shownRegion=this.region,window.clearTimeout(this.regionBridge))}let p=this.shownRegion,m=!!p&&Math.abs(d/f(p)-1)<.08,h=m?r+p.x*s:r,g=m?i+p.y*c:i,_=m?p.w*s:s,v=m?p.h*c:c,y=this.overviewImg();y&&(this.region||this.shownRegion)&&(e.imageSmoothingEnabled=!0,e.imageSmoothingQuality=`low`,e.drawImage(y,r,i,s,c));let b=Math.min(_/l,v/u),x=l*b,S=u*b;if(e.imageSmoothingEnabled=!0,e.imageSmoothingQuality=`high`,e.drawImage(t,0,0,l,u,h+(_-x)/2,g+(v-S)/2,x,S),this.cursor){let{cx:e,cy:t}=this.normToCanvas(this.cursor.nx,this.cursor.ny);this.drawCursor(e,t)}let C=this.overviewEl;if(this.region&&C&&C.videoWidth>0)this.maybeSnapshot(C,C.videoWidth,C.videoHeight);else if(!this.region&&!this.shownRegion&&o>0){let e=a/o;e>0&&Math.abs(d/e-1)<.06&&this.maybeSnapshot(t,l,u)}}drawCursor(e,t){let n=this.ctx;n.save(),n.beginPath(),n.arc(e,t,9,0,Math.PI*2),n.strokeStyle=`rgba(78,161,255,0.95)`,n.lineWidth=2,n.stroke(),n.beginPath(),n.arc(e,t,2.5,0,Math.PI*2),n.fillStyle=`rgba(78,161,255,0.95)`,n.fill(),n.restore()}};function xe(e,t,n,r){let i=document.createElement(`div`);i.className=`wd-place-layer`;let a=document.createElement(`div`);a.className=`wd-place-marker`;let o=document.createElement(`div`);o.className=`wd-place-bar`;let s=document.createElement(`p`);s.className=`wd-place-hint`,s.textContent=n.hint,o.appendChild(s);let c=null;n.withText&&(c=document.createElement(`textarea`),c.className=`wd-input wd-input-area wd-place-text`,c.placeholder=`Type the prompt to send…`,o.appendChild(c));let l=document.createElement(`div`);l.className=`wd-place-buttons`;let u=document.createElement(`button`);u.className=`wd-btn`,u.textContent=`Cancel`;let d=document.createElement(`button`);d.className=`wd-btn wd-go`,d.textContent=n.confirmLabel,l.append(u,d),o.appendChild(l),t.append(i,a,o),t.classList.add(`wd-placing`);let f={nx:.5,ny:.5},p=0,m=()=>{let{cx:t,cy:n}=e.normToCanvas(f.nx,f.ny);a.style.left=`${t}px`,a.style.top=`${n}px`,p=requestAnimationFrame(m)};p=requestAnimationFrame(m);let h=new Map,g=`idle`,_=null,v={x:0,y:0},y=!1,b=e=>{let t=i.getBoundingClientRect();return{x:e.clientX-t.left,y:e.clientY-t.top}},x=t=>{let{cx:n,cy:r}=e.normToCanvas(f.nx,f.ny);return Math.hypot(t.x-n,t.y-r)<38};i.addEventListener(`pointerdown`,t=>{i.setPointerCapture?.(t.pointerId);let n=b(t);if(h.set(t.pointerId,n),h.size===2){let e=[...h.values()],t=e[0],n=e[1];_={dist:Math.hypot(t.x-n.x,t.y-n.y),mx:(t.x+n.x)/2,my:(t.y+n.y)/2},g=`idle`}else h.size===1&&(v=n,y=!1,g=x(n)?`marker`:`pan`,g===`marker`&&(f=e.canvasToNorm(n.x,n.y)))}),i.addEventListener(`pointermove`,t=>{let n=h.get(t.pointerId);if(!n)return;let r=b(t);if(h.set(t.pointerId,r),h.size>=2&&_){let t=[...h.values()],n=t[0],r=t[1],i=Math.hypot(n.x-r.x,n.y-r.y),a=(n.x+r.x)/2,o=(n.y+r.y)/2;Math.abs(i-_.dist)>4&&(e.zoomAround(1+(i-_.dist)/200,a,o),_.dist=i),e.panByCanvasPixels(a-_.mx,o-_.my),_.mx=a,_.my=o;return}Math.hypot(r.x-v.x,r.y-v.y)>6&&(y=!0);let i=r.x-n.x,a=r.y-n.y;g===`marker`?f=e.canvasToNorm(r.x,r.y):g===`pan`&&e.panByCanvasPixels(i,a)});let S=t=>{let n=h.get(t.pointerId);h.delete(t.pointerId),n&&g===`pan`&&!y&&(f=e.canvasToNorm(n.x,n.y)),h.size<2&&(_=null),h.size===0&&(g=`idle`)};i.addEventListener(`pointerup`,S),i.addEventListener(`pointercancel`,S);let C=e=>{cancelAnimationFrame(p),t.classList.remove(`wd-placing`),i.remove(),a.remove(),o.remove(),r(e)};u.onclick=()=>C(null),d.onclick=()=>{let e=c?.value.trim()??``;if(n.withText&&!e){c?.focus();return}C({nx:f.nx,ny:f.ny,text:n.withText?e:void 0})}}var Se=0;function H(){return`w${Date.now().toString(36)}${(Se++).toString(36)}`}function Ce(){return new Date().toLocaleString(void 0,{month:`short`,day:`numeric`,hour:`2-digit`,minute:`2-digit`})}function we(e){let t=Math.max(0,Math.round((e-Date.now())/1e3)),n=Math.floor(t/3600),r=Math.floor(t%3600/60),i=t%60;return n>0?`${n}h ${r}m`:r>0?`${r}m ${String(i).padStart(2,`0`)}s`:`${i}s`}function U(e,t,n){let r=document.createElement(e);return t&&(r.className=t),n!==void 0&&(r.textContent=n),r}var Te={claude:`Claude Code`,codex:`Codex CLI`,gemini:`Gemini CLI`,aider:`Aider`,copilot:`Copilot CLI`,opencode:`opencode`,cursor:`Cursor Agent`,amp:`Amp`,unknown:`AI agent`};function W(e){return Te[e]??`AI agent`}function G(e){switch(e){case`working`:return`working`;case`blocked`:return`needs you`;case`idle`:return`idle`;case`finished`:return`finished`;case`crashed`:return`crashed`;default:return`…`}}var Ee=class{root;conn;view;notifications;requestNotifications;regions=[];timers=[];monitors=[];monitorSessions=[];alwaysAgents=new Set;renderPicker=null;renderAlways=null;countdownTimer=0;overlay;list;permissionRow;selector=null;constructor(e,t,n,r,i=()=>{}){this.root=e,this.conn=t,this.view=n,this.notifications=r,this.requestNotifications=i,this.overlay=U(`div`,`wd-dialog-overlay hidden`),this.overlay.addEventListener(`pointerdown`,e=>{e.target===this.overlay&&this.close()});let a=U(`div`,`wd-dialog`),o=U(`div`,`wd-dialog-head`);o.append(U(`h2`,``,`Auto-Whips`));let s=U(`button`,`wd-dialog-x`);s.appendChild(g(`x`)),s.onclick=()=>this.close(),o.appendChild(s);let c=U(`div`,`wd-dialog-help`),l=U(`p`,`wd-help-intro`,`Auto-Whips are a set of features that can whip and monitor agents for you automatically:`),u=U(`ul`,`wd-help-list`),d=U(`li`);d.append(U(`strong`,void 0,`Alerts`),document.createTextNode(` — watch part of the screen and ping you when it changes (e.g. your agent finishes).`));let f=U(`li`);f.append(U(`strong`,void 0,`Timers`),document.createTextNode(` — ping you after a set time, and can auto-click or send a prompt when they fire.`));let p=U(`li`);p.append(U(`strong`,void 0,`Session Monitoring`),document.createTextNode(` — pick a running AI session and get pinged the moment the agent stops working (it's waiting on you or has gone idle).`)),u.append(d,f,p);let m=U(`p`,`wd-help-note`,`Enable browser notifications to be reminded even when the browser is closed.`);c.append(l,u,m),this.permissionRow=U(`div`,`wd-perm-row`),this.list=U(`div`,`wd-watch-list`);let h=U(`button`,`wd-btn wd-go`);h.append(g(`plus`),U(`span`,`wd-btn-label`,`Add alert`)),h.onclick=()=>this.beginSelection();let _=U(`button`,`wd-btn wd-go`);_.append(g(`clock`),U(`span`,`wd-btn-label`,`Add timer`)),_.onclick=()=>this.beginTimer();let v=U(`button`,`wd-btn wd-go`);v.append(g(`activity`),U(`span`,`wd-btn-label`,`Add Session Monitoring`)),v.onclick=()=>this.beginMonitor();let y=U(`div`,`wd-dialog-actions wd-actions-stack`);y.append(h,_,v),a.append(o,c,this.permissionRow,this.list,y),this.overlay.appendChild(a),this.root.appendChild(this.overlay),this.renderPermission(),this.conn.on(`monitorSessions`,e=>{this.monitorSessions=e,this.renderPicker?.()})}setRegions(e){this.regions=e,this.renderList()}setTimers(e){this.timers=e,this.renderList()}setMonitors(e){this.monitors=e,this.renderList()}setAlwaysAgents(e){this.alwaysAgents=new Set(e),this.renderAlways?.()}open(){this.renderPermission(),this.renderList(),this.overlay.classList.remove(`hidden`),window.clearInterval(this.countdownTimer),this.countdownTimer=window.setInterval(()=>{this.timers.length&&this.renderList()},1e3)}close(){this.overlay.classList.add(`hidden`),window.clearInterval(this.countdownTimer),this.countdownTimer=0}renderPermission(){this.permissionRow.replaceChildren();let e=U(`span`,`wd-perm-dot`),t=U(`span`,`wd-perm-text`),n=this.notifications.permission;if(n===`granted`)e.dataset.state=`on`,t.textContent=`Browser notifications are on.`,this.permissionRow.append(e,t);else if(n===`denied`)e.dataset.state=`off`,t.textContent=`Notifications are blocked — enable them in your browser settings.`,this.permissionRow.append(e,t);else if(n===`unsupported`)e.dataset.state=`off`,t.textContent=`This browser can't show notifications. Keep this page open for in-app alerts.`,this.permissionRow.append(e,t);else{e.dataset.state=`warn`,t.textContent=`Allow notifications to be alerted while this page is in the background.`;let n=U(`button`,`wd-btn wd-perm-enable`);n.append(U(`span`,`wd-btn-label`,`Enable`)),n.onclick=async()=>{await this.requestNotifications(),this.renderPermission()},this.permissionRow.append(e,t,n)}}renderList(){if(this.list.replaceChildren(),this.regions.length===0&&this.timers.length===0&&this.monitors.length===0){this.list.appendChild(U(`p`,`wd-dialog-help`,`No alerts, timers, or session monitors yet.`));return}for(let e of this.regions){let t=U(`div`,`wd-watch-row`),n=U(`button`,`wd-watch-name`,e.label);n.title=`Edit this alert`,n.onclick=()=>this.beginSelection(e),t.appendChild(n);let r=U(`button`,`wd-btn wd-icon-only`);r.appendChild(g(`trash`)),r.setAttribute(`aria-label`,`Remove ${e.label}`),r.onclick=()=>{this.conn.send({type:`watch-remove`,id:e.id}),this.regions=this.regions.filter(t=>t.id!==e.id),this.renderList()},t.appendChild(r),this.list.appendChild(t)}for(let e of this.timers){let t=U(`div`,`wd-watch-row`),n=U(`div`,`wd-timer-info`),r=U(`span`,`wd-timer-name`);r.append(g(`clock`,15),document.createTextNode(e.label));let i=U(`span`,`wd-timer-remain`,we(e.fireAtMs));n.append(r,i),t.appendChild(n),e.hasAction&&t.appendChild(U(`span`,`wd-timer-tag`,`auto`));let a=U(`button`,`wd-btn wd-icon-only`);a.appendChild(g(`trash`)),a.setAttribute(`aria-label`,`Cancel ${e.label}`),a.onclick=()=>{this.conn.send({type:`timer-remove`,id:e.id}),this.timers=this.timers.filter(t=>t.id!==e.id),this.renderList()},t.appendChild(a),this.list.appendChild(t)}for(let e of this.monitors){let t=U(`div`,`wd-watch-row`),n=U(`div`,`wd-timer-info`),r=U(`span`,`wd-timer-name`);r.append(g(`activity`,15),document.createTextNode(`${W(e.agent)} · ${e.label}`));let i=U(`span`,`wd-mon-state`);i.dataset.state=e.live?e.state:`finished`,i.textContent=e.live?G(e.state):`ended`,n.append(r,i),t.appendChild(n);let a=U(`button`,`wd-btn wd-icon-only`);a.appendChild(g(`trash`)),a.setAttribute(`aria-label`,`Stop monitoring ${e.label}`),a.onclick=()=>{this.conn.send({type:`monitor-remove`,id:e.id}),this.monitors=this.monitors.filter(t=>t.id!==e.id),this.renderList()},t.appendChild(a),this.list.appendChild(t)}}beginSelection(e){this.close(),this.selector&&this.selector.remove();let t=this.root.getBoundingClientRect(),n=e?this.boxFromRegion(e,t):{x:t.width*.3,y:t.height*.3,w:t.width*.4,h:t.height*.25},r=U(`div`,`wd-selector-info`,e?`Move or resize the area to watch, then Save — you'll be alerted when its pixels change.`:`You'll get a browser notification when the pixels inside this box change. The agent watches this part of the screen.`),i=U(`div`,`wd-selector`),a=U(`div`,`wd-selector-move`);a.appendChild(g(`drag`));let o=U(`div`,`wd-selector-handle`),s=U(`div`,`wd-selector-bar`),c=U(`button`,`wd-btn wd-go`);c.append(U(`span`,`wd-btn-label`,e?`Save`:`Create`));let l=U(`button`,`wd-btn`);l.append(U(`span`,`wd-btn-label`,`Cancel`)),s.append(l,c),i.append(a,o,s),this.root.append(r,i),this.selector=i;let u=()=>{i.remove(),r.remove(),this.selector=null,this.open()},d=()=>{i.style.left=`${n.x}px`,i.style.top=`${n.y}px`,i.style.width=`${n.w}px`,i.style.height=`${n.h}px`};d();let f=(e,t)=>{e.addEventListener(`pointerdown`,n=>{n.preventDefault(),n.stopPropagation(),e.setPointerCapture(n.pointerId);let r=n.clientX,i=n.clientY,a=e=>{t(e.clientX-r,e.clientY-i),r=e.clientX,i=e.clientY,d()},o=()=>{e.removeEventListener(`pointermove`,a),e.removeEventListener(`pointerup`,o)};e.addEventListener(`pointermove`,a),e.addEventListener(`pointerup`,o)})};f(a,(e,r)=>{n.x=Math.max(0,Math.min(t.width-n.w,n.x+e)),n.y=Math.max(0,Math.min(t.height-n.h,n.y+r))}),f(o,(e,r)=>{n.w=Math.max(40,Math.min(t.width-n.x,n.w+e)),n.h=Math.max(40,Math.min(t.height-n.y,n.h+r))}),l.onclick=u,c.onclick=()=>{let t=this.toScreenRegion(n,e);t&&(this.conn.send({type:`watch-add`,region:t}),this.regions=e?this.regions.map(e=>e.id===t.id?t:e):[...this.regions,t],this.notifications.permission==="default"&&this.requestNotifications(),this.notifications.flash(e?`Alert updated`:`Alert created`,e?`"${t.label}" updated.`:`Monitoring "${t.label}". We'll notify you when it changes.`,`success`)),u()}}boxFromRegion(e,t){let n=this.view.normToCanvas(e.x,e.y),r=this.view.normToCanvas(e.x+e.w,e.y+e.h),i=Math.max(40,r.cx-n.cx),a=Math.max(40,r.cy-n.cy);return{x:Math.max(0,Math.min(t.width-i,n.cx)),y:Math.max(0,Math.min(t.height-a,n.cy)),w:i,h:a}}toScreenRegion(e,t){let n=this.view.canvasToNorm(e.x,e.y),r=this.view.canvasToNorm(e.x+e.w,e.y+e.h),i=Math.min(n.nx,r.nx),a=Math.min(n.ny,r.ny),o=Math.abs(r.nx-n.nx),s=Math.abs(r.ny-n.ny);return o<.005||s<.005?null:{id:t?.id??H(),label:t?.label??Ce(),x:i,y:a,w:o,h:s}}beginTimer(){this.close();let e=U(`div`,`wd-dialog-overlay`),t=()=>{e.remove(),this.open()};e.addEventListener(`pointerdown`,n=>{n.target===e&&t()});let n=U(`div`,`wd-dialog`),r=U(`div`,`wd-dialog-head`);r.append(U(`h2`,``,`Set timer`));let i=U(`button`,`wd-dialog-x`);i.appendChild(g(`x`)),i.onclick=t,r.appendChild(i);let a=U(`p`,`wd-dialog-help`,`Waiting for a session limit to reset? Get notified when the time's up. You can also schedule an action for the moment it hits zero — click Retry, or enter a new prompt.`),o=U(`div`,`wd-form-row`);o.appendChild(U(`label`,`wd-form-label`,`Remind me in`));let s=U(`input`,`wd-input wd-input-num`);s.type=`number`,s.min=`0`,s.max=`168`,s.value=`0`,s.inputMode=`numeric`;let c=U(`input`,`wd-input wd-input-num`);c.type=`number`,c.min=`0`,c.max=`59`,c.value=`30`,c.inputMode=`numeric`;let l=U(`div`,`wd-preset-row`);for(let[e,t,n]of[[`15m`,0,15],[`30m`,0,30],[`1h`,1,0],[`2h`,2,0],[`5h`,5,0]]){let r=U(`button`,`wd-preset`,e);r.type=`button`,r.onclick=()=>{s.value=String(t),c.value=String(n)},l.appendChild(r)}let u=(e,t,n,r)=>{let i=U(`div`,`wd-stepper`),a=U(`button`,`wd-step-btn`,`−`);a.type=`button`;let o=U(`button`,`wd-step-btn`,`+`);o.type=`button`;let s=e=>Math.max(0,Math.min(n,e));return a.onclick=()=>e.value=String(s((Number(e.value)||0)-t)),o.onclick=()=>e.value=String(s((Number(e.value)||0)+t)),i.append(a,e,U(`span`,`wd-form-unit`,r),o),i},d=U(`div`,`wd-form-duration`);d.append(u(s,1,168,`h`),u(c,5,59,`m`)),o.append(l,d);let f=U(`div`,`wd-form-row`);f.appendChild(U(`label`,`wd-form-label`,`Label`));let p=U(`input`,`wd-input`);p.placeholder=`e.g. Claude is back`,f.appendChild(p);let m=U(`div`,`wd-form-row`);m.appendChild(U(`label`,`wd-form-label`,`When it fires`));let h=U(`select`,`wd-input`);for(let[e,t]of[[`none`,`Just remind me`],[`click`,`Click a button`],[`key`,`Click & press Enter`],[`text`,`Click, type & send a prompt`]]){let n=document.createElement(`option`);n.value=e,n.textContent=t,h.appendChild(n)}m.appendChild(h);let _=U(`div`,`wd-dialog-actions`),v=U(`button`,`wd-btn`);v.append(U(`span`,`wd-btn-label`,`Cancel`)),v.onclick=t;let y=U(`button`,`wd-btn wd-go`),b=U(`span`,`wd-btn-label`,`Start timer`);y.append(g(`clock`),b);let x=()=>{b.textContent=h.value===`none`?`Start timer`:`Next: place target`};h.onchange=x,x();let S=()=>{let e=Math.max(0,Math.min(168,Math.round(Number(s.value)||0))),t=Math.max(0,Math.min(59,Math.round(Number(c.value)||0))),n=(e*60+t)*6e4;return n>=6e4?n:null},C=()=>{let e=Math.max(0,Math.round(Number(s.value)||0)),t=Math.max(0,Math.round(Number(c.value)||0));return`${e?`${e}h `:``}${t}m`},w=e=>{let n=H(),r=S(),i=p.value.trim()||`Timer (${C()})`;this.conn.send({type:`timer-add`,id:n,fireInMs:r,label:i,action:e}),this.timers=[...this.timers,{id:n,label:i,fireAtMs:Date.now()+r,hasAction:!!e}],this.notifications.permission==="default"&&this.requestNotifications(),this.notifications.flash(`Timer started`,`"${i}" — I'll ping you in ${C()}.`,`success`),t()};y.onclick=()=>{if(S()==null){this.notifications.flash(`Set a time`,`Choose at least 1 minute.`,`warning`);return}let t=h.value;if(t===`none`){w(void 0);return}e.style.display=`none`;let n=t===`text`?`Drag the crosshair onto the target input and type your prompt. The timer will click it to focus, insert the text and press Enter when time is up.`:t===`key`?`Drag the crosshair onto the field. The timer will click it to focus and press Enter when time is up.`:`Drag the crosshair onto the button or UI. The timer will click it when time is up.`;xe(this.view,this.root,{withText:t===`text`,hint:n,confirmLabel:`Confirm target`},n=>{if(!n){e.style.display=``;return}let r;r=t===`click`?{kind:`click`,x:n.nx,y:n.ny,button:`left`}:t===`key`?{kind:`key`,key:`Enter`,x:n.nx,y:n.ny}:{kind:`text`,text:n.text??``,x:n.nx,y:n.ny},w(r)})},_.append(v,y),n.append(r,a,o,f,m,_),e.appendChild(n),this.root.appendChild(e)}beginMonitor(){this.close();let e=U(`div`,`wd-dialog-overlay`),t=()=>{this.renderPicker=null,this.renderAlways=null,e.remove(),this.open()};e.addEventListener(`pointerdown`,n=>{n.target===e&&t()});let n=U(`div`,`wd-dialog`),r=U(`div`,`wd-dialog-head`);r.append(U(`h2`,``,`Monitor a session`));let i=U(`button`,`wd-dialog-x`);i.appendChild(g(`x`)),i.onclick=t,r.appendChild(i);let a=U(`p`,`wd-dialog-help`,`Pick a running AI session to watch. WhipDesk finds them automatically — no setup, no wrappers. You'll get one ping the moment the agent stops working (it's waiting on you or has gone idle).`),o=U(`div`,`wd-mon-pick-head`),s=U(`button`,`wd-btn`);s.append(U(`span`,`wd-btn-label`,`Rescan`)),s.onclick=()=>this.conn.send({type:`monitor-scan`}),o.append(U(`span`,`wd-form-label`,`Running sessions`),s);let c=U(`div`,`wd-mon-pick`),l=``,u=()=>{if(c.replaceChildren(),this.monitorSessions.length===0){l=``,v(),p(),c.appendChild(U(`p`,`wd-dialog-help`,`No AI sessions detected yet. Start Claude Code, Codex, Gemini, or Aider, then Rescan.`));return}(!l||!this.monitorSessions.some(e=>e.key===l))&&(l=this.monitorSessions[0].key);for(let e of this.monitorSessions){let t=U(`button`,`wd-mon-item`);t.type=`button`,e.key===l&&t.classList.add(`on`);let n=U(`div`,`wd-mon-item-main`);n.append(g(`activity`,15),U(`span`,`wd-mon-item-title`,`${W(e.agent)} · ${e.title}`));let r=U(`span`,`wd-mon-state`);r.dataset.state=e.state,r.textContent=G(e.state),t.append(n,r),t.onclick=()=>{l=e.key,u()},c.appendChild(t)}v(),p()};this.renderPicker=u;let d=U(`div`,`wd-form-row`);d.appendChild(U(`label`,`wd-form-label`,`Always alert me`));let f=U(`div`,`wd-mon-always`);d.appendChild(f);let p=()=>{f.replaceChildren();let e=this.monitorSessions.find(e=>e.key===l);if(!e){f.appendChild(U(`p`,`wd-dialog-help`,`Pick a session above to always alert for its agent.`));return}let t=e.agent,n=U(`label`,`wd-check`),r=U(`input`);r.type=`checkbox`,r.checked=this.alwaysAgents.has(t),r.onchange=()=>{r.checked?this.alwaysAgents.add(t):this.alwaysAgents.delete(t),this.conn.send({type:`monitor-always`,agent:t,enabled:r.checked}),r.checked&&this.notifications.permission==="default"&&this.requestNotifications(),p()};let i=U(`span`,`wd-check-text`);i.append(document.createTextNode(`Always monitor every ${W(t)} session`),U(`span`,`wd-check-sub`,`Keeps alerting across agent and WhipDesk restarts — no need to re-add it.`)),n.append(r,i),f.appendChild(n)};this.renderAlways=p;let m=U(`div`,`wd-dialog-actions`),h=U(`button`,`wd-btn`);h.append(U(`span`,`wd-btn-label`,`Cancel`)),h.onclick=t;let _=U(`button`,`wd-btn wd-go`);_.append(g(`activity`),U(`span`,`wd-btn-label`,`Add monitor`));let v=()=>_.disabled=!l;v(),_.onclick=()=>{let e=this.monitorSessions.find(e=>e.key===l);if(!e)return;let n=H(),r=e.title;this.conn.send({type:`monitor-add`,id:n,key:e.key,agent:e.agent,label:r}),this.monitors=[...this.monitors,{id:n,key:e.key,agent:e.agent,label:r,state:e.state,live:!0}],this.notifications.permission==="default"&&this.requestNotifications(),this.notifications.flash(`Monitoring started`,`Watching ${W(e.agent)} · ${r}.`,`success`),t()},m.append(h,_),n.append(r,a,o,c,d,m),e.appendChild(n),this.root.appendChild(e),u(),this.conn.send({type:`monitor-scan`})}};function De(){let e=new URLSearchParams(location.hash.replace(/^#/,``));return{token:e.get(`t`)??e.get(`token`)??``,remote:e.get(`remote`)===`1`||e.has(`device`),device:e.get(`device`)??e.get(`d`)??``}}function Oe(){return`${location.protocol===`https:`?`wss`:`ws`}://${location.host}/ws`}async function ke(){let e=window.__WHIPDESK_FB__;if(e?.apiKey)return e;try{let e=await fetch(`firebase.json`,{cache:`no-store`});if(e.ok)return await e.json()}catch{}return null}var K=document.getElementById(`app`);if(!K)throw Error(`#app not found`);var q=$(`canvas`,`wd-screen`);q.id=`wd-screen`;var Ae=$(`div`,`wd-toasts`);K.append(q,Ae);var{token:J,remote:je,device:Y}=De(),X=new be(q),Z=new ce(Ae),Me=new le(K),Q=new p(K);async function Ne(e){if(je&&Y){if(e)return new me(Y,J,e);Z.show({type:`notification`,id:`no-fb`,title:`Remote unavailable`,body:`Firebase config not found for remote mode.`,level:`error`,source:`client`,t:Date.now()})}return new u(Oe(),J)}async function Pe(){let e=je&&Y?await ke():null,t=await Ne(e),n=new se(q,X,t),r=new Ee(K,t,X,Z,()=>e?I(e,Z):Z.requestPermission()),i=new re(K,{conn:t,view:X,input:n,notifications:Z,watchers:r}),a=document.createElement(`video`);a.muted=!0,a.autoplay=!0,a.playsInline=!0,a.setAttribute(`playsinline`,``),a.style.display=`none`,K.append(a);let o=document.createElement(`video`);o.muted=!0,o.autoplay=!0,o.playsInline=!0,o.setAttribute(`playsinline`,``),o.style.display=`none`,K.append(o);let s=!1,c=!1,l=``,u=0,d=e=>e.w>=.999&&e.h>=.999,f=e=>{let n=e.w>=.92&&e.h>=.92?{x:0,y:0,w:1,h:1}:e,r=`${n.x.toFixed(2)},${n.y.toFixed(2)},${n.w.toFixed(2)},${n.h.toFixed(2)}`;r!==l&&(l=r,t.send({type:`set-viewport`,x:n.x,y:n.y,w:n.w,h:n.h}))};X.setOnView(e=>{let t=X.getZoom()>1.01;window.clearTimeout(u),u=window.setTimeout(()=>f(t?e:{x:0,y:0,w:1,h:1}),t?120:0)}),t.on(`status`,e=>{i.setStatus(e),e===`connected`?(c=!1,Q.hide()):(!s||c)&&Q.show(s?`Reconnecting…`:void 0),e===`disconnected`&&(l=``)}),t.on(`transport`,e=>i.setTransport(e)),t.on(`welcome`,e=>{let n=!s;s=!0,Q.hide(),Me.hide(),X.setScreen(e.screen),i.setWelcome(e),n&&(l=`0.000,0.000,1.000,1.000`,X.setZoom(1),t.send({type:`set-viewport`,x:0,y:0,w:1,h:1}))}),t.on(`versionMismatch`,e=>Fe(e.agentVersion)),t.on(`pinRequired`,e=>{Q.hide(),Me.show(e,e=>t.submitPin(e))}),t.on(`presence`,e=>i.setPresence(e));let p=0,m=0,h=0,g=()=>i.setAlertCount(p+m+h);if(t.on(`watchers`,e=>{p=e.length,r.setRegions(e),g()}),t.on(`timers`,e=>{m=e.length,r.setTimers(e),g()}),t.on(`monitors`,e=>{h=e.length,r.setMonitors(e),g()}),t.on(`monitorAlways`,e=>{r.setAlwaysAgents(e)}),t.on(`screenRegion`,e=>{let t=d(e)?null:e;e.active?X.setFrameRegionActive(t):X.setFrameRegion(t)}),t.on(`videoTrack`,e=>{if(!e){a.srcObject=null,X.setVideoSource(null);return}a.srcObject=e,a.play().catch(()=>{}),X.setVideoSource(a)}),t.on(`overviewTrack`,e=>{if(!e){o.srcObject=null,X.setOverviewSource(null);return}o.srcObject=e,o.play().catch(()=>{}),X.setOverviewSource(o)}),t.on(`netStats`,({fps:e,rtt:t})=>i.setNetStats(e,t)),t.on(`screenMeta`,({screen:e,activeDisplay:t})=>{X.setScreen(e),t!==void 0&&i.setActiveDisplay(t)}),t.on(`notification`,e=>Z.show(e)),t.on(`error`,e=>i.flashError(e)),e){let n=!1;t.on(`welcome`,()=>{n||Z.permission!==`granted`||(n=!0,I(e,Z))})}let _=()=>{document.hidden||t.isHealthy()||(c=!0,Q.show(`Reconnecting…`),t.wake())};document.addEventListener(`visibilitychange`,()=>{t.setVisible(!document.hidden),document.hidden||_()}),window.addEventListener(`pageshow`,_),J||i.flashError(`No pairing token in the URL (#t=…). Re-open the link/QR from the agent.`),t.connect()}Pe();function $(e,t){let n=document.createElement(e);return n.className=t,n}function Fe(e){let t=`wd-outdated-banner`;if(document.getElementById(t))return;let n=document.createElement(`div`);n.id=t,n.style.cssText=`position:fixed;top:0;left:0;right:0;z-index:9999;background:#7a2e00;color:#fff;font:14px/1.4 system-ui,sans-serif;padding:10px 40px 10px 14px;text-align:center;box-shadow:0 2px 8px rgba(0,0,0,.35)`,n.innerHTML=`⚠️ This WhipDesk agent${e?` (agent ${e})`:``} is out of date. Update from <a style="color:#ffd9a6" href="https://github.com/BinaryBananaLLC/WhipDesk/releases/latest" target="_blank" rel="noreferrer noopener">the latest release</a> or run <code>npm i -g whipdesk@latest</code>.`;let r=document.createElement(`button`);r.textContent=`✕`,r.setAttribute(`aria-label`,`Dismiss`),r.style.cssText=`position:absolute;right:8px;top:6px;background:none;border:none;color:#fff;font-size:16px;cursor:pointer`,r.onclick=()=>n.remove(),n.appendChild(r),document.body.appendChild(n)}
|