Skip to content

Commit fc7d5c6

Browse files
committed
feat: integrate Flock support into Loro adaptors
- Updated dependencies to use `loro-crdt` version 1.8.5 and added `@loro-dev/flock` as a peer dependency. - Introduced `FlockAdaptor` and `FlockServerAdaptor` to bridge Flock with the Loro ecosystem. - Enhanced `useLoroSync` hook to support Flock integration. - Updated example and tests to demonstrate Flock functionality. - Added Flock type to the protocol for better interoperability.
1 parent 5b06566 commit fc7d5c6

File tree

14 files changed

+797
-120
lines changed

14 files changed

+797
-120
lines changed

examples/excalidraw-example/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"dependencies": {
1515
"@excalidraw/excalidraw": "^0.17.0",
1616
"loro-adaptors": "workspace:*",
17-
"loro-crdt": "^1.7.1",
17+
"loro-crdt": "^1.8.5",
1818
"loro-websocket": "workspace:*",
1919
"react": "^18.2.0",
2020
"react-dom": "^18.2.0",
@@ -36,4 +36,4 @@
3636
"vite-plugin-wasm": "^3.5.0",
3737
"ws": "^8.16.0"
3838
}
39-
}
39+
}

examples/excalidraw-example/src/hooks/useLoroSync.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import {
88
type Value,
99
} from "loro-crdt";
1010
import { LoroWebsocketClient } from "loro-websocket/client";
11-
import { LoroAdaptor, LoroEphemeralAdaptor } from "loro-adaptors";
11+
import {
12+
LoroAdaptor,
13+
createLoroEphemeralAdaptor,
14+
} from "loro-adaptors";
1215
import type {
1316
AppState as ExcalidrawAppState,
1417
ExcalidrawImperativeAPI,
@@ -46,6 +49,11 @@ type SceneUpdateArgs = Parameters<
4649
type SceneElements = NonNullable<SceneUpdateArgs["elements"]>;
4750
type SceneAppStateUpdate = NonNullable<SceneUpdateArgs["appState"]>;
4851
type PresenceStoreState = Record<string, PresenceEntry>;
52+
type EphemeralStoreInstance = ReturnType<typeof createLoroEphemeralAdaptor> extends {
53+
getStore(): infer S;
54+
}
55+
? S
56+
: EphemeralStore<PresenceStoreState>;
4957

5058
export function useLoroSync({
5159
roomId,
@@ -57,8 +65,8 @@ export function useLoroSync({
5765
}: UseLoroSyncOptions) {
5866
const docRef = useRef<LoroDoc | null>(null);
5967
const clientRef = useRef<LoroWebsocketClient | null>(null);
60-
const ephemeralRef =
61-
useRef<EphemeralStore<PresenceStoreState> | null>(null);
68+
const ephemeralRef = useRef<EphemeralStoreInstance | null>(null);
69+
const ephemeralAdaptorRef = useRef<ReturnType<typeof createLoroEphemeralAdaptor> | null>(null);
6270

6371
const [isConnected, setIsConnected] = useState(false);
6472
const [collaborators, setCollaborators] = useState<Map<string, Collaborator>>(new Map());
@@ -69,11 +77,13 @@ export function useLoroSync({
6977
useEffect(() => {
7078
const doc = new LoroDoc();
7179
const client = new LoroWebsocketClient({ url: wsUrl });
72-
const ephemeral = new EphemeralStore<PresenceStoreState>(30000); // 30 second timeout
80+
const ephAdaptor = createLoroEphemeralAdaptor({ timeout: 30000 });
81+
const ephemeral = ephAdaptor.getStore();
7382

7483
docRef.current = doc;
7584
clientRef.current = client;
7685
ephemeralRef.current = ephemeral;
86+
ephemeralAdaptorRef.current = ephAdaptor;
7787

7888
// Set up document structure
7989
const elementsContainer = doc.getList("elements");
@@ -121,7 +131,6 @@ export function useLoroSync({
121131
recalcCollaborators();
122132
// Join rooms via loro-websocket client and adaptors
123133
const adaptor = new LoroAdaptor(doc, { peerId: userId });
124-
const ephAdaptor = new LoroEphemeralAdaptor(ephemeral, {});
125134

126135
let rooms: { destroy: () => Promise<void> }[] = [];
127136
client
@@ -148,7 +157,7 @@ export function useLoroSync({
148157
});
149158
// Adaptor destroy will also clean underlying stores/docs, but ensure store is destroyed
150159
try {
151-
ephemeral.destroy();
160+
ephemeralAdaptorRef.current?.destroy();
152161
} catch { }
153162
};
154163
}, [roomId, userId, wsUrl]);
@@ -157,30 +166,34 @@ export function useLoroSync({
157166
const updateCursor = useCallback((position: CursorPosition) => {
158167
if (!ephemeralRef.current) return;
159168

160-
const currentState = ephemeralRef.current.get(userId) || {};
161-
ephemeralRef.current.set(userId, {
169+
const currentState = (ephemeralRef.current.get(userId) ??
170+
{}) as PresenceEntry;
171+
const nextState: PresenceEntry = {
162172
...currentState,
163173
userId,
164174
userName,
165175
userColor,
166176
cursor: position,
167177
lastActive: Date.now(),
168-
});
178+
};
179+
ephemeralRef.current.set(userId, nextState);
169180
}, [userId, userName, userColor]);
170181

171182
// Update selected elements
172183
const updateSelection = useCallback((selectedElementIds: string[]) => {
173184
if (!ephemeralRef.current) return;
174185

175-
const currentState = ephemeralRef.current.get(userId) || {};
176-
ephemeralRef.current.set(userId, {
186+
const currentState = (ephemeralRef.current.get(userId) ??
187+
{}) as PresenceEntry;
188+
const nextState: PresenceEntry = {
177189
...currentState,
178190
userId,
179191
userName,
180192
userColor,
181193
selectedElementIds,
182194
lastActive: Date.now(),
183-
});
195+
};
196+
ephemeralRef.current.set(userId, nextState);
184197
}, [userId, userName, userColor]);
185198

186199
// Record local Excalidraw changes into LoroDoc with minimal ops

packages/loro-adaptors/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"loro-protocol": "workspace:*"
1616
},
1717
"peerDependencies": {
18-
"loro-crdt": "^1.7.1"
18+
"loro-crdt": "^1.8.5",
19+
"@loro-dev/flock": "^1.0.0"
1920
},
2021
"devDependencies": {
2122
"@types/node": "^20.0.0",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export * from "./loro-adaptor";
22
export * from "./loro-ephemeral-adaptor";
33
export * from "./elo-loro-adaptor";
4-
4+
export * from "./flock-adaptor";

0 commit comments

Comments
 (0)