room-kit 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,271 +26,268 @@ const keyInput = document.getElementById("key-input") as HTMLInputElement;
26
26
  const messageInput = document.getElementById("message-input") as HTMLInputElement;
27
27
 
28
28
  let joinedRoom: JoinedRoom<typeof chatRoomType> | null = null;
29
- let stopMessageListener: (() => void) | null = null;
30
- let stopSystemListener: (() => void) | null = null;
31
- let stopPresenceListener: (() => void) | null = null;
29
+ let stopRoomListeners: (() => void) | null = null;
32
30
  let presenceOffset = 0;
33
31
  const presencePageSize = 4;
34
32
 
35
33
  function setStatus(message: string): void {
36
- status.textContent = message;
34
+ status.textContent = message;
37
35
  }
38
36
 
39
37
  function setWorkspaceVisible(visible: boolean): void {
40
- workspace.classList.toggle("hidden", !visible);
41
- joinPanel.classList.toggle("hidden", visible);
38
+ workspace.classList.toggle("hidden", !visible);
39
+ joinPanel.classList.toggle("hidden", visible);
42
40
  }
43
41
 
44
42
  function clearMessages(): void {
45
- messages.innerHTML = "";
43
+ messages.innerHTML = "";
46
44
  }
47
45
 
48
46
  function renderMessage(message: ChatMessage): void {
49
- const item = document.createElement("article");
50
- item.className = "message";
51
- item.innerHTML = `
47
+ const item = document.createElement("article");
48
+ item.className = "message";
49
+ item.innerHTML = `
52
50
  <div class="message-header">
53
51
  <span class="message-name">${escapeHtml(message.name)}</span>
54
52
  <span class="message-time">${formatTime(message.sentAt)}</span>
55
53
  </div>
56
54
  <div class="message-text">${escapeHtml(message.text)}</div>
57
55
  `;
58
- messages.appendChild(item);
59
- messages.scrollTop = messages.scrollHeight;
56
+ messages.appendChild(item);
57
+ messages.scrollTop = messages.scrollHeight;
60
58
  }
61
59
 
62
60
  function renderSystemNotice(text: string, sentAt: string): void {
63
- const item = document.createElement("article");
64
- item.className = "message";
65
- item.innerHTML = `
61
+ const item = document.createElement("article");
62
+ item.className = "message";
63
+ item.innerHTML = `
66
64
  <div class="message-header">
67
65
  <span class="message-name">System</span>
68
66
  <span class="message-time">${formatTime(sentAt)}</span>
69
67
  </div>
70
68
  <div class="message-text">${escapeHtml(text)}</div>
71
69
  `;
72
- messages.appendChild(item);
73
- messages.scrollTop = messages.scrollHeight;
70
+ messages.appendChild(item);
71
+ messages.scrollTop = messages.scrollHeight;
74
72
  }
75
73
 
76
74
  function renderHistory(history: ChatMessage[]): void {
77
- clearMessages();
78
- for (const message of history) {
79
- renderMessage(message);
80
- }
75
+ clearMessages();
76
+ for (const message of history) {
77
+ renderMessage(message);
78
+ }
81
79
  }
82
80
 
