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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-13 12:16:58 -07:00
parent 8c13f6ad71
commit cc88cdd0d8
2 changed files with 59 additions and 2 deletions

View File

@ -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 (
<div className={`relative bg-slate-900 rounded-xl overflow-hidden border border-slate-700/50 ${className}`}>
{/* Toolbar */}

40
src/lib/canvas-sync.ts Normal file
View File

@ -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<string, unknown>;
}
export function isCanvasMessage(event: MessageEvent): event is MessageEvent<CanvasShapeMessage> {
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<string, unknown>[],
rspaceUrl?: string
): Promise<void> {
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}`);
}
}