@carmaclouds/core 2.3.1

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 (118) hide show
  1. package/dist/cache/CacheManager.d.ts.map +1 -0
  2. package/dist/cache/CacheManager.js +131 -0
  3. package/dist/cache/CacheManager.js.map +1 -0
  4. package/dist/index.d.ts +18 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +22 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/ir/index.d.ts +11 -0
  9. package/dist/ir/index.d.ts.map +1 -0
  10. package/dist/ir/index.js +9 -0
  11. package/dist/ir/index.js.map +1 -0
  12. package/dist/ir/normalize.d.ts +10 -0
  13. package/dist/ir/normalize.d.ts.map +1 -0
  14. package/dist/ir/normalize.js +207 -0
  15. package/dist/ir/normalize.js.map +1 -0
  16. package/dist/ir/persistence.d.ts +26 -0
  17. package/dist/ir/persistence.d.ts.map +1 -0
  18. package/dist/ir/persistence.js +21 -0
  19. package/dist/ir/persistence.js.map +1 -0
  20. package/dist/ir/sync.d.ts +12 -0
  21. package/dist/ir/sync.d.ts.map +1 -0
  22. package/dist/ir/sync.js +36 -0
  23. package/dist/ir/sync.js.map +1 -0
  24. package/dist/ir/types.d.ts +143 -0
  25. package/dist/ir/types.d.ts.map +1 -0
  26. package/dist/ir/types.js +13 -0
  27. package/dist/ir/types.js.map +1 -0
  28. package/dist/ir/views/dnd5e.d.ts +40 -0
  29. package/dist/ir/views/dnd5e.d.ts.map +1 -0
  30. package/dist/ir/views/dnd5e.js +50 -0
  31. package/dist/ir/views/dnd5e.js.map +1 -0
  32. package/dist/render/character.d.ts +19 -0
  33. package/dist/render/character.d.ts.map +1 -0
  34. package/dist/render/character.js +156 -0
  35. package/dist/render/character.js.map +1 -0
  36. package/dist/render/h.d.ts +27 -0
  37. package/dist/render/h.d.ts.map +1 -0
  38. package/dist/render/h.js +64 -0
  39. package/dist/render/h.js.map +1 -0
  40. package/dist/render/index.d.ts +11 -0
  41. package/dist/render/index.d.ts.map +1 -0
  42. package/dist/render/index.js +8 -0
  43. package/dist/render/index.js.map +1 -0
  44. package/dist/render/mount.d.ts +31 -0
  45. package/dist/render/mount.d.ts.map +1 -0
  46. package/dist/render/mount.js +63 -0
  47. package/dist/render/mount.js.map +1 -0
  48. package/dist/supabase/fields.d.ts.map +1 -0
  49. package/dist/supabase/fields.js +120 -0
  50. package/dist/supabase/fields.js.map +1 -0
  51. package/dist/types/character.d.ts.map +1 -0
  52. package/dist/types/character.js +5 -0
  53. package/dist/types/character.js.map +1 -0
  54. package/package.json +73 -0
  55. package/src/browser.js +51 -0
  56. package/src/cache/CacheManager.ts +174 -0
  57. package/src/common/browser-polyfill.js +319 -0
  58. package/src/common/debug.js +123 -0
  59. package/src/common/html-utils.js +134 -0
  60. package/src/common/theme-manager.js +265 -0
  61. package/src/index.ts +25 -0
  62. package/src/ir/__fixtures__/dnd5e-character.json +75962 -0
  63. package/src/ir/__fixtures__/non-dnd-character.json +14218 -0
  64. package/src/ir/index.ts +10 -0
  65. package/src/ir/normalize.ts +245 -0
  66. package/src/ir/persistence.ts +37 -0
  67. package/src/ir/sync.ts +49 -0
  68. package/src/ir/types.ts +161 -0
  69. package/src/ir/views/dnd5e.ts +94 -0
  70. package/src/lib/indexeddb-cache.js +320 -0
  71. package/src/modules/action-announcements.js +102 -0
  72. package/src/modules/action-display.js +1557 -0
  73. package/src/modules/action-executor.js +860 -0
  74. package/src/modules/action-filters.js +167 -0
  75. package/src/modules/action-options.js +117 -0
  76. package/src/modules/card-creator.js +142 -0
  77. package/src/modules/character-portrait.js +169 -0
  78. package/src/modules/character-trait-popups.js +959 -0
  79. package/src/modules/character-traits.js +814 -0
  80. package/src/modules/class-feature-edge-cases.js +1320 -0
  81. package/src/modules/color-utils.js +69 -0
  82. package/src/modules/combat-maneuver-edge-cases.js +660 -0
  83. package/src/modules/companions-manager.js +178 -0
  84. package/src/modules/concentration-tracker.js +178 -0
  85. package/src/modules/data-manager.js +514 -0
  86. package/src/modules/dice-roller.js +719 -0
  87. package/src/modules/effects-manager.js +743 -0
  88. package/src/modules/feature-modals.js +1264 -0
  89. package/src/modules/formula-resolver.js +444 -0
  90. package/src/modules/gm-mode.js +184 -0
  91. package/src/modules/health-modals.js +399 -0
  92. package/src/modules/hp-management.js +752 -0
  93. package/src/modules/inventory-manager.js +242 -0
  94. package/src/modules/macro-system.js +825 -0
  95. package/src/modules/notification-system.js +92 -0
  96. package/src/modules/racial-feature-edge-cases.js +746 -0
  97. package/src/modules/resource-manager.js +775 -0
  98. package/src/modules/sheet-builder.js +654 -0
  99. package/src/modules/spell-action-modals.js +583 -0
  100. package/src/modules/spell-cards.js +602 -0
  101. package/src/modules/spell-casting.js +723 -0
  102. package/src/modules/spell-display.js +314 -0
  103. package/src/modules/spell-edge-cases.js +509 -0
  104. package/src/modules/spell-macros.js +201 -0
  105. package/src/modules/spell-modals.js +1221 -0
  106. package/src/modules/spell-slots.js +224 -0
  107. package/src/modules/status-bar-bridge.js +101 -0
  108. package/src/modules/ui-utilities.js +284 -0
  109. package/src/modules/warlock-invocations.js +219 -0
  110. package/src/modules/window-management.js +211 -0
  111. package/src/render/character.ts +234 -0
  112. package/src/render/h.ts +74 -0
  113. package/src/render/index.ts +10 -0
  114. package/src/render/mount.ts +94 -0
  115. package/src/supabase/client.js +1383 -0
  116. package/src/supabase/config.js +60 -0
  117. package/src/supabase/fields.ts +129 -0
  118. package/src/types/character.ts +85 -0
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Browser API Compatibility Layer - Chrome & Firefox Support
3
+ *
4
+ * This provides a consistent browserAPI interface across Chrome and Firefox.
5
+ * Firefox uses the 'browser' namespace (Promise-based), while Chrome uses 'chrome' (callback-based).
6
+ */
7
+
8
+ console.log('🌐 Loading browser polyfill...');
9
+
10
+ // Use 'self' for service workers, 'window' for content scripts/popups
11
+ const globalScope = typeof window !== 'undefined' ? window : self;
12
+
13
+ // Detect browser and use appropriate API
14
+ let browserAPI;
15
+
16
+ if (typeof browser !== 'undefined' && browser.runtime) {
17
+ // Firefox - uses native Promise-based API
18
+ console.log('🦊 Detected Firefox');
19
+ browserAPI = browser;
20
+ } else if (typeof chrome !== 'undefined' && chrome.runtime) {
21
+ // Chrome - wrap callback-based API to be consistent with Firefox
22
+ console.log('🌐 Detected Chrome');
23
+ // Helper to check if Chrome extension context is valid
24
+ const isChromeContextValid = () => {
25
+ try {
26
+ return chrome && chrome.runtime && chrome.runtime.id;
27
+ } catch (error) {
28
+ return false;
29
+ }
30
+ };
31
+
32
+ browserAPI = {
33
+ runtime: {
34
+ sendMessage: (message, callback) => {
35
+ // If callback is provided, use callback-based API
36
+ if (typeof callback === 'function') {
37
+ try {
38
+ if (!isChromeContextValid()) {
39
+ console.error('❌ Extension context invalidated');
40
+ callback(null);
41
+ return;
42
+ }
43
+ chrome.runtime.sendMessage(message, callback);
44
+ } catch (error) {
45
+ // Handle "Extension context invalidated" error in Chrome
46
+ console.error('❌ Extension context error:', error.message);
47
+ callback(null);
48
+ }
49
+ return;
50
+ }
51
+ // Otherwise return a Promise
52
+ return new Promise((resolve, reject) => {
53
+ try {
54
+ if (!isChromeContextValid()) {
55
+ reject(new Error('Extension context invalidated'));
56
+ return;
57
+ }
58
+ chrome.runtime.sendMessage(message, (response) => {
59
+ if (chrome.runtime.lastError) {
60
+ reject(new Error(chrome.runtime.lastError.message));
61
+ } else {
62
+ resolve(response);
63
+ }
64
+ });
65
+ } catch (error) {
66
+ // Handle "Extension context invalidated" error in Chrome
67
+ console.error('❌ Extension context error:', error.message);
68
+ reject(error);
69
+ }
70
+ });
71
+ },
72
+ onMessage: chrome.runtime.onMessage,
73
+ getURL: (path) => {
74
+ try {
75
+ if (!isChromeContextValid()) return null;
76
+ return chrome.runtime.getURL(path);
77
+ } catch (error) {
78
+ console.error('❌ Extension context error:', error.message);
79
+ return null;
80
+ }
81
+ },
82
+ getManifest: () => {
83
+ try {
84
+ if (!isChromeContextValid()) return null;
85
+ return chrome.runtime.getManifest();
86
+ } catch (error) {
87
+ console.error('❌ Extension context error:', error.message);
88
+ return null;
89
+ }
90
+ },
91
+ get id() {
92
+ try {
93
+ if (!isChromeContextValid()) return null;
94
+ return chrome.runtime.id;
95
+ } catch (error) {
96
+ console.error('❌ Extension context error:', error.message);
97
+ return null;
98
+ }
99
+ },
100
+ get lastError() {
101
+ try {
102
+ if (!isChromeContextValid()) return null;
103
+ return chrome.runtime.lastError;
104
+ } catch (error) {
105
+ return null;
106
+ }
107
+ },
108
+ connectNative: (application) => {
109
+ try {
110
+ if (!isChromeContextValid()) {
111
+ console.error('❌ Extension context invalidated');
112
+ return null;
113
+ }
114
+ const port = chrome.runtime.connectNative(application);
115
+ // Check for immediate errors
116
+ if (chrome.runtime.lastError) {
117
+ console.warn('⚠️ Native messaging error:', chrome.runtime.lastError.message);
118
+ return null;
119
+ }
120
+ return port;
121
+ } catch (error) {
122
+ console.error('❌ Native messaging error:', error.message);
123
+ return null;
124
+ }
125
+ }
126
+ },
127
+ storage: {
128
+ local: {
129
+ get: (keys) => {
130
+ return new Promise((resolve, reject) => {
131
+ try {
132
+ if (!isChromeContextValid()) {
133
+ reject(new Error('Extension context invalidated'));
134
+ return;
135
+ }
136
+ chrome.storage.local.get(keys, (result) => {
137
+ if (chrome.runtime.lastError) {
138
+ reject(new Error(chrome.runtime.lastError.message));
139
+ } else {
140
+ resolve(result);
141
+ }
142
+ });
143
+ } catch (error) {
144
+ reject(new Error('Extension context invalidated'));
145
+ }
146
+ });
147
+ },
148
+ set: (items) => {
149
+ return new Promise((resolve, reject) => {
150
+ try {
151
+ if (!isChromeContextValid()) {
152
+ reject(new Error('Extension context invalidated'));
153
+ return;
154
+ }
155
+ chrome.storage.local.set(items, () => {
156
+ if (chrome.runtime.lastError) {
157
+ reject(new Error(chrome.runtime.lastError.message));
158
+ } else {
159
+ resolve();
160
+ }
161
+ });
162
+ } catch (error) {
163
+ reject(new Error('Extension context invalidated'));
164
+ }
165
+ });
166
+ },
167
+ remove: (keys) => {
168
+ return new Promise((resolve, reject) => {
169
+ try {
170
+ if (!isChromeContextValid()) {
171
+ reject(new Error('Extension context invalidated'));
172
+ return;
173
+ }
174
+ chrome.storage.local.remove(keys, () => {
175
+ if (chrome.runtime.lastError) {
176
+ reject(new Error(chrome.runtime.lastError.message));
177
+ } else {
178
+ resolve();
179
+ }
180
+ });
181
+ } catch (error) {
182
+ reject(new Error('Extension context invalidated'));
183
+ }
184
+ });
185
+ },
186
+ clear: () => {
187
+ return new Promise((resolve, reject) => {
188
+ try {
189
+ if (!isChromeContextValid()) {
190
+ reject(new Error('Extension context invalidated'));
191
+ return;
192
+ }
193
+ chrome.storage.local.clear(() => {
194
+ if (chrome.runtime.lastError) {
195
+ reject(new Error(chrome.runtime.lastError.message));
196
+ } else {
197
+ resolve();
198
+ }
199
+ });
200
+ } catch (error) {
201
+ reject(new Error('Extension context invalidated'));
202
+ }
203
+ });
204
+ }
205
+ }
206
+ },
207
+ tabs: {
208
+ query: (queryInfo) => {
209
+ return new Promise((resolve, reject) => {
210
+ try {
211
+ if (!isChromeContextValid()) {
212
+ reject(new Error('Extension context invalidated'));
213
+ return;
214
+ }
215
+ chrome.tabs.query(queryInfo, (tabs) => {
216
+ if (chrome.runtime.lastError) {
217
+ reject(new Error(chrome.runtime.lastError.message));
218
+ } else {
219
+ resolve(tabs);
220
+ }
221
+ });
222
+ } catch (error) {
223
+ reject(new Error('Extension context invalidated'));
224
+ }
225
+ });
226
+ },
227
+ sendMessage: (tabId, message, callback) => {
228
+ // If callback is provided, use callback-based API
229
+ if (typeof callback === 'function') {
230
+ try {
231
+ if (!isChromeContextValid()) {
232
+ console.error('❌ Extension context invalidated');
233
+ callback(null);
234
+ return;
235
+ }
236
+ chrome.tabs.sendMessage(tabId, message, callback);
237
+ } catch (error) {
238
+ // Handle "Extension context invalidated" error in Chrome
239
+ console.error('❌ Extension context error:', error.message);
240
+ callback(null);
241
+ }
242
+ return;
243
+ }
244
+ // Otherwise return a Promise
245
+ return new Promise((resolve, reject) => {
246
+ try {
247
+ if (!isChromeContextValid()) {
248
+ reject(new Error('Extension context invalidated'));
249
+ return;
250
+ }
251
+ chrome.tabs.sendMessage(tabId, message, (response) => {
252
+ if (chrome.runtime.lastError) {
253
+ reject(new Error(chrome.runtime.lastError.message));
254
+ } else {
255
+ resolve(response);
256
+ }
257
+ });
258
+ } catch (error) {
259
+ // Handle "Extension context invalidated" error in Chrome
260
+ console.error('❌ Extension context error:', error.message);
261
+ reject(error);
262
+ }
263
+ });
264
+ },
265
+ create: (createProperties) => {
266
+ return new Promise((resolve, reject) => {
267
+ try {
268
+ if (!isChromeContextValid()) {
269
+ reject(new Error('Extension context invalidated'));
270
+ return;
271
+ }
272
+ chrome.tabs.create(createProperties, (tab) => {
273
+ if (chrome.runtime.lastError) {
274
+ reject(new Error(chrome.runtime.lastError.message));
275
+ } else {
276
+ resolve(tab);
277
+ }
278
+ });
279
+ } catch (error) {
280
+ reject(new Error('Extension context invalidated'));
281
+ }
282
+ });
283
+ },
284
+ update: (tabId, updateProperties) => {
285
+ return new Promise((resolve, reject) => {
286
+ try {
287
+ if (!isChromeContextValid()) {
288
+ reject(new Error('Extension context invalidated'));
289
+ return;
290
+ }
291
+ chrome.tabs.update(tabId, updateProperties, (tab) => {
292
+ if (chrome.runtime.lastError) {
293
+ reject(new Error(chrome.runtime.lastError.message));
294
+ } else {
295
+ resolve(tab);
296
+ }
297
+ });
298
+ } catch (error) {
299
+ reject(new Error('Extension context invalidated'));
300
+ }
301
+ });
302
+ }
303
+ }
304
+ };
305
+ } else {
306
+ console.error('❌ FATAL: No browser API available!');
307
+ throw new Error('No browser API available');
308
+ }
309
+
310
+ // Expose as browserAPI for consistent naming
311
+ globalScope.browserAPI = browserAPI;
312
+
313
+ // Verify API is available
314
+ if (!globalScope.browserAPI || !globalScope.browserAPI.runtime) {
315
+ console.error('❌ FATAL: Browser API not available!');
316
+ throw new Error('Browser API not available');
317
+ }
318
+
319
+ console.log('✅ Browser API ready:', (typeof browser !== 'undefined' && browserAPI === browser) ? 'Firefox' : 'Chrome');
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Debug Logging Utility
3
+ *
4
+ * Centralized logging system that can be enabled/disabled.
5
+ * In production, debug logs are suppressed to improve performance.
6
+ * Errors are always logged regardless of debug mode.
7
+ */
8
+
9
+ // Debug mode can be controlled by:
10
+ // 1. Build process (set DEBUG = false for production)
11
+ // 2. Extension storage (allow users to enable debug mode)
12
+ // 3. URL parameter (for testing)
13
+ // NOTE: This value is replaced by the build script.
14
+ // - Development builds: DEBUG = true
15
+ // - Production builds: DEBUG = false
16
+ // - Use --dev flag to force DEBUG = true in production build
17
+ const DEBUG = true; // Hardcoded for debugging
18
+
19
+ /**
20
+ * Debug logger instance
21
+ * Provides methods for different log levels
22
+ */
23
+ const debug = {
24
+ /**
25
+ * General debug information
26
+ * Only logged when DEBUG = true
27
+ */
28
+ log: (...args) => {
29
+ if (DEBUG) {
30
+ console.log(...args);
31
+ }
32
+ },
33
+
34
+ /**
35
+ * Warning messages
36
+ * Only logged when DEBUG = true
37
+ */
38
+ warn: (...args) => {
39
+ if (DEBUG) {
40
+ console.warn(...args);
41
+ }
42
+ },
43
+
44
+ /**
45
+ * Error messages
46
+ * ALWAYS logged, regardless of DEBUG mode
47
+ */
48
+ error: (...args) => {
49
+ console.error(...args);
50
+ },
51
+
52
+ /**
53
+ * Info messages (less noisy than log)
54
+ * Only logged when DEBUG = true
55
+ */
56
+ info: (...args) => {
57
+ if (DEBUG) {
58
+ console.info(...args);
59
+ }
60
+ },
61
+
62
+ /**
63
+ * Group logs together for better readability
64
+ * Only when DEBUG = true
65
+ */
66
+ group: (label, ...args) => {
67
+ if (DEBUG) {
68
+ console.group(label, ...args);
69
+ }
70
+ },
71
+
72
+ groupEnd: () => {
73
+ if (DEBUG) {
74
+ console.groupEnd();
75
+ }
76
+ },
77
+
78
+ /**
79
+ * Table output for structured data
80
+ * Only when DEBUG = true
81
+ */
82
+ table: (data) => {
83
+ if (DEBUG) {
84
+ console.table(data);
85
+ }
86
+ },
87
+
88
+ /**
89
+ * Performance timing
90
+ * Only when DEBUG = true
91
+ */
92
+ time: (label) => {
93
+ if (DEBUG) {
94
+ console.time(label);
95
+ }
96
+ },
97
+
98
+ timeEnd: (label) => {
99
+ if (DEBUG) {
100
+ console.timeEnd(label);
101
+ }
102
+ },
103
+
104
+ /**
105
+ * Check if debug mode is enabled
106
+ */
107
+ isEnabled: () => DEBUG
108
+ };
109
+
110
+ // Export for use in other modules
111
+ if (typeof module !== 'undefined' && module.exports) {
112
+ module.exports = debug;
113
+ }
114
+
115
+ // Make available globally for content scripts
116
+ if (typeof window !== 'undefined') {
117
+ window.debug = debug;
118
+ }
119
+
120
+ // Make available for service workers
121
+ if (typeof self !== 'undefined' && !self.window) {
122
+ self.debug = debug;
123
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * HTML Utilities
3
+ * Functions for safe HTML handling and XSS prevention
4
+ * Configurable for different projects (owlcloud, rollcloud, etc.)
5
+ */
6
+
7
+ /**
8
+ * Escape HTML special characters to prevent XSS
9
+ * @param {string} str - String to escape
10
+ * @returns {string} Escaped string safe for innerHTML
11
+ */
12
+ function escapeHTML(str) {
13
+ if (!str) return '';
14
+ if (typeof str !== 'string') str = String(str);
15
+
16
+ const div = document.createElement('div');
17
+ div.textContent = str;
18
+ return div.innerHTML;
19
+ }
20
+
21
+ /**
22
+ * Validate postMessage event origin
23
+ * @param {MessageEvent} event - The message event
24
+ * @param {string[]} allowedOrigins - Array of allowed origins (can include * wildcards)
25
+ * @returns {boolean} True if origin is allowed
26
+ */
27
+ function validateMessageOrigin(event, allowedOrigins) {
28
+ if (!event || !event.origin) {
29
+ debug.warn('❌ Invalid message event or missing origin');
30
+ return false;
31
+ }
32
+
33
+ const origin = event.origin;
34
+
35
+ for (const allowed of allowedOrigins) {
36
+ // Handle wildcard subdomains (e.g., "https://*.dicecloud.com")
37
+ if (allowed.includes('*')) {
38
+ // Convert wildcard to regex pattern
39
+ const pattern = allowed
40
+ .replace(/\./g, '\\.') // Escape dots
41
+ .replace(/\*/g, '[^.]+'); // * matches subdomain
42
+ const regex = new RegExp('^' + pattern + '$');
43
+
44
+ if (regex.test(origin)) {
45
+ debug.log('✅ Message origin validated:', origin);
46
+ return true;
47
+ }
48
+ } else {
49
+ // Exact match
50
+ if (origin === allowed) {
51
+ debug.log('✅ Message origin validated:', origin);
52
+ return true;
53
+ }
54
+ }
55
+ }
56
+
57
+ debug.warn('❌ Rejected message from untrusted origin:', origin);
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * Create configured HTML utilities for a specific project
63
+ * @param {Object} config - Configuration object
64
+ * @param {string[]} config.allowedOrigins - Array of allowed origins for postMessage
65
+ * @param {string} config.defaultOrigin - Default origin for postMessage
66
+ * @returns {Object} Configured HTML utilities
67
+ */
68
+ function createHTMLUtils(config = {}) {
69
+ const allowedOrigins = config.allowedOrigins || [
70
+ 'https://dicecloud.com',
71
+ 'https://*.dicecloud.com'
72
+ ];
73
+ const defaultOrigin = config.defaultOrigin || 'https://dicecloud.com';
74
+
75
+ return {
76
+ escapeHTML,
77
+ validateMessageOrigin,
78
+ allowedOrigins,
79
+
80
+ /**
81
+ * Safe wrapper for postMessage with explicit origin
82
+ * @param {Window} target - Target window
83
+ * @param {*} message - Message to send
84
+ * @param {string} origin - Target origin (defaults to configured default)
85
+ */
86
+ safePostMessage(target, message, origin = defaultOrigin) {
87
+ if (!target || target.closed) {
88
+ debug.warn('⚠️ Target window is closed or invalid');
89
+ return false;
90
+ }
91
+
92
+ try {
93
+ target.postMessage(message, origin);
94
+ debug.log('📤 Message sent to:', origin);
95
+ return true;
96
+ } catch (error) {
97
+ debug.error('❌ Failed to send message:', error);
98
+ return false;
99
+ }
100
+ },
101
+
102
+ /**
103
+ * Create a safe message handler with origin validation
104
+ * @param {Function} handler - Message handler function (receives validated event)
105
+ * @param {string[]} customAllowedOrigins - Optional custom allowed origins
106
+ * @returns {Function} Wrapped handler with validation
107
+ */
108
+ createSafeMessageHandler(handler, customAllowedOrigins = null) {
109
+ const origins = customAllowedOrigins || allowedOrigins;
110
+ return function(event) {
111
+ if (!validateMessageOrigin(event, origins)) {
112
+ return; // Reject invalid origins
113
+ }
114
+
115
+ // Call original handler with validated event
116
+ handler(event);
117
+ };
118
+ }
119
+ };
120
+ }
121
+
122
+ // Export for use in other modules
123
+ if (typeof module !== 'undefined' && module.exports) {
124
+ module.exports = {
125
+ escapeHTML,
126
+ validateMessageOrigin,
127
+ createHTMLUtils
128
+ };
129
+ }
130
+
131
+ // Make factory available globally
132
+ if (typeof window !== 'undefined') {
133
+ window.createHTMLUtils = createHTMLUtils;
134
+ }