@colyseus/sdk 0.17.12 → 0.17.13

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.
Files changed (62) hide show
  1. package/build/3rd_party/discord.cjs +1 -1
  2. package/build/3rd_party/discord.cjs.map +1 -1
  3. package/build/3rd_party/discord.mjs +1 -1
  4. package/build/Auth.cjs +1 -1
  5. package/build/Auth.cjs.map +1 -1
  6. package/build/Auth.mjs +1 -1
  7. package/build/Client.cjs +1 -1
  8. package/build/Client.cjs.map +1 -1
  9. package/build/Client.mjs +1 -1
  10. package/build/Client.mjs.map +1 -1
  11. package/build/Connection.cjs +1 -1
  12. package/build/Connection.mjs +1 -1
  13. package/build/HTTP.cjs +23 -1
  14. package/build/HTTP.cjs.map +1 -1
  15. package/build/HTTP.mjs +23 -2
  16. package/build/HTTP.mjs.map +1 -1
  17. package/build/Protocol.cjs +1 -1
  18. package/build/Protocol.mjs +1 -1
  19. package/build/Room.cjs +1 -1
  20. package/build/Room.cjs.map +1 -1
  21. package/build/Room.mjs +1 -1
  22. package/build/Storage.cjs +1 -1
  23. package/build/Storage.mjs +1 -1
  24. package/build/core/nanoevents.cjs +1 -1
  25. package/build/core/nanoevents.cjs.map +1 -1
  26. package/build/core/nanoevents.mjs +1 -1
  27. package/build/core/signal.cjs +2 -1
  28. package/build/core/signal.cjs.map +1 -1
  29. package/build/core/signal.mjs +2 -1
  30. package/build/core/signal.mjs.map +1 -1
  31. package/build/core/utils.cjs +1 -1
  32. package/build/core/utils.mjs +1 -1
  33. package/build/debug.cjs +2461 -0
  34. package/build/debug.cjs.map +1 -0
  35. package/build/debug.mjs +2452 -0
  36. package/build/debug.mjs.map +1 -0
  37. package/build/errors/Errors.cjs +8 -1
  38. package/build/errors/Errors.cjs.map +1 -1
  39. package/build/errors/Errors.mjs +8 -2
  40. package/build/errors/Errors.mjs.map +1 -1
  41. package/build/index.cjs +1 -1
  42. package/build/index.mjs +1 -1
  43. package/build/legacy.cjs +1 -1
  44. package/build/legacy.mjs +1 -1
  45. package/build/serializer/NoneSerializer.cjs +1 -1
  46. package/build/serializer/NoneSerializer.mjs +1 -1
  47. package/build/serializer/SchemaSerializer.cjs +1 -1
  48. package/build/serializer/SchemaSerializer.mjs +2 -2
  49. package/build/serializer/Serializer.cjs +1 -1
  50. package/build/serializer/Serializer.mjs +1 -1
  51. package/build/transport/H3Transport.cjs +1 -1
  52. package/build/transport/H3Transport.cjs.map +1 -1
  53. package/build/transport/H3Transport.mjs +1 -1
  54. package/build/transport/H3Transport.mjs.map +1 -1
  55. package/build/transport/WebSocketTransport.cjs +1 -1
  56. package/build/transport/WebSocketTransport.mjs +1 -1
  57. package/dist/colyseus-cocos-creator.js +1 -1
  58. package/dist/colyseus.js +1 -1
  59. package/dist/debug.js +4277 -2394
  60. package/dist/debug.js.map +1 -1
  61. package/package.json +2 -2
  62. package/src/debug.ts +5 -5
