Skip to content
This repository was archived by the owner on Oct 22, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/cursors/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.actorcore
node_modules
43 changes: 43 additions & 0 deletions examples/cursors/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Real-time Collaborative Cursors for RivetKit

Example project demonstrating real-time cursor tracking and collaborative canvas with [RivetKit](https://rivetkit.org).

[Learn More →](https://github.com/rivet-dev/rivetkit)

[Discord](https://rivet.dev/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-dev/rivetkit/issues)

## Getting Started

### Prerequisites

- Node.js 18+

### Installation

```sh
git clone https://github.com/rivet-dev/rivetkit
cd rivetkit/examples/cursors
npm install
```

### Development

```sh
npm run dev
```

Open your browser to `http://localhost:5173`. Open multiple tabs or windows to see real-time cursor tracking and text placement across different users.

## Features

- Real-time cursor position tracking
- Multiple users with color-coded cursors
- Click-to-place text on canvas
- Multiple room support for different collaborative spaces
- Persistent text labels across sessions
- Event-driven architecture with RivetKit actors
- TypeScript support throughout

## License

Apache 2.0
33 changes: 33 additions & 0 deletions examples/cursors/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "example-cursors",
"version": "2.0.20",
"private": true,
"type": "module",
"scripts": {
"dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"",
"dev:backend": "tsx --watch src/backend/server.ts",
"dev:frontend": "vite",
"check-types": "tsc --noEmit",
"test": "vitest run"
},
"devDependencies": {
"@types/node": "^22.13.9",
"@types/prompts": "^2",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^4.2.0",
"concurrently": "^8.2.2",
"prompts": "^2.4.2",
"tsx": "^3.12.7",
"typescript": "^5.5.2",
"vite": "^5.0.0",
"vitest": "^3.1.1",
"@rivetkit/react": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"dependencies": {
"rivetkit": "workspace:*"
},
"stableVersion": "0.8.0"
}
68 changes: 68 additions & 0 deletions examples/cursors/src/backend/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { actor, setup } from "rivetkit";

export type CursorPosition = {
userId: string;
x: number;
y: number;
timestamp: number;
};

export type TextLabel = {
id: string;
userId: string;
text: string;
x: number;
y: number;
timestamp: number;
};

export const cursorRoom = actor({
// Persistent state that survives restarts: https://rivet.dev/docs/actors/state
state: {
cursors: {} as Record<string, CursorPosition>,
textLabels: [] as TextLabel[],
},

actions: {
// Update cursor position
updateCursor: (c, userId: string, x: number, y: number) => {
const cursor: CursorPosition = { userId, x, y, timestamp: Date.now() };
c.state.cursors[userId] = cursor;
// Send events to all connected clients: https://rivet.dev/docs/actors/events
c.broadcast("cursorMoved", cursor);
return cursor;
},

// Place text on the canvas
placeText: (c, userId: string, text: string, x: number, y: number) => {
const textLabel: TextLabel = {
id: `${userId}-${Date.now()}`,
userId,
text,
x,
y,
timestamp: Date.now(),
};
c.state.textLabels.push(textLabel);
c.broadcast("textPlaced", textLabel);
return textLabel;
},

// Get all cursors
getCursors: (c) => c.state.cursors,

// Get all text labels
getTextLabels: (c) => c.state.textLabels,

// Remove cursor when user disconnects
removeCursor: (c, userId: string) => {
delete c.state.cursors[userId];
c.broadcast("cursorRemoved", userId);
},
},
});

// Register actors for use: https://rivet.dev/docs/setup
export const registry = setup({
use: { cursorRoom },
});
8 changes: 8 additions & 0 deletions examples/cursors/src/backend/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { registry } from "./registry";

registry.start({
cors: {
origin: "http://localhost:5173",
credentials: true,
},
});
Loading
Loading