83
81
  function renderPresence(): void {
84
- if (!joinedRoom) {
85
- presenceCount.textContent = "0 online";
86
- presencePage.textContent = "Showing 0 members.";
87
- presenceList.innerHTML = "";
88
- return;
89
- }
90
-
91
- const presence = joinedRoom.presence.current;
92
- presenceCount.textContent = `${presence.count} online`;
93
- presencePage.textContent = `Showing ${Math.min(presenceOffset + 1, presence.count)}-${Math.min(
94
- presenceOffset + presence.members.length,
95
- presence.count,
96
- )} of ${presence.count} members.`;
97
- presenceList.innerHTML = "";
98
-
99
- if (presence.members.length === 0) {
100
- const empty = document.createElement("li");
101
- empty.className = "presence-empty";
102
- empty.textContent = "Nobody is here yet.";
103
- presenceList.appendChild(empty);
104
- return;
105
- }
106
-
107
- for (const entry of presence.members) {
108
- const item = document.createElement("li");
109
- item.className = "presence-item";
110
- item.innerHTML = `
82
+ if (!joinedRoom) {
83
+ presenceCount.textContent = "0 online";
84
+ presencePage.textContent = "Showing 0 members.";
85
+ presenceList.innerHTML = "";
86
+ return;
87
+ }
88
+
89
+ const presence = joinedRoom.presence.current;
90
+ presenceCount.textContent = `${presence.count} online`;
91
+ presencePage.textContent = `Showing ${Math.min(presenceOffset + 1, presence.count)}-${Math.min(
92
+ presenceOffset + presence.members.length,
93
+ presence.count,
94
+ )} of ${presence.count} members.`;
95
+ presenceList.innerHTML = "";
96
+
97
+ if (presence.members.length === 0) {
98
+ const empty = document.createElement("li");
99
+ empty.className = "presence-empty";
100
+ empty.textContent = "Nobody is here yet.";
101
+ presenceList.appendChild(empty);
102
+ return;
103
+ }
104
+
105
+ for (const entry of presence.members) {
106
+ const item = document.createElement("li");
107
+ item.className = "presence-item";
108
+ item.innerHTML = `
111
109
  <span>${escapeHtml(entry.memberProfile.userName)}</span>
112
110
  <span class="presence-pill" aria-hidden="true"></span>
113
111
  `;
114
- presenceList.appendChild(item);
115
- }
112
+ presenceList.appendChild(item);
113
+ }
116
114
  }
117
115
 
118
116
  async function refreshPresence(): Promise<void> {
119
- if (!joinedRoom) {
120
- return;
121
- }
122
-
123
- const [count, page] = await Promise.all([
124
- joinedRoom.presence.count(),
125
- joinedRoom.presence.list({ offset: presenceOffset, limit: presencePageSize }),
126
- ]);
127
-
128
- presenceCount.textContent = `${count} online`;
129
- presencePage.textContent = page.members.length === 0
130
- ? `Showing 0 of ${count} members.`
131
- : `Showing ${page.offset + 1}-${page.offset + page.members.length} of ${count} members.`;
132
-
133
- presenceList.innerHTML = "";
134
- if (page.members.length === 0) {
135
- const empty = document.createElement("li");
136
- empty.className = "presence-empty";
137
- empty.textContent = "Nobody is here yet.";
138
- presenceList.appendChild(empty);
139
- return;
140
- }
141
-
142
- for (const entry of page.members) {
143
- const item = document.createElement("li");
144
- item.className = "presence-item";
145
- item.innerHTML = `
117
+ if (!joinedRoom) {
118
+ return;
119
+ }
120
+
121
+ const [count, page] = await Promise.all([
122
+ joinedRoom.presence.count(),
123
+ joinedRoom.presence.list({ offset: presenceOffset, limit: presencePageSize }),
124
+ ]);
125
+
126
+ presenceCount.textContent = `${count} online`;
127
+ presencePage.textContent = page.members.length === 0
128
+ ? `Showing 0 of ${count} members.`
129
+ : `Showing ${page.offset + 1}-${page.offset + page.members.length} of ${count} members.`;
130
+
131
+ presenceList.innerHTML = "";
132
+ if (page.members.length === 0) {
133
+ const empty = document.createElement("li");
134
+ empty.className = "presence-empty";
135
+ empty.textContent = "Nobody is here yet.";
136
+ presenceList.appendChild(empty);
137
+ return;
138
+ }
139
+
140
+ for (const entry of page.members) {
141
+ const item = document.createElement("li");
142
+ item.className = "presence-item";
143
+ item.innerHTML = `
146
144
  <span>${escapeHtml(entry.memberProfile.userName)}</span>
147
145
  <span class="presence-pill" aria-hidden="true"></span>
148
146
  `;
149
- presenceList.appendChild(item);
150
- }
147
+ presenceList.appendChild(item);
148
+ }
151
149
  }
152
150
 
