From 70cb91954179e981fdf9597e165d95ffdeb8ff55 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 13 Apr 2026 23:32:33 -0400 Subject: [PATCH] fix(rnetwork): add auth headers to graph viewer API calls; add Blender multi-user - Fix 401 errors on rNetwork by passing encryptid-token as Bearer auth on /api/info, /api/graph, /api/workspaces fetch calls - Add blender-multiuser replication server (multi-user-server:0.5.8) to docker-compose with health check and resource limits - Add Multiplayer tab to folk-blender shape with connection info, server status check, and setup instructions - Add /api/blender-multiuser/status endpoint for TCP health probe Co-Authored-By: Claude Opus 4.6 --- docker-compose.yml | 27 ++++ lib/folk-blender.ts | 152 +++++++++++++++++- .../rnetwork/components/folk-graph-viewer.ts | 8 +- server/index.ts | 27 ++++ 4 files changed, 209 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2618d540..b6588399 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -283,6 +283,33 @@ services: retries: 5 start_period: 10s + # ── Blender Multi-User replication server (always-on, persistent TCP) ── + blender-multiuser: + image: registry.gitlab.com/slumber/multi-user/multi-user-server:0.5.8 + container_name: blender-multiuser + restart: unless-stopped + mem_limit: 512m + cpus: 1 + ports: + - "5555:5555" + - "5556:5556" + - "5557:5557" + - "5558:5558" + environment: + - port=5555 + - password=${BLENDER_MULTIUSER_PASSWORD} + - timeout=5000 + - log_level=INFO + - log_file=multiuser_server.log + networks: + - rspace-internal + healthcheck: + test: ["CMD-SHELL", "python3 -c 'import socket; s=socket.socket(); s.settimeout(2); s.connect((\"localhost\",5555)); s.close()' || exit 1"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + # ── On-demand sidecars (started/stopped by server/sidecar-manager.ts) ── # Build: docker compose --profile sidecar build # Create: docker compose --profile sidecar create diff --git a/lib/folk-blender.ts b/lib/folk-blender.ts index 1c47cd88..ad15281d 100644 --- a/lib/folk-blender.ts +++ b/lib/folk-blender.ts @@ -289,6 +289,94 @@ const styles = css` font-size: 13px; margin: 12px; } + + .multiplayer-area { + flex: 1; + overflow: auto; + padding: 12px; + display: none; + } + + .mp-status { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + font-size: 13px; + font-weight: 600; + } + + .mp-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: #94a3b8; + } + + .mp-dot.online { background: #22c55e; } + .mp-dot.offline { background: #ef4444; } + + .mp-field { + margin-bottom: 10px; + } + + .mp-field label { + display: block; + font-size: 11px; + font-weight: 600; + color: #64748b; + margin-bottom: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .mp-conn { + display: flex; + gap: 6px; + } + + .mp-conn input { + flex: 1; + padding: 7px 10px; + border: 1px solid #e2e8f0; + border-radius: 6px; + font-size: 13px; + font-family: monospace; + background: #f8fafc; + color: #1e293b; + } + + .mp-copy-btn { + padding: 7px 12px; + border: 1px solid #e2e8f0; + border-radius: 6px; + background: #f8fafc; + cursor: pointer; + font-size: 13px; + } + + .mp-copy-btn:hover { background: #e2e8f0; } + + .mp-instructions { + font-size: 12px; + color: #64748b; + line-height: 1.6; + margin-top: 12px; + padding: 10px; + background: #f8fafc; + border-radius: 6px; + border: 1px solid #e2e8f0; + } + + .mp-instructions ol { + margin: 4px 0 0 16px; + padding: 0; + } + + .mp-instructions a { + color: #ea580c; + text-decoration: underline; + } `; declare global { @@ -318,11 +406,12 @@ export class FolkBlender extends FolkShape { #renderUrl: string | null = null; #script: string | null = null; #blendUrl: string | null = null; - #activeTab: "preview" | "code" = "preview"; + #activeTab: "preview" | "code" | "multiplayer" = "preview"; #promptInput: HTMLTextAreaElement | null = null; #generateBtn: HTMLButtonElement | null = null; #previewArea: HTMLElement | null = null; #codeArea: HTMLElement | null = null; + #multiplayerArea: HTMLElement | null = null; #downloadRow: HTMLElement | null = null; override createRenderRoot() { @@ -350,6 +439,7 @@ export class FolkBlender extends FolkShape {
+
@@ -361,6 +451,28 @@ export class FolkBlender extends FolkShape { +
+
+ + Checking... +
+
+ +
+ + +
+
+
+ How to connect: +
    +
  1. Install Multi-User addon in Blender 4.3+
  2. +
  3. Open sidebar → Multi-User tab
  4. +
  5. Paste the connection address above
  6. +
  7. Click Connect — you're in the shared session!
  8. +
+
+