@@ -32,7 +32,7 @@ interface BlockingCoordinator {
32
32
33
33
/**
34
34
* Returns the inline script content that should be injected as a blocking script.
35
- * This script sets up the global coordinator immediately.
35
+ * This script sets up the global coordinator immediately and intercepts all script loading .
36
36
*/
37
37
export function getBlockingCoordinatorScript ( ) : string {
38
38
return `
@@ -86,30 +86,36 @@ export function getBlockingCoordinatorScript(): string {
86
86
87
87
var self = this;
88
88
89
- // Set up load monitoring
90
- var originalOnLoad = scriptElement.onload;
91
- var originalOnError = scriptElement.onerror;
92
-
93
- scriptElement.onload = function(event) {
89
+ // Set up load monitoring with multiple methods for better compatibility
90
+ var handleLoad = function() {
94
91
scriptElement.setAttribute('data-clerk-loaded', 'true');
95
92
self.setState('loaded');
96
- if (originalOnLoad) originalOnLoad.call(this, event);
97
93
};
98
94
99
- scriptElement.onerror = function(event ) {
95
+ var handleError = function() {
100
96
self.setState('error', new Error('ClerkJS failed to load'));
101
- if (originalOnError) originalOnError.call(this, event);
102
97
};
103
98
104
- // Also use addEventListener for better compatibility
105
- scriptElement.addEventListener('load', function() {
106
- scriptElement.setAttribute('data-clerk-loaded', 'true');
107
- self.setState('loaded');
108
- });
99
+ // Use multiple approaches to catch load events
100
+ if (scriptElement.onload !== undefined) {
101
+ var originalOnLoad = scriptElement.onload;
102
+ scriptElement.onload = function(event) {
103
+ handleLoad();
104
+ if (originalOnLoad) originalOnLoad.call(this, event);
105
+ };
106
+ }
109
107
110
- scriptElement.addEventListener('error', function() {
111
- self.setState('error', new Error('ClerkJS failed to load'));
112
- });
108
+ if (scriptElement.onerror !== undefined) {
109
+ var originalOnError = scriptElement.onerror;
110
+ scriptElement.onerror = function(event) {
111
+ handleError();
112
+ if (originalOnError) originalOnError.call(this, event);
113
+ };
114
+ }
115
+
116
+ // Also use addEventListener for better compatibility
117
+ scriptElement.addEventListener('load', handleLoad);
118
+ scriptElement.addEventListener('error', handleError);
113
119
},
114
120
115
121
registerCallback: function(callback) {
@@ -151,54 +157,96 @@ export function getBlockingCoordinatorScript(): string {
151
157
}
152
158
};
153
159
154
- // Intercept script creation to catch ClerkJS scripts before they load
155
- var originalCreateElement = document.createElement;
156
- document.createElement = function(tagName) {
157
- var element = originalCreateElement.apply(this, arguments);
158
-
159
- if (tagName.toLowerCase() === 'script') {
160
- // Set up a property setter to intercept src assignment
161
- var originalSrc = element.src;
162
- var srcSet = false;
163
-
164
- Object.defineProperty(element, 'src', {
165
- get: function() {
166
- return originalSrc;
167
- },
168
- set: function(value) {
169
- originalSrc = value;
170
- srcSet = true;
171
-
172
- // Check if this should be allowed after src is set
173
- // (need to wait for src to be set to make decision)
174
- setTimeout(function() {
175
- if (!coordinator.shouldAllowScript(element)) {
176
- // Prevent the script from loading by clearing its src
177
- element.src = '';
178
- element.remove();
179
- }
180
- }, 0);
181
- },
182
- configurable: true
183
- });
160
+ // Function to check and potentially intercept a script
161
+ function interceptScript(scriptElement) {
162
+ if (scriptElement && scriptElement.tagName === 'SCRIPT') {
163
+ if (scriptElement.hasAttribute('data-clerk-js-script')) {
164
+ return coordinator.shouldAllowScript(scriptElement);
165
+ }
184
166
}
185
-
186
- return element;
187
- };
167
+ return true;
168
+ }
188
169
189
- // Also intercept appendChild to catch scripts added directly
170
+ // Intercept appendChild on all nodes
190
171
var originalAppendChild = Node.prototype.appendChild;
191
172
Node.prototype.appendChild = function(child) {
192
- if (child.tagName === 'SCRIPT' && child.hasAttribute('data-clerk-js-script')) {
193
- if (!coordinator.shouldAllowScript(child)) {
194
- // Return a dummy element to prevent errors
195
- return child;
196
- }
173
+ if (!interceptScript(child)) {
174
+ // Create a dummy script element to return
175
+ var dummy = document.createElement('script');
176
+ dummy.src = child.src;
177
+ Object.keys(child.attributes || {}).forEach(function(key) {
178
+ var attr = child.attributes[key];
179
+ if (attr && attr.name && attr.value) {
180
+ dummy.setAttribute(attr.name, attr.value);
181
+ }
182
+ });
183
+ return dummy;
197
184
}
198
185
199
186
return originalAppendChild.call(this, child);
200
187
};
201
188
189
+ // Intercept insertBefore
190
+ var originalInsertBefore = Node.prototype.insertBefore;
191
+ Node.prototype.insertBefore = function(newNode, referenceNode) {
192
+ if (!interceptScript(newNode)) {
193
+ var dummy = document.createElement('script');
194
+ dummy.src = newNode.src;
195
+ return dummy;
196
+ }
197
+
198
+ return originalInsertBefore.call(this, newNode, referenceNode);
199
+ };
200
+
201
+ // Also watch for scripts being set via innerHTML or similar
202
+ var observer = new MutationObserver(function(mutations) {
203
+ mutations.forEach(function(mutation) {
204
+ if (mutation.type === 'childList') {
205
+ Array.prototype.slice.call(mutation.addedNodes).forEach(function(node) {
206
+ if (node.nodeType === 1) { // Element node
207
+ if (node.tagName === 'SCRIPT' && node.hasAttribute('data-clerk-js-script')) {
208
+ if (!coordinator.shouldAllowScript(node)) {
209
+ node.remove();
210
+ }
211
+ }
212
+
213
+ // Also check children in case scripts are added in bulk
214
+ var scripts = node.querySelectorAll ? node.querySelectorAll('script[data-clerk-js-script]') : [];
215
+ for (var i = 0; i < scripts.length; i++) {
216
+ if (!coordinator.shouldAllowScript(scripts[i])) {
217
+ scripts[i].remove();
218
+ }
219
+ }
220
+ }
221
+ });
222
+ }
223
+ });
224
+ });
225
+
226
+ // Start observing
227
+ if (document.body) {
228
+ observer.observe(document.body, {
229
+ childList: true,
230
+ subtree: true
231
+ });
232
+ } else {
233
+ // If body doesn't exist yet, wait for it
234
+ document.addEventListener('DOMContentLoaded', function() {
235
+ observer.observe(document.body, {
236
+ childList: true,
237
+ subtree: true
238
+ });
239
+ });
240
+ }
241
+
242
+ // Also observe head if it exists
243
+ if (document.head) {
244
+ observer.observe(document.head, {
245
+ childList: true,
246
+ subtree: true
247
+ });
248
+ }
249
+
202
250
// Expose the coordinator globally
203
251
window.__clerkJSBlockingCoordinator = coordinator;
204
252
})();
0 commit comments