@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.
- package/dist/cache/CacheManager.d.ts.map +1 -0
- package/dist/cache/CacheManager.js +131 -0
- package/dist/cache/CacheManager.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/index.d.ts +11 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +9 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/ir/normalize.d.ts +10 -0
- package/dist/ir/normalize.d.ts.map +1 -0
- package/dist/ir/normalize.js +207 -0
- package/dist/ir/normalize.js.map +1 -0
- package/dist/ir/persistence.d.ts +26 -0
- package/dist/ir/persistence.d.ts.map +1 -0
- package/dist/ir/persistence.js +21 -0
- package/dist/ir/persistence.js.map +1 -0
- package/dist/ir/sync.d.ts +12 -0
- package/dist/ir/sync.d.ts.map +1 -0
- package/dist/ir/sync.js +36 -0
- package/dist/ir/sync.js.map +1 -0
- package/dist/ir/types.d.ts +143 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/ir/types.js +13 -0
- package/dist/ir/types.js.map +1 -0
- package/dist/ir/views/dnd5e.d.ts +40 -0
- package/dist/ir/views/dnd5e.d.ts.map +1 -0
- package/dist/ir/views/dnd5e.js +50 -0
- package/dist/ir/views/dnd5e.js.map +1 -0
- package/dist/render/character.d.ts +19 -0
- package/dist/render/character.d.ts.map +1 -0
- package/dist/render/character.js +156 -0
- package/dist/render/character.js.map +1 -0
- package/dist/render/h.d.ts +27 -0
- package/dist/render/h.d.ts.map +1 -0
- package/dist/render/h.js +64 -0
- package/dist/render/h.js.map +1 -0
- package/dist/render/index.d.ts +11 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +8 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/mount.d.ts +31 -0
- package/dist/render/mount.d.ts.map +1 -0
- package/dist/render/mount.js +63 -0
- package/dist/render/mount.js.map +1 -0
- package/dist/supabase/fields.d.ts.map +1 -0
- package/dist/supabase/fields.js +120 -0
- package/dist/supabase/fields.js.map +1 -0
- package/dist/types/character.d.ts.map +1 -0
- package/dist/types/character.js +5 -0
- package/dist/types/character.js.map +1 -0
- package/package.json +73 -0
- package/src/browser.js +51 -0
- package/src/cache/CacheManager.ts +174 -0
- package/src/common/browser-polyfill.js +319 -0
- package/src/common/debug.js +123 -0
- package/src/common/html-utils.js +134 -0
- package/src/common/theme-manager.js +265 -0
- package/src/index.ts +25 -0
- package/src/ir/__fixtures__/dnd5e-character.json +75962 -0
- package/src/ir/__fixtures__/non-dnd-character.json +14218 -0
- package/src/ir/index.ts +10 -0
- package/src/ir/normalize.ts +245 -0
- package/src/ir/persistence.ts +37 -0
- package/src/ir/sync.ts +49 -0
- package/src/ir/types.ts +161 -0
- package/src/ir/views/dnd5e.ts +94 -0
- package/src/lib/indexeddb-cache.js +320 -0
- package/src/modules/action-announcements.js +102 -0
- package/src/modules/action-display.js +1557 -0
- package/src/modules/action-executor.js +860 -0
- package/src/modules/action-filters.js +167 -0
- package/src/modules/action-options.js +117 -0
- package/src/modules/card-creator.js +142 -0
- package/src/modules/character-portrait.js +169 -0
- package/src/modules/character-trait-popups.js +959 -0
- package/src/modules/character-traits.js +814 -0
- package/src/modules/class-feature-edge-cases.js +1320 -0
- package/src/modules/color-utils.js +69 -0
- package/src/modules/combat-maneuver-edge-cases.js +660 -0
- package/src/modules/companions-manager.js +178 -0
- package/src/modules/concentration-tracker.js +178 -0
- package/src/modules/data-manager.js +514 -0
- package/src/modules/dice-roller.js +719 -0
- package/src/modules/effects-manager.js +743 -0
- package/src/modules/feature-modals.js +1264 -0
- package/src/modules/formula-resolver.js +444 -0
- package/src/modules/gm-mode.js +184 -0
- package/src/modules/health-modals.js +399 -0
- package/src/modules/hp-management.js +752 -0
- package/src/modules/inventory-manager.js +242 -0
- package/src/modules/macro-system.js +825 -0
- package/src/modules/notification-system.js +92 -0
- package/src/modules/racial-feature-edge-cases.js +746 -0
- package/src/modules/resource-manager.js +775 -0
- package/src/modules/sheet-builder.js +654 -0
- package/src/modules/spell-action-modals.js +583 -0
- package/src/modules/spell-cards.js +602 -0
- package/src/modules/spell-casting.js +723 -0
- package/src/modules/spell-display.js +314 -0
- package/src/modules/spell-edge-cases.js +509 -0
- package/src/modules/spell-macros.js +201 -0
- package/src/modules/spell-modals.js +1221 -0
- package/src/modules/spell-slots.js +224 -0
- package/src/modules/status-bar-bridge.js +101 -0
- package/src/modules/ui-utilities.js +284 -0
- package/src/modules/warlock-invocations.js +219 -0
- package/src/modules/window-management.js +211 -0
- package/src/render/character.ts +234 -0
- package/src/render/h.ts +74 -0
- package/src/render/index.ts +10 -0
- package/src/render/mount.ts +94 -0
- package/src/supabase/client.js +1383 -0
- package/src/supabase/config.js +60 -0
- package/src/supabase/fields.ts +129 -0
- 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
|
+
}
|