Skip to content

Commit

Permalink
feat: 서버 인스턴스 메서드 추가
Browse files Browse the repository at this point in the history
- 블록 추가
- 블록 삭제
- 페이지 아이디 생성
  • Loading branch information
Ludovico7 committed Nov 20, 2024
1 parent 81f4767 commit 16a9038
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 163 deletions.
6 changes: 6 additions & 0 deletions client/src/apis/useSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CursorPosition,
WorkSpaceSerializedProps,
} from "@noctaCrdt/Interfaces";
import { PageSerializedProps } from "node_modules/@noctaCrdt/Page";

import { useEffect, useRef, useState } from "react";
import { io, Socket } from "socket.io-client";
Expand All @@ -24,6 +25,7 @@ interface RemoteOperationHandlers {
interface UseSocketReturn {
socket: Socket | null;
fetchWorkspaceData: () => WorkSpaceSerializedProps;
fetchPageData: () => PageSerializedProps;
sendBlockUpdateOperation: (operation: RemoteBlockUpdateOperation) => void;
sendBlockInsertOperation: (operation: RemoteBlockInsertOperation) => void;
sendCharInsertOperation: (operation: RemoteCharInsertOperation) => void;
Expand Down Expand Up @@ -115,6 +117,7 @@ export const useSocket = (): UseSocketReturn => {
onRemoteCursor,
}: RemoteOperationHandlers) => {
if (!socketRef.current) return;
socketRef.current.on("fetch/page");
socketRef.current.on("update/block", onRemoteBlockUpdate);
socketRef.current.on("insert/block", onRemoteBlockInsert);
socketRef.current.on("delete/block", onRemoteBlockDelete);
Expand All @@ -136,8 +139,11 @@ export const useSocket = (): UseSocketReturn => {
return workspace!;
};

const fetchPageData = (): PageSerializedProps => {};

return {
fetchWorkspaceData,
fetchPageData,
socket: socketRef.current,
sendBlockUpdateOperation,
sendBlockInsertOperation,
Expand Down
2 changes: 1 addition & 1 deletion client/tsconfig.tsbuildinfo

Large diffs are not rendered by default.

128 changes: 84 additions & 44 deletions server/src/crdt/crdt.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
RemoteCharDeleteOperation,
RemoteBlockInsertOperation,
RemoteCharInsertOperation,
RemoteBlockUpdateOperation,
CursorPosition,
} from "@noctaCrdt/Interfaces";
import { Logger } from "@nestjs/common";
Expand Down Expand Up @@ -66,7 +67,6 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
client.emit("assignId", assignedId);
// 현재 문서 상태 전송
const currentWorkSpace = await this.workSpaceService.getWorkspace().serialize();
console.log(currentWorkSpace);
client.emit("workspace", currentWorkSpace);

// 다른 클라이언트들에게 새 사용자 입장 알림
Expand Down Expand Up @@ -107,6 +107,48 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
}
}

/**
* 블록 업데이트 연산 처리
*/
@SubscribeMessage("update/block")
async handleBlockUpdate(
@MessageBody() data: RemoteBlockUpdateOperation,
@ConnectedSocket() client: Socket,
): Promise<void> {
const clientInfo = this.clientMap.get(client.id);
try {
this.logger.debug(
`블록 Update 연산 수신 - Client ID: ${clientInfo?.clientId}, Data:`,
JSON.stringify(data),
);
// 1. 워크스페이스 가져오기
const workspace = this.workSpaceService.getWorkspace();

// 2. 해당 페이지 가져오기
const pageId = data.pageId;

Check failure on line 128 in server/src/crdt/crdt.gateway.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Use object destructuring
const page = workspace.pageList.find((p) => p.id === pageId);
if (!page) {
throw new Error(`Page with id ${pageId} not found`);
}

// 3. 업데이트된 블록 정보를 페이지의 CRDT에 적용
page.crdt.remoteUpdate(data.node);

// 5. 다른 클라이언트들에게 업데이트된 블록 정보 브로드캐스트
const operation = {
node: data.node,
pageId: data.pageId,
};
client.broadcast.emit("update/block", operation);
} catch (error) {
this.logger.error(
`블록 Update 연산 처리 중 오류 발생 - Client ID: ${clientInfo?.clientId}`,
error.stack,
);
throw new WsException(`Update 연산 실패: ${error.message}`);
}
}

