본문으로 건너뛰기

다중 사용자 실시간 업데이트

이 문서는 DHTMLX Scheduler의 실시간 업데이트 모듈에 대한 서버 측 지원을 구현하는 방법을 설명합니다.

노트
노트

이 문서는 DHTMLX Scheduler v7.2용 Live Updates 모드의 구현을 다룹니다. 이전 버전에 대한 세부 정보는 여기에서 확인할 수 있습니다.

원리

DHTMLX Scheduler는 다중 사용자가 실시간으로 변경 내용을 동기화할 수 있도록 RemoteEvents 도우미를 제공합니다.

핵심 워크플로우

  • Scheduler가 초기화될 때 RemoteEvents 클라이언트가 WebSocket 연결을 엽니다.
  • 사용자의 변경 사항(“create”, “edit” 또는 “delete” 이벤트)은 REST API를 사용해 DataProcessor를 통해 서버로 전송됩니다.
  • 서버는 이를 처리한 후 WebSocket를 통해 모든 연결된 클라이언트에 업데이트를 브로드캐스트합니다.
  • RemoteEvents 클라이언트가 업데이트를 수신하고 Scheduler에 적용하여 사용자 간의 동기화를 보장합니다.

이 설계는 이 백엔드 모듈이 같은 애플리케이션 내에서 다수의 DHTMLX 위젯(예: Kanban, Gantt, Scheduler)을 지원하도록 허용합니다. 공유 형식은 각 위젯에 대해 별도 백엔드가 필요 없이 데이터 동기화를 간소화합니다.

프런트 엔드 통합

RemoteEvents를 초기화하고 Scheduler 데이터가 로드되는 코드 섹션에서 같은 위치에 DataProcessor를 설정합니다.

const AUTH_TOKEN = "token";
scheduler.init('scheduler_here', new Date(2027, 3, 20), "week");
scheduler.load("/events");

const dp = scheduler.createDataProcessor({
url: "/events",
mode: "REST-JSON",
headers: {
"Remote-Token": AUTH_TOKEN
}
});

const { RemoteEvents, remoteUpdates } = scheduler.ext.liveUpdates;
const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on(remoteUpdates);

주요 세부 정보

  • RemoteEvents 생성자는 인증 토큰이 필요하며, 이는 서버 검증을 위해 "Remote-Token" 헤더로 전송됩니다.
  • 첫 번째 인수는 WebSocket 엔드포인트를 지정합니다(예: /api/v1).
  • remoteUpdates 도우미는 수신된 WebSocket 메시지를 처리하고 Scheduler 데이터를 동기화합니다.

백엔드 구현

이 섹션은 실시간 업데이트를 지원하는 백엔드를 구축하는 방법을 설명합니다.

간략한 예제

구현을 테스트하려면:

  • 백엔드 프로젝트를 추출하고 npm installnpm run start 명령으로 실행합니다.
  • 프런트엔드 예제를 두 개의 서로 다른 브라우저 탭에서 엽니다.
  • 한 탭에서 이벤트를 수정하면 변경 내용이 두 번째 탭에 나타나야 합니다.

서버 측 워크플로

1. 핸드셰이크 요청

인스턴스화되면, RemoteEvents는 연결을 초기화하기 위해 서버에 GET 요청을 보냅니다.

예시:

GET /api/v1
Remote-Token: AUTH_TOKEN

응답:

{"api":{},"data":{},"websocket":true}

2. WebSocket 연결

응답을 받은 후, RemoteEvents는 제공된 엔드포인트로 WebSocket 연결을 수립합니다.

예시:

ws://${URL}?token=${token}&ws=1

서버는 토큰을 검증하고 아래와 같은 메시지로 응답합니다:

{"action":"start","body":"connectionId"}

구현 예시:

app.get('/api/v1', (req, res) => {
const token = req.headers['remote-token'];
if (!token || !verifyAuthHeader(token)) {
return res.status(403).json({ error: 'Forbidden' });
}
res.json({ api: {}, data: {}, websocket: true });
});

wss.on('connection', (ws, req) => {
const token = new URLSearchParams(req.url.split('?')[1]).get('token');
if (!token || !verifyAuthToken(token)) {
ws.close(1008, 'Unauthorized');
return;
}
const connectionId = generateConnectionId();
ws.send(JSON.stringify({ action: 'start', body: connectionId }));
});

3. 구독

연결이 확립된 후, RemoteEvents는 Scheduler의 경우 특정 엔티티에 대한 업데이트를 구독합니다:

{"action":"subscribe","name":"events"}

언subscribe하려면:

{"action":"unsubscribe","name":"events"}
노트

이 형식은 애플리케이션이 여러 DHTMLX 위젯을 동시에 사용하는 시나리오를 지원합니다. 각 위젯은 자신의 데이터에 관련된 업데이트에만 구독합니다.

예:

ws.on('message', function(message) {
try {
const msg = JSON.parse(message);
const client = clients.get(connectionId);

if (!client) return;

if (msg.action === 'subscribe') {
client.subscriptions.add(msg.name);
} else if (msg.action === 'unsubscribe') {
client.subscriptions.delete(msg.name);
}
} catch (err) {
console.error('Error parsing WebSocket message:', err);
}
});

