109 lines
3.0 KiB
JavaScript
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 }
|
|
}, '*')
|
|
})
|
|
}
|
|
})() |