/**
* 블록 삽입 연산 처리
*/
Expand All @@ -121,23 +163,25 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
`Insert 연산 수신 - Client ID: ${clientInfo?.clientId}, Data:`,
JSON.stringify(data),
);
// 클라이언트의 char 변경을 보고 char변경이 일어난 block정보를 나머지 client에게 broadcast한다.
// TODO 클라이언트로부터 받은 정보를 서버의 인스턴스에 저장한다.

Check failure on line 166 in server/src/crdt/crdt.gateway.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Delete `·`

Check failure on line 167 in server/src/crdt/crdt.gateway.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Delete `······`
console.log("블럭입니다", data);

Check failure on line 169 in server/src/crdt/crdt.gateway.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Delete `······`
// 몇번 page의 editorCRDT에 추가가 되냐

Check failure on line 170 in server/src/crdt/crdt.gateway.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Delete `·`
const currentPage = this.workSpaceService.getWorkspace().pageList.find((p) => p.id === data.pageId);

Check failure on line 171 in server/src/crdt/crdt.gateway.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Replace `.getWorkspace()` with `⏎········.getWorkspace()⏎········`
if (!currentPage) {
throw new Error(`Page with id ${data.pageId} not found`);
}

// block정보만 들어옴. block에는 Prev,next 정보가 다 있어서 어디에 들어갈지 안다.
// await this.workSpaceService.getWorkspace().handleInsert(data);
// 서버의 editorCRDT의 linkedList에 들어가야한다.
// EditorCRDT가 반영이 되야한다~
// 어디 workspace인지, 어디 페이지인지 확인한 후 블럭을 삽입해야 한다.
currentPage.crdt.LinkedList.insertById(data.node);

console.log("블럭입니다", data);
// const block = this.workSpaceService.getCRDT().LinkedList.getNode(data.node.id); // 변경이 일어난 block
const block = "";
client.broadcast.emit("insert/block", {
operation: data,
node: block,
timestamp: new Date().toISOString(),
sourceClientId: clientInfo?.clientId,
});
const operation = {
node: data.node,
pageId: data.pageId,
};
client.broadcast.emit("insert/block", operation);