153
151
  function formatTime(value: string): string {
154
- const date = new Date(value);
155
- return Number.isNaN(date.getTime())
156
- ? ""
157
- : new Intl.DateTimeFormat(undefined, {
158
- hour: "2-digit",
159
- minute: "2-digit",
160
- }).format(date);
152
+ const date = new Date(value);
153
+ return Number.isNaN(date.getTime())
154
+ ? ""
155
+ : new Intl.DateTimeFormat(undefined, {
156
+ hour: "2-digit",
157
+ minute: "2-digit",
158
+ }).format(date);
161
159
  }
162
160
 
163
161
  function escapeHtml(value: string): string {
164
- return String(value)
165
- .replaceAll("&", "&amp;")
166
- .replaceAll("<", "&lt;")
167
- .replaceAll(">", "&gt;")
168
- .replaceAll('"', "&quot;")
169
- .replaceAll("'", "&#39;");
162
+ return String(value)
163
+ .replaceAll("&", "&amp;")
164
+ .replaceAll("<", "&lt;")
165
+ .replaceAll(">", "&gt;")
166
+ .replaceAll('"', "&quot;")
167
+ .replaceAll("'", "&#39;");
170
168
  }
171
169
 
172
170
  async function connectRoom(): Promise<void> {
173
- const payload = {
174
- roomId: roomInput.value.trim().toLowerCase(),
175
- roomKey: keyInput.value,
176
- userName: nameInput.value,
177
- };
178
-
179
- setStatus("Joining room...");
180
-
181
- try {
182
- presenceOffset = 0;
183
- joinedRoom = await chatClient.join(payload);
184
- renderHistory(joinedRoom.roomProfile.history);
185
- await refreshPresence();
186
-
187
- stopMessageListener = joinedRoom.on.message((message) => {
188
- renderMessage(message);
189
- });
190
-
191
- stopSystemListener = joinedRoom.on.systemNotice((notice) => {
192
- renderSystemNotice(notice.text, notice.sentAt);
193
- });
194
-
195
- stopPresenceListener = joinedRoom.presence.onChange(() => {
196
- void refreshPresence();
197
- });
198
-
199
- roomTitle.textContent = joinedRoom.roomId;
200
- roomSubtitle.textContent = `Signed in as ${payload.userName}. The room stays private behind the shared key.`;
201
- setWorkspaceVisible(true);
202
- setStatus(`Connected to ${joinedRoom.roomId}.`);
203
- messageInput.focus();
204
- } catch (error) {
205
- joinedRoom = null;
206
- setStatus(error instanceof Error ? error.message : String(error));
207
- }
171
+ const payload = {
172
+ roomId: roomInput.value.trim().toLowerCase(),
173
+ roomKey: keyInput.value,
174
+ userName: nameInput.value,
175
+ };
176
+
177
+ setStatus("Joining room...");
178
+
179
+ try {
180
+ presenceOffset = 0;
181
+ joinedRoom = await chatClient.join(payload);
182
+ renderHistory(joinedRoom.roomProfile.history);
183
+ stopRoomListeners = joinedRoom.listen({
184
+ events: {
185
+ message: (message) => {
186
+ renderMessage(message);
187
+ },
188
+ systemNotice: (notice) => {
189
+ renderSystemNotice(notice.text, notice.sentAt);
190
+ },
191
+ },
192
+ presence: {
193
+ onChange: () => {
194
+ void refreshPresence();
195
+ },
196
+ },
197
+ });
198
+ await refreshPresence();
199
+
200
+ roomTitle.textContent = joinedRoom.roomId;
201
+ roomSubtitle.textContent = `Signed in as ${payload.userName}. The room stays private behind the shared key.`;
202
+ setWorkspaceVisible(true);
203
+ setStatus(`Connected to ${joinedRoom.roomId}.`);
204
+ messageInput.focus();
205
+ } catch (error) {
206
+ joinedRoom = null;
207
+ setStatus(error instanceof Error ? error.message : String(error));
208
+ }
208
209
  }
209
210
 
