Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions _layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@

<title>{{ page.title }}</title>
<script>
// Expose Jekyll config to JavaScript for parent origin detection
window.JEKYLL_CONFIG = {
url: "{{ site.url }}",
baseurl: "{{ site.baseurl }}"
};

// Only set base target="_parent" if NOT in an iframe
// This allows iframed pages to navigate within the iframe
if (window.self === window.top) {
Expand Down
220 changes: 181 additions & 39 deletions assets/js/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,182 @@
// Ensure search results stay on developers.procore.com under /documentation
var basePath = "/documentation";

// Helper function to check if a hostname matches a domain (handles subdomains correctly)
function isDomainMatch(hostname, domain) {
if (!hostname || !domain) return false;
// Exact match
if (hostname === domain) return true;
// Subdomain match: hostname ends with '.' + domain
// This prevents matching malicious domains like 'malicious-developers.procore.com'
return hostname.endsWith('.' + domain);
}

// Helper function to normalize origin (remove trailing slash, ensure proper format)
function normalizeOrigin(origin) {
if (!origin) return null;
// Remove trailing slash if present
origin = origin.replace(/\/+$/, '');
// Ensure it's a valid origin format (protocol://host)
try {
var url = new URL(origin);
var hostname = url.hostname;

// Check hostname against known domains (parse URL first, then check hostname)
if (isDomainMatch(hostname, 'developers.procore.com')) {
return 'https://developers.procore.com';
}
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('127.0.0.1') || hostname.startsWith('localhost')) {
return 'http://' + hostname;
}

return url.origin;
} catch (e) {
// If it's not a full URL, try to construct it
if (origin.startsWith('http://') || origin.startsWith('https://')) {
// Try parsing again with the protocol
try {
var url = new URL(origin);
var hostname = url.hostname;

if (isDomainMatch(hostname, 'developers.procore.com')) {
return 'https://developers.procore.com';
}
if (hostname === 'localhost' || hostname === '127.0.0.1') {
return 'http://' + hostname;
}

return url.origin;
} catch (e2) {
return origin;
}
}
// If it's just a hostname string, check it directly
if (isDomainMatch(origin, 'developers.procore.com')) {
return 'https://developers.procore.com';
}
if (origin === 'localhost' || origin === '127.0.0.1' || origin.startsWith('localhost') || origin.startsWith('127.0.0.1')) {
return 'http://' + origin.replace(/^https?:\/\//, '');
}
return origin;
}
}

// Function to get parent origin (works for both same-origin and cross-origin)
// Note: Production is cross-origin (procore.github.io -> developers.procore.com)
function getParentOrigin() {
if (window.self === window.top) {
return null; // Not in an iframe
}

var parentOrigin = null;

try {
// For same-origin iframes, we can access window.top.location.origin
// This will fail in production (cross-origin) and fall through to catch block
if (window.top.location.origin) {
parentOrigin = window.top.location.origin;
console.log("getParentOrigin: Using window.top.location.origin:", parentOrigin);
return parentOrigin;
}
} catch (e) {
// Cross-origin: can't access window.top.location (production scenario)
// Use other methods to determine parent origin
console.log("getParentOrigin: Cross-origin detected, trying alternative methods");

// Try ancestorOrigins API (modern browsers)
if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) {
parentOrigin = normalizeOrigin(window.location.ancestorOrigins[0]);
console.log("getParentOrigin: Using ancestorOrigins[0] (normalized):", parentOrigin);
if (parentOrigin) return parentOrigin;
}

// Try document.referrer
if (document.referrer) {
try {
parentOrigin = normalizeOrigin(new URL(document.referrer).origin);
console.log("getParentOrigin: Using document.referrer origin (normalized):", parentOrigin);
if (parentOrigin) return parentOrigin;
} catch (err) {
console.error("getParentOrigin: Error parsing referrer origin:", err, "referrer:", document.referrer);
}
}

// Fallback: Use Jekyll config value (from _config.yml, exposed via window.JEKYLL_CONFIG)
// This is the production parent origin: developers.procore.com
if (typeof window.JEKYLL_CONFIG !== 'undefined' && window.JEKYLL_CONFIG.url) {
try {
parentOrigin = normalizeOrigin(new URL(window.JEKYLL_CONFIG.url).origin);
console.log("getParentOrigin: Using JEKYLL_CONFIG.url as fallback (normalized):", parentOrigin);
if (parentOrigin) return parentOrigin;
} catch (err) {
console.error("getParentOrigin: Error parsing JEKYLL_CONFIG.url:", err);
}
}

// Hardcoded fallback for production
// If we're on procore.github.io, the parent is likely developers.procore.com
if (window.location.hostname === 'procore.github.io' ||
window.location.hostname.endsWith('.github.io')) {
parentOrigin = normalizeOrigin('https://developers.procore.com');
console.log("getParentOrigin: Using hardcoded production fallback (normalized):", parentOrigin);
if (parentOrigin) return parentOrigin;
}

console.warn("getParentOrigin: Could not determine parent origin. Available info:", {
ancestorOrigins: window.location.ancestorOrigins,
referrer: document.referrer,
hostname: window.location.hostname,
jekyllConfig: typeof window.JEKYLL_CONFIG !== 'undefined' ? window.JEKYLL_CONFIG : 'undefined'
});
}

return parentOrigin;
}

