From cc88cdd0d8c8d206c4be354973b1971af1a0d328 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Fri, 13 Feb 2026 12:16:58 -0700 Subject: [PATCH] Add canvas sync bridge for rspace.online integration PostMessage listener in CanvasEmbed receives shape updates from the embedded rspace canvas iframe. Canvas-sync utility provides type guards and API helpers for pushing shapes to rspace. Co-Authored-By: Claude Opus 4.6 --- src/components/CanvasEmbed.tsx | 21 ++++++++++++++++-- src/lib/canvas-sync.ts | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/lib/canvas-sync.ts diff --git a/src/components/CanvasEmbed.tsx b/src/components/CanvasEmbed.tsx index 6eb6f0e..3cf19d8 100644 --- a/src/components/CanvasEmbed.tsx +++ b/src/components/CanvasEmbed.tsx @@ -1,14 +1,31 @@ 'use client'; +import { useEffect, useCallback } from 'react'; +import { isCanvasMessage, CanvasShapeMessage } from '@/lib/canvas-sync'; + interface CanvasEmbedProps { canvasSlug: string; className?: string; + onShapeUpdate?: (message: CanvasShapeMessage) => void; } -export function CanvasEmbed({ canvasSlug, className = '' }: CanvasEmbedProps) { - const rspaceUrl = process.env.NEXT_PUBLIC_RSPACE_URL || 'https://rspace.online'; +export function CanvasEmbed({ canvasSlug, className = '', onShapeUpdate }: CanvasEmbedProps) { const canvasUrl = `https://${canvasSlug}.rspace.online`; + const handleMessage = useCallback( + (event: MessageEvent) => { + if (!isCanvasMessage(event)) return; + if (event.data.communitySlug !== canvasSlug) return; + onShapeUpdate?.(event.data); + }, + [canvasSlug, onShapeUpdate] + ); + + useEffect(() => { + window.addEventListener('message', handleMessage); + return () => window.removeEventListener('message', handleMessage); + }, [handleMessage]); + return (
{/* Toolbar */} diff --git a/src/lib/canvas-sync.ts b/src/lib/canvas-sync.ts new file mode 100644 index 0000000..2dddef8 --- /dev/null +++ b/src/lib/canvas-sync.ts @@ -0,0 +1,40 @@ +/** + * Canvas sync utilities for rtrips.online <-> rspace.online integration + * + * Listens for postMessage events from the embedded rspace canvas iframe + * and provides methods to push structured data to the canvas. + */ + +export interface CanvasShapeMessage { + source: 'rspace-canvas'; + type: 'shape-updated' | 'shape-deleted'; + communitySlug: string; + shapeId: string; + data: Record; +} + +export function isCanvasMessage(event: MessageEvent): event is MessageEvent { + return event.data?.source === 'rspace-canvas'; +} + +/** + * Push shapes to an rspace canvas community via the shapes API + */ +export async function pushShapesToCanvas( + canvasSlug: string, + shapes: Record[], + rspaceUrl?: string +): Promise { + const baseUrl = rspaceUrl || process.env.RSPACE_INTERNAL_URL || 'http://rspace-online:3000'; + + const response = await fetch(`${baseUrl}/api/communities/${canvasSlug}/shapes`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ shapes }), + }); + + if (!response.ok) { + const text = await response.text(); + throw new Error(`Failed to push shapes: ${response.status} ${text}`); + } +}