livedesk 0.1.126 → 0.1.128
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 +10 -0
- package/bin/livedesk.js +2 -2
- package/hub/src/remote-hub.js +10 -6
- package/hub/src/server.js +290 -19
- package/package.json +2 -2
- package/web/dist/assets/icons-DsPRskqi.js +1 -0
- package/web/dist/assets/index-aRXStJiu.js +10 -0
- package/web/dist/assets/react-BzjWCwsk.js +1 -0
- package/web/dist/assets/supabase-C7qjtLN3.js +29 -0
- package/web/dist/index.html +4 -1
- package/web/dist/assets/index-SKaOSr0A.js +0 -38
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ npx -y livedesk hub
|
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
This starts the LiveDesk Hub, opens the local screen wall, and accepts clients.
|
|
13
|
+
The Hub UI/API listens on `127.0.0.1` by default while the client endpoint
|
|
14
|
+
continues to listen on the LAN.
|
|
13
15
|
|
|
14
16
|
## Client
|
|
15
17
|
|
|
@@ -22,6 +24,14 @@ The client signs in with Google, discovers the active Hub, and connects to the w
|
|
|
22
24
|
On Windows, enable **Start with Windows** on the connection page to reconnect
|
|
23
25
|
automatically after reboot.
|
|
24
26
|
|
|
27
|
+
## Plans
|
|
28
|
+
|
|
29
|
+
- Free: 5 personal devices with a standard wall banner ad.
|
|
30
|
+
- Plus LTD launch: 30 personal devices, no ads, USD 79 one-time.
|
|
31
|
+
- Pro LTD launch: commercial use and larger walls, no ads, USD 199 one-time.
|
|
32
|
+
|
|
33
|
+
Monthly and yearly subscriptions are planned after the LTD launch.
|
|
34
|
+
|
|
25
35
|
## Free ad slot
|
|
26
36
|
|
|
27
37
|
Free accounts show a standard bottom ad slot. Set these Vite build variables
|
package/bin/livedesk.js
CHANGED
|
@@ -34,7 +34,7 @@ Commands:
|
|
|
34
34
|
Hub options:
|
|
35
35
|
--no-open Do not open the browser automatically.
|
|
36
36
|
--url <url> Browser URL to open. Default: ${DEFAULT_MANAGER_URL}
|
|
37
|
-
--host <host> Hub HTTP host. Default:
|
|
37
|
+
--host <host> Hub HTTP host. Default: 127.0.0.1
|
|
38
38
|
--port <port> Hub HTTP port. Default: 5179
|
|
39
39
|
--remote-port <port>
|
|
40
40
|
Client connection port. Default: 5197
|
|
@@ -304,7 +304,7 @@ async function runManager(args) {
|
|
|
304
304
|
const packagedWebDist = resolve(packageRoot, 'web', 'dist');
|
|
305
305
|
const env = {
|
|
306
306
|
...process.env,
|
|
307
|
-
LIVEDESK_HUB_HTTP_HOST: options.host || process.env.LIVEDESK_HUB_HTTP_HOST || '
|
|
307
|
+
LIVEDESK_HUB_HTTP_HOST: options.host || process.env.LIVEDESK_HUB_HTTP_HOST || '127.0.0.1',
|
|
308
308
|
LIVEDESK_HUB_HTTP_PORT: String(httpPort),
|
|
309
309
|
REMOTE_HUB_PORT: String(remotePort),
|
|
310
310
|
REMOTE_HUB_PAIR_TOKEN: pairToken,
|
package/hub/src/remote-hub.js
CHANGED
|
@@ -17,11 +17,11 @@ const MAX_AGENT_TASK_CHARS = 4000;
|
|
|
17
17
|
const MAX_AGENT_TASK_RESULT_CHARS = 3000;
|
|
18
18
|
const RECENT_TASK_LIMIT = 12;
|
|
19
19
|
const RECENT_TASK_BATCH_LIMIT = 16;
|
|
20
|
-
const RECENT_FRAME_CACHE_TTL_MS =
|
|
21
|
-
const RECENT_THUMBNAIL_FRAME_CACHE_LIMIT =
|
|
22
|
-
const RECENT_LIVE_FRAME_CACHE_LIMIT =
|
|
23
|
-
const RECENT_THUMBNAIL_FRAME_CACHE_MAX_BYTES =
|
|
24
|
-
const RECENT_LIVE_FRAME_CACHE_MAX_BYTES =
|
|
20
|
+
const RECENT_FRAME_CACHE_TTL_MS = 4000;
|
|
21
|
+
const RECENT_THUMBNAIL_FRAME_CACHE_LIMIT = 2;
|
|
22
|
+
const RECENT_LIVE_FRAME_CACHE_LIMIT = 1;
|
|
23
|
+
const RECENT_THUMBNAIL_FRAME_CACHE_MAX_BYTES = 2 * 1024 * 1024;
|
|
24
|
+
const RECENT_LIVE_FRAME_CACHE_MAX_BYTES = 3 * 1024 * 1024;
|
|
25
25
|
const LIVE_STREAM_PENDING_REUSE_MS = 5000;
|
|
26
26
|
const LIVE_STREAM_MIN_FRESH_MS = 3000;
|
|
27
27
|
const LIVE_STREAM_MAX_FRESH_MS = 12000;
|
|
@@ -2840,7 +2840,7 @@ export function createRemoteHub(options = {}) {
|
|
|
2840
2840
|
|
|
2841
2841
|
function buildFramePayloadBuffer(framePayload, frameData) {
|
|
2842
2842
|
if (Buffer.isBuffer(framePayload)) {
|
|
2843
|
-
return
|
|
2843
|
+
return framePayload;
|
|
2844
2844
|
}
|
|
2845
2845
|
|
|
2846
2846
|
const raw = String(frameData || '');
|
|
@@ -2931,6 +2931,8 @@ export function createRemoteHub(options = {}) {
|
|
|
2931
2931
|
durationUs: Number.isFinite(Number(message.durationUs)) ? Number(message.durationUs) : 0,
|
|
2932
2932
|
sourceGapMs: Number.isFinite(Number(message.sourceGapMs)) ? Number(message.sourceGapMs) : 0,
|
|
2933
2933
|
agentSendGapMs: Number.isFinite(Number(message.agentSendGapMs)) ? Number(message.agentSendGapMs) : 0,
|
|
2934
|
+
agentSendDurationMs: Number.isFinite(Number(message.agentSendDurationMs)) ? Number(message.agentSendDurationMs) : 0,
|
|
2935
|
+
agentSendDurationFrameSeq: Number.isFinite(Number(message.agentSendDurationFrameSeq)) ? Number(message.agentSendDurationFrameSeq) : 0,
|
|
2934
2936
|
agentPaceMs: Number.isFinite(Number(message.agentPaceMs)) ? Number(message.agentPaceMs) : 0,
|
|
2935
2937
|
droppedByAgent: Number.isFinite(Number(message.droppedByAgent)) ? Number(message.droppedByAgent) : 0,
|
|
2936
2938
|
hardwareEncoder: safeString(message.hardwareEncoder, 80),
|
|
@@ -3113,6 +3115,8 @@ export function createRemoteHub(options = {}) {
|
|
|
3113
3115
|
durationUs: Number.isFinite(Number(message.durationUs)) ? Number(message.durationUs) : 0,
|
|
3114
3116
|
sourceGapMs: Number.isFinite(Number(message.sourceGapMs)) ? Number(message.sourceGapMs) : 0,
|
|
3115
3117
|
agentSendGapMs: Number.isFinite(Number(message.agentSendGapMs)) ? Number(message.agentSendGapMs) : 0,
|
|
3118
|
+
agentSendDurationMs: Number.isFinite(Number(message.agentSendDurationMs)) ? Number(message.agentSendDurationMs) : 0,
|
|
3119
|
+
agentSendDurationFrameSeq: Number.isFinite(Number(message.agentSendDurationFrameSeq)) ? Number(message.agentSendDurationFrameSeq) : 0,
|
|
3116
3120
|
agentPaceMs: Number.isFinite(Number(message.agentPaceMs)) ? Number(message.agentPaceMs) : 0,
|
|
3117
3121
|
droppedByAgent: Number.isFinite(Number(message.droppedByAgent)) ? Number(message.droppedByAgent) : 0,
|
|
3118
3122
|
hardwareEncoder: safeString(message.hardwareEncoder, 80),
|
package/hub/src/server.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import cors from 'cors';
|
|
4
3
|
import express from 'express';
|
|
5
4
|
import crypto from 'node:crypto';
|
|
6
5
|
import { createServer } from 'node:http';
|
|
@@ -19,7 +18,7 @@ const webDistCandidates = [
|
|
|
19
18
|
const webDistPath = webDistCandidates.find(candidate => existsSync(resolve(candidate, 'index.html'))) || webDistCandidates[webDistCandidates.length - 1];
|
|
20
19
|
const webIndexPath = resolve(webDistPath, 'index.html');
|
|
21
20
|
const packageInfo = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
|
|
22
|
-
const httpHost = process.env.LIVEDESK_HUB_HTTP_HOST || '
|
|
21
|
+
const httpHost = process.env.LIVEDESK_HUB_HTTP_HOST || '127.0.0.1';
|
|
23
22
|
const httpPort = Number(process.env.LIVEDESK_HUB_HTTP_PORT || process.env.PORT || 5179);
|
|
24
23
|
const frameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_BACKPRESSURE_BYTES', 16 * 1024 * 1024);
|
|
25
24
|
const frameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_QUEUE_PACKETS', 12);
|
|
@@ -29,6 +28,23 @@ const frameClientsByDeviceId = new Map();
|
|
|
29
28
|
const frameWildcardClients = new Set();
|
|
30
29
|
const inputClients = new Set();
|
|
31
30
|
const audioClients = new Set();
|
|
31
|
+
const FREE_DEVICE_LIMIT = 5;
|
|
32
|
+
const PLUS_DEVICE_LIMIT = 30;
|
|
33
|
+
const LICENSE_VERIFY_MAX_AGE_MS = 6 * 60 * 60 * 1000;
|
|
34
|
+
const supabaseUrl = String(process.env.LIVEDESK_SUPABASE_URL || 'https://otbyfkjxrkngvjziawki.supabase.co').replace(/\/+$/, '');
|
|
35
|
+
const supabasePublishableKey = String(process.env.LIVEDESK_SUPABASE_PUBLISHABLE_KEY || 'sb_publishable_NpUs0RDJH2YnllsqTKO6TQ_1jTdSsNQ');
|
|
36
|
+
const testLicensePlan = process.env.LIVEDESK_TEST_MODE === '1'
|
|
37
|
+
&& ['ltd', 'pro'].includes(String(process.env.LIVEDESK_TEST_LICENSE_PLAN || '').toLowerCase())
|
|
38
|
+
? String(process.env.LIVEDESK_TEST_LICENSE_PLAN).toLowerCase()
|
|
39
|
+
: '';
|
|
40
|
+
let connectedDeviceCount = 0;
|
|
41
|
+
let verifiedLicense = {
|
|
42
|
+
userId: '',
|
|
43
|
+
plan: 'free',
|
|
44
|
+
status: 'inactive',
|
|
45
|
+
expiresAt: '',
|
|
46
|
+
verifiedAt: 0
|
|
47
|
+
};
|
|
32
48
|
let frameClientSeq = 0;
|
|
33
49
|
let inputClientSeq = 0;
|
|
34
50
|
let audioClientSeq = 0;
|
|
@@ -39,6 +55,9 @@ function readPositiveIntegerEnv(name, fallback) {
|
|
|
39
55
|
}
|
|
40
56
|
|
|
41
57
|
function handleRemoteHubEvent(type, event) {
|
|
58
|
+
if (type === 'RemoteDeviceConnected' || type === 'RemoteDeviceDisconnected') {
|
|
59
|
+
connectedDeviceCount = Number(remoteHub.getStatus({ includeSecrets: false }).connectedDeviceCount || 0);
|
|
60
|
+
}
|
|
42
61
|
if (type !== 'RemoteDeviceConnected') {
|
|
43
62
|
return;
|
|
44
63
|
}
|
|
@@ -59,6 +78,96 @@ function handleRemoteHubEvent(type, event) {
|
|
|
59
78
|
}
|
|
60
79
|
}
|
|
61
80
|
|
|
81
|
+
function activeLicensePlan() {
|
|
82
|
+
if (testLicensePlan) {
|
|
83
|
+
return testLicensePlan;
|
|
84
|
+
}
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
if (verifiedLicense.status !== 'active'
|
|
87
|
+
|| now - verifiedLicense.verifiedAt > LICENSE_VERIFY_MAX_AGE_MS) {
|
|
88
|
+
return 'free';
|
|
89
|
+
}
|
|
90
|
+
const expiresAt = verifiedLicense.expiresAt ? Date.parse(verifiedLicense.expiresAt) : Number.POSITIVE_INFINITY;
|
|
91
|
+
if (Number.isFinite(expiresAt) && expiresAt <= now) {
|
|
92
|
+
return 'free';
|
|
93
|
+
}
|
|
94
|
+
return verifiedLicense.plan === 'pro' ? 'pro' : verifiedLicense.plan === 'ltd' ? 'ltd' : 'free';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function activeDeviceLimit() {
|
|
98
|
+
const plan = activeLicensePlan();
|
|
99
|
+
return plan === 'pro' ? Number.POSITIVE_INFINITY : plan === 'ltd' ? PLUS_DEVICE_LIMIT : FREE_DEVICE_LIMIT;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function hasHubFeatureAccess() {
|
|
103
|
+
return connectedDeviceCount <= activeDeviceLimit();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function licenseSnapshot() {
|
|
107
|
+
const plan = activeLicensePlan();
|
|
108
|
+
const limit = activeDeviceLimit();
|
|
109
|
+
return {
|
|
110
|
+
plan,
|
|
111
|
+
status: plan === 'free' ? 'free' : 'active',
|
|
112
|
+
deviceLimit: Number.isFinite(limit) ? limit : null,
|
|
113
|
+
connectedDeviceCount,
|
|
114
|
+
featureAccess: connectedDeviceCount <= limit,
|
|
115
|
+
verifiedAt: verifiedLicense.verifiedAt ? new Date(verifiedLicense.verifiedAt).toISOString() : '',
|
|
116
|
+
expiresAt: verifiedLicense.expiresAt || ''
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function syncVerifiedLicense(accessToken) {
|
|
121
|
+
const token = String(accessToken || '').trim();
|
|
122
|
+
if (!token) {
|
|
123
|
+
throw new Error('supabase-access-token-required');
|
|
124
|
+
}
|
|
125
|
+
const headers = {
|
|
126
|
+
apikey: supabasePublishableKey,
|
|
127
|
+
Authorization: `Bearer ${token}`,
|
|
128
|
+
Accept: 'application/json'
|
|
129
|
+
};
|
|
130
|
+
const userResponse = await fetch(`${supabaseUrl}/auth/v1/user`, { headers });
|
|
131
|
+
if (!userResponse.ok) {
|
|
132
|
+
throw new Error(`supabase-user-verification-failed:${userResponse.status}`);
|
|
133
|
+
}
|
|
134
|
+
const user = await userResponse.json();
|
|
135
|
+
const userId = String(user?.id || '').trim();
|
|
136
|
+
if (!userId) {
|
|
137
|
+
throw new Error('supabase-user-missing');
|
|
138
|
+
}
|
|
139
|
+
const query = new URLSearchParams({
|
|
140
|
+
select: 'user_id,product_key,plan,status,expires_at,updated_at',
|
|
141
|
+
user_id: `eq.${userId}`,
|
|
142
|
+
product_key: 'eq.livedesk',
|
|
143
|
+
limit: '1'
|
|
144
|
+
});
|
|
145
|
+
const entitlementResponse = await fetch(`${supabaseUrl}/rest/v1/livedesk_entitlements?${query}`, { headers });
|
|
146
|
+
if (!entitlementResponse.ok) {
|
|
147
|
+
throw new Error(`supabase-entitlement-query-failed:${entitlementResponse.status}`);
|
|
148
|
+
}
|
|
149
|
+
const rows = await entitlementResponse.json();
|
|
150
|
+
const entitlement = Array.isArray(rows) ? rows[0] : null;
|
|
151
|
+
const plan = entitlement?.plan === 'pro' ? 'pro' : entitlement?.plan === 'ltd' ? 'ltd' : 'free';
|
|
152
|
+
const status = entitlement?.status === 'active' ? 'active' : 'inactive';
|
|
153
|
+
verifiedLicense = {
|
|
154
|
+
userId,
|
|
155
|
+
plan,
|
|
156
|
+
status,
|
|
157
|
+
expiresAt: String(entitlement?.expires_at || ''),
|
|
158
|
+
verifiedAt: Date.now()
|
|
159
|
+
};
|
|
160
|
+
return licenseSnapshot();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function requireHubFeatureAccess(_req, res, next) {
|
|
164
|
+
if (hasHubFeatureAccess()) {
|
|
165
|
+
next();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
res.status(402).json({ ok: false, error: 'livedesk-plan-device-limit', license: licenseSnapshot() });
|
|
169
|
+
}
|
|
170
|
+
|
|
62
171
|
const remoteHub = createRemoteHub({
|
|
63
172
|
managerPackage: '@livedesk/hub',
|
|
64
173
|
managerVersion: packageInfo.version,
|
|
@@ -80,20 +189,85 @@ const frameWss = new WebSocketServer({ noServer: true, perMessageDeflate: false
|
|
|
80
189
|
const inputWss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
|
|
81
190
|
const audioWss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
|
|
82
191
|
|
|
192
|
+
function normalizeHostname(value) {
|
|
193
|
+
return String(value || '').trim().toLowerCase().replace(/^\[|\]$/g, '');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isLoopbackHostname(value) {
|
|
197
|
+
const hostname = normalizeHostname(value);
|
|
198
|
+
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isPrivateHubHostname(value) {
|
|
202
|
+
const hostname = normalizeHostname(value);
|
|
203
|
+
if (isLoopbackHostname(hostname)
|
|
204
|
+
|| hostname === '0.0.0.0'
|
|
205
|
+
|| hostname.startsWith('10.')
|
|
206
|
+
|| hostname.startsWith('192.168.')
|
|
207
|
+
|| hostname.startsWith('169.254.')
|
|
208
|
+
|| hostname.startsWith('fc')
|
|
209
|
+
|| hostname.startsWith('fd')
|
|
210
|
+
|| hostname.startsWith('fe80:')) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
const match = /^(172)\.(\d{1,3})\./.exec(hostname);
|
|
214
|
+
return !!match && Number(match[2]) >= 16 && Number(match[2]) <= 31;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function isLoopbackAddress(value) {
|
|
218
|
+
const address = String(value || '').trim().toLowerCase();
|
|
219
|
+
return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function requestHostname(req) {
|
|
223
|
+
try {
|
|
224
|
+
return normalizeHostname(new URL(`http://${req.headers.host || '127.0.0.1'}`).hostname);
|
|
225
|
+
} catch {
|
|
226
|
+
return '';
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isTrustedBrowserRequest(req) {
|
|
231
|
+
const origin = String(req.headers.origin || '').trim();
|
|
232
|
+
if (!origin) {
|
|
233
|
+
const fetchSite = String(req.headers['sec-fetch-site'] || '').toLowerCase();
|
|
234
|
+
return isLoopbackAddress(req.socket?.remoteAddress)
|
|
235
|
+
|| fetchSite === 'same-origin'
|
|
236
|
+
|| fetchSite === 'none';
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const parsed = new URL(origin);
|
|
240
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
const originHost = normalizeHostname(parsed.hostname);
|
|
244
|
+
const targetHost = requestHostname(req);
|
|
245
|
+
return (originHost === targetHost && isPrivateHubHostname(targetHost))
|
|
246
|
+
|| (isLoopbackHostname(originHost) && isLoopbackHostname(targetHost));
|
|
247
|
+
} catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
83
252
|
app.use((req, res, next) => {
|
|
84
|
-
if (req
|
|
253
|
+
if (!isTrustedBrowserRequest(req)) {
|
|
254
|
+
res.status(403).json({ ok: false, error: 'untrusted-hub-origin' });
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const origin = String(req.headers.origin || '').trim();
|
|
258
|
+
if (origin) {
|
|
259
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
85
260
|
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
|
261
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
262
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
86
263
|
res.setHeader('Vary', 'Origin, Access-Control-Request-Headers, Access-Control-Request-Private-Network');
|
|
87
264
|
}
|
|
265
|
+
if (req.method === 'OPTIONS') {
|
|
266
|
+
res.status(204).end();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
88
269
|
next();
|
|
89
270
|
});
|
|
90
|
-
|
|
91
|
-
app.use(cors({
|
|
92
|
-
origin: true,
|
|
93
|
-
credentials: false,
|
|
94
|
-
methods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
|
|
95
|
-
allowedHeaders: ['Content-Type']
|
|
96
|
-
}));
|
|
97
271
|
app.use(express.json({ limit: '32mb' }));
|
|
98
272
|
|
|
99
273
|
const MAX_FILE_TRANSFER_FILES = 24;
|
|
@@ -280,7 +454,8 @@ function ensureFrameClientSendLane(ws) {
|
|
|
280
454
|
ws.liveDeskFrameSendLane = {
|
|
281
455
|
queue: [],
|
|
282
456
|
draining: false,
|
|
283
|
-
dropped: 0
|
|
457
|
+
dropped: 0,
|
|
458
|
+
awaitingKeyFrames: new Set()
|
|
284
459
|
};
|
|
285
460
|
}
|
|
286
461
|
return ws.liveDeskFrameSendLane;
|
|
@@ -291,6 +466,26 @@ function dropQueuedFrameForLane(lane) {
|
|
|
291
466
|
const dropIndex = deltaIndex >= 0 ? deltaIndex : 0;
|
|
292
467
|
const dropped = lane.queue.splice(dropIndex, 1);
|
|
293
468
|
lane.dropped += dropped.length;
|
|
469
|
+
const droppedItem = dropped[0];
|
|
470
|
+
if (droppedItem?.isH264 && droppedItem.deviceId) {
|
|
471
|
+
lane.awaitingKeyFrames.add(droppedItem.deviceId);
|
|
472
|
+
let recoveryKeySeen = false;
|
|
473
|
+
lane.queue = lane.queue.filter(item => {
|
|
474
|
+
if (!item?.isH264 || item.deviceId !== droppedItem.deviceId) {
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
if (item.isKeyFrame) {
|
|
478
|
+
recoveryKeySeen = true;
|
|
479
|
+
lane.awaitingKeyFrames.delete(item.deviceId);
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
if (recoveryKeySeen) {
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
lane.dropped += 1;
|
|
486
|
+
return false;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
294
489
|
}
|
|
295
490
|
|
|
296
491
|
function drainFrameClientSendLane(ws) {
|
|
@@ -332,6 +527,13 @@ function enqueueFramePacketForClient(ws, packet, meta) {
|
|
|
332
527
|
return false;
|
|
333
528
|
}
|
|
334
529
|
const lane = ensureFrameClientSendLane(ws);
|
|
530
|
+
if (meta.isH264 && meta.isKeyFrame) {
|
|
531
|
+
lane.awaitingKeyFrames.delete(meta.deviceId);
|
|
532
|
+
} else if (meta.isH264 && lane.awaitingKeyFrames.has(meta.deviceId)) {
|
|
533
|
+
lane.dropped += 1;
|
|
534
|
+
ws.liveDeskFrameBackpressureDrops = Number(ws.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
335
537
|
if (ws.bufferedAmount > frameBackpressureBytes) {
|
|
336
538
|
ws.liveDeskFrameBackpressured = true;
|
|
337
539
|
ws.liveDeskFrameBackpressureDrops = Number(ws.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
@@ -377,6 +579,10 @@ function updateFrameSubscription(ws, payload = {}) {
|
|
|
377
579
|
}
|
|
378
580
|
|
|
379
581
|
function startFrameSubscriptionLive(ws, reason = 'subscribe', onlyDeviceId = '', overrideLiveOptions = null) {
|
|
582
|
+
if (!hasHubFeatureAccess()) {
|
|
583
|
+
sendJson(ws, { type: 'RemoteFrameSubscriptionError', error: 'livedesk-plan-device-limit', license: licenseSnapshot() });
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
380
586
|
const subscribedIds = [...(ws.liveDeskDeviceIds || [])];
|
|
381
587
|
if (!ws.liveDeskAutoStart || subscribedIds.length === 0) {
|
|
382
588
|
return;
|
|
@@ -503,6 +709,8 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
|
|
|
503
709
|
durationUs: Number(frame.durationUs || 0) || 0,
|
|
504
710
|
sourceGapMs: Number(frame.sourceGapMs || 0) || 0,
|
|
505
711
|
agentSendGapMs: Number(frame.agentSendGapMs || 0) || 0,
|
|
712
|
+
agentSendDurationMs: Number(frame.agentSendDurationMs || 0) || 0,
|
|
713
|
+
agentSendDurationFrameSeq: Number(frame.agentSendDurationFrameSeq || 0) || 0,
|
|
506
714
|
agentPaceMs: Number(frame.agentPaceMs || 0) || 0,
|
|
507
715
|
droppedByAgent: Number(frame.droppedByAgent || 0) || 0,
|
|
508
716
|
hardwareEncoder: frame.hardwareEncoder || '',
|
|
@@ -548,6 +756,9 @@ function buildRemoteAudioBinaryPacket(audioEvent) {
|
|
|
548
756
|
}
|
|
549
757
|
|
|
550
758
|
function broadcastRemoteBinaryFrame(frameEvent) {
|
|
759
|
+
if (!hasHubFeatureAccess()) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
551
762
|
const deviceId = String(frameEvent?.deviceId || frameEvent?.frame?.deviceId || '').trim();
|
|
552
763
|
if (!deviceId || frameClients.size === 0) {
|
|
553
764
|
return;
|
|
@@ -581,6 +792,9 @@ function broadcastRemoteBinaryFrame(frameEvent) {
|
|
|
581
792
|
}
|
|
582
793
|
|
|
583
794
|
function broadcastRemoteBinaryAudio(audioEvent) {
|
|
795
|
+
if (!hasHubFeatureAccess()) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
584
798
|
const deviceId = String(audioEvent?.deviceId || audioEvent?.frame?.deviceId || '').trim();
|
|
585
799
|
if (!deviceId || audioClients.size === 0) {
|
|
586
800
|
return;
|
|
@@ -639,13 +853,60 @@ app.get('/api/health', (_req, res) => {
|
|
|
639
853
|
|
|
640
854
|
app.get('/api/remote/status', (_req, res) => {
|
|
641
855
|
noStore(res);
|
|
856
|
+
const secretStatus = remoteHub.getStatus({ includeSecrets: true });
|
|
642
857
|
res.json({
|
|
643
|
-
...remoteHub.getStatus({ includeSecrets:
|
|
858
|
+
...remoteHub.getStatus({ includeSecrets: false }),
|
|
859
|
+
pairingPin: secretStatus.pairingPin,
|
|
644
860
|
product: 'LiveDesk',
|
|
645
861
|
agentPackage: '@livedesk/client'
|
|
646
862
|
});
|
|
647
863
|
});
|
|
648
864
|
|
|
865
|
+
app.get('/api/remote/registry-credentials', (_req, res) => {
|
|
866
|
+
noStore(res);
|
|
867
|
+
const status = remoteHub.getStatus({ includeSecrets: true });
|
|
868
|
+
res.json({
|
|
869
|
+
pairToken: status.pairToken,
|
|
870
|
+
pairingPin: status.pairingPin,
|
|
871
|
+
pairingPinUpdatedAt: status.pairingPinUpdatedAt
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
app.get('/api/remote/license', (_req, res) => {
|
|
876
|
+
noStore(res);
|
|
877
|
+
res.json(licenseSnapshot());
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
app.post('/api/remote/license/sync', async (req, res) => {
|
|
881
|
+
noStore(res);
|
|
882
|
+
try {
|
|
883
|
+
const authorization = String(req.headers.authorization || '');
|
|
884
|
+
const accessToken = authorization.replace(/^Bearer\s+/i, '').trim();
|
|
885
|
+
res.json(await syncVerifiedLicense(accessToken));
|
|
886
|
+
} catch (err) {
|
|
887
|
+
verifiedLicense = {
|
|
888
|
+
userId: '',
|
|
889
|
+
plan: 'free',
|
|
890
|
+
status: 'inactive',
|
|
891
|
+
expiresAt: '',
|
|
892
|
+
verifiedAt: Date.now()
|
|
893
|
+
};
|
|
894
|
+
res.status(401).json({ ok: false, error: err instanceof Error ? err.message : String(err), license: licenseSnapshot() });
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
app.delete('/api/remote/license', (_req, res) => {
|
|
899
|
+
noStore(res);
|
|
900
|
+
verifiedLicense = {
|
|
901
|
+
userId: '',
|
|
902
|
+
plan: 'free',
|
|
903
|
+
status: 'inactive',
|
|
904
|
+
expiresAt: '',
|
|
905
|
+
verifiedAt: Date.now()
|
|
906
|
+
};
|
|
907
|
+
res.json(licenseSnapshot());
|
|
908
|
+
});
|
|
909
|
+
|
|
649
910
|
app.post('/api/remote/pairing-pin', (_req, res) => {
|
|
650
911
|
noStore(res);
|
|
651
912
|
try {
|
|
@@ -736,12 +997,12 @@ app.post('/api/remote/devices/:deviceId/ping', (req, res) => {
|
|
|
736
997
|
}));
|
|
737
998
|
});
|
|
738
999
|
|
|
739
|
-
app.post('/api/remote/devices/:deviceId/input', (req, res) => {
|
|
1000
|
+
app.post('/api/remote/devices/:deviceId/input', requireHubFeatureAccess, (req, res) => {
|
|
740
1001
|
noStore(res);
|
|
741
1002
|
res.json(remoteHub.sendInputControl(req.params.deviceId, req.body || {}));
|
|
742
1003
|
});
|
|
743
1004
|
|
|
744
|
-
app.post('/api/remote/files/transfer', (req, res) => {
|
|
1005
|
+
app.post('/api/remote/files/transfer', requireHubFeatureAccess, (req, res) => {
|
|
745
1006
|
noStore(res);
|
|
746
1007
|
const deviceIds = normalizeDeviceIds(req.body?.deviceIds);
|
|
747
1008
|
if (deviceIds.length === 0) {
|
|
@@ -782,7 +1043,7 @@ app.post('/api/remote/files/transfer', (req, res) => {
|
|
|
782
1043
|
});
|
|
783
1044
|
});
|
|
784
1045
|
|
|
785
|
-
app.post('/api/remote/devices/:deviceId/tasks', (req, res) => {
|
|
1046
|
+
app.post('/api/remote/devices/:deviceId/tasks', requireHubFeatureAccess, (req, res) => {
|
|
786
1047
|
noStore(res);
|
|
787
1048
|
res.json(remoteHub.requestAgentTask(req.params.deviceId, {
|
|
788
1049
|
instruction: req.body?.instruction,
|
|
@@ -794,7 +1055,7 @@ app.post('/api/remote/devices/:deviceId/tasks', (req, res) => {
|
|
|
794
1055
|
}));
|
|
795
1056
|
});
|
|
796
1057
|
|
|
797
|
-
app.post('/api/remote/tasks', (req, res) => {
|
|
1058
|
+
app.post('/api/remote/tasks', requireHubFeatureAccess, (req, res) => {
|
|
798
1059
|
noStore(res);
|
|
799
1060
|
const requestedIds = normalizeDeviceIds(req.body?.deviceIds);
|
|
800
1061
|
const approvalLevel = req.body?.approvalLevel === 'ai-assist' ? 'ai-assist' : 'task-only';
|
|
@@ -843,7 +1104,7 @@ app.post('/api/remote/devices/:deviceId/thumbnail/request', (req, res) => {
|
|
|
843
1104
|
res.json(remoteHub.requestThumbnail(req.params.deviceId, req.body || {}));
|
|
844
1105
|
});
|
|
845
1106
|
|
|
846
|
-
app.get('/api/remote/devices/:deviceId/live/frame', (req, res) => {
|
|
1107
|
+
app.get('/api/remote/devices/:deviceId/live/frame', requireHubFeatureAccess, (req, res) => {
|
|
847
1108
|
noStore(res);
|
|
848
1109
|
if (wantsBinaryFrame(req)) {
|
|
849
1110
|
sendFrameBinary(req, res, 'live');
|
|
@@ -859,7 +1120,7 @@ app.get('/api/remote/devices/:deviceId/live/frame', (req, res) => {
|
|
|
859
1120
|
res.json({ ok: true, frame });
|
|
860
1121
|
});
|
|
861
1122
|
|
|
862
|
-
app.post('/api/remote/devices/:deviceId/live/start', (req, res) => {
|
|
1123
|
+
app.post('/api/remote/devices/:deviceId/live/start', requireHubFeatureAccess, (req, res) => {
|
|
863
1124
|
noStore(res);
|
|
864
1125
|
res.json(remoteHub.startLiveStream(req.params.deviceId, req.body || {}));
|
|
865
1126
|
});
|
|
@@ -869,7 +1130,7 @@ app.post('/api/remote/devices/:deviceId/live/stop', (req, res) => {
|
|
|
869
1130
|
res.json(remoteHub.stopLiveStream(req.params.deviceId, req.body || {}));
|
|
870
1131
|
});
|
|
871
1132
|
|
|
872
|
-
app.post('/api/remote/devices/:deviceId/audio/start', (req, res) => {
|
|
1133
|
+
app.post('/api/remote/devices/:deviceId/audio/start', requireHubFeatureAccess, (req, res) => {
|
|
873
1134
|
noStore(res);
|
|
874
1135
|
res.json(remoteHub.startAudioStream(req.params.deviceId, req.body || {}));
|
|
875
1136
|
});
|
|
@@ -895,6 +1156,11 @@ if (existsSync(webIndexPath)) {
|
|
|
895
1156
|
}
|
|
896
1157
|
|
|
897
1158
|
httpServer.on('upgrade', (req, socket, head) => {
|
|
1159
|
+
if (!isTrustedBrowserRequest(req)) {
|
|
1160
|
+
socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
|
|
1161
|
+
socket.destroy();
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
898
1164
|
try {
|
|
899
1165
|
const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
|
|
900
1166
|
if (parsed.pathname === '/api/remote/frames/ws') {
|
|
@@ -990,6 +1256,10 @@ inputWss.on('connection', ws => {
|
|
|
990
1256
|
inputClients.add(ws);
|
|
991
1257
|
ws.liveDeskInputClientId = `riws-${++inputClientSeq}`;
|
|
992
1258
|
ws.on('message', data => {
|
|
1259
|
+
if (!hasHubFeatureAccess()) {
|
|
1260
|
+
sendJson(ws, { type: 'RemoteInputError', error: 'livedesk-plan-device-limit', license: licenseSnapshot() });
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
993
1263
|
let payload;
|
|
994
1264
|
try {
|
|
995
1265
|
payload = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data || ''));
|
|
@@ -1024,6 +1294,7 @@ inputWss.on('connection', ws => {
|
|
|
1024
1294
|
});
|
|
1025
1295
|
|
|
1026
1296
|
await remoteHub.start();
|
|
1297
|
+
connectedDeviceCount = Number(remoteHub.getStatus({ includeSecrets: false }).connectedDeviceCount || 0);
|
|
1027
1298
|
httpServer.listen(httpPort, httpHost, () => {
|
|
1028
1299
|
const status = remoteHub.getStatus({ includeSecrets: true });
|
|
1029
1300
|
console.log(`[LiveDesk Hub] HTTP API http://${httpHost}:${httpPort}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livedesk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.128",
|
|
4
4
|
"description": "LiveDesk Hub and client launcher",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"node": ">=20"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@livedesk/client": "0.1.
|
|
33
|
+
"@livedesk/client": "0.1.87",
|
|
34
34
|
"cors": "^2.8.5",
|
|
35
35
|
"express": "^4.21.2",
|
|
36
36
|
"ws": "^8.18.3"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var b={exports:{}},n={};var V;function et(){if(V)return n;V=1;var f=Symbol.for("react.transitional.element"),p=Symbol.for("react.portal"),l=Symbol.for("react.fragment"),d=Symbol.for("react.strict_mode"),m=Symbol.for("react.profiler"),_=Symbol.for("react.consumer"),R=Symbol.for("react.context"),C=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),T=Symbol.for("react.memo"),g=Symbol.for("react.lazy"),K=Symbol.for("react.activity"),H=Symbol.iterator;function G(t){return t===null||typeof t!="object"?null:(t=H&&t[H]||t["@@iterator"],typeof t=="function"?t:null)}var P={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},L=Object.assign,O={};function k(t,e,r){this.props=t,this.context=e,this.refs=O,this.updater=r||P}k.prototype.isReactComponent={},k.prototype.setState=function(t,e){if(typeof t!="object"&&typeof t!="function"&&t!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,t,e,"setState")},k.prototype.forceUpdate=function(t){this.updater.enqueueForceUpdate(this,t,"forceUpdate")};function z(){}z.prototype=k.prototype;function M(t,e,r){this.props=t,this.context=e,this.refs=O,this.updater=r||P}var $=M.prototype=new z;$.constructor=M,L($,k.prototype),$.isPureReactComponent=!0;var q=Array.isArray;function A(){}var a={H:null,A:null,T:null,S:null},I=Object.prototype.hasOwnProperty;function N(t,e,r){var o=r.ref;return{$$typeof:f,type:t,key:e,ref:o!==void 0?o:null,props:r}}function Z(t,e){return N(t.type,e,t.props)}function S(t){return typeof t=="object"&&t!==null&&t.$$typeof===f}function X(t){var e={"=":"=0",":":"=2"};return"$"+t.replace(/[=:]/g,function(r){return e[r]})}var Y=/\/+/g;function j(t,e){return typeof t=="object"&&t!==null&&t.key!=null?X(""+t.key):e.toString(36)}function Q(t){switch(t.status){case"fulfilled":return t.value;case"rejected":throw t.reason;default:switch(typeof t.status=="string"?t.then(A,A):(t.status="pending",t.then(function(e){t.status==="pending"&&(t.status="fulfilled",t.value=e)},function(e){t.status==="pending"&&(t.status="rejected",t.reason=e)})),t.status){case"fulfilled":return t.value;case"rejected":throw t.reason}}throw t}function v(t,e,r,o,s){var c=typeof t;(c==="undefined"||c==="boolean")&&(t=null);var i=!1;if(t===null)i=!0;else switch(c){case"bigint":case"string":case"number":i=!0;break;case"object":switch(t.$$typeof){case f:case p:i=!0;break;case g:return i=t._init,v(i(t._payload),e,r,o,s)}}if(i)return s=s(t),i=o===""?"."+j(t,0):o,q(s)?(r="",i!=null&&(r=i.replace(Y,"$&/")+"/"),v(s,e,r,"",function(tt){return tt})):s!=null&&(S(s)&&(s=Z(s,r+(s.key==null||t&&t.key===s.key?"":(""+s.key).replace(Y,"$&/")+"/")+i)),e.push(s)),1;i=0;var h=o===""?".":o+":";if(q(t))for(var y=0;y<t.length;y++)o=t[y],c=h+j(o,y),i+=v(o,e,r,c,s);else if(y=G(t),typeof y=="function")for(t=y.call(t),y=0;!(o=t.next()).done;)o=o.value,c=h+j(o,y++),i+=v(o,e,r,c,s);else if(c==="object"){if(typeof t.then=="function")return v(Q(t),e,r,o,s);throw e=String(t),Error("Objects are not valid as a React child (found: "+(e==="[object Object]"?"object with keys {"+Object.keys(t).join(", ")+"}":e)+"). If you meant to render a collection of children, use an array instead.")}return i}function w(t,e,r){if(t==null)return t;var o=[],s=0;return v(t,o,"","",function(c){return e.call(r,c,s++)}),o}function J(t){if(t._status===-1){var e=t._result;e=e(),e.then(function(r){(t._status===0||t._status===-1)&&(t._status=1,t._result=r)},function(r){(t._status===0||t._status===-1)&&(t._status=2,t._result=r)}),t._status===-1&&(t._status=0,t._result=e)}if(t._status===1)return t._result.default;throw t._result}var U=typeof reportError=="function"?reportError:function(t){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var e=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof t=="object"&&t!==null&&typeof t.message=="string"?String(t.message):String(t),error:t});if(!window.dispatchEvent(e))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",t);return}console.error(t)},F={map:w,forEach:function(t,e,r){w(t,function(){e.apply(this,arguments)},r)},count:function(t){var e=0;return w(t,function(){e++}),e},toArray:function(t){return w(t,function(e){return e})||[]},only:function(t){if(!S(t))throw Error("React.Children.only expected to receive a single React element child.");return t}};return n.Activity=K,n.Children=F,n.Component=k,n.Fragment=l,n.Profiler=m,n.PureComponent=M,n.StrictMode=d,n.Suspense=x,n.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=a,n.__COMPILER_RUNTIME={__proto__:null,c:function(t){return a.H.useMemoCache(t)}},n.cache=function(t){return function(){return t.apply(null,arguments)}},n.cacheSignal=function(){return null},n.cloneElement=function(t,e,r){if(t==null)throw Error("The argument must be a React element, but you passed "+t+".");var o=L({},t.props),s=t.key;if(e!=null)for(c in e.key!==void 0&&(s=""+e.key),e)!I.call(e,c)||c==="key"||c==="__self"||c==="__source"||c==="ref"&&e.ref===void 0||(o[c]=e[c]);var c=arguments.length-2;if(c===1)o.children=r;else if(1<c){for(var i=Array(c),h=0;h<c;h++)i[h]=arguments[h+2];o.children=i}return N(t.type,s,o)},n.createContext=function(t){return t={$$typeof:R,_currentValue:t,_currentValue2:t,_threadCount:0,Provider:null,Consumer:null},t.Provider=t,t.Consumer={$$typeof:_,_context:t},t},n.createElement=function(t,e,r){var o,s={},c=null;if(e!=null)for(o in e.key!==void 0&&(c=""+e.key),e)I.call(e,o)&&o!=="key"&&o!=="__self"&&o!=="__source"&&(s[o]=e[o]);var i=arguments.length-2;if(i===1)s.children=r;else if(1<i){for(var h=Array(i),y=0;y<i;y++)h[y]=arguments[y+2];s.children=h}if(t&&t.defaultProps)for(o in i=t.defaultProps,i)s[o]===void 0&&(s[o]=i[o]);return N(t,c,s)},n.createRef=function(){return{current:null}},n.forwardRef=function(t){return{$$typeof:C,render:t}},n.isValidElement=S,n.lazy=function(t){return{$$typeof:g,_payload:{_status:-1,_result:t},_init:J}},n.memo=function(t,e){return{$$typeof:T,type:t,compare:e===void 0?null:e}},n.startTransition=function(t){var e=a.T,r={};a.T=r;try{var o=t(),s=a.S;s!==null&&s(r,o),typeof o=="object"&&o!==null&&typeof o.then=="function"&&o.then(A,U)}catch(c){U(c)}finally{e!==null&&r.types!==null&&(e.types=r.types),a.T=e}},n.unstable_useCacheRefresh=function(){return a.H.useCacheRefresh()},n.use=function(t){return a.H.use(t)},n.useActionState=function(t,e,r){return a.H.useActionState(t,e,r)},n.useCallback=function(t,e){return a.H.useCallback(t,e)},n.useContext=function(t){return a.H.useContext(t)},n.useDebugValue=function(){},n.useDeferredValue=function(t,e){return a.H.useDeferredValue(t,e)},n.useEffect=function(t,e){return a.H.useEffect(t,e)},n.useEffectEvent=function(t){return a.H.useEffectEvent(t)},n.useId=function(){return a.H.useId()},n.useImperativeHandle=function(t,e,r){return a.H.useImperativeHandle(t,e,r)},n.useInsertionEffect=function(t,e){return a.H.useInsertionEffect(t,e)},n.useLayoutEffect=function(t,e){return a.H.useLayoutEffect(t,e)},n.useMemo=function(t,e){return a.H.useMemo(t,e)},n.useOptimistic=function(t,e){return a.H.useOptimistic(t,e)},n.useReducer=function(t,e,r){return a.H.useReducer(t,e,r)},n.useRef=function(t){return a.H.useRef(t)},n.useState=function(t){return a.H.useState(t)},n.useSyncExternalStore=function(t,e,r){return a.H.useSyncExternalStore(t,e,r)},n.useTransition=function(){return a.H.useTransition()},n.version="19.2.7",n}var D;function nt(){return D||(D=1,b.exports=et()),b.exports}var E=nt();const ot=f=>f.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),rt=f=>f.replace(/^([A-Z])|[\s-_]+(\w)/g,(p,l,d)=>d?d.toUpperCase():l.toLowerCase()),B=f=>{const p=rt(f);return p.charAt(0).toUpperCase()+p.slice(1)},W=(...f)=>f.filter((p,l,d)=>!!p&&p.trim()!==""&&d.indexOf(p)===l).join(" ").trim(),st=f=>{for(const p in f)if(p.startsWith("aria-")||p==="role"||p==="title")return!0};var ut={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};const ct=E.forwardRef(({color:f="currentColor",size:p=24,strokeWidth:l=2,absoluteStrokeWidth:d,className:m="",children:_,iconNode:R,...C},x)=>E.createElement("svg",{ref:x,...ut,width:p,height:p,stroke:f,strokeWidth:d?Number(l)*24/Number(p):l,className:W("lucide",m),...!_&&!st(C)&&{"aria-hidden":"true"},...C},[...R.map(([T,g])=>E.createElement(T,g)),...Array.isArray(_)?_:[_]]));const u=(f,p)=>{const l=E.forwardRef(({className:d,...m},_)=>E.createElement(ct,{ref:_,iconNode:p,className:W(`lucide-${ot(B(f))}`,`lucide-${f}`,d),...m}));return l.displayName=B(f),l};const at=[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2",key:"169zse"}]],Ot=u("activity",at);const it=[["path",{d:"M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z",key:"3c2336"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],zt=u("badge-check",it);const ft=[["path",{d:"M12 7v14",key:"1akyts"}],["path",{d:"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z",key:"ruj8y"}]],qt=u("book-open",ft);const pt=[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]],It=u("chevron-left",pt);const yt=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],Yt=u("chevron-right",yt);const lt=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],Ut=u("circle-check",lt);const dt=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1",key:"tgr4d6"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2",key:"116196"}]],Vt=u("clipboard",dt);const ht=[["rect",{width:"20",height:"14",x:"2",y:"5",rx:"2",key:"ynyp8z"}],["line",{x1:"2",x2:"22",y1:"10",y2:"10",key:"1b3vmo"}]],Dt=u("credit-card",ht);const _t=[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3",key:"msslwz"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5",key:"1wlel7"}],["path",{d:"M3 12A9 3 0 0 0 21 12",key:"mv7ke4"}]],Bt=u("database",_t);const kt=[["path",{d:"M21.54 15H17a2 2 0 0 0-2 2v4.54",key:"1djwo0"}],["path",{d:"M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17",key:"1tzkfa"}],["path",{d:"M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05",key:"14pb5j"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],Wt=u("earth",kt);const vt=[["path",{d:"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2",key:"usdka0"}]],Kt=u("folder-open",vt);const mt=[["rect",{width:"7",height:"7",x:"3",y:"3",rx:"1",key:"1g98yp"}],["rect",{width:"7",height:"7",x:"14",y:"3",rx:"1",key:"6d4xhi"}],["rect",{width:"7",height:"7",x:"14",y:"14",rx:"1",key:"nxv5o0"}],["rect",{width:"7",height:"7",x:"3",y:"14",rx:"1",key:"1bb6yr"}]],Gt=u("layout-grid",mt);const Et=[["circle",{cx:"12",cy:"16",r:"1",key:"1au0dj"}],["rect",{x:"3",y:"10",width:"18",height:"12",rx:"2",key:"6s8ecr"}],["path",{d:"M7 10V7a5 5 0 0 1 10 0v3",key:"1pqi11"}]],Zt=u("lock-keyhole",Et);const Ct=[["path",{d:"m10 17 5-5-5-5",key:"1bsop3"}],["path",{d:"M15 12H3",key:"6jk70r"}],["path",{d:"M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4",key:"u53s6r"}]],Xt=u("log-in",Ct);const gt=[["path",{d:"m16 17 5-5-5-5",key:"1bji2h"}],["path",{d:"M21 12H9",key:"dn1m92"}],["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}]],Qt=u("log-out",gt);const wt=[["rect",{width:"20",height:"14",x:"2",y:"3",rx:"2",key:"48i651"}],["line",{x1:"8",x2:"16",y1:"21",y2:"21",key:"1svkeh"}],["line",{x1:"12",x2:"12",y1:"17",y2:"21",key:"vw1qmm"}]],Jt=u("monitor",wt);const Rt=[["path",{d:"M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z",key:"edeuup"}]],Ft=u("mouse-pointer-2",Rt);const xt=[["polygon",{points:"6 3 20 12 6 21 6 3",key:"1oa8hb"}]],te=u("play",xt);const Tt=[["path",{d:"M12 2v10",key:"mnfbl"}],["path",{d:"M18.4 6.6a9 9 0 1 1-12.77.04",key:"obofu9"}]],ee=u("power",Tt);const Mt=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]],ne=u("rotate-ccw",Mt);const $t=[["path",{d:"M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z",key:"1ffxy3"}],["path",{d:"m21.854 2.147-10.94 10.939",key:"12cjpa"}]],oe=u("send",$t);const At=[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]],re=u("server",At);const Nt=[["path",{d:"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",key:"1qme2f"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]],se=u("settings",Nt);const St=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],ue=u("shield-check",St);const jt=[["path",{d:"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z",key:"r04s7s"}]],ce=u("star",jt);const bt=[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]],ae=u("terminal",bt);const Ht=[["path",{d:"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z",key:"uqj9uw"}],["path",{d:"M16 9a5 5 0 0 1 0 6",key:"1q6k2b"}],["path",{d:"M19.364 18.364a9 9 0 0 0 0-12.728",key:"ijwkga"}]],ie=u("volume-2",Ht);const Pt=[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0",key:"dnpr2z"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0",key:"1x1e6c"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}]],fe=u("wifi",Pt);const Lt=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],pe=u("x",Lt);export{Ot as A,qt as B,Dt as C,Bt as D,Wt as E,Kt as F,Gt as L,Jt as M,te as P,ne as R,se as S,ae as T,ie as V,fe as W,pe as X,E as a,Ft as b,Xt as c,Qt as d,re as e,ue as f,Vt as g,ce as h,zt as i,Ut as j,It as k,Yt as l,Zt as m,ee as n,oe as o,nt as r};
|