@@ -0,0 +1,2461 @@
1
+ // Copyright (c) 2026 Endel Dreyer.
2
+ //
3
+ // This software is released under the MIT License.
4
+ // https://opensource.org/license/MIT
5
+ //
6
+ // colyseus.js@0.17.13
7
+ 'use strict';
8
+
9
+ var Client = require('./Client.cjs');
10
+
11
+ const logoIcon = `<svg viewBox="0 0 488.94 541.2" style="width: 100%; height: 100%;">
12
+ <g>
13
+ <g>
14
+ <path fill="#ffffff" d="m80.42,197.14c13.82,11.25,30.56,22.25,50.78,32.11,14.87-28.67,72.09-100.71,233.32-79.68l-14.4-55.35c-200.24-17.18-257.81,77.11-269.7,102.92Z"/>
15
+ <path fill="#ffffff" d="m44.53,167.77c22.44-40.73,99.17-124.23,290.19-105.83L310.19,1.59S109.9-21.63,8.9,109.47c3.62,10.55,13.31,33.34,35.63,58.29Z"/>
16
+ <path fill="#ffffff" d="m407.7,291.25c-32.14,3.35-62.02,4.95-89.63,4.95C123.09,296.2,36.78,219.6,0,164.95v251.98s15.77,162.98,488.94,115.66l-81.24-241.33Z"/>
17
+ </g>
18
+ </g>
19
+ </svg>`;
20
+ const envelopeUp = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4.5a.5.5 0 0 1-1 0V5.383l-7 4.2-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h5.5a.5.5 0 0 1 0 1H2a2 2 0 0 1-2-1.99zm1 7.105 4.708-2.897L1 5.383zM1 4v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1"></path><path d="M12.5 16a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7m.354-5.354 1.25 1.25a.5.5 0 0 1-.708.708L13 12.207V14a.5.5 0 0 1-1 0v-1.717l-.28.305a.5.5 0 0 1-.737-.676l1.149-1.25a.5.5 0 0 1 .722-.016"></path></svg>`;
21
+ const envelopeDown = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4.5a.5.5 0 0 1-1 0V5.383l-7 4.2-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h5.5a.5.5 0 0 1 0 1H2a2 2 0 0 1-2-1.99zm1 7.105 4.708-2.897L1 5.383zM1 4v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1"></path><path d="M12.5 16a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7m.354-1.646a.5.5 0 0 1-.722-.016l-1.149-1.25a.5.5 0 1 1 .737-.676l.28.305V11a.5.5 0 0 1 1 0v1.793l.396-.397a.5.5 0 0 1 .708.708z"></path></svg>`;
22
+ const messageIcon = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="200px" width="200px" xmlns="http://www.w3.org/2000/svg"><path d="M498.1 5.6c10.1 7 15.4 19.1 13.5 31.2l-64 416c-1.5 9.7-7.4 18.2-16 23s-18.9 5.4-28 1.6L284 427.7l-68.5 74.1c-8.9 9.7-22.9 12.9-35.2 8.1S160 493.2 160 480V396.4c0-4 1.5-7.8 4.2-10.7L331.8 202.8c5.8-6.3 5.6-16-.4-22s-15.7-6.4-22-.7L106 360.8 17.7 316.6C7.1 311.3 .3 300.7 0 288.9s5.9-22.8 16.1-28.7l448-256c10.7-6.1 23.9-5.5 34 1.4z"/></svg>`;
23
+ const treeViewIcon = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 256 256" height="200px" width="200px" xmlns="http://www.w3.org/2000/svg"><path d="M160,136v-8H88v64a8,8,0,0,0,8,8h64v-8a16,16,0,0,1,16-16h32a16,16,0,0,1,16,16v32a16,16,0,0,1-16,16H176a16,16,0,0,1-16-16v-8H96a24,24,0,0,1-24-24V80H64A16,16,0,0,1,48,64V32A16,16,0,0,1,64,16H96a16,16,0,0,1,16,16V64A16,16,0,0,1,96,80H88v32h72v-8a16,16,0,0,1,16-16h32a16,16,0,0,1,16,16v32a16,16,0,0,1-16,16H176A16,16,0,0,1,160,136Z"></path></svg>`;
24
+ const infoIcon = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="200px" width="200px" xmlns="http://www.w3.org/2000/svg"><path d="M256 48C141.2 48 48 141.2 48 256s93.2 208 208 208 208-93.2 208-208S370.8 48 256 48zm21 312h-42V235h42v125zm0-166h-42v-42h42v42z"></path></svg>`;
25
+ const settingsIcon = `<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="200px" width="200px" xmlns="http://www.w3.org/2000/svg"><path d="M12.003 21c-.732 .001 -1.465 -.438 -1.678 -1.317a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c.886 .215 1.325 .957 1.318 1.694"></path><path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"></path><path d="M19.001 19m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path><path d="M19.001 15.5v1.5"></path><path d="M19.001 21v1.5"></path><path d="M22.032 17.25l-1.299 .75"></path><path d="M17.27 20l-1.3 .75"></path><path d="M15.97 17.25l1.3 .75"></path><path d="M20.733 20l1.3 .75"></path></svg>`;
26
+ const eyeSlashIcon = `<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>`;
27
+ const closeIcon = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M400 145.49 366.51 112 256 222.51 145.49 112 112 145.49 222.51 256 112 366.51 145.49 400 256 289.49 366.51 400 400 366.51 289.49 256 400 145.49z"></path></svg>`;
28
+ // Store debug info per room
29
+ const roomDebugInfo = new Map();
30
+ // Single interval for all panels
31
+ let globalUpdateInterval = null;
32
+ // Preferences state
33
+ const preferences = {
34
+ maxLatency: 350, // milliseconds
35
+ latencySimulation: {
36
+ enabled: false,
37
+ delay: 0 // milliseconds
38
+ },
39
+ panelPosition: {
40
+ position: 'top-right' // 'bottom-right', 'bottom-left', 'top-left', 'top-right'
41
+ }
42
+ };
43
+ // Load preferences from localStorage
44
+ function loadPreferences() {
45
+ try {
46
+ const savedPrefs = localStorage.getItem('colyseus-debug-preferences') || '{}';
47
+ const prefs = JSON.parse(savedPrefs);
48
+ // Load position
49
+ if (prefs.position && ['bottom-right', 'bottom-left', 'top-left', 'top-right'].includes(prefs.position)) {
50
+ preferences.panelPosition.position = prefs.position;
51
+ }
52
+ // Load latency
53
+ if (prefs.latency !== undefined && prefs.latency !== null) {
54
+ const latencyValue = parseInt(prefs.latency, 10);
55
+ if (!isNaN(latencyValue) && latencyValue >= 0 && latencyValue <= 500) {
56
+ preferences.latencySimulation.delay = latencyValue;
57
+ preferences.latencySimulation.enabled = latencyValue > 0;
58
+ }
59
+ }
60
+ // Load hidden state
61
+ if (prefs.hidden === true) {
62
+ panelsHidden = true;
63
+ }
64
+ }
65
+ catch (e) {
66
+ // localStorage might not be available or JSON parse failed, ignore
67
+ }
68
+ }
69
+ // Save preferences to localStorage
70
+ function savePreferences() {
71
+ try {
72
+ localStorage.setItem('colyseus-debug-preferences', JSON.stringify({
73
+ position: preferences.panelPosition.position,
74
+ latency: preferences.latencySimulation.delay,
75
+ hidden: panelsHidden
76
+ }));
77
+ }
78
+ catch (e) {
79
+ // localStorage might not be available, ignore
80
+ }
81
+ }
82
+ // Panel visibility state
83
+ let panelsHidden = false;
84
+ // Track open modals as an ordered stack (most recent at end)
85
+ let modalStack = [];
86
+ const BASE_MODAL_ZINDEX = 10000;
87
+ // Load preferences on script load
88
+ loadPreferences();
89
+ // Function to select a modal (bring to front)
90
+ function selectModal(modal) {
91
+ if (!modal)
92
+ return;
93
+ // Remove modal from stack if already present
94
+ const index = modalStack.indexOf(modal);
95
+ if (index > -1) {
96
+ modalStack.splice(index, 1);
97
+ }
98
+ // Add to end of stack (most recent)
99
+ modalStack.push(modal);
100
+ // Update z-indexes for all modals based on their position in stack
101
+ modalStack.forEach((m, i) => {
102
+ if (document.body.contains(m)) {
103
+ m.style.zIndex = (BASE_MODAL_ZINDEX + i).toString();
104
+ }
105
+ });
106
+ }
107
+ // Function to remove modal from stack
108
+ function removeModalFromStack(modal) {
109
+ const index = modalStack.indexOf(modal);
110
+ if (index > -1) {
111
+ modalStack.splice(index, 1);
112
+ }
113
+ }
114
+ // Global ESC key handler - closes most recent modal
115
+ document.addEventListener('keydown', function (e) {
116
+ if (e.key === 'Escape' && modalStack.length > 0) {
117
+ // Get the most recent modal (top of stack)
118
+ const topModal = modalStack[modalStack.length - 1];
119
+ if (topModal && document.body.contains(topModal)) {
120
+ topModal.remove();
121
+ }
122
+ }
123
+ });
124
+ // Shared modal creation utilities
125
+ function createModalOverlay() {
126
+ var overlay = document.createElement('div');
127
+ overlay.style.position = 'fixed';
128
+ overlay.style.top = '0';
129
+ overlay.style.left = '0';
130
+ overlay.style.right = '0';
131
+ overlay.style.bottom = '0';
132
+ overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
133
+ overlay.style.zIndex = BASE_MODAL_ZINDEX.toString();
134
+ overlay.style.display = 'flex';
135
+ overlay.style.justifyContent = 'center';
136
+ overlay.style.alignItems = 'center';
137
+ return overlay;
138
+ }
139
+ function createModal(options) {
140
+ var opts = options || {};
141
+ var modal = document.createElement('div');
142
+ // Set ID if provided
143
+ if (opts.id) {
144
+ modal.id = opts.id;
145
+ }
146
+ // Base styles
147
+ modal.style.position = 'fixed';
148
+ modal.style.backgroundColor = opts.backgroundColor || '#1e1e1e';
149
+ modal.style.borderRadius = '8px';
150
+ modal.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.5)';
151
+ modal.style.color = '#fff';
152
+ modal.style.fontFamily = 'system-ui, -apple-system, sans-serif';
153
+ modal.style.zIndex = BASE_MODAL_ZINDEX.toString();
154
+ modal.style.display = 'flex';
155
+ modal.style.flexDirection = 'column';
156
+ modal.style.overflow = 'hidden';
157
+ // Size
158
+ if (opts.width)
159
+ modal.style.width = opts.width;
160
+ if (opts.height)
161
+ modal.style.height = opts.height;
162
+ if (opts.minWidth)
163
+ modal.style.minWidth = opts.minWidth;
164
+ if (opts.minHeight)
165
+ modal.style.minHeight = opts.minHeight;
166
+ if (opts.maxWidth)
167
+ modal.style.maxWidth = opts.maxWidth;
168
+ if (opts.maxHeight)
169
+ modal.style.maxHeight = opts.maxHeight;
170
+ // Position
171
+ modal.style.top = opts.top || '50%';
172
+ modal.style.left = opts.left || '50%';
173
+ modal.style.transform = opts.transform || 'translate(-50%, -50%)';
174
+ // Mark modal as selected when clicked
175
+ modal.addEventListener('mousedown', function (e) {
176
+ selectModal(modal);
177
+ });
178
+ // Mark modal as selected when opened
179
+ selectModal(modal);
180
+ // Override remove to cleanup modal from stack
181
+ var originalRemove = modal.remove.bind(modal);
182
+ modal.remove = function () {
183
+ // Remove from modal stack
184
+ removeModalFromStack(modal);
185
+ // Auto-cleanup room onLeave listener
186
+ if (opts.room && opts.trackOnLeave && opts.onLeaveCallback) {
187
+ var callbackToRemove = opts.onLeaveCallback.current || opts.onLeaveCallback;
188
+ opts.room.onLeave.remove(callbackToRemove);
189
+ }
190
+ if (opts.onRemove) {
191
+ opts.onRemove();
192
+ }
193
+ originalRemove();
194
+ };
195
+ return modal;
196
+ }
197
+ function createModalHeader(options) {
198
+ var opts = options || {};
199
+ var header = document.createElement('div');
200
+ header.style.display = 'flex';
201
+ header.style.justifyContent = 'space-between';
202
+ header.style.alignItems = 'center';
203
+ header.style.padding = opts.padding || '8px';
204
+ header.style.borderBottom = '1px solid rgba(255, 255, 255, 0.15)';
205
+ header.style.paddingBottom = opts.paddingBottom || '4px';
206
+ header.style.marginBottom = opts.marginBottom || '6px';
207
+ header.style.cursor = opts.draggable !== false ? 'move' : 'default';
208
+ header.style.userSelect = 'none';
209
+ header.style.flexShrink = '0';
210
+ header.style.position = 'relative';
211
+ header.style.zIndex = '1';
212
+ // Title
213
+ var title = document.createElement('div');
214
+ title.textContent = opts.title || '';
215
+ title.style.margin = '0';
216
+ title.style.fontSize = opts.titleSize || '11px';
217
+ title.style.fontWeight = 'bold';
218
+ title.style.fontFamily = opts.titleFont || 'monospace';
219
+ title.style.flex = '1';
220
+ title.style.display = 'flex';
221
+ title.style.alignItems = 'center';
222
+ // Status dot (optional)
223
+ if (opts.statusDot) {
224
+ var statusDot = document.createElement('div');
225
+ statusDot.style.width = '8px';
226
+ statusDot.style.height = '8px';
227
+ statusDot.style.borderRadius = '50%';
228
+ statusDot.style.marginRight = '8px';
229
+ statusDot.style.flexShrink = '0';
230
+ statusDot.style.transition = 'background-color 0.3s';
231
+ statusDot.style.backgroundColor = opts.statusColor || '#22c55e';
232
+ title.insertBefore(statusDot, title.firstChild);
233
+ if (opts.statusDotRef) {
234
+ opts.statusDotRef.element = statusDot;
235
+ }
236
+ }
237
+ // Close button
238
+ var closeButton = document.createElement('button');
239
+ closeButton.innerHTML = closeIcon;
240
+ closeButton.style.background = 'none';
241
+ closeButton.style.border = 'none';
242
+ closeButton.style.color = '#fff';
243
+ closeButton.style.fontSize = '18px';
244
+ closeButton.style.cursor = 'pointer';
245
+ closeButton.style.padding = '0';
246
+ closeButton.style.margin = 'auto';
247
+ closeButton.style.width = '20px';
248
+ closeButton.style.height = '20px';
249
+ closeButton.style.display = 'flex';
250
+ closeButton.style.alignItems = 'center';
251
+ closeButton.style.justifyContent = 'center';
252
+ closeButton.style.borderRadius = '4px';
253
+ closeButton.style.transition = 'background-color 0.2s';
254
+ closeButton.style.opacity = '0.6';
255
+ closeButton.addEventListener('mouseenter', function () {
256
+ closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
257
+ closeButton.style.opacity = '1';
258
+ });
259
+ closeButton.addEventListener('mouseleave', function () {
260
+ closeButton.style.backgroundColor = 'transparent';
261
+ closeButton.style.opacity = '0.6';
262
+ });
263
+ closeButton.addEventListener('click', function (e) {
264
+ e.stopPropagation();
265
+ if (opts.onClose) {
266
+ opts.onClose();
267
+ }
268
+ else if (opts.modal) {
269
+ opts.modal.remove();
270
+ }
271
+ });
272
+ header.appendChild(title);
273
+ header.appendChild(closeButton);
274
+ return { header: header, title: title, closeButton: closeButton };
275
+ }
276
+ function makeDraggable(modal, dragHandle) {
277
+ var isDragging = false;
278
+ var dragOffsetX = 0;
279
+ var dragOffsetY = 0;
280
+ dragHandle.addEventListener('mousedown', function (e) {
281
+ isDragging = true;
282
+ var rect = modal.getBoundingClientRect();
283
+ dragOffsetX = e.clientX - rect.left;
284
+ dragOffsetY = e.clientY - rect.top;
285
+ // Set position to current absolute position before removing transform
286
+ modal.style.left = rect.left + 'px';
287
+ modal.style.top = rect.top + 'px';
288
+ modal.style.transform = 'none';
289
+ e.preventDefault();
290
+ });
291
+ var onMouseMove = function (e) {
292
+ if (isDragging) {
293
+ var newLeft = e.clientX - dragOffsetX;
294
+ var newTop = e.clientY - dragOffsetY;
295
+ modal.style.left = newLeft + 'px';
296
+ modal.style.top = newTop + 'px';
297
+ }
298
+ };
299
+ var onMouseUp = function () {
300
+ isDragging = false;
301
+ };
302
+ document.addEventListener('mousemove', onMouseMove);
303
+ document.addEventListener('mouseup', onMouseUp);
304
+ // Return cleanup function
305
+ return function cleanup() {
306
+ document.removeEventListener('mousemove', onMouseMove);
307
+ document.removeEventListener('mouseup', onMouseUp);
308
+ };
309
+ }
310
+ // Function to get border color based on latency simulation value
311
+ function getBorderColor(latencyValue, opacity) {
312
+ var maxLatency = preferences.maxLatency;
313
+ var percentage = latencyValue / maxLatency;
314
+ var r, g, b = 0;
315
+ if (percentage <= 0.5) {
316
+ // Green to Yellow: (0, 200, 0) -> (200, 200, 0)
317
+ var segmentPercent = percentage * 2; // 0 to 1 for this segment
318
+ r = Math.round(segmentPercent * 200);
319
+ g = 200;
320
+ }
321
+ else {
322
+ // Yellow to Red: (200, 200, 0) -> (200, 0, 0)
323
+ var segmentPercent = (percentage - 0.5) * 2; // 0 to 1 for this segment
324
+ r = 200;
325
+ g = Math.round((1 - segmentPercent) * 200);
326
+ }
327
+ return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + opacity + ')';
328
+ }
329
+ function initialize() {
330
+ if (panelsHidden)
331
+ return;
332
+ var container = document.createElement('div');
333
+ container.id = 'debug-logo-container';
334
+ container.style.position = 'fixed';
335
+ container.style.zIndex = '1000';
336
+ container.style.width = '21px';
337
+ container.style.height = '21px';
338
+ container.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
339
+ container.style.border = '3px solid ' + getBorderColor(preferences.latencySimulation.delay, 0.7);
340
+ container.style.borderRadius = '50%';
341
+ container.style.padding = '10px';
342
+ container.style.boxSizing = 'content-box';
343
+ container.style.display = 'flex';
344
+ container.style.justifyContent = 'center';
345
+ container.style.alignItems = 'center';
346
+ container.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.3)';
347
+ container.style.transition = 'border-color 0.3s ease, background-color 0.3s ease';
348
+ container.style.cursor = 'pointer';
349
+ // Apply initial position
350
+ applyPanelPosition();
351
+ // container on hover effect
352
+ container.addEventListener('mouseenter', function () {
353
+ container.style.backgroundColor = 'rgba(0, 0, 0, 0.9)';
354
+ container.style.borderColor = getBorderColor(preferences.latencySimulation.delay, 0.9);
355
+ });
356
+ container.addEventListener('mouseleave', function () {
357
+ container.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
358
+ container.style.borderColor = getBorderColor(preferences.latencySimulation.delay, 0.7);
359
+ });
360
+ var icon = document.createElement('div');
361
+ icon.style.width = '100%';
362
+ icon.style.height = '100%';
363
+ icon.style.display = 'flex';
364
+ icon.style.justifyContent = 'center';
365
+ icon.style.alignItems = 'center';
366
+ // Use insertAdjacentHTML for better Safari compatibility with SVG
367
+ icon.insertAdjacentHTML('beforeend', logoIcon);
368
+ container.appendChild(icon);
369
+ document.body.appendChild(container);
370
+ // Create menu first
371
+ createMenu(container);
372
+ // Apply initial position after menu is created
373
+ applyPanelPosition();
374
+ }
375
+ // Create menu that opens on logo click
376
+ function createMenu(logoContainer) {
377
+ var menu = document.createElement('div');
378
+ menu.id = 'debug-menu';
379
+ menu.style.position = 'fixed';
380
+ // Position will be set by applyPanelPosition
381
+ menu.style.backgroundColor = 'rgba(0, 0, 0, 0.95)';
382
+ menu.style.color = '#fff';
383
+ menu.style.padding = '0 0 8px 0';
384
+ menu.style.borderRadius = '6px';
385
+ menu.style.fontFamily = 'system-ui, -apple-system, sans-serif';
386
+ menu.style.fontSize = '12px';
387
+ menu.style.zIndex = '1001';
388
+ menu.style.minWidth = '200px';
389
+ menu.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.5)';
390
+ menu.style.display = 'none';
391
+ menu.style.overflow = 'hidden';
392
+ // Host name display
393
+ var hostContainer = document.createElement('div');
394
+ hostContainer.style.padding = '6px 12px';
395
+ hostContainer.style.cursor = 'default';
396
+ hostContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
397
+ hostContainer.style.borderBottom = '1px solid rgba(255, 255, 255, 0.15)';
398
+ hostContainer.style.marginBottom = '4px';
399
+ hostContainer.style.borderTopLeftRadius = '6px';
400
+ hostContainer.style.borderTopRightRadius = '6px';
401
+ var hostValue = document.createElement('div');
402
+ hostValue.id = 'debug-menu-host';
403
+ hostValue.style.fontSize = '11px';
404
+ hostValue.style.color = '#fff';
405
+ hostValue.style.fontFamily = 'monospace';
406
+ hostValue.style.whiteSpace = 'nowrap';
407
+ hostValue.style.overflow = 'hidden';
408
+ hostValue.style.textOverflow = 'ellipsis';
409
+ hostValue.style.textAlign = 'center';
410
+ hostValue.style.fontWeight = '500';
411
+ // Function to update host display
412
+ function updateHostDisplay() {
413
+ var hostText = 'N/A';
414
+ if (roomDebugInfo.size > 0) {
415
+ // Get host from first room
416
+ var firstRoom = roomDebugInfo.values().next().value;
417
+ if (firstRoom && firstRoom.host) {
418
+ hostText = firstRoom.host;
419
+ }
420
+ }
421
+ hostValue.textContent = hostText;
422
+ }
423
+ // Update host display initially
424
+ updateHostDisplay();
425
+ hostContainer.appendChild(hostValue);
426
+ menu.appendChild(hostContainer);
427
+ // Simulate latency option
428
+ var latencyContainer = document.createElement('div');
429
+ latencyContainer.style.padding = '8px 12px';
430
+ latencyContainer.style.cursor = 'default';
431
+ var latencyLabel = document.createElement('div');
432
+ latencyLabel.style.marginBottom = '8px';
433
+ latencyLabel.style.display = 'flex';
434
+ latencyLabel.style.alignItems = 'center';
435
+ latencyLabel.style.justifyContent = 'space-between';
436
+ var latencyValueSpan = document.createElement('span');
437
+ latencyValueSpan.id = 'latency-value';
438
+ latencyValueSpan.style.color = '#888';
439
+ latencyValueSpan.style.fontSize = '11px';
440
+ latencyValueSpan.textContent = preferences.latencySimulation.delay + 'ms';
441
+ var latencyTextSpan = document.createElement('span');
442
+ latencyTextSpan.textContent = 'Simulate latency';
443
+ latencyLabel.appendChild(latencyTextSpan);
444
+ latencyLabel.appendChild(latencyValueSpan);
445
+ var latencySlider = document.createElement('input');
446
+ latencySlider.type = 'range';
447
+ latencySlider.min = '0';
448
+ latencySlider.max = preferences.maxLatency.toString();
449
+ latencySlider.value = preferences.latencySimulation.delay.toString();
450
+ latencySlider.style.border = 'none';
451
+ latencySlider.style.width = '100%';
452
+ latencySlider.style.height = '20px';
453
+ latencySlider.style.padding = '0';
454
+ latencySlider.style.margin = '0';
455
+ latencySlider.style.outline = 'none';
456
+ latencySlider.style.cursor = 'pointer';
457
+ latencySlider.style.webkitAppearance = 'none';
458
+ latencySlider.style.appearance = 'none';
459
+ latencySlider.style.background = 'transparent';
460
+ latencySlider.id = 'latency-slider';
461
+ // Function to calculate color from green (0) -> yellow (250) -> red (500)
462
+ function getSliderColor(value, min, max) {
463
+ var percentage = (value - min) / (max - min);
464
+ var r, g, b = 0;
465
+ if (percentage <= 0.5) {
466
+ // Green to Yellow: (0, 200, 0) -> (200, 200, 0)
467
+ var segmentPercent = percentage * 2; // 0 to 1 for this segment
468
+ r = Math.round(segmentPercent * 200);
469
+ g = 200;
470
+ }
471
+ else {
472
+ // Yellow to Red: (200, 200, 0) -> (200, 0, 0)
473
+ var segmentPercent = (percentage - 0.5) * 2; // 0 to 1 for this segment
474
+ r = 200;
475
+ g = Math.round((1 - segmentPercent) * 200);
476
+ }
477
+ return 'rgb(' + r + ', ' + g + ', ' + b + ')';
478
+ }
479
+ // Function to update slider track color
480
+ function updateSliderColor(value) {
481
+ var color = getSliderColor(value, 0, preferences.maxLatency);
482
+ var valuePercent = (value / preferences.maxLatency) * 100;
483
+ var yellowPercent = 50; // Yellow at 250ms (50% of 500ms)
484
+ var gradient;
485
+ if (value <= preferences.maxLatency / 2) {
486
+ // Value is in green->yellow range: show green -> yellow
487
+ var yellowColor = getSliderColor(preferences.maxLatency / 2, 0, preferences.maxLatency);
488
+ gradient = `linear-gradient(to right, #00c800 0%, ${yellowColor} ${valuePercent}%, #333 ${valuePercent}%, #333 100%)`;
489
+ }
490
+ else {
491
+ // Value is in yellow->red range: show green -> yellow -> current color
492
+ var yellowColor = getSliderColor(preferences.maxLatency / 2, 0, preferences.maxLatency);
493
+ gradient = `linear-gradient(to right, #00c800 0%, ${yellowColor} ${yellowPercent}%, ${color} ${valuePercent}%, #333 ${valuePercent}%, #333 100%)`;
494
+ }
495
+ var styleId = 'latency-slider-style';
496
+ var existingStyle = document.getElementById(styleId);
497
+ if (existingStyle) {
498
+ existingStyle.remove();
499
+ }
500
+ var style = document.createElement('style');
501
+ style.id = styleId;
502
+ style.textContent = `
503
+ #latency-slider::-webkit-slider-runnable-track {
504
+ background: ${gradient};
505
+ height: 6px;
506
+ border-radius: 3px;
507
+ border: none;
508
+ }
509
+ #latency-slider::-moz-range-track {
510
+ background: ${gradient};
511
+ height: 6px;
512
+ border-radius: 3px;
513
+ border: none;
514
+ }
515
+ `;
516
+ document.head.appendChild(style);
517
+ }
518
+ // Initialize slider color
519
+ updateSliderColor(parseInt(latencySlider.value));
520
+ // Add custom styling via CSS (inline style limitations)
521
+ var style = document.createElement('style');
522
+ style.textContent = `
523
+ #latency-slider {
524
+ background: transparent !important;
525
+ background-color: transparent !important;
526
+ }
527
+ #latency-slider::-webkit-slider-thumb {
528
+ -webkit-appearance: none;
529
+ appearance: none;
530
+ width: 16px;
531
+ height: 16px;
532
+ border-radius: 50%;
533
+ background: #fff;
534
+ background-color: #fff;
535
+ cursor: pointer;
536
+ border: 2px solid #888;
537
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
538
+ transition: transform 0.1s ease, box-shadow 0.1s ease;
539
+ margin-top: -5px;
540
+ }
541
+ #latency-slider::-webkit-slider-thumb:hover {
542
+ transform: scale(1.1);
543
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
544
+ }
545
+ #latency-slider::-moz-range-thumb {
546
+ width: 16px;
547
+ height: 16px;
548
+ border-radius: 50%;
549
+ background: #fff;
550
+ cursor: pointer;
551
+ border: 2px solid #888;
552
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
553
+ transition: transform 0.1s ease, box-shadow 0.1s ease;
554
+ }
555
+ #latency-slider::-moz-range-thumb:hover {
556
+ transform: scale(1.1);
557
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
558
+ }
559
+ `;
560
+ document.head.appendChild(style);
561
+ // Function to update container border color
562
+ function updateContainerBackgroundColor() {
563
+ var container = document.getElementById('debug-logo-container');
564
+ if (container) {
565
+ // Update to normal state (hover handlers will update on hover)
566
+ container.style.borderColor = getBorderColor(preferences.latencySimulation.delay, 0.7);
567
+ }
568
+ }
569
+ // Update latency value display
570
+ latencySlider.addEventListener('input', function () {
571
+ var value = parseInt(latencySlider.value);
572
+ latencyValueSpan.textContent = value + 'ms';
573
+ preferences.latencySimulation.delay = value;
574
+ preferences.latencySimulation.enabled = value > 0;
575
+ updateSliderColor(value);
576
+ updateContainerBackgroundColor();
577
+ savePreferences();
578
+ });
579
+ latencyContainer.appendChild(latencyLabel);
580
+ latencyContainer.appendChild(latencySlider);
581
+ menu.appendChild(latencyContainer);
582
+ // Separator
583
+ var separator = document.createElement('div');
584
+ separator.style.height = '1px';
585
+ separator.style.backgroundColor = 'rgba(255, 255, 255, 0.15)';
586
+ separator.style.margin = '4px 0';
587
+ menu.appendChild(separator);
588
+ // Settings option
589
+ var settingsOption = document.createElement('div');
590
+ settingsOption.style.padding = '8px 12px';
591
+ settingsOption.style.cursor = 'pointer';
592
+ settingsOption.style.transition = 'background-color 0.2s';
593
+ settingsOption.style.display = 'flex';
594
+ settingsOption.style.alignItems = 'center';
595
+ settingsOption.style.gap = '8px';
596
+ var settingsIconWrapper = document.createElement('span');
597
+ settingsIconWrapper.style.display = 'inline-flex';
598
+ settingsIconWrapper.style.alignItems = 'center';
599
+ settingsIconWrapper.style.width = '16px';
600
+ settingsIconWrapper.style.height = '16px';
601
+ settingsIconWrapper.innerHTML = settingsIcon.replace('height="200px" width="200px"', 'height="16" width="16"');
602
+ var settingsText = document.createElement('span');
603
+ settingsText.textContent = 'Preferences';
604
+ settingsOption.appendChild(settingsIconWrapper);
605
+ settingsOption.appendChild(settingsText);
606
+ settingsOption.addEventListener('mouseenter', function () {
607
+ settingsOption.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
608
+ });
609
+ settingsOption.addEventListener('mouseleave', function () {
610
+ settingsOption.style.backgroundColor = 'transparent';
611
+ });
612
+ settingsOption.addEventListener('click', function (e) {
613
+ e.stopPropagation();
614
+ menuVisible = false;
615
+ menu.style.display = 'none';
616
+ if (hostUpdateInterval) {
617
+ clearInterval(hostUpdateInterval);
618
+ hostUpdateInterval = null;
619
+ }
620
+ openSettingsModal();
621
+ });
622
+ menu.appendChild(settingsOption);
623
+ document.body.appendChild(menu);
624
+ // Toggle menu on logo click
625
+ var menuVisible = false;
626
+ var hostUpdateInterval = null;
627
+ logoContainer.addEventListener('click', function (e) {
628
+ e.stopPropagation();
629
+ menuVisible = !menuVisible;
630
+ menu.style.display = menuVisible ? 'block' : 'none';
631
+ if (menuVisible) {
632
+ updateHostDisplay();
633
+ // Update host every second while menu is visible
634
+ hostUpdateInterval = setInterval(updateHostDisplay, 1000);
635
+ }
636
+ else {
637
+ if (hostUpdateInterval) {
638
+ clearInterval(hostUpdateInterval);
639
+ hostUpdateInterval = null;
640
+ }
641
+ }
642
+ });
643
+ // Close menu when clicking outside
644
+ document.addEventListener('click', function (e) {
645
+ if (menuVisible && !menu.contains(e.target) && !logoContainer.contains(e.target)) {
646
+ menuVisible = false;
647
+ menu.style.display = 'none';
648
+ if (hostUpdateInterval) {
649
+ clearInterval(hostUpdateInterval);
650
+ hostUpdateInterval = null;
651
+ }
652
+ }
653
+ });
654
+ }
655
+ // Create and open Settings modal
656
+ function openSettingsModal() {
657
+ // Remove existing modal if present
658
+ var existingModal = document.getElementById('debug-settings-modal');
659
+ if (existingModal) {
660
+ existingModal.remove();
661
+ }
662
+ // Create overlay using shared utility
663
+ var overlay = createModalOverlay();
664
+ overlay.id = 'debug-settings-overlay';
665
+ // Create modal (non-fixed positioning for overlay)
666
+ var modal = document.createElement('div');
667
+ modal.id = 'debug-settings-modal';
668
+ modal.style.position = 'relative'; // relative position for centered overlay content
669
+ modal.style.backgroundColor = 'rgba(30, 30, 30, 0.98)';
670
+ modal.style.borderRadius = '8px';
671
+ modal.style.width = '90%';
672
+ modal.style.maxWidth = '500px';
673
+ modal.style.maxHeight = '90vh';
674
+ modal.style.overflowY = 'auto';
675
+ modal.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.5)';
676
+ modal.style.color = '#fff';
677
+ modal.style.fontFamily = 'system-ui, -apple-system, sans-serif';
678
+ // Modal header
679
+ var header = document.createElement('div');
680
+ header.style.display = 'flex';
681
+ header.style.justifyContent = 'space-between';
682
+ header.style.alignItems = 'center';
683
+ header.style.padding = '20px 24px';
684
+ header.style.borderBottom = '1px solid rgba(255, 255, 255, 0.1)';
685
+ var title = document.createElement('h2');
686
+ title.textContent = 'Preferences';
687
+ title.style.margin = '0';
688
+ title.style.fontSize = '18px';
689
+ title.style.fontWeight = '600';
690
+ var closeButton = document.createElement('button');
691
+ closeButton.innerHTML = '×';
692
+ closeButton.style.background = 'none';
693
+ closeButton.style.border = 'none';
694
+ closeButton.style.color = '#fff';
695
+ closeButton.style.fontSize = '24px';
696
+ closeButton.style.cursor = 'pointer';
697
+ closeButton.style.padding = '0';
698
+ closeButton.style.width = '32px';
699
+ closeButton.style.height = '32px';
700
+ closeButton.style.display = 'flex';
701
+ closeButton.style.alignItems = 'center';
702
+ closeButton.style.justifyContent = 'center';
703
+ closeButton.style.borderRadius = '4px';
704
+ closeButton.style.transition = 'background-color 0.2s';
705
+ closeButton.addEventListener('mouseenter', function () {
706
+ closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
707
+ });
708
+ closeButton.addEventListener('mouseleave', function () {
709
+ closeButton.style.backgroundColor = 'transparent';
710
+ });
711
+ closeButton.addEventListener('click', function () {
712
+ overlay.remove();
713
+ });
714
+ header.appendChild(title);
715
+ header.appendChild(closeButton);
716
+ modal.appendChild(header);
717
+ // Position option
718
+ var positionContainer = document.createElement('div');
719
+ positionContainer.style.padding = '20px 24px';
720
+ positionContainer.style.borderBottom = '1px solid rgba(255, 255, 255, 0.1)';
721
+ positionContainer.style.display = 'flex';
722
+ positionContainer.style.justifyContent = 'space-between';
723
+ positionContainer.style.alignItems = 'center';
724
+ positionContainer.style.gap = '16px';
725
+ var positionTextContainer = document.createElement('div');
726
+ positionTextContainer.style.flex = '1';
727
+ var positionTitle = document.createElement('div');
728
+ positionTitle.style.fontSize = '14px';
729
+ positionTitle.style.fontWeight = '600';
730
+ positionTitle.style.marginBottom = '4px';
731
+ positionTitle.textContent = 'Position';
732
+ var positionDescription = document.createElement('div');
733
+ positionDescription.style.fontSize = '12px';
734
+ positionDescription.style.color = 'rgba(255, 255, 255, 0.7)';
735
+ positionDescription.textContent = 'Adjust the placement of the panels.';
736
+ positionTextContainer.appendChild(positionTitle);
737
+ positionTextContainer.appendChild(positionDescription);
738
+ var positionSelect = document.createElement('select');
739
+ positionSelect.style.minWidth = '150px';
740
+ positionSelect.style.padding = '8px 12px';
741
+ positionSelect.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
742
+ positionSelect.style.border = '1px solid rgba(255, 255, 255, 0.2)';
743
+ positionSelect.style.borderRadius = '4px';
744
+ positionSelect.style.color = '#fff';
745
+ positionSelect.style.fontSize = '14px';
746
+ positionSelect.style.cursor = 'pointer';
747
+ positionSelect.style.outline = 'none';
748
+ var positions = [
749
+ { value: 'bottom-left', label: 'Bottom Left' },
750
+ { value: 'bottom-right', label: 'Bottom Right' },
751
+ { value: 'top-left', label: 'Top Left' },
752
+ { value: 'top-right', label: 'Top Right' }
753
+ ];
754
+ positions.forEach(function (pos) {
755
+ var option = document.createElement('option');
756
+ option.value = pos.value;
757
+ option.textContent = pos.label;
758
+ if (preferences.panelPosition.position === pos.value) {
759
+ option.selected = true;
760
+ }
761
+ positionSelect.appendChild(option);
762
+ });
763
+ positionSelect.addEventListener('change', function () {
764
+ preferences.panelPosition.position = positionSelect.value;
765
+ applyPanelPosition();
766
+ savePreferences();
767
+ });
768
+ positionContainer.appendChild(positionTextContainer);
769
+ positionContainer.appendChild(positionSelect);
770
+ modal.appendChild(positionContainer);
771
+ // Disable instruction
772
+ var disableContainer = document.createElement('div');
773
+ disableContainer.style.padding = '20px 24px';
774
+ disableContainer.style.borderBottom = '1px solid rgba(255, 255, 255, 0.1)';
775
+ var disableTitle = document.createElement('div');
776
+ disableTitle.style.fontSize = '14px';
777
+ disableTitle.style.fontWeight = '600';
778
+ disableTitle.style.marginBottom = '4px';
779
+ disableTitle.textContent = 'Disable Dev Tools';
780
+ var disableDescription = document.createElement('div');
781
+ disableDescription.style.fontSize = '12px';
782
+ disableDescription.style.color = 'rgba(255, 255, 255, 0.7)';
783
+ disableDescription.style.marginBottom = '8px';
784
+ disableDescription.innerHTML = 'To disable this UI completely, remove the <code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; font-family: monospace; font-size: 11px;">debug.js</code> script from your HTML file.';
785
+ disableContainer.appendChild(disableTitle);
786
+ disableContainer.appendChild(disableDescription);
787
+ modal.appendChild(disableContainer);
788
+ // Hide panels button
789
+ var hideContainer = document.createElement('div');
790
+ hideContainer.style.padding = '20px 24px';
791
+ hideContainer.style.display = 'flex';
792
+ hideContainer.style.justifyContent = 'space-between';
793
+ hideContainer.style.alignItems = 'center';
794
+ hideContainer.style.gap = '16px';
795
+ var hideTextContainer = document.createElement('div');
796
+ hideTextContainer.style.flex = '1';
797
+ var hideTitle = document.createElement('div');
798
+ hideTitle.style.fontSize = '14px';
799
+ hideTitle.style.fontWeight = '600';
800
+ hideTitle.style.marginBottom = '4px';
801
+ hideTitle.textContent = 'Hide Dev Tools for this session';
802
+ var hideDescription = document.createElement('div');
803
+ hideDescription.style.fontSize = '12px';
804
+ hideDescription.style.color = 'rgba(255, 255, 255, 0.7)';
805
+ hideDescription.textContent = 'Hide Dev Tools until you refresh the page.';
806
+ hideTextContainer.appendChild(hideTitle);
807
+ hideTextContainer.appendChild(hideDescription);
808
+ var hideButton = document.createElement('button');
809
+ hideButton.textContent = 'Hide';
810
+ hideButton.style.padding = '8px 16px';
811
+ hideButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
812
+ hideButton.style.border = '1px solid rgba(255, 255, 255, 0.2)';
813
+ hideButton.style.borderRadius = '4px';
814
+ hideButton.style.color = '#fff';
815
+ hideButton.style.fontSize = '14px';
816
+ hideButton.style.cursor = 'pointer';
817
+ hideButton.style.transition = 'background-color 0.2s';
818
+ hideButton.style.display = 'flex';
819
+ hideButton.style.alignItems = 'center';
820
+ hideButton.style.gap = '8px';
821
+ hideButton.style.flexShrink = '0';
822
+ hideButton.insertAdjacentHTML('afterbegin', eyeSlashIcon);
823
+ hideButton.addEventListener('mouseenter', function () {
824
+ hideButton.style.backgroundColor = 'rgba(255, 255, 255, 0.15)';
825
+ });
826
+ hideButton.addEventListener('mouseleave', function () {
827
+ hideButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
828
+ });
829
+ hideButton.addEventListener('click', function () {
830
+ hidePanelsForSession();
831
+ overlay.remove();
832
+ });
833
+ hideContainer.appendChild(hideTextContainer);
834
+ hideContainer.appendChild(hideButton);
835
+ modal.appendChild(hideContainer);
836
+ overlay.appendChild(modal);
837
+ document.body.appendChild(overlay);
838
+ // Mark as selected modal when opened
839
+ selectModal(overlay);
840
+ // Update close button to cleanup from modal stack
841
+ var originalOverlayRemove = overlay.remove.bind(overlay);
842
+ overlay.remove = function () {
843
+ removeModalFromStack(overlay);
844
+ originalOverlayRemove();
845
+ };
846
+ // Mark modal as selected when clicked
847
+ modal.addEventListener('mousedown', function (e) {
848
+ selectModal(overlay);
849
+ });
850
+ // Close on overlay click
851
+ overlay.addEventListener('click', function (e) {
852
+ if (e.target === overlay) {
853
+ overlay.remove();
854
+ }
855
+ });
856
+ }
857
+ // Create and open Send Messages modal
858
+ function openSendMessagesModal(uniquePanelId) {
859
+ var _a;
860
+ var debugInfo = roomDebugInfo.get(uniquePanelId);
861
+ if (!debugInfo || !debugInfo.room) {
862
+ console.warn('Room not found for panel:', uniquePanelId);
863
+ return;
864
+ }
865
+ var room = debugInfo.room;
866
+ var messageTypes = debugInfo.messageTypes;
867
+ if (!messageTypes) {
868
+ console.warn('No message types available for this room');
869
+ return;
870
+ }
871
+ // Remove existing modal if present
872
+ var existingModal = document.getElementById('debug-send-messages-modal');
873
+ if (existingModal) {
874
+ existingModal.remove();
875
+ }
876
+ // Status dot reference
877
+ var statusDotRef = {};
878
+ var updateConnectionStatus = null;
879
+ var onLeaveCallbackRef = { current: null };
880
+ // Function to update status dot color
881
+ const updateSendMsgStatusDot = () => {
882
+ var _a;
883
+ if (statusDotRef.element) {
884
+ statusDotRef.element.style.backgroundColor = ((_a = room.connection) === null || _a === void 0 ? void 0 : _a.isOpen) ? '#22c55e' : '#ef4444';
885
+ }
886
+ };
887
+ // Initial callback
888
+ onLeaveCallbackRef.current = updateSendMsgStatusDot;
889
+ room.onLeave(updateSendMsgStatusDot);
890
+ // Create modal using shared utility
891
+ const modal = createModal({
892
+ id: 'debug-send-messages-modal',
893
+ width: '400px',
894
+ minWidth: '300px',
895
+ maxWidth: '90vw',
896
+ maxHeight: '90vh',
897
+ room: room,
898
+ trackOnLeave: true,
899
+ onLeaveCallback: onLeaveCallbackRef
900
+ });
901
+ // Create header using shared utility
902
+ const headerComponents = createModalHeader({
903
+ title: debugInfo.roomName + ' - Send Message',
904
+ modal: modal,
905
+ statusDot: true,
906
+ statusColor: ((_a = room.connection) === null || _a === void 0 ? void 0 : _a.isOpen) ? '#22c55e' : '#ef4444',
907
+ statusDotRef: statusDotRef
908
+ });
909
+ modal.appendChild(headerComponents.header);
910
+ // Make modal draggable
911
+ makeDraggable(modal, headerComponents.header);
912
+ // Update status dot initially
913
+ updateSendMsgStatusDot();
914
+ // Form content container (scrollable)
915
+ var formContainer = document.createElement('div');
916
+ formContainer.style.padding = '8px';
917
+ formContainer.style.overflowY = 'auto';
918
+ formContainer.style.backgroundColor = '#1e1e1e';
919
+ // Message Type Selector
920
+ var typeLabel = document.createElement('label');
921
+ typeLabel.textContent = 'Message Type';
922
+ typeLabel.style.display = 'block';
923
+ typeLabel.style.fontSize = '11px';
924
+ typeLabel.style.fontWeight = '600';
925
+ typeLabel.style.marginBottom = '4px';
926
+ typeLabel.style.color = 'rgba(255, 255, 255, 0.9)';
927
+ var typeSelect = document.createElement('select');
928
+ typeSelect.style.width = '100%';
929
+ typeSelect.style.padding = '6px 8px';
930
+ typeSelect.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
931
+ typeSelect.style.border = '1px solid rgba(255, 255, 255, 0.2)';
932
+ typeSelect.style.borderRadius = '4px';
933
+ typeSelect.style.color = '#fff';
934
+ typeSelect.style.fontSize = '12px';
935
+ typeSelect.style.cursor = 'pointer';
936
+ typeSelect.style.outline = 'none';
937
+ typeSelect.style.marginBottom = '12px';
938
+ // Add default option
939
+ var defaultOption = document.createElement('option');
940
+ defaultOption.value = '';
941
+ defaultOption.textContent = 'Select a message type';
942
+ defaultOption.disabled = true;
943
+ defaultOption.selected = true;
944
+ typeSelect.appendChild(defaultOption);
945
+ // Add message types
946
+ Object.keys(messageTypes).forEach(function (msgType) {
947
+ var option = document.createElement('option');
948
+ option.value = msgType;
949
+ option.textContent = msgType;
950
+ typeSelect.appendChild(option);
951
+ });
952
+ // Add wildcard option for custom message types
953
+ var wildcardOption = document.createElement('option');
954
+ wildcardOption.value = '*';
955
+ wildcardOption.textContent = '* (Custom)';
956
+ typeSelect.appendChild(wildcardOption);
957
+ formContainer.appendChild(typeLabel);
958
+ formContainer.appendChild(typeSelect);
959
+ // Custom Message Type Input Container (shown when "*" is selected)
960
+ var customTypeContainer = document.createElement('div');
961
+ customTypeContainer.style.display = 'none';
962
+ customTypeContainer.style.marginBottom = '12px';
963
+ var customTypeLabel = document.createElement('label');
964
+ customTypeLabel.textContent = 'Message Type';
965
+ customTypeLabel.style.display = 'block';
966
+ customTypeLabel.style.fontSize = '11px';
967
+ customTypeLabel.style.fontWeight = '600';
968
+ customTypeLabel.style.marginBottom = '4px';
969
+ customTypeLabel.style.color = 'rgba(255, 255, 255, 0.9)';
970
+ var customTypeInput = document.createElement('input');
971
+ customTypeInput.type = 'text';
972
+ customTypeInput.placeholder = 'Enter message type name';
973
+ customTypeInput.style.width = '100%';
974
+ customTypeInput.style.padding = '6px 8px';
975
+ customTypeInput.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
976
+ customTypeInput.style.border = '1px solid rgba(255, 255, 255, 0.2)';
977
+ customTypeInput.style.borderRadius = '4px';
978
+ customTypeInput.style.color = '#fff';
979
+ customTypeInput.style.fontSize = '11px';
980
+ customTypeInput.style.fontFamily = 'monospace';
981
+ customTypeInput.style.outline = 'none';
982
+ customTypeContainer.appendChild(customTypeLabel);
983
+ customTypeContainer.appendChild(customTypeInput);
984
+ formContainer.appendChild(customTypeContainer);
985
+ // Message Payload Input Container
986
+ var payloadContainer = document.createElement('div');
987
+ payloadContainer.style.display = 'none';
988
+ payloadContainer.style.marginBottom = '12px';
989
+ var payloadLabel = document.createElement('label');
990
+ payloadLabel.textContent = 'Payload';
991
+ payloadLabel.style.display = 'block';
992
+ payloadLabel.style.fontSize = '11px';
993
+ payloadLabel.style.fontWeight = '600';
994
+ payloadLabel.style.marginBottom = '4px';
995
+ payloadLabel.style.color = 'rgba(255, 255, 255, 0.9)';
996
+ var payloadFieldsContainer = document.createElement('div');
997
+ payloadFieldsContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
998
+ payloadFieldsContainer.style.border = '1px solid rgba(255, 255, 255, 0.2)';
999
+ payloadFieldsContainer.style.borderRadius = '4px';
1000
+ payloadFieldsContainer.style.padding = '8px';
1001
+ payloadFieldsContainer.style.fontFamily = 'monospace';
1002
+ payloadFieldsContainer.style.fontSize = '11px';
1003
+ var payloadTextarea = document.createElement('textarea');
1004
+ payloadTextarea.style.width = '100%';
1005
+ payloadTextarea.style.minHeight = '80px';
1006
+ payloadTextarea.style.padding = '6px 8px';
1007
+ payloadTextarea.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
1008
+ payloadTextarea.style.border = '1px solid rgba(255, 255, 255, 0.2)';
1009
+ payloadTextarea.style.borderRadius = '4px';
1010
+ payloadTextarea.style.color = '#fff';
1011
+ payloadTextarea.style.fontSize = '11px';
1012
+ payloadTextarea.style.fontFamily = 'monospace';
1013
+ payloadTextarea.style.outline = 'none';
1014
+ payloadTextarea.style.resize = 'vertical';
1015
+ payloadTextarea.placeholder = '{}';
1016
+ payloadTextarea.value = '{}';
1017
+ payloadContainer.appendChild(payloadLabel);
1018
+ payloadContainer.appendChild(payloadFieldsContainer);
1019
+ payloadContainer.appendChild(payloadTextarea);
1020
+ formContainer.appendChild(payloadContainer);
1021
+ // Error message container
1022
+ var errorContainer = document.createElement('div');
1023
+ errorContainer.style.display = 'none';
1024
+ errorContainer.style.padding = '6px 8px';
1025
+ errorContainer.style.backgroundColor = 'rgba(220, 38, 38, 0.2)';
1026
+ errorContainer.style.border = '1px solid rgba(220, 38, 38, 0.4)';
1027
+ errorContainer.style.borderRadius = '4px';
1028
+ errorContainer.style.marginBottom = '8px';
1029
+ errorContainer.style.fontSize = '11px';
1030
+ errorContainer.style.color = '#fca5a5';
1031
+ formContainer.appendChild(errorContainer);
1032
+ // Variables to store current message type and its schema
1033
+ var currentFormInputs = {};
1034
+ var currentMessageType = '';
1035
+ // Update payload fields based on selected message type
1036
+ typeSelect.addEventListener('change', function () {
1037
+ var selectedType = typeSelect.value;
1038
+ currentMessageType = selectedType;
1039
+ if (selectedType) {
1040
+ // Show/hide custom type input based on selection
1041
+ if (selectedType === '*') {
1042
+ customTypeContainer.style.display = 'block';
1043
+ customTypeInput.focus();
1044
+ }
1045
+ else {
1046
+ customTypeContainer.style.display = 'none';
1047
+ }
1048
+ payloadContainer.style.display = 'block';
1049
+ errorContainer.style.display = 'none';
1050
+ currentFormInputs = {};
1051
+ // Clear previous fields
1052
+ payloadFieldsContainer.innerHTML = '';
1053
+ var schema = messageTypes[selectedType];
1054
+ // If schema exists and has properties, create form inputs
1055
+ if (schema && schema.properties && Object.keys(schema.properties).length > 0) {
1056
+ payloadTextarea.style.display = 'none';
1057
+ payloadFieldsContainer.style.display = 'block';
1058
+ // Generate form fields based on schema
1059
+ Object.keys(schema.properties).forEach(function (fieldName) {
1060
+ var fieldSchema = schema.properties[fieldName];
1061
+ var fieldContainer = document.createElement('div');
1062
+ fieldContainer.style.marginBottom = '8px';
1063
+ var fieldLabel = document.createElement('label');
1064
+ fieldLabel.textContent = fieldName;
1065
+ if (schema.required && schema.required.includes(fieldName)) {
1066
+ fieldLabel.textContent += ' *';
1067
+ }
1068
+ fieldLabel.style.display = 'block';
1069
+ fieldLabel.style.fontSize = '10px';
1070
+ fieldLabel.style.marginBottom = '3px';
1071
+ fieldLabel.style.color = 'rgba(255, 255, 255, 0.8)';
1072
+ var fieldInput;
1073
+ if (fieldSchema.type === 'boolean') {
1074
+ fieldInput = document.createElement('input');
1075
+ fieldInput.type = 'checkbox';
1076
+ fieldInput.style.width = '16px';
1077
+ fieldInput.style.height = '16px';
1078
+ fieldInput.style.cursor = 'pointer';
1079
+ }
1080
+ else if (fieldSchema.type === 'number' || fieldSchema.type === 'integer') {
1081
+ fieldInput = document.createElement('input');
1082
+ fieldInput.type = 'number';
1083
+ if (fieldSchema.type === 'integer') {
1084
+ fieldInput.step = '1';
1085
+ }
1086
+ fieldInput.style.width = '100%';
1087
+ fieldInput.style.padding = '4px 6px';
1088
+ fieldInput.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
1089
+ fieldInput.style.border = '1px solid rgba(255, 255, 255, 0.2)';
1090
+ fieldInput.style.borderRadius = '3px';
1091
+ fieldInput.style.color = '#fff';
1092
+ fieldInput.style.fontSize = '11px';
1093
+ fieldInput.style.outline = 'none';
1094
+ }
1095
+ else {
1096
+ fieldInput = document.createElement('input');
1097
+ fieldInput.type = 'text';
1098
+ fieldInput.style.width = '100%';
1099
+ fieldInput.style.padding = '4px 6px';
1100
+ fieldInput.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
1101
+ fieldInput.style.border = '1px solid rgba(255, 255, 255, 0.2)';
1102
+ fieldInput.style.borderRadius = '3px';
1103
+ fieldInput.style.color = '#fff';
1104
+ fieldInput.style.fontSize = '11px';
1105
+ fieldInput.style.outline = 'none';
1106
+ }
1107
+ if (fieldSchema.description) {
1108
+ var fieldDesc = document.createElement('div');
1109
+ fieldDesc.textContent = fieldSchema.description;
1110
+ fieldDesc.style.fontSize = '9px';
1111
+ fieldDesc.style.color = 'rgba(255, 255, 255, 0.5)';
1112
+ fieldDesc.style.marginTop = '2px';
1113
+ fieldContainer.appendChild(fieldLabel);
1114
+ fieldContainer.appendChild(fieldInput);
1115
+ fieldContainer.appendChild(fieldDesc);
1116
+ }
1117
+ else {
1118
+ fieldContainer.appendChild(fieldLabel);
1119
+ fieldContainer.appendChild(fieldInput);
1120
+ }
1121
+ payloadFieldsContainer.appendChild(fieldContainer);
1122
+ currentFormInputs[fieldName] = { input: fieldInput, schema: fieldSchema };
1123
+ });
1124
+ }
1125
+ else {
1126
+ // Use JSON textarea for free-form input (no schema or empty schema)
1127
+ payloadTextarea.style.display = 'block';
1128
+ payloadFieldsContainer.style.display = 'none';
1129
+ payloadTextarea.value = '{}';
1130
+ // Update placeholder based on whether schema exists
1131
+ if (!schema) {
1132
+ payloadTextarea.placeholder = 'Enter JSON payload (no message format defined)\n\nExample:\n{\n "key": "value"\n}';
1133
+ }
1134
+ else {
1135
+ payloadTextarea.placeholder = '{}';
1136
+ }
1137
+ }
1138
+ }
1139
+ else {
1140
+ payloadContainer.style.display = 'none';
1141
+ }
1142
+ });
1143
+ // Send Button
1144
+ var sendButton = document.createElement('button');
1145
+ sendButton.textContent = 'Send';
1146
+ sendButton.style.width = '100%';
1147
+ sendButton.style.padding = '8px 12px';
1148
+ sendButton.style.backgroundColor = '#8b5cf6';
1149
+ sendButton.style.border = 'none';
1150
+ sendButton.style.borderRadius = '4px';
1151
+ sendButton.style.color = '#fff';
1152
+ sendButton.style.fontSize = '12px';
1153
+ sendButton.style.fontWeight = '600';
1154
+ sendButton.style.cursor = 'pointer';
1155
+ sendButton.style.transition = 'background-color 0.2s';
1156
+ var isButtonInSuccessState = false;
1157
+ var hoverColor = '#7c3aed';
1158
+ var normalColor = '#8b5cf6';
1159
+ sendButton.addEventListener('mouseenter', function () {
1160
+ if (!isButtonInSuccessState && !sendButton.disabled) {
1161
+ sendButton.style.backgroundColor = hoverColor;
1162
+ }
1163
+ });
1164
+ sendButton.addEventListener('mouseleave', function () {
1165
+ if (!isButtonInSuccessState && !sendButton.disabled) {
1166
+ sendButton.style.backgroundColor = normalColor;
1167
+ }
1168
+ });
1169
+ // Update the connection status function to also manage button state
1170
+ updateConnectionStatus = function () {
1171
+ var _a;
1172
+ const isConnected = (_a = room.connection) === null || _a === void 0 ? void 0 : _a.isOpen;
1173
+ if (statusDotRef.element) {
1174
+ statusDotRef.element.style.backgroundColor = isConnected ? '#22c55e' : '#ef4444';
1175
+ }
1176
+ // Update button disabled state
1177
+ sendButton.disabled = !isConnected;
1178
+ if (!isConnected) {
1179
+ sendButton.style.backgroundColor = '#6b7280';
1180
+ sendButton.style.cursor = 'not-allowed';
1181
+ sendButton.style.opacity = '0.5';
1182
+ }
1183
+ else if (!isButtonInSuccessState) {
1184
+ sendButton.style.backgroundColor = normalColor;
1185
+ sendButton.style.cursor = 'pointer';
1186
+ sendButton.style.opacity = '1';
1187
+ }
1188
+ };
1189
+ // Swap out the onLeave callback to use the combined update function
1190
+ room.onLeave.remove(onLeaveCallbackRef.current);
1191
+ room.onLeave(updateConnectionStatus);
1192
+ onLeaveCallbackRef.current = updateConnectionStatus;
1193
+ updateConnectionStatus();
1194
+ sendButton.addEventListener('click', function () {
1195
+ var _a;
1196
+ errorContainer.style.display = 'none';
1197
+ // Check if room is connected
1198
+ if (!((_a = room.connection) === null || _a === void 0 ? void 0 : _a.isOpen)) {
1199
+ errorContainer.textContent = 'Cannot send message: Room is not connected';
1200
+ errorContainer.style.display = 'block';
1201
+ return;
1202
+ }
1203
+ if (!currentMessageType) {
1204
+ errorContainer.textContent = 'Please select a message type';
1205
+ errorContainer.style.display = 'block';
1206
+ return;
1207
+ }
1208
+ // Determine actual message type to send
1209
+ var actualMessageType = currentMessageType;
1210
+ if (currentMessageType === '*') {
1211
+ actualMessageType = customTypeInput.value.trim();
1212
+ if (!actualMessageType) {
1213
+ errorContainer.textContent = 'Please enter a message type name';
1214
+ errorContainer.style.display = 'block';
1215
+ return;
1216
+ }
1217
+ }
1218
+ try {
1219
+ var payload;
1220
+ // Build payload from form inputs or textarea
1221
+ if (Object.keys(currentFormInputs).length > 0) {
1222
+ payload = {};
1223
+ var schema = messageTypes[currentMessageType];
1224
+ for (var fieldName in currentFormInputs) {
1225
+ var fieldData = currentFormInputs[fieldName];
1226
+ var input = fieldData.input;
1227
+ var fieldSchema = fieldData.schema;
1228
+ var value;
1229
+ if (fieldSchema.type === 'boolean') {
1230
+ value = input.checked;
1231
+ }
1232
+ else if (fieldSchema.type === 'number' || fieldSchema.type === 'integer') {
1233
+ value = input.value ? parseFloat(input.value) : undefined;
1234
+ }
1235
+ else {
1236
+ value = input.value || undefined;
1237
+ }
1238
+ // Only include required fields or fields with values
1239
+ if (value !== undefined || (schema.required && schema.required.includes(fieldName))) {
1240
+ payload[fieldName] = value;
1241
+ }
1242
+ }
1243
+ }
1244
+ else {
1245
+ payload = JSON.parse(payloadTextarea.value);
1246
+ }
1247
+ // Send the message
1248
+ room.send(actualMessageType, payload);
1249
+ // Change button to success state
1250
+ isButtonInSuccessState = true;
1251
+ sendButton.textContent = 'Message sent!';
1252
+ sendButton.style.backgroundColor = '#22c55e';
1253
+ sendButton.style.cursor = 'default';
1254
+ // Restore button after 1.5 seconds
1255
+ setTimeout(function () {
1256
+ isButtonInSuccessState = false;
1257
+ sendButton.textContent = 'Send';
1258
+ sendButton.style.backgroundColor = normalColor;
1259
+ sendButton.style.cursor = 'pointer';
1260
+ }, 800);
1261
+ }
1262
+ catch (e) {
1263
+ errorContainer.textContent = 'Error: ' + e.message;
1264
+ errorContainer.style.display = 'block';
1265
+ }
1266
+ });
1267
+ formContainer.appendChild(sendButton);
1268
+ modal.appendChild(formContainer);
1269
+ document.body.appendChild(modal);
1270
+ }
1271
+ // Create and open State Inspector modal
1272
+ function openStateInspectorModal(uniquePanelId) {
1273
+ var _a;
1274
+ var debugInfo = roomDebugInfo.get(uniquePanelId);
1275
+ if (!debugInfo || !debugInfo.room) {
1276
+ console.warn('Room not found for panel:', uniquePanelId);
1277
+ return;
1278
+ }
1279
+ var room = debugInfo.room;
1280
+ // Remove existing modal if present
1281
+ var existingModal = document.getElementById('debug-state-inspector-modal');
1282
+ if (existingModal) {
1283
+ existingModal.remove();
1284
+ }
1285
+ // Load saved position and size from localStorage
1286
+ var savedStateInspectorPrefs = null;
1287
+ try {
1288
+ var saved = localStorage.getItem('colyseus-state-inspector-preferences');
1289
+ if (saved) {
1290
+ savedStateInspectorPrefs = JSON.parse(saved);
1291
+ }
1292
+ }
1293
+ catch (e) {
1294
+ // Ignore localStorage errors
1295
+ }
1296
+ // Default values
1297
+ var defaultWidth = 600;
1298
+ var defaultHeight = 500;
1299
+ var defaultLeft = '50%';
1300
+ var defaultTop = '50%';
1301
+ var defaultTransform = 'translate(-50%, -50%)';
1302
+ // Use saved preferences if available
1303
+ if (savedStateInspectorPrefs) {
1304
+ if (savedStateInspectorPrefs.width && savedStateInspectorPrefs.width >= 300) {
1305
+ defaultWidth = savedStateInspectorPrefs.width;
1306
+ }
1307
+ if (savedStateInspectorPrefs.height && savedStateInspectorPrefs.height >= 200) {
1308
+ defaultHeight = savedStateInspectorPrefs.height;
1309
+ }
1310
+ if (savedStateInspectorPrefs.left !== undefined && savedStateInspectorPrefs.top !== undefined) {
1311
+ // Constrain position to window boundaries
1312
+ var maxLeft = window.innerWidth - defaultWidth;
1313
+ var maxTop = window.innerHeight - defaultHeight;
1314
+ var constrainedLeft = Math.max(0, Math.min(savedStateInspectorPrefs.left, maxLeft));
1315
+ var constrainedTop = Math.max(0, Math.min(savedStateInspectorPrefs.top, maxTop));
1316
+ defaultLeft = constrainedLeft + 'px';
1317
+ defaultTop = constrainedTop + 'px';
1318
+ defaultTransform = 'none';
1319
+ }
1320
+ }
1321
+ // Function to save state inspector preferences
1322
+ function saveStateInspectorPreferences() {
1323
+ try {
1324
+ var rect = modal.getBoundingClientRect();
1325
+ var prefs = {
1326
+ width: rect.width,
1327
+ height: rect.height,
1328
+ left: rect.left,
1329
+ top: rect.top
1330
+ };
1331
+ localStorage.setItem('colyseus-state-inspector-preferences', JSON.stringify(prefs));
1332
+ }
1333
+ catch (e) {
1334
+ // Ignore localStorage errors
1335
+ }
1336
+ }
1337
+ // Status dot reference
1338
+ var statusDotRef = {};
1339
+ // Function to update status dot color
1340
+ const updateStateViewerStatusDot = () => {
1341
+ var _a;
1342
+ if (statusDotRef.element) {
1343
+ statusDotRef.element.style.backgroundColor = ((_a = room.connection) === null || _a === void 0 ? void 0 : _a.isOpen) ? '#22c55e' : '#ef4444';
1344
+ }
1345
+ };
1346
+ // Register the onLeave callback
1347
+ room.onLeave(updateStateViewerStatusDot);
1348
+ // Create modal using shared utility with automatic onLeave tracking
1349
+ const modal = createModal({
1350
+ id: 'debug-state-inspector-modal',
1351
+ width: defaultWidth + 'px',
1352
+ height: defaultHeight + 'px',
1353
+ minWidth: '300px',
1354
+ minHeight: '200px',
1355
+ maxWidth: '90vw',
1356
+ maxHeight: '90vh',
1357
+ top: defaultTop,
1358
+ left: defaultLeft,
1359
+ transform: defaultTransform,
1360
+ room: room,
1361
+ trackOnLeave: true,
1362
+ onLeaveCallback: updateStateViewerStatusDot
1363
+ });
1364
+ // Create header using shared utility
1365
+ const headerComponents = createModalHeader({
1366
+ title: `${debugInfo.roomName} - State Viewer`,
1367
+ modal: modal,
1368
+ statusDot: true,
1369
+ statusColor: ((_a = room.connection) === null || _a === void 0 ? void 0 : _a.isOpen) ? '#22c55e' : '#ef4444',
1370
+ statusDotRef: statusDotRef
1371
+ });
1372
+ const header = headerComponents.header;
1373
+ const closeButton = headerComponents.closeButton;
1374
+ modal.appendChild(header);
1375
+ // Update status dot initially
1376
+ updateStateViewerStatusDot();
1377
+ // State content container
1378
+ var contentContainer = document.createElement('div');
1379
+ contentContainer.style.padding = '8px';
1380
+ contentContainer.style.overflowY = 'auto';
1381
+ contentContainer.style.flex = '1';
1382
+ contentContainer.style.minHeight = '0';
1383
+ contentContainer.style.backgroundColor = '#1e1e1e';
1384
+ contentContainer.id = 'debug-state-content';
1385
+ // Single event listener for all expand buttons (event delegation)
1386
+ contentContainer.addEventListener('click', function (e) {
1387
+ var expandButton = e.target.closest('[data-expand-button]');
1388
+ if (expandButton) {
1389
+ e.stopPropagation();
1390
+ toggleExpand(expandButton);
1391
+ }
1392
+ });
1393
+ // // Counter for unique IDs
1394
+ // var stateNodeCounter = 0;
1395
+ // Track expanded paths across re-renders
1396
+ var expandedPaths = new Set();
1397
+ // Helper function to escape HTML
1398
+ function escapeHtml(text) {
1399
+ var div = document.createElement('div');
1400
+ div.textContent = text;
1401
+ return div.innerHTML;
1402
+ }
1403
+ // Helper function to check if a value is expandable (object, array, or collection)
1404
+ function isExpandable(value) {
1405
+ if (value === null || value === undefined) {
1406
+ return false;
1407
+ }
1408
+ var valueType = typeof value;
1409
+ var isObject = valueType === 'object' && !(value instanceof Array);
1410
+ var hasForEach = valueType === 'object' && typeof value.forEach === 'function';
1411
+ var isArray = value instanceof Array;
1412
+ return isObject || isArray || hasForEach;
1413
+ }
1414
+ // Helper function to get label color based on field name
1415
+ function getLabelColor(fieldName) {
1416
+ return typeof fieldName === 'string' && fieldName.startsWith('"') ? '#CE9178' : '#DCDCAA';
1417
+ }
1418
+ // Helper function to get display state for expandable nodes
1419
+ function getDisplayState(depth, isPathExpanded) {
1420
+ var isRoot = depth === 0;
1421
+ return {
1422
+ isRoot: isRoot,
1423
+ initialDisplay: isRoot ? 'block' : (isPathExpanded ? 'block' : 'none'),
1424
+ initialIcon: (isRoot || isPathExpanded) ? '▼' : '▶',
1425
+ rootClass: isRoot ? ' data-root-node="true"' : ''
1426
+ };
1427
+ }
1428
+ // Helper function to get refId display string
1429
+ function getRefIdDisplay(refId) {
1430
+ return refId !== null && refId !== undefined ? '<span style="color: #909090; font-size: 11px; margin-left: 4px;">(ref: ' + refId + ')</span>' : '';
1431
+ }
1432
+ // Helper function to render expand button HTML
1433
+ function renderExpandButton(displayState, displayLabel, refIdDisplay, size) {
1434
+ var html = '<div data-expand-button' + displayState.rootClass + ' style="display: flex; align-items: center; cursor: ' + (displayState.isRoot ? 'default' : 'pointer') + '; user-select: none; margin: 0; padding: 1px 0;" onmouseover="' + (displayState.isRoot ? '' : 'this.style.backgroundColor=\'rgba(255,255,255,0.05)\'') + '" onmouseout="' + (displayState.isRoot ? '' : 'this.style.backgroundColor=\'transparent\'') + '">';
1435
+ html += '<span data-expand-icon style="margin-right: 4px; color: #858585; font-size: 10px; width: 10px; display: inline-block; text-align: center; user-select: none;">' + displayState.initialIcon + '</span>';
1436
+ if (displayLabel) {
1437
+ html += '<span style="color: ' + getLabelColor(displayLabel) + ';">' + escapeHtml(String(displayLabel)) + '</span>';
1438
+ }
1439
+ if (size !== null && size !== undefined && size !== '?') {
1440
+ // ×
1441
+ html += ' <span style="color: #858585;">(' + size + ') </span>';
1442
+ }
1443
+ html += refIdDisplay;
1444
+ html += '</div>';
1445
+ return html;
1446
+ }
1447
+ // Helper function to render a key-value pair
1448
+ function renderKeyValue(key, value, depth, currentPath, keyDisplay, isKeyString) {
1449
+ if (isExpandable(value)) {
1450
+ // For expandable values, renderState handles its own container with proper indentation
1451
+ // Pass the fieldName so it displays the key as the label
1452
+ if (keyDisplay !== null) {
1453
+ return renderState(value, depth, String(key), currentPath, keyDisplay);
1454
+ }
1455
+ else {
1456
+ return renderState(value, depth, String(key), currentPath);
1457
+ }
1458
+ }
1459
+ else {
1460
+ // For primitive values, wrap in a div with key-value formatting
1461
+ // Add padding-left to align with expandable items (icon width 10px + margin-right 4px = 14px)
1462
+ var indent = depth * 6;
1463
+ var html = '<div style="margin-left: ' + indent + 'px; padding-left: 14px;">';
1464
+ if (keyDisplay !== null) {
1465
+ var keyColor = isKeyString ? '#CE9178' : '#DCDCAA';
1466
+ html += '<span style="color: ' + keyColor + ';">' + (isKeyString ? '"' + escapeHtml(String(keyDisplay)) + '"' : escapeHtml(String(keyDisplay))) + '</span><span style="color: #858585;">: </span>';
1467
+ }
1468
+ else {
1469
+ html += '<span style="color: #DCDCAA;">' + escapeHtml(String(key)) + '</span><span style="color: #858585;">: </span>';
1470
+ }
1471
+ html += renderState(value, depth + 1, String(key), currentPath);
1472
+ html += '</div>';
1473
+ return html;
1474
+ }
1475
+ }
1476
+ // Function to render state recursively
1477
+ function renderState(obj, depth = 0, parentKey = '', path = '', fieldName = null) {
1478
+ var maxDepth = 10;
1479
+ if (depth > maxDepth) {
1480
+ return '<span style="color: #858585; font-style: italic;">[Max depth reached]</span>';
1481
+ }
1482
+ if (obj === null) {
1483
+ return '<span style="color: #858585;">null</span>';
1484
+ }
1485
+ if (obj === undefined) {
1486
+ return '<span style="color: #858585;">undefined</span>';
1487
+ }
1488
+ var type = typeof obj;
1489
+ var indent = depth * 6;
1490
+ var refId = obj["~refId"];
1491
+ var nodeId = 'state-node-' + refId;
1492
+ var currentPath = path ? path + '.' + (parentKey || '') : (parentKey || 'root');
1493
+ var isPathExpanded = expandedPaths.has(currentPath);
1494
+ var refIdDisplay = getRefIdDisplay(refId);
1495
+ if (type === 'string') {
1496
+ return '<span style="color: #CE9178;">"' + escapeHtml(String(obj)) + '"</span>';
1497
+ }
1498
+ if (type === 'number') {
1499
+ return '<span style="color: #B5CEA8;">' + obj + '</span>';
1500
+ }
1501
+ if (type === 'boolean') {
1502
+ return '<span style="color: #569CD6;">' + obj + '</span>';
1503
+ }
1504
+ // Check if object has forEach method
1505
+ var hasForEach = typeof obj.forEach === 'function';
1506
+ var collectionSize = (hasForEach) && (obj.size || obj.length) || null;
1507
+ if (hasForEach) {
1508
+ var size = (collectionSize !== null ? collectionSize : '?');
1509
+ var contentId = nodeId + '-content';
1510
+ var displayState = getDisplayState(depth, isPathExpanded);
1511
+ var displayLabel = fieldName !== null ? fieldName : (displayState.isRoot ? 'State' : '');
1512
+ var html = '<div style="margin-left: ' + indent + 'px;" data-path="' + escapeHtml(currentPath) + '">';
1513
+ html += renderExpandButton(displayState, displayLabel, refIdDisplay, size);
1514
+ html += '<div id="' + contentId + '" style="display: ' + displayState.initialDisplay + ';">';
1515
+ // Handle forEach collections (Map, Set, etc.)
1516
+ var isSet = obj instanceof Set || (obj.constructor && obj.constructor.name === 'Set');
1517
+ try {
1518
+ obj.forEach(function (value, key) {
1519
+ if (isSet) {
1520
+ // For Sets, forEach passes (value, value, set), so we only use the first parameter
1521
+ html += renderState(value, depth + 1, String(key), currentPath);
1522
+ }
1523
+ else {
1524
+ // For Maps, format key and use renderKeyValue to handle expandable values and proper formatting
1525
+ var isKeyNumber = typeof key === 'number';
1526
+ var keyStr = isKeyNumber ? '[' + String(key) + ']' : String(key);
1527
+ html += renderKeyValue(key, value, depth + 1, currentPath, keyStr, typeof key === 'string');
1528
+ }
1529
+ });
1530
+ }
1531
+ catch (e) {
1532
+ var errorIndent = (depth + 1) * 6;
1533
+ html += '<div style="margin-left: ' + errorIndent + 'px; color: #e74856;">Error iterating: ' + escapeHtml(e.message) + '</div>';
1534
+ }
1535
+ html += '</div>';
1536
+ html += '</div>';
1537
+ return html;
1538
+ }
1539
+ if (type === 'object') {
1540
+ var keys = Object.keys(obj);
1541
+ var contentId = nodeId + '-content';
1542
+ var displayState = getDisplayState(depth, isPathExpanded);
1543
+ var displayLabel = fieldName !== null ? fieldName : (displayState.isRoot ? 'state' : '');
1544
+ var html = '<div style="margin-left: ' + indent + 'px;" data-path="' + escapeHtml(currentPath) + '">';
1545
+ if (keys.length === 0) {
1546
+ if (displayLabel) {
1547
+ html += '<span style="color: ' + getLabelColor(displayLabel) + ';">' + escapeHtml(String(displayLabel)) + '</span><span style="color: #858585;"> {}</span>' + refIdDisplay;
1548
+ }
1549
+ else {
1550
+ html += '<span style="color: #858585;">{}</span>' + refIdDisplay;
1551
+ }
1552
+ }
1553
+ else {
1554
+ html += renderExpandButton(displayState, displayLabel, refIdDisplay, null);
1555
+ html += '<div id="' + contentId + '" style="display: ' + displayState.initialDisplay + ';">';
1556
+ for (var i = 0; i < keys.length; i++) {
1557
+ var key = keys[i];
1558
+ if (key === "~refId")
1559
+ continue; // skip refId
1560
+ html += renderKeyValue(key, obj[key], depth + 1, currentPath, key, false);
1561
+ }
1562
+ html += '</div>';
1563
+ }
1564
+ html += '</div>';
1565
+ return html;
1566
+ }
1567
+ return '<span style="color: #858585;">' + escapeHtml(String(obj)) + '</span>';
1568
+ }
1569
+ // Function to toggle expand/collapse
1570
+ function toggleExpand(expandButton) {
1571
+ // Don't allow collapsing root node
1572
+ var content = expandButton.nextElementSibling;
1573
+ var icon = expandButton.querySelector('[data-expand-icon]');
1574
+ if (content && content.style) {
1575
+ var isHidden = content.style.display === 'none';
1576
+ content.style.display = isHidden ? 'block' : 'none';
1577
+ if (icon) {
1578
+ icon.textContent = isHidden ? '▼' : '▶';
1579
+ }
1580
+ // Track expanded state by path
1581
+ var pathElement = expandButton.closest('[data-path]');
1582
+ if (pathElement) {
1583
+ var path = pathElement.getAttribute('data-path');
1584
+ if (isHidden) {
1585
+ expandedPaths.add(path);
1586
+ }
1587
+ else {
1588
+ expandedPaths.delete(path);
1589
+ }
1590
+ }
1591
+ }
1592
+ }
1593
+ // Function to update state display
1594
+ function updateStateDisplay() {
1595
+ try {
1596
+ // Save currently expanded paths before clearing
1597
+ var currentExpandedPaths = new Set();
1598
+ var existingExpandButtons = contentContainer.querySelectorAll('[data-expand-button]');
1599
+ for (var i = 0; i < existingExpandButtons.length; i++) {
1600
+ var button = existingExpandButtons[i];
1601
+ var pathElement = button.closest('[data-path]');
1602
+ if (pathElement) {
1603
+ var path = pathElement.getAttribute('data-path');
1604
+ var content = button.nextElementSibling;
1605
+ if (content && content.style && content.style.display !== 'none') {
1606
+ currentExpandedPaths.add(path);
1607
+ }
1608
+ }
1609
+ }
1610
+ // Merge with previously tracked expanded paths
1611
+ // Keep paths that were expanded before, or are currently expanded
1612
+ var pathsToKeep = new Set();
1613
+ expandedPaths.forEach(function (path) {
1614
+ pathsToKeep.add(path);
1615
+ });
1616
+ currentExpandedPaths.forEach(function (path) {
1617
+ pathsToKeep.add(path);
1618
+ });
1619
+ expandedPaths = pathsToKeep;
1620
+ // stateNodeCounter = 0; // Reset counter for new render
1621
+ var state = room.state || {};
1622
+ contentContainer.innerHTML = '<div style="font-family: \'Consolas\', \'Monaco\', \'Courier New\', monospace; font-size: 12px; line-height: 1.5; color: #d4d4d4; padding: 8px;">' + renderState(state) + '</div>';
1623
+ // Event delegation: single click listener handles all expand buttons
1624
+ }
1625
+ catch (e) {
1626
+ contentContainer.innerHTML = '<div style="color: #e74856; padding: 20px;">Error accessing room state: ' + escapeHtml(e.message) + '</div>';
1627
+ }
1628
+ }
1629
+ // Throttle function to prevent excessive re-renders
1630
+ // TODO: prevent re-renders of unchanged refIds instead of just throttling
1631
+ function throttle(func, wait) {
1632
+ var timeout;
1633
+ var previous = 0;
1634
+ return function executedFunction() {
1635
+ var context = this;
1636
+ var args = arguments;
1637
+ var now = Date.now();
1638
+ var remaining = wait - (now - previous);
1639
+ if (remaining <= 0 || remaining > wait) {
1640
+ if (timeout) {
1641
+ clearTimeout(timeout);
1642
+ timeout = null;
1643
+ }
1644
+ previous = now;
1645
+ func.apply(context, args);
1646
+ }
1647
+ else if (!timeout) {
1648
+ timeout = setTimeout(function () {
1649
+ previous = Date.now();
1650
+ timeout = null;
1651
+ func.apply(context, args);
1652
+ }, remaining);
1653
+ }
1654
+ };
1655
+ }
1656
+ // Create throttled version of updateStateDisplay
1657
+ var throttledUpdateStateDisplay = throttle(updateStateDisplay, 200);
1658
+ // Initial render - root is always expanded
1659
+ expandedPaths.add('root');
1660
+ updateStateDisplay();
1661
+ // Update state when it changes
1662
+ const originalTriggerChanges = room.serializer.decoder.triggerChanges;
1663
+ room.serializer.decoder.triggerChanges = function (changes) {
1664
+ originalTriggerChanges === null || originalTriggerChanges === void 0 ? void 0 : originalTriggerChanges.apply(this, arguments);
1665
+ throttledUpdateStateDisplay();
1666
+ /**
1667
+ * TODO: keep track of which refIds have changed and only re-render those
1668
+ */
1669
+ };
1670
+ modal.appendChild(contentContainer);
1671
+ document.body.appendChild(modal);
1672
+ // Drag and resize state variables
1673
+ var isDragging = false;
1674
+ var dragStartX = 0;
1675
+ var dragStartY = 0;
1676
+ var modalStartX = 0;
1677
+ var modalStartY = 0;
1678
+ var isResizing = false;
1679
+ var resizeHandle = null;
1680
+ var resizeStartX = 0;
1681
+ var resizeStartY = 0;
1682
+ var resizeStartWidth = 0;
1683
+ var resizeStartHeight = 0;
1684
+ var resizeStartLeft = 0;
1685
+ var resizeStartTop = 0;
1686
+ header.addEventListener('mousedown', function (e) {
1687
+ const target = e.target;
1688
+ // Don't drag if clicking on a resize handle
1689
+ if (target.classList && target.classList.contains('resize-handle')) {
1690
+ return;
1691
+ }
1692
+ if (target === closeButton || closeButton.contains(target)) {
1693
+ return; // Don't drag when clicking close button
1694
+ }
1695
+ isDragging = true;
1696
+ dragStartX = e.clientX;
1697
+ dragStartY = e.clientY;
1698
+ var rect = modal.getBoundingClientRect();
1699
+ modalStartX = rect.left;
1700
+ modalStartY = rect.top;
1701
+ modal.style.cursor = 'move';
1702
+ e.preventDefault();
1703
+ });
1704
+ var handleMouseMove = function (e) {
1705
+ if (isResizing && resizeHandle) {
1706
+ // Handle resize
1707
+ var deltaX = e.clientX - resizeStartX;
1708
+ var deltaY = e.clientY - resizeStartY;
1709
+ var newWidth = resizeStartWidth;
1710
+ var newHeight = resizeStartHeight;
1711
+ var newLeft = resizeStartLeft;
1712
+ var newTop = resizeStartTop;
1713
+ if (resizeHandle.includes('e')) {
1714
+ newWidth = resizeStartWidth + deltaX;
1715
+ }
1716
+ if (resizeHandle.includes('w')) {
1717
+ newWidth = resizeStartWidth - deltaX;
1718
+ newLeft = resizeStartLeft + deltaX;
1719
+ }
1720
+ if (resizeHandle.includes('s')) {
1721
+ newHeight = resizeStartHeight + deltaY;
1722
+ }
1723
+ if (resizeHandle.includes('n')) {
1724
+ newHeight = resizeStartHeight - deltaY;
1725
+ newTop = resizeStartTop + deltaY;
1726
+ }
1727
+ // Apply constraints
1728
+ newWidth = Math.max(parseInt(modal.style.minWidth) || 300, Math.min(newWidth, window.innerWidth - newLeft));
1729
+ newHeight = Math.max(parseInt(modal.style.minHeight) || 200, Math.min(newHeight, window.innerHeight - newTop));
1730
+ modal.style.width = newWidth + 'px';
1731
+ modal.style.height = newHeight + 'px';
1732
+ modal.style.left = newLeft + 'px';
1733
+ modal.style.top = newTop + 'px';
1734
+ modal.style.transform = 'none';
1735
+ }
1736
+ else if (isDragging) {
1737
+ // Handle drag
1738
+ var deltaX = e.clientX - dragStartX;
1739
+ var deltaY = e.clientY - dragStartY;
1740
+ var newX = modalStartX + deltaX;
1741
+ var newY = modalStartY + deltaY;
1742
+ // Constrain to viewport
1743
+ var maxX = window.innerWidth - modal.offsetWidth;
1744
+ var maxY = window.innerHeight - modal.offsetHeight;
1745
+ newX = Math.max(0, Math.min(newX, maxX));
1746
+ newY = Math.max(0, Math.min(newY, maxY));
1747
+ modal.style.left = newX + 'px';
1748
+ modal.style.top = newY + 'px';
1749
+ modal.style.transform = 'none';
1750
+ }
1751
+ };
1752
+ document.addEventListener('mousemove', handleMouseMove);
1753
+ var handleMouseUp = function () {
1754
+ if (isDragging) {
1755
+ isDragging = false;
1756
+ modal.style.cursor = '';
1757
+ saveStateInspectorPreferences();
1758
+ }
1759
+ if (isResizing) {
1760
+ isResizing = false;
1761
+ resizeHandle = null;
1762
+ saveStateInspectorPreferences();
1763
+ }
1764
+ };
1765
+ document.addEventListener('mouseup', handleMouseUp);
1766
+ // Resize functionality
1767
+ var resizeHandleSize = 8;
1768
+ var cornerHandleSize = 12; // Larger handles for corners to make them easier to grab
1769
+ // Create edge handles first, then corner handles (so corners are on top)
1770
+ var edgeHandles = ['n', 's', 'e', 'w'];
1771
+ var cornerHandles = ['nw', 'ne', 'sw', 'se'];
1772
+ // Create edge handles (leaving space for corners)
1773
+ edgeHandles.forEach(function (handle) {
1774
+ var handleEl = document.createElement('div');
1775
+ handleEl.className = 'resize-handle resize-' + handle;
1776
+ handleEl.style.position = 'absolute';
1777
+ handleEl.style.backgroundColor = 'transparent';
1778
+ handleEl.style.zIndex = '10000';
1779
+ handleEl.style.pointerEvents = 'auto';
1780
+ if (handle === 'n' || handle === 's') {
1781
+ handleEl.style.height = resizeHandleSize + 'px';
1782
+ handleEl.style.left = cornerHandleSize + 'px';
1783
+ handleEl.style.right = cornerHandleSize + 'px';
1784
+ if (handle === 'n') {
1785
+ handleEl.style.top = '0';
1786
+ handleEl.style.cursor = 'n-resize';
1787
+ }
1788
+ else {
1789
+ handleEl.style.bottom = '0';
1790
+ handleEl.style.cursor = 's-resize';
1791
+ }
1792
+ }
1793
+ else {
1794
+ handleEl.style.width = resizeHandleSize + 'px';
1795
+ handleEl.style.top = cornerHandleSize + 'px';
1796
+ handleEl.style.bottom = cornerHandleSize + 'px';
1797
+ if (handle === 'e') {
1798
+ handleEl.style.right = '0';
1799
+ handleEl.style.cursor = 'e-resize';
1800
+ }
1801
+ else {
1802
+ handleEl.style.left = '0';
1803
+ handleEl.style.cursor = 'w-resize';
1804
+ }
1805
+ }
1806
+ handleEl.addEventListener('mousedown', function (e) {
1807
+ e.stopPropagation();
1808
+ e.preventDefault();
1809
+ isResizing = true;
1810
+ resizeHandle = handle;
1811
+ resizeStartX = e.clientX;
1812
+ resizeStartY = e.clientY;
1813
+ var rect = modal.getBoundingClientRect();
1814
+ resizeStartWidth = rect.width;
1815
+ resizeStartHeight = rect.height;
1816
+ resizeStartLeft = rect.left;
1817
+ resizeStartTop = rect.top;
1818
+ });
1819
+ modal.appendChild(handleEl);
1820
+ });
1821
+ // Create corner handles (higher z-index so they're on top)
1822
+ cornerHandles.forEach(function (handle) {
1823
+ var handleEl = document.createElement('div');
1824
+ handleEl.className = 'resize-handle resize-' + handle;
1825
+ handleEl.style.position = 'absolute';
1826
+ handleEl.style.backgroundColor = 'transparent';
1827
+ handleEl.style.zIndex = '10002';
1828
+ handleEl.style.pointerEvents = 'auto';
1829
+ handleEl.style.width = cornerHandleSize + 'px';
1830
+ handleEl.style.height = cornerHandleSize + 'px';
1831
+ if (handle === 'nw') {
1832
+ handleEl.style.top = '0';
1833
+ handleEl.style.left = '0';
1834
+ handleEl.style.cursor = 'nw-resize';
1835
+ }
1836
+ else if (handle === 'ne') {
1837
+ handleEl.style.top = '0';
1838
+ handleEl.style.right = '0';
1839
+ handleEl.style.cursor = 'ne-resize';
1840
+ }
1841
+ else if (handle === 'sw') {
1842
+ handleEl.style.bottom = '0';
1843
+ handleEl.style.left = '0';
1844
+ handleEl.style.cursor = 'sw-resize';
1845
+ }
1846
+ else if (handle === 'se') {
1847
+ handleEl.style.bottom = '0';
1848
+ handleEl.style.right = '0';
1849
+ handleEl.style.cursor = 'se-resize';
1850
+ }
1851
+ handleEl.addEventListener('mousedown', function (e) {
1852
+ e.stopPropagation();
1853
+ e.preventDefault();
1854
+ isResizing = true;
1855
+ resizeHandle = handle;
1856
+ resizeStartX = e.clientX;
1857
+ resizeStartY = e.clientY;
1858
+ var rect = modal.getBoundingClientRect();
1859
+ resizeStartWidth = rect.width;
1860
+ resizeStartHeight = rect.height;
1861
+ resizeStartLeft = rect.left;
1862
+ resizeStartTop = rect.top;
1863
+ });
1864
+ modal.appendChild(handleEl);
1865
+ });
1866
+ // Remove state change listener when modal is closed
1867
+ var originalRemove = modal.remove;
1868
+ modal.remove = function () {
1869
+ // Restore original trigger changes
1870
+ room.serializer.decoder.triggerChanges = originalTriggerChanges;
1871
+ originalRemove.call(this);
1872
+ };
1873
+ }
1874
+ // Apply panel position based on current setting
1875
+ function applyPanelPosition() {
1876
+ var logoContainer = document.getElementById('debug-logo-container');
1877
+ var menu = document.getElementById('debug-menu');
1878
+ var panels = document.querySelectorAll('[id^="debug-panel-"]');
1879
+ var positions = {
1880
+ 'bottom-right': { bottom: '14px', right: '14px', top: 'auto', left: 'auto' },
1881
+ 'bottom-left': { bottom: '14px', left: '14px', top: 'auto', right: 'auto' },
1882
+ 'top-left': { top: '14px', left: '14px', bottom: 'auto', right: 'auto' },
1883
+ 'top-right': { top: '14px', right: '14px', bottom: 'auto', left: 'auto' }
1884
+ };
1885
+ var pos = positions[preferences.panelPosition.position] || positions['bottom-right'];
1886
+ // Update logo container
1887
+ if (logoContainer) {
1888
+ logoContainer.style.bottom = pos.bottom;
1889
+ logoContainer.style.right = pos.right;
1890
+ logoContainer.style.top = pos.top;
1891
+ logoContainer.style.left = pos.left;
1892
+ }
1893
+ // Update menu position
1894
+ if (menu) {
1895
+ if (preferences.panelPosition.position.startsWith('bottom')) {
1896
+ menu.style.bottom = '60px';
1897
+ menu.style.top = 'auto';
1898
+ }
1899
+ else {
1900
+ // For top positions, menu appears below the logo
1901
+ menu.style.top = '60px';
1902
+ menu.style.bottom = 'auto';
1903
+ }
1904
+ menu.style.right = pos.right;
1905
+ menu.style.left = pos.left;
1906
+ }
1907
+ // Update panels
1908
+ repositionDebugPanels();
1909
+ }
1910
+ // Hide panels for this session
1911
+ function hidePanelsForSession() {
1912
+ panelsHidden = true;
1913
+ savePreferences(); // Save the hidden state
1914
+ var logoContainer = document.getElementById('debug-logo-container');
1915
+ var menu = document.getElementById('debug-menu');
1916
+ var panels = document.querySelectorAll('[id^="debug-panel-"]');
1917
+ if (logoContainer) {
1918
+ logoContainer.style.display = 'none';
1919
+ }
1920
+ if (menu) {
1921
+ menu.style.display = 'none';
1922
+ }
1923
+ panels.forEach(function (panel) {
1924
+ panel.style.display = 'none';
1925
+ });
1926
+ }
1927
+ // Helper function to format bytes
1928
+ function formatBytes(bytes) {
1929
+ if (bytes === 0)
1930
+ return '0 B';
1931
+ var k = 1024;
1932
+ var sizes = ['B', 'KB', 'MB', 'GB'];
1933
+ var i = Math.floor(Math.log(bytes) / Math.log(k));
1934
+ return (bytes / Math.pow(k, i)).toFixed(1) + ' ' + sizes[i];
1935
+ }
1936
+ // Helper function to create debug panel for a room
1937
+ function createDebugPanel(uniquePanelId, debugInfo) {
1938
+ // Check if panel already exists
1939
+ var existingPanel = document.getElementById('debug-panel-' + uniquePanelId);
1940
+ if (existingPanel) {
1941
+ return existingPanel;
1942
+ }
1943
+ var panel = document.createElement('div');
1944
+ panel.id = 'debug-panel-' + uniquePanelId;
1945
+ panel.style.position = 'fixed';
1946
+ // Position will be set by repositionDebugPanels
1947
+ panel.style.backgroundColor = 'rgba(0, 0, 0, 0.85)';
1948
+ panel.style.color = '#fff';
1949
+ panel.style.padding = '8px';
1950
+ panel.style.borderRadius = '6px';
1951
+ panel.style.fontFamily = 'monospace';
1952
+ panel.style.fontSize = '11px';
1953
+ panel.style.zIndex = '999';
1954
+ panel.style.minWidth = '180px';
1955
+ panel.style.marginRight = '6px';
1956
+ panel.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.5)';
1957
+ panel.style.display = panelsHidden ? 'none' : 'block';
1958
+ var title = document.createElement('div');
1959
+ title.id = 'debug-title-' + uniquePanelId;
1960
+ title.style.fontWeight = 'bold';
1961
+ title.style.marginBottom = '6px';
1962
+ title.style.borderBottom = '1px solid rgba(255, 255, 255, 0.15)';
1963
+ title.style.paddingBottom = '4px';
1964
+ title.style.display = 'flex';
1965
+ title.style.alignItems = 'center';
1966
+ title.style.gap = '4px';
1967
+ title.style.position = 'relative';
1968
+ title.innerHTML = '<span style="display: inline-flex; align-items: center;"></span><span id="debug-title-text-' + uniquePanelId + '"></span><span id="debug-message-icon-' + uniquePanelId + '" style="display: none; align-items: center; margin-left: auto; cursor: pointer; opacity: 0.6; transition: opacity 0.2s; margin-right: 4px; width: 16px; height: 16px;">' + messageIcon.replace('height="200px" width="200px"', 'height="16" width="16"') + '</span><span id="debug-hamburger-icon-' + uniquePanelId + '" style="display: inline-flex; align-items: center; cursor: pointer; opacity: 0.6; transition: opacity 0.2s; margin-right: 4px; width: 16px; height: 16px;">' + treeViewIcon.replace('height="200px" width="200px"', 'height="16" width="16"') + '</span><span id="debug-info-icon-' + uniquePanelId + '" style="display: inline-flex; align-items: center; cursor: pointer; opacity: 0.6; transition: opacity 0.2s; width: 16px; height: 16px;">' + infoIcon + '</span>';
1969
+ // Create tooltip for info icon
1970
+ var tooltip = document.createElement('div');
1971
+ tooltip.id = 'debug-tooltip-' + uniquePanelId;
1972
+ tooltip.style.position = 'absolute';
1973
+ tooltip.style.top = '100%';
1974
+ tooltip.style.right = '0';
1975
+ tooltip.style.marginTop = '4px';
1976
+ tooltip.style.padding = '6px 8px';
1977
+ tooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.95)';
1978
+ tooltip.style.color = '#fff';
1979
+ tooltip.style.borderRadius = '4px';
1980
+ tooltip.style.fontSize = '10px';
1981
+ tooltip.style.fontFamily = 'monospace';
1982
+ tooltip.style.zIndex = '1000';
1983
+ tooltip.style.display = 'none';
1984
+ tooltip.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.5)';
1985
+ tooltip.style.lineHeight = '1.4';
1986
+ tooltip.innerHTML = '<div><strong>Room ID:</strong> ' + debugInfo.roomId + '</div><div><strong>Session ID:</strong> N/A</div><div><strong>Host:</strong> N/A</div>';
1987
+ title.appendChild(tooltip);
1988
+ // Add hover handlers - use a small delay to ensure element exists
1989
+ setTimeout(function () {
1990
+ var infoIconElement = document.getElementById('debug-info-icon-' + uniquePanelId);
1991
+ if (infoIconElement) {
1992
+ var showTooltip = function () {
1993
+ tooltip.style.display = 'block';
1994
+ infoIconElement.style.opacity = '1';
1995
+ };
1996
+ var hideTooltip = function () {
1997
+ tooltip.style.display = 'none';
1998
+ infoIconElement.style.opacity = '0.6';
1999
+ };
2000
+ infoIconElement.addEventListener('mouseenter', showTooltip);
2001
+ infoIconElement.addEventListener('mouseleave', hideTooltip);
2002
+ // Also handle tooltip hover to keep it visible
2003
+ tooltip.style.pointerEvents = 'auto';
2004
+ tooltip.addEventListener('mouseenter', showTooltip);
2005
+ tooltip.addEventListener('mouseleave', hideTooltip);
2006
+ }
2007
+ // Add click handler for hamburger icon
2008
+ var hamburgerIconElement = document.getElementById('debug-hamburger-icon-' + uniquePanelId);
2009
+ if (hamburgerIconElement) {
2010
+ hamburgerIconElement.addEventListener('mouseenter', function () {
2011
+ hamburgerIconElement.style.opacity = '1';
2012
+ });
2013
+ hamburgerIconElement.addEventListener('mouseleave', function () {
2014
+ hamburgerIconElement.style.opacity = '0.6';
2015
+ });
2016
+ hamburgerIconElement.addEventListener('click', function (e) {
2017
+ e.stopPropagation();
2018
+ openStateInspectorModal(uniquePanelId);
2019
+ });
2020
+ }
2021
+ // Add click handler for message icon
2022
+ var messageIconElement = document.getElementById('debug-message-icon-' + uniquePanelId);
2023
+ if (messageIconElement) {
2024
+ messageIconElement.addEventListener('mouseenter', function () {
2025
+ messageIconElement.style.opacity = '1';
2026
+ });
2027
+ messageIconElement.addEventListener('mouseleave', function () {
2028
+ messageIconElement.style.opacity = '0.6';
2029
+ });
2030
+ messageIconElement.addEventListener('click', function (e) {
2031
+ e.stopPropagation();
2032
+ openSendMessagesModal(uniquePanelId);
2033
+ });
2034
+ }
2035
+ }, 0);
2036
+ var content = document.createElement('div');
2037
+ content.id = 'debug-content-' + uniquePanelId;
2038
+ panel.appendChild(title);
2039
+ panel.appendChild(content);
2040
+ // Prepend panel to body so new panels appear first
2041
+ if (document.body.firstChild) {
2042
+ document.body.insertBefore(panel, document.body.firstChild);
2043
+ }
2044
+ else {
2045
+ document.body.appendChild(panel);
2046
+ }
2047
+ return panel;
2048
+ }
2049
+ // Reposition all debug panels to stack vertically
2050
+ function repositionDebugPanels() {
2051
+ if (panelsHidden)
2052
+ return;
2053
+ var panels = Array.from(document.querySelectorAll('[id^="debug-panel-"]'))
2054
+ .filter(function (panel) { return panel.style.display !== 'none'; })
2055
+ .reverse(); // Reverse to get oldest first (since new panels are prepended)
2056
+ // Calculate logoIcon container width: 22px width + 10px padding on each side = 42px
2057
+ // Add 6px margin to prevent overlap
2058
+ var logoIconOffset = 42 + 6;
2059
+ var positions = {
2060
+ 'bottom-right': {
2061
+ start: { bottom: '14px', right: '14px', top: 'auto', left: 'auto' },
2062
+ offset: function (panel, currentRight) { return { right: currentRight + 'px', left: 'auto' }; }
2063
+ },
2064
+ 'bottom-left': {
2065
+ start: { bottom: '14px', left: '14px', top: 'auto', right: 'auto' },
2066
+ offset: function (panel, currentLeft) { return { left: currentLeft + 'px', right: 'auto' }; }
2067
+ },
2068
+ 'top-left': {
2069
+ start: { top: '14px', left: '14px', bottom: 'auto', right: 'auto' },
2070
+ offset: function (panel, currentLeft) { return { left: currentLeft + 'px', right: 'auto' }; }
2071
+ },
2072
+ 'top-right': {
2073
+ start: { top: '14px', right: '14px', bottom: 'auto', left: 'auto' },
2074
+ offset: function (panel, currentRight) { return { right: currentRight + 'px', left: 'auto' }; }
2075
+ }
2076
+ };
2077
+ var pos = positions[preferences.panelPosition.position] || positions['bottom-right'];
2078
+ var baseOffset = 14 + logoIconOffset;
2079
+ var currentOffset = baseOffset;
2080
+ panels.forEach(function (panel) {
2081
+ // Set base position
2082
+ Object.keys(pos.start).forEach(function (key) {
2083
+ panel.style[key] = pos.start[key];
2084
+ });
2085
+ // Apply offset
2086
+ var offset = pos.offset(panel, currentOffset);
2087
+ Object.keys(offset).forEach(function (key) {
2088
+ panel.style[key] = offset[key];
2089
+ });
2090
+ currentOffset += panel.offsetWidth + 6;
2091
+ });
2092
+ }
2093
+ // Update debug panel content
2094
+ function updateDebugPanel(uniquePanelId, debugInfo) {
2095
+ var contentId = 'debug-content-' + uniquePanelId;
2096
+ var panelId = 'debug-panel-' + uniquePanelId;
2097
+ var titleId = 'debug-title-' + uniquePanelId;
2098
+ var content = document.getElementById(contentId);
2099
+ var panel = document.getElementById(panelId);
2100
+ var title = document.getElementById(titleId);
2101
+ if (!content || !panel) {
2102
+ // Only create if panel doesn't exist
2103
+ if (!panel) {
2104
+ createDebugPanel(uniquePanelId, debugInfo);
2105
+ content = document.getElementById(contentId);
2106
+ title = document.getElementById(titleId);
2107
+ repositionDebugPanels();
2108
+ }
2109
+ else {
2110
+ content = document.getElementById(contentId);
2111
+ title = document.getElementById(titleId);
2112
+ }
2113
+ }
2114
+ // Update title with room name only (roomId, sessionId, and Host are in tooltip)
2115
+ document.getElementById('debug-title-text-' + uniquePanelId).textContent = debugInfo.roomName;
2116
+ document.getElementById('debug-tooltip-' + uniquePanelId).innerHTML = '<div><strong>Room ID:</strong> ' + debugInfo.roomId + '</div><div><strong>Session ID:</strong> ' + debugInfo.sessionId + '</div><div><strong>Host:</strong> ' + debugInfo.host + '</div>';
2117
+ var html = '<div style="line-height: 1.3;">';
2118
+ html += '<div style="font-size: 10px; display: flex; gap: 8px;">';
2119
+ html += '<div style="flex: 1;">';
2120
+ html += '<div style="margin-bottom: 4px;"><div style="display: flex; align-items: center; gap: 6px;"><span style="display: inline-flex; align-items: center; width: 18px; height: 18px; color: #FF9800;">' + envelopeUp + '</span><span style="color: #FF9800;">' + formatBytes(debugInfo.bytesSentPerSec) + '/s</span></div><div style="margin-left: 24px; opacity: 0.7; font-size: 9px;">' + debugInfo.messagesSentPerSec.toFixed(0) + ' messages</div></div>';
2121
+ html += '<div><div style="display: flex; align-items: center; gap: 6px;"><span style="display: inline-flex; align-items: center; width: 18px; height: 18px; color: #2196F3;">' + envelopeDown + '</span><span style="color: #2196F3;">' + formatBytes(debugInfo.bytesReceivedPerSec) + '/s</span></div><div style="margin-left: 24px; opacity: 0.7; font-size: 9px;">' + debugInfo.messagesReceivedPerSec.toFixed(0) + ' messages</div></div>';
2122
+ html += '</div>';
2123
+ html += '<div style="display: flex; flex-direction: column; gap: 4px;">';
2124
+ html += '<canvas id="graph-sent-' + uniquePanelId + '" width="80" height="30" style="display: block;"></canvas>';
2125
+ html += '<canvas id="graph-received-' + uniquePanelId + '" width="80" height="30" style="display: block;"></canvas>';
2126
+ html += '</div>';
2127
+ html += '</div>';
2128
+ html += '</div>';
2129
+ content.innerHTML = html;
2130
+ // Draw graphs after a short delay to ensure canvas elements are rendered
2131
+ setTimeout(function () {
2132
+ drawGraph('graph-sent-' + uniquePanelId, debugInfo.bytesSentHistory, '#FF9800');
2133
+ drawGraph('graph-received-' + uniquePanelId, debugInfo.bytesReceivedHistory, '#2196F3');
2134
+ }, 10);
2135
+ }
2136
+ // Draw graph on canvas
2137
+ function drawGraph(canvasId, data, color) {
2138
+ var canvas = document.getElementById(canvasId);
2139
+ if (!canvas)
2140
+ return;
2141
+ var ctx = canvas.getContext('2d');
2142
+ var width = canvas.width;
2143
+ var height = canvas.height;
2144
+ // Clear canvas
2145
+ ctx.clearRect(0, 0, width, height);
2146
+ if (!data || data.length === 0)
2147
+ return;
2148
+ // Find min and max values
2149
+ var maxValue = Math.max.apply(Math, data);
2150
+ var minValue = Math.min.apply(Math, data);
2151
+ var range = maxValue - minValue || 1; // Avoid division by zero
2152
+ // Padding
2153
+ var padding = 2;
2154
+ var graphWidth = width - padding * 2;
2155
+ var graphHeight = height - padding * 2;
2156
+ // Draw grid lines
2157
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
2158
+ ctx.lineWidth = 0.5;
2159
+ for (var i = 0; i <= 4; i++) {
2160
+ var y = padding + (graphHeight / 4) * i;
2161
+ ctx.beginPath();
2162
+ ctx.moveTo(padding, y);
2163
+ ctx.lineTo(width - padding, y);
2164
+ ctx.stroke();
2165
+ }
2166
+ // Draw the line
2167
+ ctx.strokeStyle = color;
2168
+ ctx.lineWidth = 1.5;
2169
+ ctx.beginPath();
2170
+ for (var i = 0; i < data.length; i++) {
2171
+ var x = padding + (graphWidth / (data.length - 1 || 1)) * i;
2172
+ var normalizedValue = (data[i] - minValue) / range;
2173
+ var y = padding + graphHeight - (normalizedValue * graphHeight);
2174
+ if (i === 0) {
2175
+ ctx.moveTo(x, y);
2176
+ }
2177
+ else {
2178
+ ctx.lineTo(x, y);
2179
+ }
2180
+ }
2181
+ ctx.stroke();
2182
+ // Fill area under the line
2183
+ if (data.length > 0) {
2184
+ ctx.lineTo(width - padding, height - padding);
2185
+ ctx.lineTo(padding, height - padding);
2186
+ ctx.closePath();
2187
+ ctx.fillStyle = color;
2188
+ ctx.globalAlpha = 0.2;
2189
+ ctx.fill();
2190
+ ctx.globalAlpha = 1.0;
2191
+ }
2192
+ }
2193
+ // Calculate per-second rates
2194
+ function calculateRates(debugInfo) {
2195
+ var now = Date.now();
2196
+ var elapsed = (now - debugInfo.lastUpdate) / 1000; // seconds
2197
+ if (elapsed > 0) {
2198
+ debugInfo.bytesSentPerSec = debugInfo.bytesSentDelta / elapsed;
2199
+ debugInfo.bytesReceivedPerSec = debugInfo.bytesReceivedDelta / elapsed;
2200
+ debugInfo.messagesSentPerSec = debugInfo.messagesSentDelta / elapsed;
2201
+ debugInfo.messagesReceivedPerSec = debugInfo.messagesReceivedDelta / elapsed;
2202
+ // Add to history
2203
+ debugInfo.bytesSentHistory.push(debugInfo.bytesSentPerSec);
2204
+ debugInfo.bytesReceivedHistory.push(debugInfo.bytesReceivedPerSec);
2205
+ // debugInfo.historyTimestamps.push(now);
2206
+ // Limit history length
2207
+ var maxLen = debugInfo.maxHistoryLength || 60;
2208
+ if (debugInfo.bytesSentHistory.length > maxLen) {
2209
+ debugInfo.bytesSentHistory.shift();
2210
+ debugInfo.bytesReceivedHistory.shift();
2211
+ // debugInfo.historyTimestamps.shift();
2212
+ }
2213
+ // Reset deltas
2214
+ debugInfo.bytesSentDelta = 0;
2215
+ debugInfo.bytesReceivedDelta = 0;
2216
+ debugInfo.messagesSentDelta = 0;
2217
+ debugInfo.messagesReceivedDelta = 0;
2218
+ debugInfo.lastUpdate = now;
2219
+ }
2220
+ // Update panel
2221
+ updateDebugPanel(debugInfo.uniquePanelId, debugInfo);
2222
+ }
2223
+ // Start global update interval if not already running
2224
+ function ensureGlobalUpdateInterval() {
2225
+ if (globalUpdateInterval === null) {
2226
+ globalUpdateInterval = setInterval(function () {
2227
+ // Loop through all panels and calculate rates
2228
+ roomDebugInfo.forEach(function (debugInfo, uniquePanelId) {
2229
+ calculateRates(debugInfo);
2230
+ });
2231
+ // Clean up interval if no more panels
2232
+ if (roomDebugInfo.size === 0) {
2233
+ clearInterval(globalUpdateInterval);
2234
+ globalUpdateInterval = null;
2235
+ }
2236
+ }, 1000);
2237
+ }
2238
+ }
2239
+ function applyMonkeyPatches() {
2240
+ // Helper function to patch a room
2241
+ function patchRoom(room) {
2242
+ var _a, _b;
2243
+ if (!room) {
2244
+ return room;
2245
+ }
2246
+ // Generate a consistent room ID
2247
+ const roomId = room.roomId;
2248
+ const sessionId = room.sessionId;
2249
+ // Generate unique panel ID: use sessionId if available, otherwise use roomId + timestamp
2250
+ const uniquePanelId = sessionId && sessionId !== 'N/A' && sessionId !== ''
2251
+ ? sessionId
2252
+ : roomId + '-' + Date.now() + '-' + Math.random().toString(36).substring(2, 9);
2253
+ const transport = (_a = room.connection) === null || _a === void 0 ? void 0 : _a.transport;
2254
+ const endpoint = ((_b = transport.ws) === null || _b === void 0 ? void 0 : _b.url) || 'N/A';
2255
+ const debugInfo = {
2256
+ uniquePanelId: uniquePanelId,
2257
+ roomId: roomId,
2258
+ roomName: room.name || 'N/A',
2259
+ sessionId: sessionId || 'N/A',
2260
+ endpoint,
2261
+ host: new URL(endpoint).host,
2262
+ room, // Store room reference for state inspector
2263
+ bytesSent: 0,
2264
+ bytesReceived: 0,
2265
+ messagesSent: 0,
2266
+ messagesReceived: 0,
2267
+ bytesSentDelta: 0,
2268
+ bytesReceivedDelta: 0,
2269
+ messagesSentDelta: 0,
2270
+ messagesReceivedDelta: 0,
2271
+ bytesSentPerSec: 0,
2272
+ bytesReceivedPerSec: 0,
2273
+ messagesSentPerSec: 0,
2274
+ messagesReceivedPerSec: 0,
2275
+ lastUpdate: Date.now(),
2276
+ bytesSentHistory: [],
2277
+ bytesReceivedHistory: [],
2278
+ // historyTimestamps: [],
2279
+ maxHistoryLength: 60, // Keep last 60 data points (1 minute at 1 second intervals)
2280
+ messageTypes: null // Will store message types from __playground_message_types
2281
+ };
2282
+ roomDebugInfo.set(uniquePanelId, debugInfo);
2283
+ // Listen for __playground_message_types message
2284
+ room.onMessage('__playground_message_types', (messageTypes) => {
2285
+ debugInfo.messageTypes = messageTypes;
2286
+ // Show/hide message icon based on message types availability
2287
+ var messageIconElement = document.getElementById('debug-message-icon-' + uniquePanelId);
2288
+ if (messageIconElement) {
2289
+ messageIconElement.style.display = messageTypes ? 'inline-flex' : 'none';
2290
+ }
2291
+ });
2292
+ // Helper function to track received message/bytes
2293
+ function trackReceivedMessage(data) {
2294
+ // Calculate bytes received
2295
+ var bytes = 0;
2296
+ if (data instanceof Blob) {
2297
+ bytes = data.size;
2298
+ }
2299
+ else if (data instanceof ArrayBuffer) {
2300
+ bytes = data.byteLength;
2301
+ }
2302
+ else if (typeof data === 'string') {
2303
+ bytes = new Blob([data]).size;
2304
+ }
2305
+ else if (data) {
2306
+ try {
2307
+ bytes = new Blob([JSON.stringify(data)]).size;
2308
+ }
2309
+ catch (e) {
2310
+ bytes = new Blob([String(data)]).size;
2311
+ }
2312
+ }
2313
+ //
2314
+ // TODO: avoid trackig __playground_message_types messages in the stats
2315
+ //
2316
+ debugInfo.messagesReceived++;
2317
+ debugInfo.messagesReceivedDelta++;
2318
+ debugInfo.bytesReceived += bytes;
2319
+ debugInfo.bytesReceivedDelta += bytes;
2320
+ }
2321
+ function trackSentMessage(data) {
2322
+ var bytes = 0;
2323
+ if (data instanceof Blob) {
2324
+ bytes = data.size;
2325
+ }
2326
+ else if (data instanceof ArrayBuffer) {
2327
+ bytes = data.byteLength;
2328
+ }
2329
+ else if (typeof data === 'string') {
2330
+ bytes = new Blob([data]).size;
2331
+ }
2332
+ debugInfo.messagesSent++;
2333
+ debugInfo.messagesSentDelta++;
2334
+ debugInfo.bytesSent += data.length;
2335
+ debugInfo.bytesSentDelta += data.length;
2336
+ }
2337
+ // Monkey-patch: WebSocket transport
2338
+ if (transport.ws) {
2339
+ const originalOnMessage = transport.ws.onmessage;
2340
+ const ws = transport.ws;
2341
+ transport.ws.onmessage = function (event) {
2342
+ // Clone event data to avoid issues with delayed processing
2343
+ var eventData = event.data;
2344
+ if (eventData instanceof Blob) {
2345
+ eventData = eventData.slice();
2346
+ }
2347
+ else if (eventData instanceof ArrayBuffer) {
2348
+ eventData = eventData.slice(0);
2349
+ }
2350
+ else if (typeof eventData === 'string') {
2351
+ eventData = eventData;
2352
+ }
2353
+ trackReceivedMessage(eventData);
2354
+ // Apply latency simulation for received messages
2355
+ if (preferences.latencySimulation.enabled && preferences.latencySimulation.delay > 0) {
2356
+ setTimeout(function () {
2357
+ // Create a synthetic event-like object
2358
+ var syntheticEvent = {
2359
+ data: eventData,
2360
+ target: ws,
2361
+ currentTarget: ws,
2362
+ type: 'message'
2363
+ };
2364
+ originalOnMessage.call(ws, syntheticEvent);
2365
+ }, preferences.latencySimulation.delay);
2366
+ }
2367
+ else {
2368
+ return originalOnMessage.apply(this, arguments);
2369
+ }
2370
+ };
2371
+ }
2372
+ // Monkey-patch: sending messages through room connection
2373
+ const originalSend = room.connection.send.bind(room.connection);
2374
+ room.connection.send = function (data) {
2375
+ trackSentMessage(data);
2376
+ // Apply latency simulation for sent messages
2377
+ if (preferences.latencySimulation.enabled && preferences.latencySimulation.delay > 0) {
2378
+ var clonedData = data;
2379
+ if (data instanceof ArrayBuffer) {
2380
+ clonedData = data.slice(0);
2381
+ }
2382
+ else if (data instanceof Blob) {
2383
+ clonedData = data.slice(0);
2384
+ }
2385
+ else if (data instanceof Uint8Array || data instanceof DataView || (data.buffer && data.buffer instanceof ArrayBuffer)) {
2386
+ clonedData = new Uint8Array(data).buffer;
2387
+ }
2388
+ setTimeout(function () {
2389
+ originalSend(clonedData);
2390
+ }, preferences.latencySimulation.delay / 2);
2391
+ }
2392
+ else {
2393
+ return originalSend(data);
2394
+ }
2395
+ };
2396
+ updateDebugPanel(uniquePanelId, debugInfo);
2397
+ // Ensure global update interval is running
2398
+ ensureGlobalUpdateInterval();
2399
+ // Clean up on room leave
2400
+ room.onLeave.once(() => {
2401
+ roomDebugInfo.delete(uniquePanelId);
2402
+ var panel = document.getElementById('debug-panel-' + uniquePanelId);
2403
+ if (panel) {
2404
+ panel.remove();
2405
+ repositionDebugPanels();
2406
+ }
2407
+ // Clean up interval if no more panels
2408
+ if (roomDebugInfo.size === 0 && globalUpdateInterval !== null) {
2409
+ clearInterval(globalUpdateInterval);
2410
+ globalUpdateInterval = null;
2411
+ }
2412
+ });
2413
+ return room;
2414
+ }
2415
+ // Store original methods that return rooms
2416
+ var originalJoinOrCreate = Client.Client.prototype.joinOrCreate;
2417
+ var originalJoin = Client.Client.prototype.join;
2418
+ var originalCreate = Client.Client.prototype.create;
2419
+ var originalReconnect = Client.Client.prototype.reconnect;
2420
+ // Patch joinOrCreate
2421
+ Client.Client.prototype.joinOrCreate = function () {
2422
+ var promise = originalJoinOrCreate.apply(this, arguments);
2423
+ return promise.then(function (room) {
2424
+ return patchRoom(room);
2425
+ });
2426
+ };
2427
+ // Patch join
2428
+ Client.Client.prototype.join = function () {
2429
+ var promise = originalJoin.apply(this, arguments);
2430
+ return promise.then(function (room) {
2431
+ return patchRoom(room);
2432
+ });
2433
+ };
2434
+ // Patch create
2435
+ Client.Client.prototype.create = function () {
2436
+ var promise = originalCreate.apply(this, arguments);
2437
+ return promise.then(function (room) {
2438
+ return patchRoom(room);
2439
+ });
2440
+ };
2441
+ // Patch reconnect
2442
+ if (originalReconnect) {
2443
+ Client.Client.prototype.reconnect = function () {
2444
+ var promise = originalReconnect.apply(this, arguments);
2445
+ return promise.then(function (room) {
2446
+ return patchRoom(room);
2447
+ });
2448
+ };
2449
+ }
2450
+ }
2451
+ applyMonkeyPatches();
2452
+ // Initialize only after DOM is ready
2453
+ // (in case script is loaded in HEAD tag)
2454
+ if (document.readyState === 'loading') {
2455
+ document.addEventListener('DOMContentLoaded', initialize);
2456
+ }
2457
+ else {
2458
+ // DOM is already ready
2459
+ initialize();
2460
+ }
2461
+ //# sourceMappingURL=debug.cjs.map