|
48 | 48 | // Ensure search results stay on developers.procore.com under /documentation |
49 | 49 | var basePath = "/documentation"; |
50 | 50 |
|
| 51 | + // Helper function to check if a hostname matches a domain (handles subdomains correctly) |
| 52 | + function isDomainMatch(hostname, domain) { |
| 53 | + if (!hostname || !domain) return false; |
| 54 | + // Exact match |
| 55 | + if (hostname === domain) return true; |
| 56 | + // Subdomain match: hostname ends with '.' + domain |
| 57 | + // This prevents matching malicious domains like 'malicious-developers.procore.com' |
| 58 | + return hostname.endsWith('.' + domain); |
| 59 | + } |
| 60 | + |
| 61 | + // Helper function to normalize origin (remove trailing slash, ensure proper format) |
| 62 | + function normalizeOrigin(origin) { |
| 63 | + if (!origin) return null; |
| 64 | + // Remove trailing slash if present |
| 65 | + origin = origin.replace(/\/+$/, ''); |
| 66 | + // Ensure it's a valid origin format (protocol://host) |
| 67 | + try { |
| 68 | + var url = new URL(origin); |
| 69 | + var hostname = url.hostname; |
| 70 | + |
| 71 | + // Check hostname against known domains (parse URL first, then check hostname) |
| 72 | + if (isDomainMatch(hostname, 'developers.procore.com')) { |
| 73 | + return 'https://developers.procore.com'; |
| 74 | + } |
| 75 | + if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('127.0.0.1') || hostname.startsWith('localhost')) { |
| 76 | + return 'http://' + hostname; |
| 77 | + } |
| 78 | + |
| 79 | + return url.origin; |
| 80 | + } catch (e) { |
| 81 | + // If it's not a full URL, try to construct it |
| 82 | + if (origin.startsWith('http://') || origin.startsWith('https://')) { |
| 83 | + // Try parsing again with the protocol |
| 84 | + try { |
| 85 | + var url = new URL(origin); |
| 86 | + var hostname = url.hostname; |
| 87 | + |
| 88 | + if (isDomainMatch(hostname, 'developers.procore.com')) { |
| 89 | + return 'https://developers.procore.com'; |
| 90 | + } |
| 91 | + if (hostname === 'localhost' || hostname === '127.0.0.1') { |
| 92 | + return 'http://' + hostname; |
| 93 | + } |
| 94 | + |
| 95 | + return url.origin; |
| 96 | + } catch (e2) { |
| 97 | + return origin; |
| 98 | + } |
| 99 | + } |
| 100 | + // If it's just a hostname string, check it directly |
| 101 | + if (isDomainMatch(origin, 'developers.procore.com')) { |
| 102 | + return 'https://developers.procore.com'; |
| 103 | + } |
| 104 | + if (origin === 'localhost' || origin === '127.0.0.1' || origin.startsWith('localhost') || origin.startsWith('127.0.0.1')) { |
| 105 | + return 'http://' + origin.replace(/^https?:\/\//, ''); |
| 106 | + } |
| 107 | + return origin; |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + // Function to get parent origin (works for both same-origin and cross-origin) |
| 112 | + // Note: Production is cross-origin (procore.github.io -> developers.procore.com) |
| 113 | + function getParentOrigin() { |
| 114 | + if (window.self === window.top) { |
| 115 | + return null; // Not in an iframe |
| 116 | + } |
| 117 | + |
| 118 | + var parentOrigin = null; |
| 119 | + |
| 120 | + try { |
| 121 | + // For same-origin iframes, we can access window.top.location.origin |
| 122 | + // This will fail in production (cross-origin) and fall through to catch block |
| 123 | + if (window.top.location.origin) { |
| 124 | + parentOrigin = window.top.location.origin; |
| 125 | + console.log("getParentOrigin: Using window.top.location.origin:", parentOrigin); |
| 126 | + return parentOrigin; |
| 127 | + } |
| 128 | + } catch (e) { |
| 129 | + // Cross-origin: can't access window.top.location (production scenario) |
| 130 | + // Use other methods to determine parent origin |
| 131 | + console.log("getParentOrigin: Cross-origin detected, trying alternative methods"); |
| 132 | + |
| 133 | + // Try ancestorOrigins API (modern browsers) |
| 134 | + if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) { |
| 135 | + parentOrigin = normalizeOrigin(window.location.ancestorOrigins[0]); |
| 136 | + console.log("getParentOrigin: Using ancestorOrigins[0] (normalized):", parentOrigin); |
| 137 | + if (parentOrigin) return parentOrigin; |
| 138 | + } |
| 139 | + |
| 140 | + // Try document.referrer |
| 141 | + if (document.referrer) { |
| 142 | + try { |
| 143 | + parentOrigin = normalizeOrigin(new URL(document.referrer).origin); |
| 144 | + console.log("getParentOrigin: Using document.referrer origin (normalized):", parentOrigin); |
| 145 | + if (parentOrigin) return parentOrigin; |
| 146 | + } catch (err) { |
| 147 | + console.error("getParentOrigin: Error parsing referrer origin:", err, "referrer:", document.referrer); |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + // Fallback: Use Jekyll config value (from _config.yml, exposed via window.JEKYLL_CONFIG) |
| 152 | + // This is the production parent origin: developers.procore.com |
| 153 | + if (typeof window.JEKYLL_CONFIG !== 'undefined' && window.JEKYLL_CONFIG.url) { |
| 154 | + try { |
| 155 | + parentOrigin = normalizeOrigin(new URL(window.JEKYLL_CONFIG.url).origin); |
| 156 | + console.log("getParentOrigin: Using JEKYLL_CONFIG.url as fallback (normalized):", parentOrigin); |
| 157 | + if (parentOrigin) return parentOrigin; |
| 158 | + } catch (err) { |
| 159 | + console.error("getParentOrigin: Error parsing JEKYLL_CONFIG.url:", err); |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + // Hardcoded fallback for production |
| 164 | + // If we're on procore.github.io, the parent is likely developers.procore.com |
| 165 | + if (window.location.hostname === 'procore.github.io' || |
| 166 | + window.location.hostname.endsWith('.github.io')) { |
| 167 | + parentOrigin = normalizeOrigin('https://developers.procore.com'); |
| 168 | + console.log("getParentOrigin: Using hardcoded production fallback (normalized):", parentOrigin); |
| 169 | + if (parentOrigin) return parentOrigin; |
| 170 | + } |
| 171 | + |
| 172 | + console.warn("getParentOrigin: Could not determine parent origin. Available info:", { |
| 173 | + ancestorOrigins: window.location.ancestorOrigins, |
| 174 | + referrer: document.referrer, |
| 175 | + hostname: window.location.hostname, |
| 176 | + jekyllConfig: typeof window.JEKYLL_CONFIG !== 'undefined' ? window.JEKYLL_CONFIG : 'undefined' |
| 177 | + }); |
| 178 | + } |
| 179 | + |
| 180 | + return parentOrigin; |
| 181 | + } |
| 182 | + |
| 183 | + // Function to update parent window URL (works for both same-origin and cross-origin) |
| 184 | + // Note: Production is cross-origin, so postMessage is required |
| 185 | + function updateParentUrl(path, parentOrigin) { |
| 186 | + if (!parentOrigin) { |
| 187 | + console.warn("Cannot update parent URL: parentOrigin is null"); |
| 188 | + return; |
| 189 | + } |
| 190 | + |
| 191 | + var fullUrl = parentOrigin + path; |
| 192 | + console.log("Updating parent URL:", { path: path, parentOrigin: parentOrigin, fullUrl: fullUrl }); |
| 193 | + |
| 194 | + // Try direct access first (same-origin scenarios only, e.g., reverse proxy testing) |
| 195 | + // This will fail in production (cross-origin: procore.github.io -> developers.procore.com) |
| 196 | + try { |
| 197 | + if (window.top.location.origin === parentOrigin) { |
| 198 | + // Same-origin: can directly update parent URL (testing scenarios only) |
| 199 | + console.log("Using direct access (same-origin) to update parent URL"); |
| 200 | + window.top.history.pushState({}, '', fullUrl); |
| 201 | + return; |
| 202 | + } |
| 203 | + } catch (e) { |
| 204 | + // Cross-origin: can't access window.top.location (production scenario) |
| 205 | + // Must use postMessage for cross-origin communication |
| 206 | + console.log("Cannot use direct access (cross-origin), using postMessage"); |
| 207 | + } |
| 208 | + |
| 209 | + // Fallback: use postMessage (required for production cross-origin scenario) |
| 210 | + try { |
| 211 | + console.log("Sending postMessage to parent:", { type: 'documentationNavigation', path: path, fullUrl: fullUrl }); |
| 212 | + window.parent.postMessage({ |
| 213 | + type: 'documentationNavigation', |
| 214 | + path: path, |
| 215 | + fullUrl: fullUrl |
| 216 | + }, parentOrigin); |
| 217 | + } catch (err) { |
| 218 | + console.error("Could not send navigation message to parent:", err); |
| 219 | + } |
| 220 | + } |
| 221 | + |
51 | 222 | // Function to rewrite search result links for iframe display |
52 | 223 | function rewriteSearchResultLinks() { |
53 | 224 | if (window.self !== window.top) { |
54 | 225 | try { |
55 | | - var parentOrigin = window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0 |
56 | | - ? window.location.ancestorOrigins[0] |
57 | | - : document.referrer ? new URL(document.referrer).origin : null; |
| 226 | + var parentOrigin = getParentOrigin(); |
58 | 227 |
|
59 | 228 | if (parentOrigin) { |
60 | 229 | $("#results-container a").each(function() { |
|
137 | 306 | // If in iframe and link points to parent origin, navigate within iframe |
138 | 307 | if (window.self !== window.top) { |
139 | 308 | try { |
140 | | - var parentOrigin = window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0 |
141 | | - ? window.location.ancestorOrigins[0] |
142 | | - : document.referrer ? new URL(document.referrer).origin : null; |
| 309 | + var parentOrigin = getParentOrigin(); |
143 | 310 |
|
144 | 311 | if (parentOrigin && currentHref.startsWith(parentOrigin)) { |
145 | 312 | e.preventDefault(); |
146 | 313 | var path = new URL(currentHref).pathname + new URL(currentHref).search + new URL(currentHref).hash; |
147 | 314 | // Update iframe location |
148 | 315 | window.location.href = path; |
149 | | - // Update parent window URL via postMessage (cross-origin safe) |
150 | | - try { |
151 | | - window.parent.postMessage({ |
152 | | - type: 'documentationNavigation', |
153 | | - path: path, |
154 | | - fullUrl: parentOrigin + path |
155 | | - }, parentOrigin); |
156 | | - } catch (e) { |
157 | | - console.warn("Could not send navigation message to parent:", e); |
158 | | - } |
| 316 | + // Update parent window URL |
| 317 | + updateParentUrl(path, parentOrigin); |
159 | 318 | return false; |
160 | 319 | } |
161 | 320 | } catch (err) { |
|
186 | 345 | // If in an iframe, rewrite link hrefs for hover display but intercept clicks to navigate within iframe |
187 | 346 | if (window.self !== window.top) { |
188 | 347 | try { |
189 | | - var parentOrigin = window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0 |
190 | | - ? window.location.ancestorOrigins[0] |
191 | | - : document.referrer ? new URL(document.referrer).origin : null; |
| 348 | + var parentOrigin = getParentOrigin(); |
192 | 349 |
|
193 | 350 | if (parentOrigin) { |
194 | 351 | // Function to get the actual path from a href |
|
257 | 414 | var path = new URL(currentHref).pathname + new URL(currentHref).search + new URL(currentHref).hash; |
258 | 415 | // Update iframe location |
259 | 416 | window.location.href = path; |
260 | | - // Update parent window URL via postMessage (cross-origin safe) |
261 | | - try { |
262 | | - window.parent.postMessage({ |
263 | | - type: 'documentationNavigation', |
264 | | - path: path, |
265 | | - fullUrl: parentOrigin + path |
266 | | - }, parentOrigin); |
267 | | - } catch (e) { |
268 | | - console.warn("Could not send navigation message to parent:", e); |
269 | | - } |
| 417 | + // Update parent window URL |
| 418 | + updateParentUrl(path, parentOrigin); |
270 | 419 | return false; |
271 | 420 | } else if (hrefToUse.match(/^https?:\/\//) && !hrefToUse.startsWith(window.location.origin) && !hrefToUse.startsWith(parentOrigin)) { |
272 | 421 | // External link (not parent origin, not current origin) - let it open normally |
|
294 | 443 | var path = hrefToUse.startsWith("/") ? hrefToUse : "/" + hrefToUse; |
295 | 444 | // Update iframe location |
296 | 445 | window.location.href = path; |
297 | | - // Update parent window URL via postMessage (cross-origin safe) |
298 | | - try { |
299 | | - window.parent.postMessage({ |
300 | | - type: 'documentationNavigation', |
301 | | - path: path, |
302 | | - fullUrl: parentOrigin + path |
303 | | - }, parentOrigin); |
304 | | - } catch (e) { |
305 | | - console.warn("Could not send navigation message to parent:", e); |
306 | | - } |
| 446 | + // Update parent window URL |
| 447 | + updateParentUrl(path, parentOrigin); |
307 | 448 | return false; |
308 | 449 | } |
309 | 450 | // For other cases, let default behavior work |
|
314 | 455 | } |
315 | 456 | } |
316 | 457 | })(); |
| 458 | + |
0 commit comments