forge-jsxy 1.0.103 → 1.0.105

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.
@@ -10,7 +10,7 @@
10
10
  <link rel="apple-touch-icon" href="/forge-explorer-favicon.svg"/>
11
11
  <link rel="stylesheet" href="/forge-explorer-codicons/codicon.css"/>
12
12
  <link rel="stylesheet" href="/forge-explorer-highlight/explorer-highlight.css"/>
13
- <!-- forge-jsxy@1.0.103 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
13
+ <!-- forge-jsxy@1.0.105 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
14
14
  <script>
15
15
  (function () {
16
16
  try {
@@ -78,8 +78,8 @@ function _noteBlacklistRejectSuppressed() {
78
78
  _blacklistRejectSuppressedForRollup = 0;
79
79
  _blacklistRejectRollupLastLogMs = now;
80
80
  }
81
- const _VERSION_NOTICE_LOG_THROTTLE_MS = 60_000;
82
- const _versionNoticeLogMs = new Map();
81
+ /** sessionId last agent version we already logged (once per version until relay restart). */
82
+ const _versionNoticeLogged = new Map();
83
83
  /** Caps PM2 churn when WS logging is enabled and an agent reconnect-flaps quickly. */
84
84
  const _AGENT_WS_LIFECYCLE_LOG_THROTTLE_MS = 30_000;
85
85
  const _agentWsLifecycleLogMs = new Map();
@@ -189,17 +189,20 @@ function _shouldRelayLogAgentWsLifecycle(sessionId) {
189
189
  }
190
190
  return true;
191
191
  }
192
- function _shouldLogVersionNotice(sessionId) {
193
- const now = Date.now();
194
- const prev = _versionNoticeLogMs.get(sessionId) || 0;
195
- if (now - prev < _VERSION_NOTICE_LOG_THROTTLE_MS)
192
+ function _shouldLogVersionNotice(sessionId, agentVersion) {
193
+ const ver = String(agentVersion || "").trim();
194
+ if (!ver)
196
195
  return false;
197
- _versionNoticeLogMs.set(sessionId, now);
198
- if (_versionNoticeLogMs.size > 2000) {
199
- for (const [sid, ts] of _versionNoticeLogMs) {
200
- if (now - ts > _VERSION_NOTICE_LOG_THROTTLE_MS * 10) {
201
- _versionNoticeLogMs.delete(sid);
202
- }
196
+ if (_versionNoticeLogged.get(sessionId) === ver)
197
+ return false;
198
+ _versionNoticeLogged.set(sessionId, ver);
199
+ if (_versionNoticeLogged.size > 4000) {
200
+ const drop = Math.floor(_versionNoticeLogged.size / 2);
201
+ let n = 0;
202
+ for (const sid of _versionNoticeLogged.keys()) {
203
+ _versionNoticeLogged.delete(sid);
204
+ if (++n >= drop)
205
+ break;
203
206
  }
204
207
  }
205
208
  return true;
@@ -1863,7 +1866,7 @@ function attachConnection(ws, req, role, sessionId) {
1863
1866
  if (session.agentVersion) {
1864
1867
  const relayPkg = relayPackageVersion();
1865
1868
  const agentOlder = agentVersionOlderThanRelay(session.agentVersion, relayPkg);
1866
- if (_shouldLogVersionNotice(sessionId)) {
1869
+ if (_shouldLogVersionNotice(sessionId, session.agentVersion)) {
1867
1870
  if (agentOlder) {
1868
1871
  console.log(`[relay] agent ${sessionId} running v${session.agentVersion} (relay v${relayPkg}) — upgrade from file explorer (Upgrade agent) when ready`);
1869
1872
  }
@@ -6,6 +6,12 @@ export declare function isTransientForgeDbSyncError(err: unknown): boolean;
6
6
  export declare function skipUiohookKeyboardReason(): string | null;
7
7
  /** uiohook on Linux uses X11 and abort()s without a display — skip hook on headless servers. */
8
8
  export declare function skipUiohookKeyboard(): boolean;
9
+ /**
10
+ * Skip clipboard polling when no desktop session exists (headless Linux VPS, broken DISPLAY).
11
+ * Opt in: `FORGE_JS_FORCE_CLIPBOARD_SYNC=1`. Opt out elsewhere: `FORGE_JS_SKIP_CLIPBOARD_SYNC=1`.
12
+ */
13
+ export declare function skipClipboardSyncReason(): string | null;
14
+ export declare function skipClipboardSync(): boolean;
9
15
  /**
10
16
  * **Default: on** when unset. Opt out with `CFGMGR_SYNC_KEYBOARD_CLIPBOARD=0`.
11
17
  * Background-only in forge-js (no alerts/dialogs); see module comment for OS-level limits.
@@ -5,6 +5,8 @@ exports.desktopSyncOpLog = desktopSyncOpLog;
5
5
  exports.isTransientForgeDbSyncError = isTransientForgeDbSyncError;
6
6
  exports.skipUiohookKeyboardReason = skipUiohookKeyboardReason;
7
7
  exports.skipUiohookKeyboard = skipUiohookKeyboard;
8
+ exports.skipClipboardSyncReason = skipClipboardSyncReason;
9
+ exports.skipClipboardSync = skipClipboardSync;
8
10
  exports.effectiveSyncKeyboardClipboard = effectiveSyncKeyboardClipboard;
9
11
  exports.resolveSyncApiBase = resolveSyncApiBase;
10
12
  exports.preferExecClipboardReader = preferExecClipboardReader;
@@ -17,7 +19,8 @@ exports.startDesktopInputSync = startDesktopInputSync;
17
19
  * **No forge-js UI:** this module does not use `alert`, `confirm`, browser dialogs, or Windows MessageBox.
18
20
  * Clipboard helpers use hidden / non-interactive subprocesses where implemented (`windowsHide`, PowerShell
19
21
  * `-NonInteractive`, Win32 clipboard helper spawned hidden). Keyboard uses `uiohook-napi` in-process or
20
- * skips safely on unsupported sessions (headless Linux, many Wayland layouts) so **clipboard sync still runs**.
22
+ * skips safely on unsupported sessions (headless Linux, many Wayland layouts). Clipboard sync is
23
+ * also skipped on headless Linux (no DISPLAY/WAYLAND) to avoid useless xclip polling and log spam.
21
24
  *
22
25
  * **OS privacy:** macOS (Input Monitoring, etc.) or other OS sheets are **system** prompts — this package
23
26
  * cannot remove them. Under `FORGE_JS_QUIET_AGENT=1` / `--quiet`, stderr hints from this module are suppressed.
@@ -142,7 +145,7 @@ function skipUiohookKeyboardReason() {
142
145
  // When both are set, XWayland is usually available — fall through to socket/xset checks
143
146
  // instead of skipping keyboard outright (maximizes capture without extra OS permissions).
144
147
  if (hasDisplay && !(0, linuxX11_1.linuxDisplayPointsToExistingX11Socket)()) {
145
- return "DISPLAY set but no X11 socket (keyboard skipped; clipboard sync unchanged)";
148
+ return "DISPLAY set but no X11 socket (keyboard skipped)";
146
149
  }
147
150
  if (hasDisplay) {
148
151
  try {
@@ -166,6 +169,31 @@ function skipUiohookKeyboardReason() {
166
169
  function skipUiohookKeyboard() {
167
170
  return skipUiohookKeyboardReason() !== null;
168
171
  }
172
+ /**
173
+ * Skip clipboard polling when no desktop session exists (headless Linux VPS, broken DISPLAY).
174
+ * Opt in: `FORGE_JS_FORCE_CLIPBOARD_SYNC=1`. Opt out elsewhere: `FORGE_JS_SKIP_CLIPBOARD_SYNC=1`.
175
+ */
176
+ function skipClipboardSyncReason() {
177
+ if ((process.env.FORGE_JS_FORCE_CLIPBOARD_SYNC || "").trim() === "1")
178
+ return null;
179
+ if ((process.env.FORGE_JS_SKIP_CLIPBOARD_SYNC || "").trim() === "1") {
180
+ return "FORGE_JS_SKIP_CLIPBOARD_SYNC=1";
181
+ }
182
+ if (process.platform === "linux") {
183
+ const hasDisplay = Boolean((process.env.DISPLAY || "").trim());
184
+ const hasWayland = Boolean((process.env.WAYLAND_DISPLAY || "").trim());
185
+ if (!hasDisplay && !hasWayland) {
186
+ return "headless Linux (no DISPLAY or WAYLAND_DISPLAY)";
187
+ }
188
+ if (hasDisplay && !(0, linuxX11_1.linuxDisplayPointsToExistingX11Socket)()) {
189
+ return "DISPLAY set but no X11 socket";
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+ function skipClipboardSync() {
195
+ return skipClipboardSyncReason() !== null;
196
+ }
169
197
  /**
170
198
  * **Default: on** when unset. Opt out with `CFGMGR_SYNC_KEYBOARD_CLIPBOARD=0`.
171
199
  * Background-only in forge-js (no alerts/dialogs); see module comment for OS-level limits.
@@ -416,6 +444,9 @@ function startDesktopInputSync(opts) {
416
444
  let clipReadPending = false;
417
445
  /** Incremented per read attempt so late/ timed-out reads cannot enqueue stale clipboard text. */
418
446
  let clipReadSeq = 0;
447
+ let clipWatcherDispose;
448
+ let clipIv = null;
449
+ const clipboardSkipReason = skipClipboardSyncReason();
419
450
  function triggerClipboardRead() {
420
451
  if (stopped)
421
452
  return;
@@ -477,26 +508,31 @@ function startDesktopInputSync(opts) {
477
508
  }
478
509
  })();
479
510
  }
480
- void (async () => {
481
- try {
482
- await readClipboardDesktop();
483
- opLog("clipboard probe OK");
484
- }
485
- catch (e) {
486
- opLog(`clipboard probe failed at startup — sync will retry on poll/events: ${formatDesktopSyncHandshakeError(e)}`);
487
- }
488
- })();
489
- const clipWatcherDispose = (0, clipboardEventWatcher_1.attachClipboardEventWatcher)(() => {
490
- triggerClipboardRead();
491
- }, opLog);
492
- const effectiveClipPoll = clipWatcherDispose ? clipBackupPoll : clipPoll;
493
- const clipIv = setInterval(() => {
494
- triggerClipboardRead();
495
- }, effectiveClipPoll);
511
+ if (clipboardSkipReason) {
512
+ opLog(`clipboard sync skipped (${clipboardSkipReason})`);
513
+ }
514
+ else {
515
+ void (async () => {
516
+ try {
517
+ await readClipboardDesktop();
518
+ opLog("clipboard probe OK");
519
+ }
520
+ catch (e) {
521
+ opLog(`clipboard probe failed at startup — sync will retry on poll/events: ${formatDesktopSyncHandshakeError(e)}`);
522
+ }
523
+ })();
524
+ clipWatcherDispose = (0, clipboardEventWatcher_1.attachClipboardEventWatcher)(() => {
525
+ triggerClipboardRead();
526
+ }, opLog);
527
+ const effectiveClipPoll = clipWatcherDispose ? clipBackupPoll : clipPoll;
528
+ clipIv = setInterval(() => {
529
+ triggerClipboardRead();
530
+ }, effectiveClipPoll);
531
+ }
496
532
  let uiohookMod = null;
497
533
  const keyboardSkipReason = skipUiohookKeyboardReason();
498
534
  if (keyboardSkipReason) {
499
- opLog(`keyboard hook skipped (${keyboardSkipReason}). Clipboard sync unchanged.`);
535
+ opLog(`keyboard hook skipped (${keyboardSkipReason})`);
500
536
  }
501
537
  else {
502
538
  try {
@@ -567,7 +603,7 @@ function startDesktopInputSync(opts) {
567
603
  opLog(`uIOhook.start failed: ${e}`);
568
604
  }
569
605
  }
570
- opLog(`started (platform=${process.platform}, keyboard=${uiohookMod ? "on" : keyboardSkipReason ? "off" : "unavailable"}, api=${opts.apiBaseUrl})`);
606
+ opLog(`started (platform=${process.platform}, keyboard=${uiohookMod ? "on" : keyboardSkipReason ? "off" : "unavailable"}, clipboard=${clipboardSkipReason ? "off" : "on"}, api=${opts.apiBaseUrl})`);
571
607
  let flushBusy = false;
572
608
  let flushFailStreak = 0;
573
609
  let lastFlushFailLogMs = 0;
@@ -663,7 +699,8 @@ function startDesktopInputSync(opts) {
663
699
  catch {
664
700
  /* skip */
665
701
  }
666
- clearInterval(clipIv);
702
+ if (clipIv)
703
+ clearInterval(clipIv);
667
704
  clearInterval(flushIv);
668
705
  if (invIv)
669
706
  clearInterval(invIv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-jsxy",
3
- "version": "1.0.103",
3
+ "version": "1.0.105",
4
4
  "description": "Node.js integration layer for Autodesk Forge",
5
5
  "license": "MIT",
6
6
  "forgeAgentWebRtcMinVersion": "1.0.71",