Docs / Realtime WebSocket v1

Realtime WebSocket v1

Use the legacy public-room connection and its message and history formats.

Separate public demos from private features

The v1 endpoint is /_joripspace/realtime. It publishes and subscribes by room, but it does not provide v2 server-signed access tokens or per-user authorization inside a room. An unguessable room name is not access control.

Use the sample main room only for a public demo. Use the v2 guide for orders, private notifications and member-only conversations. The two versions keep rooms and data separate.

Send and receive in two browser windows

Browser realtime-v1.js
// 공개 데모 전용입니다. 회원 전용 메시지는 v2를 사용하세요.
// 페이지에 <button id="send" disabled>보내기</button><ul id="messages"></ul> 추가
const room = 'main';
const url = new URL('/_joripspace/realtime', location.origin);
url.protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
url.searchParams.set('room', room);
url.searchParams.set('resource_type', 'chat');
const socket = new WebSocket(url);
const button = document.querySelector('#send');
const seen = new Set();
function render(message) {
  if (message.type !== 'chat.message' || !message.id || seen.has(message.id)) return;
  seen.add(message.id);
  const item = document.createElement('li');
  item.textContent = String(message.data?.body || '');
  document.querySelector('#messages').append(item);
}
socket.addEventListener('message', async (event) => {
  const message = JSON.parse(event.data);
  if (message.type === 'system.connected') {
    button.disabled = false;
    // 수신 리스너를 먼저 연결한 뒤 이력과 실시간 이벤트를 ID로 병합합니다.
    const historyUrl = new URL('/_joripspace/realtime/rooms/' + room + '/messages', location.origin);
    historyUrl.searchParams.set('resource_type', 'chat');
    historyUrl.searchParams.set('limit', '50');
    const response = await fetch(historyUrl);
    if (!response.ok) return console.warn('이력을 불러오지 못했습니다.');
    const page = await response.json();
    page.messages.forEach(render);
    // 이전 이력은 next_cursor를 보관해 before 쿼리로 추가 조회합니다.
  } else if (message.type === 'error') console.warn(message.code);
  else render(message);
});
button.addEventListener('click', () => {
  if (socket.readyState !== WebSocket.OPEN) return;
  const message = { id: crypto.randomUUID(), type: 'chat.message', data: { body: '안녕하세요!' } };
  socket.send(JSON.stringify(message)); // 재시도할 때는 같은 message 객체를 재사용
});
socket.addEventListener('close', () => { button.disabled = true; });
window.addEventListener('pagehide', () => socket.close());

Connect two separate windows to the same project, room and resource_type. Enable sending only after receiving the system connection event. Writing a database row or keeping sockets in a Worker-global Map does not publish to a platform room.

History and missed-message recovery

Search and pagination
const url = new URL('/_joripspace/realtime/rooms/main/messages', location.origin);
url.searchParams.set('resource_type', 'chat');
url.searchParams.set('limit', '50');
if (nextCursor) url.searchParams.set('before', nextCursor);
url.searchParams.set('search', 'hello');
const response = await fetch(url);
if (!response.ok) throw new Error('History request failed');
const page = await response.json();

A production v1 client needs exponential reconnect backoff with jitter, multi-page history recovery, ID-based deduplication and stable time-and-ID sorting. Bound the displayed list and deduplication set. Use v2 when you need an automatic recovery SDK.

List, edit and delete messages

History is newest first. Reverse copied pages for oldest-first display, merge pages and live events by ID, then sort by created_at and ID. Reset the cursor when the search changes and prevent stale requests from overwriting newer results.

v1 has no API that mutates an existing message body. Store the source and version in the app database, then publish a new change event. Use v2 plus app-server authorization when only authors or administrators may edit or delete.

Delete a public message
// 공개 v1 room 전용. room과 messageId는 현재 목록의 값입니다.
const url = new URL('/_joripspace/realtime/rooms/' + encodeURIComponent(room)
  + '/messages/' + encodeURIComponent(messageId), location.origin);
url.searchParams.set('resource_type', 'chat');
const response = await fetch(url, { method: 'DELETE' });
if (!response.ok && response.status !== 404) throw new Error('Delete Failed');
// Success 또는 이미 없는 메시지라면 현재 화면의 해당 행을 제거합니다.
// v1은 다른 화면에 Delete 알림을 자동 전송하지 않습니다.
// 다른 화면은 이력을 다시 조회해 현재 조회 범위의 목록을 교체해야 합니다.

After deletion, compare only the currently fetched history range. A message missing from one page may still exist on another page.

Verify actual delivery

Clearing the sender input is not proof of delivery. Confirm the same message ID in two windows, isolation between rooms and history recovery for messages published during a disconnect. Never place tokens, email addresses or personal data in a WebSocket URL.