@livedesk/hub 0.1.66 → 0.1.68

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/hub",
3
- "version": "0.1.66",
3
+ "version": "0.1.68",
4
4
  "description": "VuvoDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -12,11 +12,11 @@
12
12
  "dev": "node src/server.js",
13
13
  "start": "node src/server.js",
14
14
  "check": "node --check src/server.js && node --check src/remote-hub.js && node --check src/remote-clipboard-contract.mjs && node --check src/console-direct.js",
15
- "prepublishOnly": "node ../../scripts/livedesk-release-git-gate.mjs"
15
+ "prepublishOnly": "node ../../scripts/vuvodesk-release-git-gate.mjs"
16
16
  },
17
17
  "dependencies": {
18
18
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
19
- "@livedesk/runtime-core": "0.1.8",
19
+ "@livedesk/runtime-core": "0.1.9",
20
20
  "@openai/codex-sdk": "0.145.0",
21
21
  "cors": "^2.8.5",
22
22
  "express": "^4.21.2",
@@ -86,7 +86,7 @@ export class CaptureStore {
86
86
  throw Object.assign(new Error('capture-mime-type-unsupported'), { status: 415 });
87
87
  }
88
88
  const extension = MIME_EXTENSIONS.get(mimeType) || (kind === 'screenshot' ? '.png' : '.webm');
89
- const fileNameBase = safeName(metadata.fileName, `LiveDesk_${kind}`);
89
+ const fileNameBase = safeName(metadata.fileName, `VuvoDesk_${kind}`);
90
90
  const fileName = fileNameBase.toLowerCase().endsWith(extension) ? fileNameBase : `${fileNameBase}${extension}`;
91
91
  const id = randomUUID();
92
92
  const folder = kind === 'screenshot' ? 'images' : kind === 'timelapse' ? 'timelapses' : 'recordings';
@@ -122,7 +122,7 @@ export class CaptureStore {
122
122
  const sessionId = randomUUID();
123
123
  const tempPath = path.join(this.root, '.tmp', `${sessionId}.webm.part`);
124
124
  await writeFile(tempPath, Buffer.alloc(0), { mode: 0o600 });
125
- const fileNameBase = safeName(metadata.fileName, `LiveDesk_${kind}`);
125
+ const fileNameBase = safeName(metadata.fileName, `VuvoDesk_${kind}`);
126
126
  const session = {
127
127
  sessionId,
128
128
  kind,
@@ -29,6 +29,7 @@ const MAX_SHARED_ASSEMBLY_BYTES = 12 * 1024 * 1024;
29
29
  const MAX_FRAME_MESSAGE_BYTES = (8 * 1024 * 1024) + DIRECT_CONSOLE_WIRE_CHUNK_BYTES;
30
30
  const MAX_PENDING_MEDIA_WIRE_MESSAGES = 8;
31
31
  const MAX_DATA_CHANNEL_BUFFERED_BYTES = 12 * 1024 * 1024;
32
+ const CONTROL_HEADROOM_BUFFERED_BYTES = 4 * 1024 * 1024;
32
33
  const MAX_LOCAL_WS_BUFFERED_BYTES = 4 * 1024 * 1024;
33
34
  const HTTP_TIMEOUT_MS = 15_000;
34
35
  const ICE_CONNECT_TIMEOUT_MS = 10_000;
@@ -169,6 +170,7 @@ function allowedHttpRequest(method, pathname) {
169
170
  || pathname === '/api/auth/session'
170
171
  || pathname === '/api/remote/status'
171
172
  || pathname === '/api/hub/status'
173
+ || pathname === '/api/remote/pwa-diagnostics'
172
174
  || pathname === '/api/update/status'
173
175
  || pathname === '/api/update/apply') {
174
176
  return true;
@@ -369,6 +371,8 @@ export function createHubConsoleDirect(options = {}) {
369
371
  let lastHttpStatus = 0;
370
372
  let rejectedRelayCandidates = 0;
371
373
  let dataChannelBackpressureCloses = 0;
374
+ let dataChannelSendFailures = 0;
375
+ let lastDataChannelSendFailure = null;
372
376
  let malformedWireCloses = 0;
373
377
  let wireBudgetCleanupFailures = 0;
374
378
 
@@ -651,7 +655,7 @@ export function createHubConsoleDirect(options = {}) {
651
655
  || channelBufferedAmount(right) - channelBufferedAmount(left)
652
656
  ));
653
657
  for (const state of candidates) {
654
- if (ownerBufferedAmount(owner) + requiredBytes <= MAX_DATA_CHANNEL_BUFFERED_BYTES) break;
658
+ if (ownerBufferedAmount(owner) + requiredBytes <= CONTROL_HEADROOM_BUFFERED_BYTES) break;
655
659
  dataChannelBackpressureCloses += 1;
656
660
  closeLogicalChannel(owner, state, {
657
661
  code: 1013,
@@ -680,8 +684,39 @@ export function createHubConsoleDirect(options = {}) {
680
684
  return false;
681
685
  }
682
686
  const wireBytes = chunks.reduce((total, chunk) => total + chunk.byteLength, 0);
687
+ const partialMedia = channelState.purpose === 'frame' || channelState.purpose === 'atlas';
688
+ if (partialMedia
689
+ && (wireBytes > MAX_DATA_CHANNEL_BUFFERED_BYTES
690
+ || ownerBufferedAmount(owner) + wireBytes > MAX_DATA_CHANNEL_BUFFERED_BYTES)) {
691
+ dataChannelBackpressureCloses += 1;
692
+ if (options.closeOnFailure !== false) {
693
+ closeLogicalChannel(owner, channelState, {
694
+ code: 1013,
695
+ reason: 'console-direct-datachannel-backpressure',
696
+ notify: false
697
+ });
698
+ }
699
+ return false;
700
+ }
701
+ const mediaBufferedBytes = partialMedia ? channelBufferedAmount(channelState) : 0;
702
+ if (partialMedia
703
+ && mediaBufferedBytes > 0
704
+ && mediaBufferedBytes + wireBytes > CONTROL_HEADROOM_BUFFERED_BYTES) {
705
+ // Retire only the media lane that owns the stale SCTP backlog. Its
706
+ // browser socket recovery requests a current key frame while the
707
+ // reliable peer and every healthy lane keep their own owners.
708
+ dataChannelBackpressureCloses += 1;
709
+ if (options.closeOnFailure !== false) {
710
+ closeLogicalChannel(owner, channelState, {
711
+ code: 1013,
712
+ reason: 'console-direct-media-headroom',
713
+ notify: false
714
+ });
715
+ }
716
+ return false;
717
+ }
683
718
  if (channelState === owner.control
684
- && ownerBufferedAmount(owner) + wireBytes > MAX_DATA_CHANNEL_BUFFERED_BYTES) {
719
+ && ownerBufferedAmount(owner) + wireBytes > CONTROL_HEADROOM_BUFFERED_BYTES) {
685
720
  releaseLogicalBacklogForControl(owner, wireBytes);
686
721
  }
687
722
  if (wireBytes > MAX_DATA_CHANNEL_BUFFERED_BYTES
@@ -697,11 +732,28 @@ export function createHubConsoleDirect(options = {}) {
697
732
  }
698
733
  return false;
699
734
  }
700
- for (const chunk of chunks) {
735
+ for (const [chunkIndex, chunk] of chunks.entries()) {
701
736
  if (!isOwnerActive(owner) || channelState.closed || !channelIsOpen(channelState.channel)) return false;
702
737
  try {
703
- if (channelState.channel.sendMessageBinary(chunk) !== true) throw new Error('send-rejected');
704
- } catch {
738
+ // Normalize the node-datachannel native boundary to its documented
739
+ // Node Buffer shape. Share the exact encoded bytes without copying or
740
+ // retaining a second application queue.
741
+ const nativeChunk = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
742
+ if (channelState.channel.sendMessageBinary(nativeChunk) !== true) throw new Error('send-rejected');
743
+ } catch (error) {
744
+ dataChannelSendFailures += 1;
745
+ lastDataChannelSendFailure = Object.freeze({
746
+ purpose: channelState.purpose,
747
+ channelId: channelState.channelId,
748
+ generation: owner.generation,
749
+ messageId: (channelState.nextSendMessageId - 1) >>> 0,
750
+ chunkIndex,
751
+ chunkCount: chunks.length,
752
+ chunkBytes: chunk.byteLength,
753
+ bufferedBytes: channelBufferedAmount(channelState),
754
+ error: String(error instanceof Error ? error.message : error).slice(0, 120),
755
+ occurredAt: Date.now()
756
+ });
705
757
  if (options.closeOnFailure !== false) {
706
758
  if (channelState === owner.control) retirePeer(owner, 'console-direct-control-send-failed');
707
759
  else closeLogicalChannel(owner, channelState, {
@@ -1950,6 +2002,8 @@ export function createHubConsoleDirect(options = {}) {
1950
2002
  + Number(signalHeartbeatTimerActive),
1951
2003
  rejectedRelayCandidates,
1952
2004
  dataChannelBackpressureCloses,
2005
+ dataChannelSendFailures,
2006
+ lastDataChannelSendFailure,
1953
2007
  malformedWireCloses,
1954
2008
  wireBudgetCleanupFailures,
1955
2009
  lastConnectedAt,
@@ -1982,6 +2036,7 @@ export const consoleDirectContract = Object.freeze({
1982
2036
  maxFrameMessageBytes: MAX_FRAME_MESSAGE_BYTES,
1983
2037
  maxPendingMediaWireMessages: MAX_PENDING_MEDIA_WIRE_MESSAGES,
1984
2038
  maxBufferedBytes: MAX_DATA_CHANNEL_BUFFERED_BYTES,
2039
+ controlHeadroomBufferedBytes: CONTROL_HEADROOM_BUFFERED_BYTES,
1985
2040
  maxLocalWebSocketBufferedBytes: MAX_LOCAL_WS_BUFFERED_BYTES,
1986
2041
  maxDefaultRetryDelayMs: DEFAULT_RETRY_DELAYS_MS.at(-1),
1987
2042
  accessTokenTimeoutMs: ACCESS_TOKEN_TIMEOUT_MS,
@@ -23,6 +23,7 @@ const CONSOLE_ID = '22222222-2222-4222-8222-222222222222';
23
23
  const CONNECTION_ONE = '33333333-3333-4333-8333-333333333333';
24
24
  const CONNECTION_TWO = '44444444-4444-4444-8444-444444444444';
25
25
  const CHANNEL_ID = '55555555-5555-4555-8555-555555555555';
26
+ const CHANNEL_ID_TWO = '55555555-5555-4555-8555-555555555556';
26
27
  const REQUEST_ID = '66666666-6666-4666-8666-666666666666';
27
28
  const TEAM_WORKSPACE_ID = '88888888-8888-4888-8888-888888888888';
28
29
  const TEAM_OWNER_USER_ID = '99999999-9999-4999-8999-999999999999';
@@ -123,6 +124,7 @@ class FakeDataChannel {
123
124
 
124
125
  sendMessageBinary(value) {
125
126
  if (!this.isOpen()) return false;
127
+ if (!Buffer.isBuffer(value)) throw new TypeError('Buffer expected');
126
128
  this.sent.push(new Uint8Array(value));
127
129
  return true;
128
130
  }
@@ -627,6 +629,48 @@ test('reliable control channel bridges bounded HTTP over fragmented wire message
627
629
  direct.close();
628
630
  });
629
631
 
632
+ test('reliable control channel admits the authenticated PWA diagnostic route', async () => {
633
+ let fetchCalls = 0;
634
+ const diagnosticBody = JSON.stringify({ schema: 'livedesk.pwa-runtime-diagnostics.v1', events: [] });
635
+ const { direct, signal } = await connectedDirect({
636
+ fetchImpl: async (url, init) => {
637
+ fetchCalls += 1;
638
+ assert.equal(url, 'http://127.0.0.1:5179/api/remote/pwa-diagnostics');
639
+ assert.equal(init.method, 'POST');
640
+ assert.equal(init.headers['content-type'], 'application/json');
641
+ assert.equal(Buffer.from(init.body).toString('utf8'), diagnosticBody);
642
+ return new Response('{"ok":true,"accepted":true}', {
643
+ status: 202,
644
+ statusText: 'Accepted',
645
+ headers: { 'Content-Type': 'application/json' }
646
+ });
647
+ }
648
+ });
649
+ const peer = offer(signal);
650
+ const control = new FakeDataChannel('livedesk-control-v1');
651
+ peer.emitDataChannel(control);
652
+ control.open();
653
+
654
+ sendWire(control, {
655
+ type: 'http-request',
656
+ requestId: REQUEST_ID,
657
+ method: 'POST',
658
+ path: '/api/remote/pwa-diagnostics',
659
+ headers: { 'content-type': 'application/json' },
660
+ bodyBase64: Buffer.from(diagnosticBody).toString('base64')
661
+ });
662
+ await settle();
663
+ const response = decodeWireMessages(control.sent)
664
+ .map(message => JSON.parse(message.data))
665
+ .find(message => message.type === 'http-response');
666
+ assert.equal(fetchCalls, 1, 'The PWA diagnostic request must reach the local Hub API.');
667
+ assert.equal(response.requestId, REQUEST_ID);
668
+ assert.equal(response.status, 202);
669
+ assert.equal(Buffer.from(response.bodyBase64, 'base64').toString('utf8'), '{"ok":true,"accepted":true}');
670
+ assert.equal(direct.inspect().pendingHttpRequests, 0);
671
+ direct.close();
672
+ });
673
+
630
674
  test('Team Operator can use Wall and Control but cannot mutate the Hub owner runtime', async () => {
631
675
  const teamWorkspaceAccess = {
632
676
  workspaceId: TEAM_WORKSPACE_ID,
@@ -1184,6 +1228,56 @@ test('the peer-wide DataChannel buffer cap closes only the overflowing logical l
1184
1228
  direct.close();
1185
1229
  });
1186
1230
 
1231
+ test('media pressure retires only its stale logical lane while preserving the control peer and a healthy lane', async () => {
1232
+ const { direct, signal } = await connectedDirect();
1233
+ const peer = offer(signal);
1234
+ const control = new FakeDataChannel('livedesk-control-v1');
1235
+ peer.emitDataChannel(control);
1236
+ control.open();
1237
+ const lane = new FakeDataChannel(`livedesk-ws-v1:${CHANNEL_ID}:frame`);
1238
+ peer.emitDataChannel(lane);
1239
+ lane.open();
1240
+ sendWire(control, {
1241
+ type: 'ws-open',
1242
+ connectionId: CONNECTION_ONE,
1243
+ hubEpoch: HUB_EPOCH,
1244
+ channelId: CHANNEL_ID,
1245
+ purpose: 'frame',
1246
+ path: '/api/remote/frames/ws'
1247
+ });
1248
+ const local = FakeLocalSocket.instances[0];
1249
+ local.open();
1250
+ const healthyLane = new FakeDataChannel(`livedesk-ws-v1:${CHANNEL_ID_TWO}:frame`);
1251
+ peer.emitDataChannel(healthyLane);
1252
+ healthyLane.open();
1253
+ sendWire(control, {
1254
+ type: 'ws-open',
1255
+ connectionId: CONNECTION_ONE,
1256
+ hubEpoch: HUB_EPOCH,
1257
+ channelId: CHANNEL_ID_TWO,
1258
+ purpose: 'frame',
1259
+ path: '/api/remote/frames/ws'
1260
+ });
1261
+ const healthyLocal = FakeLocalSocket.instances[1];
1262
+ healthyLocal.open();
1263
+ lane.buffered = consoleDirectContract.controlHeadroomBufferedBytes;
1264
+
1265
+ healthyLocal.receive('healthy-frame');
1266
+ assert.ok(healthyLane.sent.length > 0);
1267
+ assert.equal(healthyLane.closed, false);
1268
+ assert.equal(lane.closed, false);
1269
+
1270
+ local.receive('newest-frame');
1271
+
1272
+ assert.equal(lane.sent.length, 0);
1273
+ assert.equal(lane.closed, true);
1274
+ assert.equal(healthyLane.closed, false);
1275
+ assert.equal(control.closed, false);
1276
+ assert.equal(peer.closed, false);
1277
+ assert.equal(direct.inspect().dataChannelBackpressureCloses, 1);
1278
+ direct.close();
1279
+ });
1280
+
1187
1281
  test('a reliable control response drops stale media backlog without retiring the peer', async () => {
1188
1282
  const { direct, signal } = await connectedDirect({
1189
1283
  fetchImpl: async () => new Response('{"online":true}', {
@@ -1405,7 +1499,7 @@ test('native node-datachannel peers carry the fragmented control wire over loopb
1405
1499
  kind: 'control'
1406
1500
  });
1407
1501
  assert.ok(chunks.length > 1);
1408
- for (const chunk of chunks) assert.equal(offerChannel.sendMessageBinary(chunk), true);
1502
+ for (const chunk of chunks) assert.equal(offerChannel.sendMessageBinary(Buffer.from(chunk)), true);
1409
1503
  const message = await received;
1410
1504
  clearTimeout(messageTimer);
1411
1505
  messageTimer = null;