210
211
  async function leaveRoom(): Promise<void> {
211
- if (!joinedRoom) {
212
- return;
213
- }
214
-
215
- const roomId = joinedRoom.roomId;
216
-
217
- try {
218
- await joinedRoom.leave();
219
- } finally {
220
- stopMessageListener?.();
221
- stopSystemListener?.();
222
- stopPresenceListener?.();
223
- stopMessageListener = null;
224
- stopSystemListener = null;
225
- stopPresenceListener = null;
226
- joinedRoom = null;
227
-
228
- setWorkspaceVisible(false);
229
- joinForm.reset();
230
- clearMessages();
231
- presenceList.innerHTML = "";
232
- presenceCount.textContent = "0 online";
233
- presencePage.textContent = "Showing 0 members.";
234
- roomTitle.textContent = "-";
235
- roomSubtitle.textContent = "-";
236
- setStatus(`Left ${roomId}.`);
237
- }
212
+ if (!joinedRoom) {
213
+ return;
214
+ }
215
+
216
+ const roomId = joinedRoom.roomId;
217
+
218
+ try {
219
+ await joinedRoom.leave();
220
+ } finally {
221
+ stopRoomListeners?.();
222
+ stopRoomListeners = null;
223
+ joinedRoom = null;
224
+
225
+ setWorkspaceVisible(false);
226
+ joinForm.reset();
227
+ clearMessages();
228
+ presenceList.innerHTML = "";
229
+ presenceCount.textContent = "0 online";
230
+ presencePage.textContent = "Showing 0 members.";
231
+ roomTitle.textContent = "-";
232
+ roomSubtitle.textContent = "-";
233
+ setStatus(`Left ${roomId}.`);
234
+ }
238
235
  }
239
236
 
240
237
  joinForm.addEventListener("submit", async (event) => {
241
- event.preventDefault();
242
- await connectRoom();
238
+ event.preventDefault();
239
+ await connectRoom();
243
240
  });
244
241
 
245
242
  messageForm.addEventListener("submit", async (event) => {
246
- event.preventDefault();
247
-
248
- if (!joinedRoom) {
249
- setStatus("Join a room before sending messages.");
250
- return;
251
- }
252
-
253
- const text = messageInput.value.trim();
254
- if (!text) {
255
- return;
256
- }
257
-
258
- try {
259
- await joinedRoom.rpc.sendMessage({ text });
260
- messageInput.value = "";
261
- } catch (error) {
262
- setStatus(error instanceof Error ? error.message : String(error));
263
- }
243
+ event.preventDefault();
244
+
245
+ if (!joinedRoom) {
246
+ setStatus("Join a room before sending messages.");
247
+ return;
248
+ }
249
+
250
+ const text = messageInput.value.trim();
251
+ if (!text) {
252
+ return;
253
+ }
254
+
255
+ try {
256
+ await joinedRoom.rpc.sendMessage({ text });
257
+ messageInput.value = "";
258
+ } catch (error) {
259
+ setStatus(error instanceof Error ? error.message : String(error));
260
+ }
264
261
  });
265
262
 
266
263
  leaveButton.addEventListener("click", async () => {
267
- await leaveRoom();
264
+ await leaveRoom();
268
265
  });
269
266
 
270
267
  presencePrevButton.addEventListener("click", async () => {
271
- if (!joinedRoom || presenceOffset === 0) {
272
- return;
273
- }
268
+ if (!joinedRoom || presenceOffset === 0) {
269
+ return;
270
+ }
274
271
 
275
- presenceOffset = Math.max(0, presenceOffset - presencePageSize);
276
- await refreshPresence();
272
+ presenceOffset = Math.max(0, presenceOffset - presencePageSize);
273
+ await refreshPresence();
277
274
  });
278
275
 
279
276
  presenceNextButton.addEventListener("click", async () => {
280
- if (!joinedRoom) {
281
- return;
282
- }
277
+ if (!joinedRoom) {
278
+ return;
279
+ }
283
280
 
284
- presenceOffset += presencePageSize;
285
- await refreshPresence();
281
+ presenceOffset += presencePageSize;
282
+ await refreshPresence();
286
283
  });
287
284
 
288
285
  socket.on("connect", () => {
289
- setStatus("Connected. Join a room to start chatting.");
286
+ setStatus("Connected. Join a room to start chatting.");
290
287
  });
291
288
 
292
289
  socket.on("disconnect", () => {
293
- setStatus("Disconnected from the server.");
290
+ setStatus("Disconnected from the server.");
294
291
  });
295
292
 
296
293
  setWorkspaceVisible(false);