nose-pluto/public/browser-nav.js

109 lines
3.0 KiB
JavaScript

// browser-nav.js - Injected into NOSE apps to get the UI browser working.
(function () {
const ALLOWED_DOMAINS = ['localhost', '.local', '.nose.space']
function isAllowedOrigin(url) {
try {
const urlObj = new URL(url, window.location.href)
return ALLOWED_DOMAINS.some(domain =>
urlObj.hostname === domain || urlObj.hostname.endsWith(domain)
)
} catch {
return false
}
}
// Intercept navigation attempts
function interceptNavigation(e) {
const target = e.target.closest('a')
if (!target || !target.href) return
// Allow relative URLs and hash links
if (target.getAttribute('href').startsWith('#')) return
if (target.getAttribute('href').startsWith('/')) return
// Check if external
if (!isAllowedOrigin(target.href)) {
e.preventDefault()
if (window.parent !== window) {
window.parent.postMessage({
type: 'NAV_BLOCKED',
data: { url: target.href, reason: 'External domain not allowed' }
}, '*')
} else {
alert('Navigation to external sites is not allowed')
}
}
}
// Listen for clicks on links
document.addEventListener('click', interceptNavigation, true)
// Intercept programmatic navigation
const originalPushState = history.pushState
const originalReplaceState = history.replaceState
history.pushState = function (state, title, url) {
if (url && !isAllowedOrigin(url)) {
if (window.parent !== window) {
window.parent.postMessage({
type: 'NAV_BLOCKED',
data: { url, reason: 'External domain not allowed' }
}, '*')
}
return
}
return originalPushState.apply(this, arguments)
}
history.replaceState = function (state, title, url) {
if (url && !isAllowedOrigin(url)) {
if (window.parent !== window) {
window.parent.postMessage({
type: 'NAV_BLOCKED',
data: { url, reason: 'External domain not allowed' }
}, '*')
}
return
}
return originalReplaceState.apply(this, arguments)
}
// Listen for navigation commands from parent
window.addEventListener('message', (event) => {
if (event.data.type === 'NAV_COMMAND') {
switch (event.data.action) {
case 'back': history.back(); break
case 'forward': history.forward(); break
case 'reload': window.location.reload(); break
case 'stop': window.stop(); break
}
}
})
// Send all keydown events to parent
window.addEventListener('keydown', (e) => {
window.parent.postMessage({
type: 'KEYDOWN',
data: {
key: e.key,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
altKey: e.altKey,
metaKey: e.metaKey
}
}, '*')
})
if (window.parent !== window) {
window.parent.postMessage({ type: 'NAV_READY' }, '*')
window.addEventListener('popstate', () => {
window.parent.postMessage({
type: 'URL_CHANGED',
data: { url: location.href }
}, '*')
})
}
})()