livedesk 0.1.393 → 0.1.395

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.
@@ -112,30 +112,50 @@ function writeLocalRoleCache(role, deviceId, roleVersion = 0, assignedHubId = ''
112
112
  return true;
113
113
  }
114
114
 
115
- function spawnUnifiedRoleRestart(role) {
116
- if (isTruthy(process.env.LIVEDESK_DESKTOP_HOST)) {
117
- console.log(`[LiveDesk] Desktop supervisor will restart the runtime as ${role}.`);
118
- return false;
119
- }
120
- const unifiedEntry = String(process.env.LIVEDESK_UNIFIED_LAUNCHER_ENTRY || '').trim();
121
- const child = unifiedEntry && existsSync(unifiedEntry)
122
- ? spawn(process.execPath, [unifiedEntry, '--force-role', role, '--no-open'], {
123
- env: {
124
- ...process.env,
125
- LIVEDESK_ROLE_TRANSITION: '1',
126
- LIVEDESK_RESTART_WAIT_PID: String(process.ppid),
127
- LIVEDESK_SKIP_BROWSER_OPEN: '1'
128
- },
129
- detached: true,
130
- stdio: 'ignore',
131
- windowsHide: true
132
- })
133
- : process.platform === 'win32'
134
- ? spawn('npx.cmd', ['-y', '--prefer-online', 'livedesk@latest', '--force-role', role, '--no-open'], { detached: true, stdio: 'ignore', windowsHide: true })
135
- : spawn('npx', ['-y', '--prefer-online', 'livedesk@latest', '--force-role', role, '--no-open'], { detached: true, stdio: 'ignore' });
136
- child.unref();
137
- return true;
138
- }
115
+ export function buildUnifiedRoleRestartEnvironment(role, baseEnv = process.env, runtimePid = process.pid) {
116
+ const normalizedRole = String(role || '').trim().toLowerCase();
117
+ const waitPid = Number(runtimePid);
118
+ if (normalizedRole !== 'hub' && normalizedRole !== 'client') {
119
+ throw new Error(`Unsupported LiveDesk role restart: ${role}`);
120
+ }
121
+ if (!Number.isInteger(waitPid) || waitPid <= 1) {
122
+ throw new Error(`Invalid LiveDesk runtime PID for role restart: ${runtimePid}`);
123
+ }
124
+ return {
125
+ ...baseEnv,
126
+ LIVEDESK_ROLE_TRANSITION: '1',
127
+ // The replacement must wait only for this Client runtime. Waiting for
128
+ // npm, npx, PowerShell, or another terminal parent can deadlock the
129
+ // takeover even after Supabase has selected this device as Hub.
130
+ LIVEDESK_RESTART_WAIT_PID: String(waitPid),
131
+ LIVEDESK_ROLE_TAKEOVER: normalizedRole === 'hub' ? '1' : '',
132
+ LIVEDESK_SKIP_BROWSER_OPEN: '1'
133
+ };
134
+ }
135
+
136
+ function spawnUnifiedRoleRestart(role) {
137
+ if (isTruthy(process.env.LIVEDESK_DESKTOP_HOST)) {
138
+ console.log(`[LiveDesk] Desktop supervisor will restart the runtime as ${role}.`);
139
+ return false;
140
+ }
141
+ const unifiedEntry = String(process.env.LIVEDESK_UNIFIED_LAUNCHER_ENTRY || '').trim();
142
+ const restartEnvironment = buildUnifiedRoleRestartEnvironment(role);
143
+ const spawnOptions = {
144
+ env: restartEnvironment,
145
+ detached: true,
146
+ stdio: 'ignore',
147
+ windowsHide: true
148
+ };
149
+ const child = unifiedEntry && existsSync(unifiedEntry)
150
+ ? spawn(process.execPath, [unifiedEntry, '--force-role', role, '--no-open'], {
151
+ ...spawnOptions
152
+ })
153
+ : process.platform === 'win32'
154
+ ? spawn('npx.cmd', ['-y', '--prefer-online', 'livedesk@latest', '--force-role', role, '--no-open'], spawnOptions)
155
+ : spawn('npx', ['-y', '--prefer-online', 'livedesk@latest', '--force-role', role, '--no-open'], spawnOptions);
156
+ child.unref();
157
+ return true;
158
+ }
139
159
 
140
160
  function printHelp() {
141
161
  process.stdout.write(`
package/hub/src/server.js CHANGED
@@ -136,6 +136,7 @@ const hubWakeBaseUrl = String(process.env.LIVEDESK_WAKE_URL || 'https://livedesk
136
136
  const HUB_WAKE_NOTIFY_RETRY_MS = 60_000;
137
137
  let hubHostTargetRenewTimer = null;
138
138
  let hubHostTargetRenewInFlight = false;
139
+ let roleTransitionTakeoverPending = process.env.LIVEDESK_ROLE_TAKEOVER === '1';
139
140
  let hubHostTargetLeaseState = {
140
141
  state: 'idle',
141
142
  lastAttemptAt: '',
@@ -2913,7 +2914,7 @@ app.post('/api/auth/session', async (req, res) => {
2913
2914
  runtimeAccessTokenExpiresAt = normalizeSessionExpiryMs(expiresAt);
2914
2915
  runtimeManager.setAuthenticated(true, user.id);
2915
2916
  const hostTarget = runtimeRole === 'hub'
2916
- ? await publishHubHostTarget({ reason: 'session-received' })
2917
+ ? await publishHubHostTargetWithPendingRoleTakeover('session-received')
2917
2918
  : { ok: true, active: false };
2918
2919
  if (runtimeRole === 'hub') {
2919
2920
  startHubHostTargetLeaseRenewal();
@@ -3147,6 +3148,21 @@ async function publishHubHostTarget({ takeover = false, reason = 'renewal' } = {
3147
3148
  }
3148
3149
  }
3149
3150
 
3151
+ async function publishHubHostTargetWithPendingRoleTakeover(reason = 'renewal') {
3152
+ const takeover = roleTransitionTakeoverPending;
3153
+ const result = await publishHubHostTarget({
3154
+ takeover,
3155
+ reason: takeover ? `${reason}-role-takeover` : reason
3156
+ });
3157
+ if (takeover && result.ok) {
3158
+ // One explicit Client -> Hub selection authorizes one ownership handoff.
3159
+ // Normal renewals must never keep takeover authority after publication.
3160
+ roleTransitionTakeoverPending = false;
3161
+ delete process.env.LIVEDESK_ROLE_TAKEOVER;
3162
+ }
3163
+ return result;
3164
+ }
3165
+
3150
3166
  async function clearHubHostTarget(reason = 'shutdown') {
3151
3167
  if (hubHostTargetRenewTimer) {
3152
3168
  clearInterval(hubHostTargetRenewTimer);
@@ -3194,7 +3210,7 @@ function startHubHostTargetLeaseRenewal() {
3194
3210
  return;
3195
3211
  }
3196
3212
  hubHostTargetRenewTimer = setInterval(() => {
3197
- void publishHubHostTarget({ reason: 'renewal' });
3213
+ void publishHubHostTargetWithPendingRoleTakeover('renewal');
3198
3214
  }, HUB_HOST_TARGET_RENEW_MS);
3199
3215
  hubHostTargetRenewTimer.unref?.();
3200
3216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.393",
3
+ "version": "0.1.395",
4
4
  "livedeskClientVersion": "0.1.173",
5
5
  "buildFlavor": "production",
6
6
  "description": "LiveDesk Hub and client launcher",