@colyseus/sdk 0.17.0

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