@livedesk/hub 0.1.41 → 0.1.43
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/package.json +3 -3
- package/src/agents/agent-device-scope.js +29 -29
- package/src/agents/agent-manager.js +103 -103
- package/src/agents/agent-permission-store.js +78 -78
- package/src/agents/agent-runtime-error.js +9 -9
- package/src/agents/agent-settings.js +71 -71
- package/src/agents/codex-agent-runtime.js +594 -594
- package/src/agents/codex-mcp-server.js +100 -100
- package/src/console-relay.js +415 -0
- package/src/filesystem/directory-reader.js +187 -187
- package/src/filesystem/hub-filesystem.js +78 -78
- package/src/filesystem/path-registry.js +78 -78
- package/src/filesystem/roots.js +115 -115
- package/src/frame-packet-contract.mjs +427 -427
- package/src/http/hub-ui-session.js +119 -119
- package/src/live-capture-transition-retry.mjs +297 -297
- package/src/live-desk-update.js +167 -108
- package/src/live-stream-monitor-contract.js +38 -38
- package/src/lzo1x.js +109 -109
- package/src/mode4-atlas-pool.js +137 -137
- package/src/mode4-atlas-sizing.js +32 -32
- package/src/mode4-atlas-worker.js +599 -599
- package/src/mode4-atlas.js +306 -306
- package/src/remote-audio-subscription-contract.mjs +109 -109
- package/src/remote-audio-subscription-contract.test.mjs +72 -72
- package/src/remote-hub.js +144 -144
- package/src/server.js +244 -222
- package/src/settings/effective-device-policy.js +16 -16
- package/src/transport/udp-protocol.js +453 -453
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
|
|
3
|
-
export const HUB_UI_SESSION_COOKIE = 'livedesk_hub_ui';
|
|
4
|
-
export const HUB_UI_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
|
|
5
|
-
|
|
6
|
-
const SAFE_HTTP_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
7
|
-
|
|
8
|
-
function secureEqual(left, right) {
|
|
9
|
-
const leftBuffer = Buffer.from(String(left || ''), 'utf8');
|
|
10
|
-
const rightBuffer = Buffer.from(String(right || ''), 'utf8');
|
|
11
|
-
return leftBuffer.length === rightBuffer.length
|
|
12
|
-
&& leftBuffer.length > 0
|
|
13
|
-
&& crypto.timingSafeEqual(leftBuffer, rightBuffer);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function parseCookies(value) {
|
|
17
|
-
const result = new Map();
|
|
18
|
-
for (const part of String(value || '').split(';')) {
|
|
19
|
-
const separator = part.indexOf('=');
|
|
20
|
-
if (separator <= 0) continue;
|
|
21
|
-
const key = part.slice(0, separator).trim();
|
|
22
|
-
const rawValue = part.slice(separator + 1).trim();
|
|
23
|
-
if (!key) continue;
|
|
24
|
-
try {
|
|
25
|
-
result.set(key, decodeURIComponent(rawValue));
|
|
26
|
-
} catch {
|
|
27
|
-
result.set(key, rawValue);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function randomToken() {
|
|
34
|
-
return crypto.randomBytes(32).toString('base64url');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function createHubUiSessionAuthority({
|
|
38
|
-
ttlMs = HUB_UI_SESSION_TTL_MS,
|
|
39
|
-
maxSessions = 32,
|
|
40
|
-
now = () => Date.now()
|
|
41
|
-
} = {}) {
|
|
42
|
-
const sessions = new Map();
|
|
43
|
-
|
|
44
|
-
function purgeExpired() {
|
|
45
|
-
const current = now();
|
|
46
|
-
for (const [sessionId, session] of sessions) {
|
|
47
|
-
if (session.expiresAt <= current) sessions.delete(sessionId);
|
|
48
|
-
}
|
|
49
|
-
while (sessions.size >= maxSessions) {
|
|
50
|
-
sessions.delete(sessions.keys().next().value);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function issue(userId) {
|
|
55
|
-
const normalizedUserId = String(userId || '').trim();
|
|
56
|
-
if (!normalizedUserId) throw new Error('hub-ui-session-user-required');
|
|
57
|
-
purgeExpired();
|
|
58
|
-
const sessionId = randomToken();
|
|
59
|
-
const csrfToken = randomToken();
|
|
60
|
-
const expiresAt = now() + ttlMs;
|
|
61
|
-
sessions.set(sessionId, { userId: normalizedUserId, csrfToken, expiresAt });
|
|
62
|
-
return { sessionId, csrfToken, expiresAt };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function authorize(req, currentUserId, {
|
|
66
|
-
requireCsrf = !SAFE_HTTP_METHODS.has(String(req.method || 'GET').toUpperCase())
|
|
67
|
-
} = {}) {
|
|
68
|
-
purgeExpired();
|
|
69
|
-
const sessionId = parseCookies(req.headers?.cookie).get(HUB_UI_SESSION_COOKIE) || '';
|
|
70
|
-
if (!sessionId) {
|
|
71
|
-
return { ok: false, status: 401, error: 'hub-ui-session-required' };
|
|
72
|
-
}
|
|
73
|
-
const session = sessions.get(sessionId);
|
|
74
|
-
if (!session) {
|
|
75
|
-
return { ok: false, status: 401, error: 'hub-ui-session-expired' };
|
|
76
|
-
}
|
|
77
|
-
const normalizedUserId = String(currentUserId || '').trim();
|
|
78
|
-
if (!normalizedUserId || session.userId !== normalizedUserId) {
|
|
79
|
-
sessions.delete(sessionId);
|
|
80
|
-
return { ok: false, status: 403, error: 'hub-ui-session-account-mismatch' };
|
|
81
|
-
}
|
|
82
|
-
if (requireCsrf && !secureEqual(req.headers?.['x-livedesk-csrf'], session.csrfToken)) {
|
|
83
|
-
return { ok: false, status: 403, error: 'hub-ui-csrf-invalid' };
|
|
84
|
-
}
|
|
85
|
-
return { ok: true, sessionId, userId: session.userId, expiresAt: session.expiresAt };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
issue,
|
|
90
|
-
authorize,
|
|
91
|
-
revokeAll() {
|
|
92
|
-
sessions.clear();
|
|
93
|
-
},
|
|
94
|
-
sessionCount: () => sessions.size
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function serializeHubUiSessionCookie(sessionId, expiresAt, { secure = false } = {}) {
|
|
99
|
-
const maxAgeSeconds = Math.max(1, Math.floor((Number(expiresAt || 0) - Date.now()) / 1000));
|
|
100
|
-
return [
|
|
101
|
-
`${HUB_UI_SESSION_COOKIE}=${encodeURIComponent(String(sessionId || ''))}`,
|
|
102
|
-
'Path=/',
|
|
103
|
-
'HttpOnly',
|
|
104
|
-
'SameSite=Strict',
|
|
105
|
-
`Max-Age=${maxAgeSeconds}`,
|
|
106
|
-
secure ? 'Secure' : ''
|
|
107
|
-
].filter(Boolean).join('; ');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function clearHubUiSessionCookie({ secure = false } = {}) {
|
|
111
|
-
return [
|
|
112
|
-
`${HUB_UI_SESSION_COOKIE}=`,
|
|
113
|
-
'Path=/',
|
|
114
|
-
'HttpOnly',
|
|
115
|
-
'SameSite=Strict',
|
|
116
|
-
'Max-Age=0',
|
|
117
|
-
secure ? 'Secure' : ''
|
|
118
|
-
].filter(Boolean).join('; ');
|
|
119
|
-
}
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export const HUB_UI_SESSION_COOKIE = 'livedesk_hub_ui';
|
|
4
|
+
export const HUB_UI_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
|
|
5
|
+
|
|
6
|
+
const SAFE_HTTP_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
7
|
+
|
|
8
|
+
function secureEqual(left, right) {
|
|
9
|
+
const leftBuffer = Buffer.from(String(left || ''), 'utf8');
|
|
10
|
+
const rightBuffer = Buffer.from(String(right || ''), 'utf8');
|
|
11
|
+
return leftBuffer.length === rightBuffer.length
|
|
12
|
+
&& leftBuffer.length > 0
|
|
13
|
+
&& crypto.timingSafeEqual(leftBuffer, rightBuffer);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseCookies(value) {
|
|
17
|
+
const result = new Map();
|
|
18
|
+
for (const part of String(value || '').split(';')) {
|
|
19
|
+
const separator = part.indexOf('=');
|
|
20
|
+
if (separator <= 0) continue;
|
|
21
|
+
const key = part.slice(0, separator).trim();
|
|
22
|
+
const rawValue = part.slice(separator + 1).trim();
|
|
23
|
+
if (!key) continue;
|
|
24
|
+
try {
|
|
25
|
+
result.set(key, decodeURIComponent(rawValue));
|
|
26
|
+
} catch {
|
|
27
|
+
result.set(key, rawValue);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function randomToken() {
|
|
34
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createHubUiSessionAuthority({
|
|
38
|
+
ttlMs = HUB_UI_SESSION_TTL_MS,
|
|
39
|
+
maxSessions = 32,
|
|
40
|
+
now = () => Date.now()
|
|
41
|
+
} = {}) {
|
|
42
|
+
const sessions = new Map();
|
|
43
|
+
|
|
44
|
+
function purgeExpired() {
|
|
45
|
+
const current = now();
|
|
46
|
+
for (const [sessionId, session] of sessions) {
|
|
47
|
+
if (session.expiresAt <= current) sessions.delete(sessionId);
|
|
48
|
+
}
|
|
49
|
+
while (sessions.size >= maxSessions) {
|
|
50
|
+
sessions.delete(sessions.keys().next().value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function issue(userId) {
|
|
55
|
+
const normalizedUserId = String(userId || '').trim();
|
|
56
|
+
if (!normalizedUserId) throw new Error('hub-ui-session-user-required');
|
|
57
|
+
purgeExpired();
|
|
58
|
+
const sessionId = randomToken();
|
|
59
|
+
const csrfToken = randomToken();
|
|
60
|
+
const expiresAt = now() + ttlMs;
|
|
61
|
+
sessions.set(sessionId, { userId: normalizedUserId, csrfToken, expiresAt });
|
|
62
|
+
return { sessionId, csrfToken, expiresAt };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function authorize(req, currentUserId, {
|
|
66
|
+
requireCsrf = !SAFE_HTTP_METHODS.has(String(req.method || 'GET').toUpperCase())
|
|
67
|
+
} = {}) {
|
|
68
|
+
purgeExpired();
|
|
69
|
+
const sessionId = parseCookies(req.headers?.cookie).get(HUB_UI_SESSION_COOKIE) || '';
|
|
70
|
+
if (!sessionId) {
|
|
71
|
+
return { ok: false, status: 401, error: 'hub-ui-session-required' };
|
|
72
|
+
}
|
|
73
|
+
const session = sessions.get(sessionId);
|
|
74
|
+
if (!session) {
|
|
75
|
+
return { ok: false, status: 401, error: 'hub-ui-session-expired' };
|
|
76
|
+
}
|
|
77
|
+
const normalizedUserId = String(currentUserId || '').trim();
|
|
78
|
+
if (!normalizedUserId || session.userId !== normalizedUserId) {
|
|
79
|
+
sessions.delete(sessionId);
|
|
80
|
+
return { ok: false, status: 403, error: 'hub-ui-session-account-mismatch' };
|
|
81
|
+
}
|
|
82
|
+
if (requireCsrf && !secureEqual(req.headers?.['x-livedesk-csrf'], session.csrfToken)) {
|
|
83
|
+
return { ok: false, status: 403, error: 'hub-ui-csrf-invalid' };
|
|
84
|
+
}
|
|
85
|
+
return { ok: true, sessionId, userId: session.userId, expiresAt: session.expiresAt };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
issue,
|
|
90
|
+
authorize,
|
|
91
|
+
revokeAll() {
|
|
92
|
+
sessions.clear();
|
|
93
|
+
},
|
|
94
|
+
sessionCount: () => sessions.size
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function serializeHubUiSessionCookie(sessionId, expiresAt, { secure = false } = {}) {
|
|
99
|
+
const maxAgeSeconds = Math.max(1, Math.floor((Number(expiresAt || 0) - Date.now()) / 1000));
|
|
100
|
+
return [
|
|
101
|
+
`${HUB_UI_SESSION_COOKIE}=${encodeURIComponent(String(sessionId || ''))}`,
|
|
102
|
+
'Path=/',
|
|
103
|
+
'HttpOnly',
|
|
104
|
+
'SameSite=Strict',
|
|
105
|
+
`Max-Age=${maxAgeSeconds}`,
|
|
106
|
+
secure ? 'Secure' : ''
|
|
107
|
+
].filter(Boolean).join('; ');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function clearHubUiSessionCookie({ secure = false } = {}) {
|
|
111
|
+
return [
|
|
112
|
+
`${HUB_UI_SESSION_COOKIE}=`,
|
|
113
|
+
'Path=/',
|
|
114
|
+
'HttpOnly',
|
|
115
|
+
'SameSite=Strict',
|
|
116
|
+
'Max-Age=0',
|
|
117
|
+
secure ? 'Secure' : ''
|
|
118
|
+
].filter(Boolean).join('; ');
|
|
119
|
+
}
|