@livedesk/client 0.1.225 → 0.1.226
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/bin/livedesk-client.js +134 -41
- package/package.json +1 -1
- package/src/runtime/client-runtime-server.js +10 -2
package/bin/livedesk-client.js
CHANGED
|
@@ -90,6 +90,7 @@ let agentRestartRequest = null;
|
|
|
90
90
|
let linuxVideoAccelerationStatus = null;
|
|
91
91
|
let discoveryWakeController = new AbortController();
|
|
92
92
|
let networkChangeMonitor = null;
|
|
93
|
+
let deviceRoleMutationTail = Promise.resolve();
|
|
93
94
|
const sessionRefreshesInFlight = new WeakMap();
|
|
94
95
|
const disposeAgentTerminationHandlers = installAgentTerminationHandlers({
|
|
95
96
|
getAgentProcess: () => activeAgentProcess
|
|
@@ -177,6 +178,17 @@ export function requestLocalDiscoveryWake(reason = 'local-trigger') {
|
|
|
177
178
|
previous.abort(String(reason || 'local-trigger'));
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
export function enqueueDeviceRoleMutation(operation) {
|
|
182
|
+
if (typeof operation !== 'function') {
|
|
183
|
+
return Promise.reject(new TypeError('device-role-mutation-operation-required'));
|
|
184
|
+
}
|
|
185
|
+
const pending = deviceRoleMutationTail
|
|
186
|
+
.catch(() => undefined)
|
|
187
|
+
.then(operation);
|
|
188
|
+
deviceRoleMutationTail = pending.catch(() => undefined);
|
|
189
|
+
return pending;
|
|
190
|
+
}
|
|
191
|
+
|
|
180
192
|
function networkInterfaceSignature() {
|
|
181
193
|
return Object.entries(os.networkInterfaces())
|
|
182
194
|
.flatMap(([name, entries]) => (entries || [])
|
|
@@ -1204,12 +1216,18 @@ function isTransientNetworkError(error) {
|
|
|
1204
1216
|
return /fetch failed|network|dns|socket|connection|timeout|getaddrinfo/i.test(getNestedErrorMessage(error));
|
|
1205
1217
|
}
|
|
1206
1218
|
|
|
1207
|
-
function formatDiscoveryError(error) {
|
|
1219
|
+
export function formatDiscoveryError(error) {
|
|
1208
1220
|
if (isTransientNetworkError(error)) {
|
|
1209
1221
|
const code = getNestedErrorCode(error);
|
|
1210
1222
|
return `Network is not ready yet${code ? ` (${code})` : ''}. Waiting for DNS/Wi-Fi after sleep.`;
|
|
1211
1223
|
}
|
|
1212
|
-
|
|
1224
|
+
const message = getNestedErrorMessage(error);
|
|
1225
|
+
if (message) return message;
|
|
1226
|
+
for (const key of ['error_description', 'reason', 'details', 'hint', 'error', 'code']) {
|
|
1227
|
+
const value = error?.[key];
|
|
1228
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
1229
|
+
}
|
|
1230
|
+
return 'LiveDesk could not read the current Hub registration yet.';
|
|
1213
1231
|
}
|
|
1214
1232
|
|
|
1215
1233
|
function createHubDiscoveryError(code, message, cause = null) {
|
|
@@ -1387,6 +1405,27 @@ async function activateSupabaseSession(supabase, session) {
|
|
|
1387
1405
|
}
|
|
1388
1406
|
return activeSession;
|
|
1389
1407
|
}
|
|
1408
|
+
|
|
1409
|
+
export async function activateRoleChangeSession(supabase, currentSession = null) {
|
|
1410
|
+
if (!supabase?.auth) {
|
|
1411
|
+
throw new Error('Google sign-in is temporarily unavailable. Try again in a moment.');
|
|
1412
|
+
}
|
|
1413
|
+
const current = normalizeRuntimeAuthSession(currentSession, { requireRefreshToken: true });
|
|
1414
|
+
let session = current.ok ? current.session : await refreshSessionIfNeeded(supabase);
|
|
1415
|
+
if (!session?.access_token) {
|
|
1416
|
+
throw new Error('Sign in again before switching this computer to Hub.');
|
|
1417
|
+
}
|
|
1418
|
+
try {
|
|
1419
|
+
return await activateSupabaseSession(supabase, session);
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
if (!isRefreshTokenAlreadyUsedError(error)) throw error;
|
|
1422
|
+
session = await refreshSessionIfNeeded(supabase);
|
|
1423
|
+
if (!session?.access_token) {
|
|
1424
|
+
throw new Error('Sign in again before switching this computer to Hub.');
|
|
1425
|
+
}
|
|
1426
|
+
return activateSupabaseSession(supabase, session);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1390
1429
|
|
|
1391
1430
|
function escapeHtml(value) {
|
|
1392
1431
|
return String(value ?? '')
|
|
@@ -3105,14 +3144,17 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
3105
3144
|
res.end(JSON.stringify(hubClientPortPreflightError(portPreflight)));
|
|
3106
3145
|
return;
|
|
3107
3146
|
}
|
|
3108
|
-
const session = await
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3147
|
+
const session = await activateRoleChangeSession(
|
|
3148
|
+
supabase,
|
|
3149
|
+
dashboardState.choice?.session || savedSession
|
|
3150
|
+
);
|
|
3151
|
+
const expectedRoleVersion = Number(dashboardState.roleVersion || 0);
|
|
3152
|
+
const { data, error } = await enqueueDeviceRoleMutation(() => supabase.rpc('set_livedesk_device_role', {
|
|
3153
|
+
p_device_id: deviceId,
|
|
3154
|
+
p_role: 'hub',
|
|
3155
|
+
p_assigned_hub_id: null,
|
|
3156
|
+
p_expected_role_version: expectedRoleVersion > 0 ? expectedRoleVersion : null
|
|
3157
|
+
}));
|
|
3116
3158
|
const result = Array.isArray(data) ? data[0] : data;
|
|
3117
3159
|
if (error || result?.ok === false || !session?.access_token) {
|
|
3118
3160
|
const reason = error?.message || result?.reason || 'role-change-rejected';
|
|
@@ -3415,26 +3457,31 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3415
3457
|
relayEndpoint: options.relayEndpoint
|
|
3416
3458
|
});
|
|
3417
3459
|
},
|
|
3418
|
-
changeRole: async (role, snapshot) => {
|
|
3460
|
+
changeRole: async (role, snapshot, currentSession) => {
|
|
3419
3461
|
const activeSupabase = await getSupabase();
|
|
3420
3462
|
if (!activeSupabase) {
|
|
3421
|
-
return { ok: false, error: '
|
|
3463
|
+
return { ok: false, error: 'Google sign-in is temporarily unavailable. Try again in a moment.' };
|
|
3422
3464
|
}
|
|
3423
3465
|
const portPreflight = await preflightHubClientPort();
|
|
3424
3466
|
if (!portPreflight.ok) {
|
|
3425
3467
|
return hubClientPortPreflightError(portPreflight);
|
|
3426
3468
|
}
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
}
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
})
|
|
3469
|
+
let session;
|
|
3470
|
+
try {
|
|
3471
|
+
session = await activateRoleChangeSession(activeSupabase, currentSession);
|
|
3472
|
+
} catch (error) {
|
|
3473
|
+
return { ok: false, error: formatDiscoveryError(error) };
|
|
3474
|
+
}
|
|
3475
|
+
if (!session?.access_token) {
|
|
3476
|
+
return { ok: false, error: 'Sign in again before switching this computer to Hub.' };
|
|
3477
|
+
}
|
|
3478
|
+
const expectedRoleVersion = Number(snapshot?.roleVersion || 0);
|
|
3479
|
+
const { data, error } = await enqueueDeviceRoleMutation(() => activeSupabase.rpc('set_livedesk_device_role', {
|
|
3480
|
+
p_device_id: options.deviceId,
|
|
3481
|
+
p_role: role,
|
|
3482
|
+
p_assigned_hub_id: null,
|
|
3483
|
+
p_expected_role_version: expectedRoleVersion > 0 ? expectedRoleVersion : null
|
|
3484
|
+
}));
|
|
3438
3485
|
const result = Array.isArray(data) ? data[0] : data;
|
|
3439
3486
|
if (error || result?.ok === false) {
|
|
3440
3487
|
return { ok: false, error: error?.message || result?.reason || 'role-change-rejected' };
|
|
@@ -3853,7 +3900,9 @@ export async function runFreshHubRegistryLookup(resolveFreshTarget, options = {}
|
|
|
3853
3900
|
}
|
|
3854
3901
|
const timeoutMs = normalizeFreshRegistryTimeoutMs(options.timeoutMs);
|
|
3855
3902
|
const controller = new AbortController();
|
|
3903
|
+
const externalSignal = options.signal;
|
|
3856
3904
|
let timeoutHandle = null;
|
|
3905
|
+
let removeExternalAbort = () => undefined;
|
|
3857
3906
|
const lookupOutcome = Promise.resolve()
|
|
3858
3907
|
.then(() => resolveFreshTarget(controller.signal))
|
|
3859
3908
|
.then(
|
|
@@ -3867,6 +3916,17 @@ export async function runFreshHubRegistryLookup(resolveFreshTarget, options = {}
|
|
|
3867
3916
|
);
|
|
3868
3917
|
});
|
|
3869
3918
|
const outcomes = [lookupOutcome, timeoutOutcome];
|
|
3919
|
+
if (externalSignal) {
|
|
3920
|
+
outcomes.push(new Promise(resolveAbort => {
|
|
3921
|
+
const onAbort = () => resolveAbort({ type: 'local-trigger' });
|
|
3922
|
+
if (externalSignal.aborted) {
|
|
3923
|
+
onAbort();
|
|
3924
|
+
return;
|
|
3925
|
+
}
|
|
3926
|
+
externalSignal.addEventListener('abort', onAbort, { once: true });
|
|
3927
|
+
removeExternalAbort = () => externalSignal.removeEventListener('abort', onAbort);
|
|
3928
|
+
}));
|
|
3929
|
+
}
|
|
3870
3930
|
if (options.wakePromise && typeof options.wakePromise.then === 'function') {
|
|
3871
3931
|
outcomes.push(Promise.resolve(options.wakePromise).then(
|
|
3872
3932
|
event => ({ type: 'hub-online', event }),
|
|
@@ -3875,7 +3935,12 @@ export async function runFreshHubRegistryLookup(resolveFreshTarget, options = {}
|
|
|
3875
3935
|
}
|
|
3876
3936
|
const outcome = await Promise.race(outcomes).finally(() => {
|
|
3877
3937
|
clearTimeout(timeoutHandle);
|
|
3938
|
+
removeExternalAbort();
|
|
3878
3939
|
});
|
|
3940
|
+
if (outcome.type === 'local-trigger') {
|
|
3941
|
+
controller.abort('local-trigger');
|
|
3942
|
+
return outcome;
|
|
3943
|
+
}
|
|
3879
3944
|
if (outcome.type === 'hub-online') {
|
|
3880
3945
|
controller.abort('hub-online');
|
|
3881
3946
|
return outcome;
|
|
@@ -4050,22 +4115,33 @@ export async function resolveManagerFromSupabase(supabase, options = {}) {
|
|
|
4050
4115
|
};
|
|
4051
4116
|
}
|
|
4052
4117
|
|
|
4053
|
-
async function registerClientDeviceWithSupabase(supabase, options = {}) {
|
|
4054
|
-
if (!options.deviceId || !options.session?.access_token) {
|
|
4055
|
-
return null;
|
|
4056
|
-
}
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4118
|
+
async function registerClientDeviceWithSupabase(supabase, options = {}) {
|
|
4119
|
+
if (!options.deviceId || !options.session?.access_token) {
|
|
4120
|
+
return null;
|
|
4121
|
+
}
|
|
4122
|
+
if (roleRestartRequest?.role) {
|
|
4123
|
+
return { ok: false, skipped: true, reason: 'role-transition-pending' };
|
|
4124
|
+
}
|
|
4125
|
+
try {
|
|
4126
|
+
const { data, error } = await enqueueDeviceRoleMutation(() => {
|
|
4127
|
+
if (roleRestartRequest?.role) {
|
|
4128
|
+
return { data: { ok: false, skipped: true, reason: 'role-transition-pending' }, error: null };
|
|
4129
|
+
}
|
|
4130
|
+
return supabase.rpc('register_livedesk_device', {
|
|
4131
|
+
p_device_id: options.deviceId,
|
|
4132
|
+
p_device_name: options.deviceName || os.hostname(),
|
|
4133
|
+
p_role: 'client',
|
|
4134
|
+
p_assigned_hub_id: options.assignedHubId || null,
|
|
4135
|
+
p_platform: os.platform(),
|
|
4136
|
+
p_os_version: os.release(),
|
|
4137
|
+
p_app_version: readPackageVersion()
|
|
4138
|
+
});
|
|
4139
|
+
});
|
|
4140
|
+
const result = Array.isArray(data) ? data[0] : data;
|
|
4141
|
+
if (result?.skipped === true && result?.reason === 'role-transition-pending') {
|
|
4142
|
+
return result;
|
|
4143
|
+
}
|
|
4144
|
+
if (error || result?.ok === false) {
|
|
4069
4145
|
console.warn(`[LiveDesk Client] Device role registration unavailable: ${error?.message || result?.reason || 'unknown-error'}`);
|
|
4070
4146
|
}
|
|
4071
4147
|
return error ? null : result;
|
|
@@ -4117,9 +4193,14 @@ export async function waitForManagerFromSupabase(supabase, options = {}) {
|
|
|
4117
4193
|
}),
|
|
4118
4194
|
{
|
|
4119
4195
|
timeoutMs: options.freshTimeoutMs,
|
|
4120
|
-
wakePromise: wakeListener?.promise
|
|
4196
|
+
wakePromise: wakeListener?.promise,
|
|
4197
|
+
signal: discoveryWakeController.signal
|
|
4121
4198
|
}
|
|
4122
4199
|
);
|
|
4200
|
+
if (outcome.type === 'local-trigger') {
|
|
4201
|
+
if (shouldStop()) return null;
|
|
4202
|
+
continue;
|
|
4203
|
+
}
|
|
4123
4204
|
if (outcome.type === 'hub-online') {
|
|
4124
4205
|
wakeListener?.close();
|
|
4125
4206
|
wakeListener = null;
|
|
@@ -4285,6 +4366,18 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null, get
|
|
|
4285
4366
|
} catch (error) {
|
|
4286
4367
|
initialDiscoveryError = error;
|
|
4287
4368
|
}
|
|
4369
|
+
if (roleRestartRequest?.role) {
|
|
4370
|
+
return {
|
|
4371
|
+
...parsed,
|
|
4372
|
+
manager,
|
|
4373
|
+
pair,
|
|
4374
|
+
slot: normalizeSlotNumber(parsed.slot),
|
|
4375
|
+
connectionPage,
|
|
4376
|
+
forwarded,
|
|
4377
|
+
rediscoverOnDisconnect: shouldLogin,
|
|
4378
|
+
rediscoverOnInvalidPair: shouldLogin
|
|
4379
|
+
};
|
|
4380
|
+
}
|
|
4288
4381
|
discoverySource = resolved?.discoverySource || 'supabase';
|
|
4289
4382
|
if (resolved?.discoverySource === 'cache') {
|
|
4290
4383
|
writeSavedSessionToFile(session);
|
|
@@ -4303,7 +4396,7 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null, get
|
|
|
4303
4396
|
});
|
|
4304
4397
|
discoverySource = 'supabase';
|
|
4305
4398
|
}
|
|
4306
|
-
if (
|
|
4399
|
+
if (roleRestartRequest?.role) {
|
|
4307
4400
|
return {
|
|
4308
4401
|
...parsed,
|
|
4309
4402
|
manager,
|
package/package.json
CHANGED
|
@@ -1856,7 +1856,11 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1856
1856
|
return;
|
|
1857
1857
|
}
|
|
1858
1858
|
try {
|
|
1859
|
-
const result = await options.changeRole?.(
|
|
1859
|
+
const result = await options.changeRole?.(
|
|
1860
|
+
'hub',
|
|
1861
|
+
runtime.getSnapshot(),
|
|
1862
|
+
lastChoice?.session || savedSession || null
|
|
1863
|
+
);
|
|
1860
1864
|
respondJson(result?.ok === false ? 409 : 200, result || { ok: false, error: 'role-change-unavailable' });
|
|
1861
1865
|
} catch (error) {
|
|
1862
1866
|
respondJson(409, { ok: false, error: normalizeString(error?.message || error) });
|
|
@@ -1870,7 +1874,11 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1870
1874
|
return;
|
|
1871
1875
|
}
|
|
1872
1876
|
try {
|
|
1873
|
-
const result = await options.changeRole?.(
|
|
1877
|
+
const result = await options.changeRole?.(
|
|
1878
|
+
'hub',
|
|
1879
|
+
runtime.getSnapshot(),
|
|
1880
|
+
lastChoice?.session || savedSession || null
|
|
1881
|
+
);
|
|
1874
1882
|
respondJson(result?.ok === false ? 409 : 200, result || { ok: false, error: 'role-change-unavailable' });
|
|
1875
1883
|
} catch (error) {
|
|
1876
1884
|
respondJson(409, { ok: false, error: normalizeString(error?.message || error) });
|