// Function to update parent window URL (works for both same-origin and cross-origin)
// Note: Production is cross-origin, so postMessage is required
function updateParentUrl(path, parentOrigin) {
if (!parentOrigin) {
console.warn("Cannot update parent URL: parentOrigin is null");
return;
}

var fullUrl = parentOrigin + path;
console.log("Updating parent URL:", { path: path, parentOrigin: parentOrigin, fullUrl: fullUrl });

// Try direct access first (same-origin scenarios only, e.g., reverse proxy testing)
// This will fail in production (cross-origin: procore.github.io -> developers.procore.com)
try {
if (window.top.location.origin === parentOrigin) {
// Same-origin: can directly update parent URL (testing scenarios only)
console.log("Using direct access (same-origin) to update parent URL");
window.top.history.pushState({}, '', fullUrl);
return;
}
} catch (e) {
// Cross-origin: can't access window.top.location (production scenario)
// Must use postMessage for cross-origin communication
console.log("Cannot use direct access (cross-origin), using postMessage");
}

// Fallback: use postMessage (required for production cross-origin scenario)
try {
console.log("Sending postMessage to parent:", { type: 'documentationNavigation', path: path, fullUrl: fullUrl });
window.parent.postMessage({
type: 'documentationNavigation',
path: path,
fullUrl: fullUrl
}, parentOrigin);
} catch (err) {
console.error("Could not send navigation message to parent:", err);
}
}

// Function to rewrite search result links for iframe display
function rewriteSearchResultLinks() {
if (window.self !== window.top) {
try {
var parentOrigin = window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0
? window.location.ancestorOrigins[0]
: document.referrer ? new URL(document.referrer).origin : null;
var parentOrigin = getParentOrigin();

if (parentOrigin) {
$("#results-container a").each(function() {
Expand Down Expand Up @@ -137,25 +306,15 @@
// If in iframe and link points to parent origin, navigate within iframe
if (window.self !== window.top) {
try {
var parentOrigin = window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0
? window.location.ancestorOrigins[0]
: document.referrer ? new URL(document.referrer).origin : null;
var parentOrigin = getParentOrigin();

if (parentOrigin && currentHref.startsWith(parentOrigin)) {
e.preventDefault();
var path = new URL(currentHref).pathname + new URL(currentHref).search + new URL(currentHref).hash;
// Update iframe location
window.location.href = path;
// Update parent window URL via postMessage (cross-origin safe)
try {
window.parent.postMessage({
type: 'documentationNavigation',
path: path,
fullUrl: parentOrigin + path
}, parentOrigin);
} catch (e) {
console.warn("Could not send navigation message to parent:", e);
}
// Update parent window URL
updateParentUrl(path, parentOrigin);
return false;
}
} catch (err) {
Expand Down Expand Up @@ -186,9 +345,7 @@
// If in an iframe, rewrite link hrefs for hover display but intercept clicks to navigate within iframe
if (window.self !== window.top) {
try {
var parentOrigin = window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0
? window.location.ancestorOrigins[0]
: document.referrer ? new URL(document.referrer).origin : null;
var parentOrigin = getParentOrigin();

if (parentOrigin) {
// Function to get the actual path from a href
Expand Down Expand Up @@ -257,16 +414,8 @@
var path = new URL(currentHref).pathname + new URL(currentHref).search + new URL(currentHref).hash;
// Update iframe location
window.location.href = path;
// Update parent window URL via postMessage (cross-origin safe)
try {
window.parent.postMessage({
type: 'documentationNavigation',
path: path,
fullUrl: parentOrigin + path
}, parentOrigin);
} catch (e) {
console.warn("Could not send navigation message to parent:", e);
}
// Update parent window URL
updateParentUrl(path, parentOrigin);
return false;
} else if (hrefToUse.match(/^https?:\/\//) && !hrefToUse.startsWith(window.location.origin) && !hrefToUse.startsWith(parentOrigin)) {
// External link (not parent origin, not current origin) - let it open normally
Expand Down Expand Up @@ -294,16 +443,8 @@
var path = hrefToUse.startsWith("/") ? hrefToUse : "/" + hrefToUse;
// Update iframe location
window.location.href = path;
// Update parent window URL via postMessage (cross-origin safe)
try {
window.parent.postMessage({
type: 'documentationNavigation',
path: path,
fullUrl: parentOrigin + path
}, parentOrigin);
} catch (e) {
console.warn("Could not send navigation message to parent:", e);
}
// Update parent window URL
updateParentUrl(path, parentOrigin);
return false;
}
// For other cases, let default behavior work
Expand All @@ -314,3 +455,4 @@
}
}
})();

Loading