Check failure on line 182 in server/src/crdt/crdt.gateway.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Delete `⏎······⏎······`


} catch (error) {
this.logger.error(
`Insert 연산 처리 중 오류 발생 - Client ID: ${clientInfo?.clientId}`,
Expand Down Expand Up @@ -166,22 +210,17 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
// 원하는 block에 char node 를 삽입해야함 이제.

const targetBlockCRDT = this.workSpaceService.getWorkspace();
// await this.workSpaceService.handleCharInsert(data);
console.log("char:", data);
// const char = this.workSpaceService.getCRDT().LinkedList.getNode(data.node.id); // 변경이 일어난 block
// await this.workSpaceService.handleCharInsert(data);getNode(data.node.id); // 변경이 일어난 block
// !! TODO 블록 찾기

// BlockCRDT

// server는 EditorCRDT 없습니다. - BlockCRDT 로 사용되고있음.
const char = "";
client.broadcast.emit("insert/char", {
operation: data,
node: char,
blockId: data.blockId, // TODO : char는 BlockID를 보내야한다? Block을 보내야한다? 고민예정.
timestamp: new Date().toISOString(),
sourceClientId: clientInfo?.clientId,
});
const operation = {
node: data.node,
blockId: data.blockId,
};
client.broadcast.emit("insert/char", operation);
} catch (error) {
this.logger.error(
`Insert 연산 처리 중 오류 발생 - Client ID: ${clientInfo?.clientId}`,
Expand All @@ -208,12 +247,13 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa

const deleteNode = new BlockId(data.clock, data.targetId.client);
// await this.workSpaceService.handleDelete({ targetId: deleteNode, clock: data.clock });

client.broadcast.emit("delete", {
...data,
timestamp: new Date().toISOString(),
sourceClientId: clientInfo?.clientId,
});
const operation = {
targetId: data.targetId,
clock: data.clock,
pageId: data.pageId,
};
console.log("블럭 삭제", operation);
client.broadcast.emit("delete/block", operation);
} catch (error) {
this.logger.error(
`Delete 연산 처리 중 오류 발생 - Client ID: ${clientInfo?.clientId}`,
Expand All @@ -240,12 +280,12 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa

const deleteNode = new CharId(data.clock, data.targetId.client);
// await this.workSpaceService.handleDelete({ targetId: deleteNode, clock: data.clock }); // 얘도안됨

client.broadcast.emit("delete/char", {
...data,
timestamp: new Date().toISOString(),
sourceClientId: clientInfo?.clientId,
});
const operation = {
targetId: data.targetId,
clock: data.clock,
blockId: data.blockId,
};
client.broadcast.emit("delete/char", operation);
} catch (error) {
this.logger.error(
`Delete 연산 처리 중 오류 발생 - Client ID: ${clientInfo?.clientId}`,
Expand All @@ -267,12 +307,12 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
JSON.stringify(data),
);

// 커서 정보에 클라이언트 ID 추가하여 브로드캐스트
client.broadcast.emit("cursor", {
...data,
const operation = {
clientId: clientInfo?.clientId,
timestamp: new Date().toISOString(),
});
position: data.position,
};
// 커서 정보에 클라이언트 ID 추가하여 브로드캐스트
client.broadcast.emit("cursor", operation);
} catch (error) {
this.logger.error(
`Cursor 업데이트 중 오류 발생 - Client ID: ${clientInfo?.clientId}`,
Expand Down
127 changes: 10 additions & 117 deletions server/src/crdt/crdt.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Block, Char } from "@noctaCrdt/Node";

import { BlockLinkedList, TextLinkedList } from "@noctaCrdt/LinkedList";
import { Page as CRDTPage } from "@noctaCrdt/Page";
import { WorkSpaceSerializedProps } from "@noctaCrdt/Interfaces";

@Injectable()
export class workSpaceService implements OnModuleInit {
Expand All @@ -26,128 +27,19 @@ export class workSpaceService implements OnModuleInit {
if (doc) {
// 1. Workspace 기본 정보 복원
this.workspace = new CRDTWorkSpace(doc.id, []);

// 2. PageList 복원
if (doc.pageList && Array.isArray(doc.pageList)) {
for (const pageData of doc.pageList) {
const page = await this.deserializePage(pageData);
this.workspace.pageList.push(page);
}
}
// 3. AuthUser Map 복원
if (doc.authUser) {
this.workspace.authUser = new Map(Array.from(doc.authUser.entries()));
}
this.workspace.deserialize({
id: doc.id,
pageList: doc.pageList,
authUser: doc.authUser,
} as WorkSpaceSerializedProps);
}
console.log(doc, "잘됐나?");
} catch (error) {
console.error("Error during CrdtService initialization:", error);
throw error;
}
}

private async deserializePage(pageData: any): Promise<CRDTPage> {
return new CRDTPage(await this.deserializeEditorCRDT(pageData.crdt));
}

private async deserializeEditorCRDT(editorData: any): Promise<EditorCRDT> {
const editor = new EditorCRDT(editorData.client);
editor.clock = editorData.clock;

if (editorData.currentBlock) {
editor.currentBlock = await this.deserializeBlock(editorData.currentBlock);
}

editor.LinkedList = await this.deserializeBlockLinkedList(editorData.LinkedList);

return editor;
}

private async deserializeBlockLinkedList(listData: any): Promise<BlockLinkedList> {
const blockList = new BlockLinkedList();

// head 복원
if (listData.head) {
blockList.head = new BlockId(listData.head.clock, listData.head.client);
}

// nodeMap 복원
blockList.nodeMap = {};
if (listData.nodeMap && typeof listData.nodeMap === "object") {
for (const [key, blockData] of Object.entries(listData.nodeMap)) {
blockList.nodeMap[key] = await this.deserializeBlock(blockData);
}
} else {
console.warn("listData.nodeMap이 없습니다. 빈 nodeMap으로 초기화합니다.");
}

return blockList;
}

private async deserializeBlock(blockData: any): Promise<Block> {
// BlockId 생성
const blockId = new BlockId(blockData.id.clock, blockData.id.client);

// Block 인스턴스 생성
const block = new Block("", blockData.id);

// BlockCRDT 복원
block.crdt = await this.deserializeBlockCRDT(blockData.crdt);

// 연결 정보(next, prev) 복원
if (blockData.next) {
block.next = new BlockId(blockData.next.clock, blockData.next.client);
}
if (blockData.prev) {
block.prev = new BlockId(blockData.prev.clock, blockData.prev.client);
}

// 추가 속성 복원
block.animation = blockData.animation || "none";
block.style = Array.isArray(blockData.style) ? blockData.style : [];

return block;
}

private async deserializeBlockCRDT(crdtData: any): Promise<BlockCRDT> {
const blockCRDT = new BlockCRDT(crdtData.client);

blockCRDT.clock = crdtData.clock;
blockCRDT.currentCaret = crdtData.currentCaret;
blockCRDT.LinkedList = await this.deserializeTextLinkedList(crdtData.LinkedList);

return blockCRDT;
}

private async deserializeTextLinkedList(listData: any): Promise<TextLinkedList> {
const textList = new TextLinkedList();

// head 복원
if (listData.head) {
textList.head = new CharId(listData.head.clock, listData.head.client);
}

// nodeMap 복원
textList.nodeMap = {};
for (const [key, charData] of listData.nodeMap.entries()) {
textList.nodeMap[key] = await this.deserializeChar(charData);
}

return textList;
}

private async deserializeChar(charData: any): Promise<Char> {
const charId = new CharId(charData.id.clock, charData.id.client);
const char = new Char(charData.content, charId);

if (charData.next) {
char.next = new CharId(charData.next.clock, charData.next.client);
}
if (charData.prev) {
char.prev = new CharId(charData.prev.clock, charData.prev.client);
}

return char;
}
async getDocument(): Promise<Workspace | null> {
let doc = await this.workspaceModel.findOne();
if (!doc) {
Expand Down Expand Up @@ -213,8 +105,8 @@ export class workSpaceService implements OnModuleInit {

async handlePageInsert(payload: any): Promise<void> {
// Page 레벨 Insert 구현
const newPage = await this.deserializePage(payload);
this.workspace.pageList.push(newPage);
// const newPage = await this.getWorkspace().getPage(payload).deserializePage(payload);
// this.workspace.pageList.push(newPage);
}

async handlePageDelete(payload: any): Promise<void> {
Expand All @@ -227,6 +119,7 @@ export class workSpaceService implements OnModuleInit {

async handleBlockInsert(editorCRDT: EditorCRDT, payload: any): Promise<void> {
// Block 레벨 Insert 구현
console.log(editorCRDT, payload, "???");
editorCRDT.remoteInsert(payload);
}

Expand Down
2 changes: 1 addition & 1 deletion server/src/crdt/schemas/workspace.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class Char {
id: CharId;

@Prop({ required: true })
content: string;
value: string;

@Prop({
type: {
Expand Down

0 comments on commit 16a9038

Please sign in to comment.