feat(nbstore): add cloud implementation (#8810)
This commit is contained in:
@@ -8,7 +8,6 @@ import {
|
||||
WebSocketGateway,
|
||||
} from '@nestjs/websockets';
|
||||
import { Socket } from 'socket.io';
|
||||
import { diffUpdate, encodeStateVectorFromUpdate } from 'yjs';
|
||||
|
||||
import {
|
||||
AlreadyInSpace,
|
||||
@@ -83,6 +82,9 @@ interface LeaveSpaceAwarenessMessage {
|
||||
docId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
interface PushDocUpdatesMessage {
|
||||
spaceType: SpaceType;
|
||||
spaceId: string;
|
||||
@@ -90,6 +92,13 @@ interface PushDocUpdatesMessage {
|
||||
updates: string[];
|
||||
}
|
||||
|
||||
interface PushDocUpdateMessage {
|
||||
spaceType: SpaceType;
|
||||
spaceId: string;
|
||||
docId: string;
|
||||
update: string;
|
||||
}
|
||||
|
||||
interface LoadDocMessage {
|
||||
spaceType: SpaceType;
|
||||
spaceId: string;
|
||||
@@ -97,6 +106,12 @@ interface LoadDocMessage {
|
||||
stateVector?: string;
|
||||
}
|
||||
|
||||
interface DeleteDocMessage {
|
||||
spaceType: SpaceType;
|
||||
spaceId: string;
|
||||
docId: string;
|
||||
}
|
||||
|
||||
interface LoadDocTimestampsMessage {
|
||||
spaceType: SpaceType;
|
||||
spaceId: string;
|
||||
@@ -114,6 +129,7 @@ interface UpdateAwarenessMessage {
|
||||
docId: string;
|
||||
awarenessUpdate: string;
|
||||
}
|
||||
|
||||
@WebSocketGateway()
|
||||
export class SpaceSyncGateway
|
||||
implements OnGatewayConnection, OnGatewayDisconnect
|
||||
@@ -182,26 +198,6 @@ export class SpaceSyncGateway
|
||||
}
|
||||
}
|
||||
|
||||
async joinWorkspace(
|
||||
client: Socket,
|
||||
room: `${string}:${'sync' | 'awareness'}`
|
||||
) {
|
||||
await client.join(room);
|
||||
}
|
||||
|
||||
async leaveWorkspace(
|
||||
client: Socket,
|
||||
room: `${string}:${'sync' | 'awareness'}`
|
||||
) {
|
||||
await client.leave(room);
|
||||
}
|
||||
|
||||
assertInWorkspace(client: Socket, room: `${string}:${'sync' | 'awareness'}`) {
|
||||
if (!client.rooms.has(room)) {
|
||||
throw new NotInSpace({ spaceId: room.split(':')[0] });
|
||||
}
|
||||
}
|
||||
|
||||
// v3
|
||||
@SubscribeMessage('space:join')
|
||||
async onJoinSpace(
|
||||
@@ -233,36 +229,42 @@ export class SpaceSyncGateway
|
||||
@MessageBody()
|
||||
{ spaceType, spaceId, docId, stateVector }: LoadDocMessage
|
||||
): Promise<
|
||||
EventResponse<{ missing: string; state?: string; timestamp: number }>
|
||||
EventResponse<{ missing: string; state: string; timestamp: number }>
|
||||
> {
|
||||
const adapter = this.selectAdapter(client, spaceType);
|
||||
adapter.assertIn(spaceId);
|
||||
|
||||
const doc = await adapter.get(spaceId, docId);
|
||||
const doc = await adapter.diff(
|
||||
spaceId,
|
||||
docId,
|
||||
stateVector ? Buffer.from(stateVector, 'base64') : undefined
|
||||
);
|
||||
|
||||
if (!doc) {
|
||||
throw new DocNotFound({ spaceId, docId });
|
||||
}
|
||||
|
||||
const missing = Buffer.from(
|
||||
stateVector
|
||||
? diffUpdate(doc.bin, Buffer.from(stateVector, 'base64'))
|
||||
: doc.bin
|
||||
).toString('base64');
|
||||
|
||||
const state = Buffer.from(encodeStateVectorFromUpdate(doc.bin)).toString(
|
||||
'base64'
|
||||
);
|
||||
|
||||
return {
|
||||
data: {
|
||||
missing,
|
||||
state,
|
||||
missing: Buffer.from(doc.missing).toString('base64'),
|
||||
state: Buffer.from(doc.state).toString('base64'),
|
||||
timestamp: doc.timestamp,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@SubscribeMessage('space:delete-doc')
|
||||
async onDeleteSpaceDoc(
|
||||
@ConnectedSocket() client: Socket,
|
||||
@MessageBody() { spaceType, spaceId, docId }: DeleteDocMessage
|
||||
) {
|
||||
const adapter = this.selectAdapter(client, spaceType);
|
||||
await adapter.delete(spaceId, docId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use [space:push-doc-update] instead, client should always merge updates on their own
|
||||
*/
|
||||
@SubscribeMessage('space:push-doc-updates')
|
||||
async onReceiveDocUpdates(
|
||||
@ConnectedSocket() client: Socket,
|
||||
@@ -307,6 +309,51 @@ export class SpaceSyncGateway
|
||||
};
|
||||
}
|
||||
|
||||
@SubscribeMessage('space:push-doc-update')
|
||||
async onReceiveDocUpdate(
|
||||
@ConnectedSocket() client: Socket,
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@MessageBody()
|
||||
message: PushDocUpdateMessage
|
||||
): Promise<EventResponse<{ accepted: true; timestamp?: number }>> {
|
||||
const { spaceType, spaceId, docId, update } = message;
|
||||
const adapter = this.selectAdapter(client, spaceType);
|
||||
|
||||
// TODO(@forehalo): we might need to check write permission before push updates
|
||||
const timestamp = await adapter.push(
|
||||
spaceId,
|
||||
docId,
|
||||
[Buffer.from(update, 'base64')],
|
||||
user.id
|
||||
);
|
||||
|
||||
// TODO(@forehalo): separate different version of clients into different rooms,
|
||||
// so the clients won't receive useless updates events
|
||||
client.to(adapter.room(spaceId)).emit('space:broadcast-doc-updates', {
|
||||
spaceType,
|
||||
spaceId,
|
||||
docId,
|
||||
updates: [update],
|
||||
timestamp,
|
||||
});
|
||||
|
||||
client.to(adapter.room(spaceId)).emit('space:broadcast-doc-update', {
|
||||
spaceType,
|
||||
spaceId,
|
||||
docId,
|
||||
update,
|
||||
timestamp,
|
||||
editor: user.id,
|
||||
});
|
||||
|
||||
return {
|
||||
data: {
|
||||
accepted: true,
|
||||
timestamp,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@SubscribeMessage('space:load-doc-timestamps')
|
||||
async onLoadDocTimestamps(
|
||||
@ConnectedSocket() client: Socket,
|
||||
@@ -600,9 +647,14 @@ abstract class SyncSocketAdapter {
|
||||
return this.storage.pushDocUpdates(spaceId, docId, updates, editorId);
|
||||
}
|
||||
|
||||
get(spaceId: string, docId: string) {
|
||||
diff(spaceId: string, docId: string, stateVector?: Uint8Array) {
|
||||
this.assertIn(spaceId);
|
||||
return this.storage.getDoc(spaceId, docId);
|
||||
return this.storage.getDocDiff(spaceId, docId, stateVector);
|
||||
}
|
||||
|
||||
delete(spaceId: string, docId: string) {
|
||||
this.assertIn(spaceId);
|
||||
return this.storage.deleteDoc(spaceId, docId);
|
||||
}
|
||||
|
||||
getTimestamps(spaceId: string, timestamp?: number) {
|
||||
@@ -630,9 +682,9 @@ class WorkspaceSyncAdapter extends SyncSocketAdapter {
|
||||
return super.push(spaceId, id.guid, updates, editorId);
|
||||
}
|
||||
|
||||
override get(spaceId: string, docId: string) {
|
||||
override diff(spaceId: string, docId: string, stateVector?: Uint8Array) {
|
||||
const id = new DocID(docId, spaceId);
|
||||
return this.storage.getDoc(spaceId, id.guid);
|
||||
return this.storage.getDocDiff(spaceId, id.guid, stateVector);
|
||||
}
|
||||
|
||||
async assertAccessible(
|
||||
|
||||
Reference in New Issue
Block a user