4. 업데이트 브로드캐스팅

서버는 아래에 설명된 형식으로 이벤트 생성, 업데이트 또는 삭제와 같은 변경사항에 대해 WebSocket를 통해 업데이트를 전송합니다.

이 메시지를 수신하면 Scheduler는 remoteUpdates 도우미를 사용하여 데이터를 자동으로 동기화합니다.

이벤트 생성 Event Created

{"action":"event","body":{"name":"events",
"value":{"type":"add-event","event":EVENT_OBJECT}}}

예:

app.post('/events', (req, res) => {
const newEvent = req.body.event;
const insertedEvent = crud.events.insert(newEvent);

// 연결된 클라이언트에 변경사항 브로드캐스트
const message = {
name: 'events',
value: {
type: 'add-event', event: insertedEvent
}
};
broadcast('event', message);

res.status(200).json({ id: insertedEvent.id });
});

function broadcast(action, body) {
const entity = body.name;

for (const [connectionId, client] of clients.entries()) {
const { ws, subscriptions } = client;

if (subscriptions.has(entity) && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ action, body }));
}
}
}

이벤트 업데이트 Event Updated

{"action":"event","body":{"name":"events",
"value":{"type":"update-event","event":EVENT_OBJECT}}}

예:

app.put('/events/:id', (req, res) => {
const id = req.params.id;
const updatedEvent = req.body.event;

crud.events.update(id, updatedEvent);

// 연결된 클라이언트에 변경사항 브로드캐스트
const message = {
name: 'events',
value: {
type: 'update-event', event: updatedEvent
}
};
broadcast('event', message);

res.status(200).send();
});

이벤트 삭제 Event Deleted

{"action":"event","body":{"name":"events",
"value":{"type":"delete-event","event":{"id":ID}}}}

예:

app.delete('/events/:id', (req, res) => {
const id = req.params.id;

crud.events.delete(id);

// 연결된 클라이언트에 삭제 브로드캐스트
const message = {
name: 'events',
value: {
type: 'delete-event',
event: { id }
}
};
broadcast('event', message);

res.status(200).send();
});

고급 사용자 정의

커스텀 핸들러

설명된 형식에서 RemoteEvents 도우미는 서버와의 웹소켓 연결 초기 핸드셰이크를 수행하고 메시지를 수신하는 역할을 담당합니다. 모듈의 두 번째 부분은 수신된 웹소켓 메시지를 구문 분석하고 Scheduler에 적절한 변경을 적용하는 역할을 하는 remoteUpdates 도우미입니다.

const { RemoteEvents, remoteUpdates } = scheduler.ext.liveUpdates;
const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on(remoteUpdates);

일반적으로 이 도우미들은 추가 구성 없이도 사용할 수 있습니다. 하지만 기존 프로토콜을 확장하기 위해 커스텀 도우미를 추가하거나 원격 업데이트를 위한 커스텀 핸들러를 구현하는 것도 가능합니다.

RemoteEvents.on 메서드는 하나 또는 여러 엔티티에 대한 핸들러를 지정할 수 있는 객체 인수를 기대합니다:

const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on({
events: function(message) {
const { type, event } = message;
switch (type) {
case "add-event":
// 이벤트 추가 처리
break;
case "update-event":
// 업데이트 이벤트 처리
break;
case "delete-event":
// 삭제 이벤트 처리
break;
}
}
});

커스텀 작업을 추가해야 하는 경우, remoteEvents에 추가 핸들러를 추가하여 구현할 수 있습니다:

const { RemoteEvents, remoteUpdates } = scheduler.ext.liveUpdates;
const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on(remoteUpdates);
remoteEvents.on({
events: function(message) {
const { type, event } = message;
switch (type) {
case "custom-action":
// 커스텀 동작 처리
break;
}
}
});

핸들러는 다음 메시지에 의해 호출됩니다:

{"action":"event","body":{"name":"events",
"value":{"type":"custom-action","event":value}}}

원하는 경우 커스텀 엔티티의 업데이트를 받기 위해 핸들러를 추가하여 사용할 수 있습니다:

const { RemoteEvents, remoteUpdates } = scheduler.ext.liveUpdates;
const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on(remoteUpdates);

// 커스텀 엔티티 구독
remoteEvents.on({
calendars: function(message) {
const { type, value } = message;
switch (type) {
case "custom-action":
// 커스텀 동작 처리
break;
}
}
});

이와 같이 초기화되면, remoteEvents 객체는 웹소켓에 구독 메시지를 다음과 같은 형식으로 보냅니다:

{"action":"subscribe","name":"calendars"}

지정된 엔티티로 향하는 메시지가 수신될 때 핸들러가 호출됩니다:

{"action":"event","body":{"name":"calendars",
"value":{"type":"custom-action","value":value}}}

이 가이드는 DHTMLX Scheduler에서 실시간 업데이트를 구현하고 사용자 정의하는 기초를 제공합니다. 전체 예제는 [GitHub 저장소](https://github.com/DHTMLX/scheduler-multiuser-backend-demo/.를 참고하십시오.

Need help?
Got a question about the documentation? Reach out to our technical support team for help and guidance. For custom component solutions, visit the Services page.