@data-fair/frame 0.18.6 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"d-frame-content.min.js","sources":["../../lib/messages.js","../../lib/utils/in-iframe.js","../../lib/DFrameContent.js","../../lib/vue-reactive/util.js","../../lib/v-iframe-compat/d-frame-content.js"],"sourcesContent":["export function isMouseEventMessage(message) {\n return message[0] === 'df-global' && message[1] === 'mouse';\n}\nexport function isInitChildMessage(message) {\n return message[0] === 'df-child' && message[1] === 'init';\n}\nexport function isHeightMessage(message) {\n return message[0] === 'df-child' && message[1] === 'height';\n}\nexport function isReinitHeightMessage(message) {\n return message[0] === 'df-child' && message[1] === 'reinit-height';\n}\nexport function isStateChangeMessage(message) {\n return message[0] === 'df-child' && message[1] === 'stateChange';\n}\nexport function isCustomMessage(message) {\n return message[0] === 'df-child' && message[1] === 'custom';\n}\nexport function isNotifMessage(message) {\n return message[0] === 'df-child' && message[1] === 'notif';\n}\nexport function isAddParentUrlListenerMessage(message) {\n return message[0] === 'df-child' && message[1] === 'addParentUrlListener';\n}\nexport function isRemoveParentUrlListenerMessage(message) {\n return message[0] === 'df-child' && message[1] === 'removeParentUrlListener';\n}\nexport function isReadyMessage(message) {\n return message[0] === 'df-child' && message[1] === 'ready';\n}\nexport function isInitParentMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'init';\n}\nexport function isUpdateSrcMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'updateSrc';\n}\nexport function isParentUrlMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'parentUrl';\n}\n","const inIframe = (() => {\n try {\n return window.top !== window.self;\n }\n catch (e) {\n return true;\n }\n})();\nexport default inIframe;\n","import { isInitParentMessage, isMouseEventMessage, isUpdateSrcMessage, isParentUrlMessage } from './messages.js';\nimport inIframe from './utils/in-iframe.js';\nconst windowEventTypes = [\n 'animationstart',\n 'webkitAnimationStart',\n 'animationiteration',\n 'webkitAnimationIteration',\n 'animationend',\n 'webkitAnimationEnd',\n 'input',\n 'mouseup',\n 'mousedown',\n 'orientationchange',\n 'afterprint',\n 'beforeprint',\n 'readystatechange',\n 'touchstart',\n 'touchend',\n 'touchcancel',\n 'transitionstart',\n 'webkitTransitionStart',\n 'MSTransitionStart',\n 'oTransitionStart',\n 'otransitionstart', 'transitioniteration',\n 'webkitTransitionIteration',\n 'MSTransitionIteration',\n 'oTransitionIteration',\n 'otransitioniteration', 'transitionend',\n 'webkitTransitionEnd',\n 'MSTransitionEnd',\n 'oTransitionEnd',\n 'otransitionend', 'resize'\n];\nexport default class DFrameContent {\n initialized = false;\n options;\n pendingCheckHeight = false;\n debug = false;\n id;\n throttledCheckHeight;\n lastHeight = 0;\n parentUrlListeners = {};\n parentUrlResults = {};\n constructor(options) {\n this.options = options ?? {};\n if (typeof window === 'undefined')\n return;\n if (window.__d_frame__content) {\n throw new Error('DFrameContent was already initialized');\n }\n window.__d_frame__content = this;\n const afCallback = () => {\n this.checkHeight();\n this.pendingCheckHeight = false;\n };\n this.throttledCheckHeight = () => {\n if (this.pendingCheckHeight)\n return;\n this.pendingCheckHeight = true;\n requestAnimationFrame(afCallback);\n };\n window.addEventListener('message', (e) => this.onMessage(e));\n // simple handshake for initialization\n this.postMessageToParent(['df-child', 'init']);\n }\n log(level, ...args) {\n if (level === 'debug' && !this.debug)\n return;\n if (level === 'debug')\n console.timeLog(`d-frame:${this.id}:content`, ...args);\n if (level === 'info')\n console.info(`d-frame:${this.id}:content`, ...args);\n if (level === 'error')\n console.error(`d-frame:${this.id}:content`, ...args);\n }\n onMessage(e) {\n if (e.source === window.parent && Array.isArray(e.data)) {\n const message = e.data;\n if (isInitParentMessage(message)) {\n this.id = message[2].id;\n this.debug = !!message[2].debug;\n if (this.debug)\n console.time(`d-frame:${this.id}:content`);\n this.log('debug', 'init handshake is ok', message, this.options);\n if (message[2].src && message[2].src !== window.location.href) {\n this.log('error', `src mismatch at init, maybe the frame navigated before the initialization handshake: ${window.location.href} !== ${message[2].src}`);\n }\n if (message[2].resize !== 'no')\n this.initResize();\n if (message[2].syncParams || message[2].syncPath || message[2].stateChangeEvents)\n this.initStateChangeWatcher();\n if (message[2].mouseEvents)\n this.initMouseEvents(message[2].mouseEvents);\n this.initialized = true;\n }\n if (isUpdateSrcMessage(message)) {\n const newSrc = message[2].startsWith('/') ? window.location.origin + message[2] : message[2];\n if (newSrc === window.location.href)\n return;\n this.log('debug', `update src ${window.location.href} -> ${newSrc}`);\n if (this.options.updateSrc) {\n this.options.updateSrc(newSrc, this);\n }\n else {\n this.log('debug', 'no updateSrc method available, falling back to updating window.location.href', newSrc);\n window.location.href = newSrc;\n }\n }\n if (isMouseEventMessage(message)) {\n this.applySyncedMouseEvent(message);\n }\n if (isParentUrlMessage(message)) {\n if (!this.parentUrlListeners[message[2]]) {\n this.log('error', `received getParentUrlResponse message but no matching callback is registered: ${message[2]}`);\n }\n else {\n for (const listener of this.parentUrlListeners[message[2]]) {\n listener(message[3]);\n }\n }\n }\n }\n }\n postMessageToParent(message) {\n window.parent.postMessage(message, '*');\n }\n sendNotif(notif) {\n this.postMessageToParent(['df-child', 'notif', notif]);\n }\n sendMessage(msg) {\n this.postMessageToParent(['df-child', 'custom', msg]);\n }\n ready() {\n this.postMessageToParent(['df-child', 'ready']);\n }\n reinitHeight() {\n this.postMessageToParent(['df-child', 'reinit-height']);\n }\n // this is not a single-time callback, it can be called multiple times when parent url changes\n addParentUrlListener(partialChildHref, listener) {\n const childHref = new URL(partialChildHref, window.location.href).href;\n if (!inIframe) {\n listener(childHref);\n return;\n }\n if (this.parentUrlResults[childHref]) {\n listener(this.parentUrlResults[childHref]);\n }\n this.parentUrlListeners[childHref] = this.parentUrlListeners[childHref] ?? [];\n this.parentUrlListeners[childHref].push(listener);\n this.postMessageToParent(['df-child', 'addParentUrlListener', childHref]);\n }\n removeParentUrlListener(partialChildHref, listener) {\n const childHref = new URL(partialChildHref, window.location.href).href;\n if (this.parentUrlListeners[childHref]) {\n this.parentUrlListeners[childHref] = this.parentUrlListeners[childHref].filter(cb => cb !== listener);\n if (!this.parentUrlListeners[childHref].length) {\n delete this.parentUrlListeners[childHref];\n this.postMessageToParent(['df-child', 'removeParentUrlListener', childHref]);\n }\n }\n }\n currentSyncedMouseEvents = new Set();\n initMouseEvents(types) {\n this.log('debug', 'initMouseEvents');\n for (const eventType of types) {\n document.addEventListener(eventType, (e) => {\n if (!(e instanceof MouseEvent))\n return;\n this.transmitSyncedMouseEvent(['df-global', 'mouse', eventType, { altKey: e.altKey, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, metaKey: e.metaKey }]);\n }, true);\n }\n }\n transmitSyncedMouseEvent(message) {\n if (this.currentSyncedMouseEvents.has(message[2])) {\n this.currentSyncedMouseEvents.delete(message[2]);\n }\n else {\n this.postMessageToParent(message);\n }\n }\n applySyncedMouseEvent(message) {\n this.currentSyncedMouseEvents.add(message[2]);\n const event = new MouseEvent(message[2], { bubbles: true, cancelable: true, view: window, ...message[3] });\n document.body.dispatchEvent(event);\n }\n initResize() {\n this.log('debug', 'initResize');\n this.checkHeight();\n this.createMutationObserver();\n this.createWindowEventListeners();\n }\n initStateChangeWatcher() {\n this.log('debug', 'initStateChangeWatcher');\n // monkey patch pushState and replaceState to send all state change info to the parent window\n // this is to compensate the lack of event emission by window.history\n const oldReplaceState = window.history.replaceState;\n const newPushState = (...args) => {\n const before = window.location.href;\n // do a replace instead of a push, the push will be done in the parent window if sync-state is activated\n const ret = oldReplaceState.apply(window.history, args);\n if (window.location.href !== before) {\n this.postMessageToParent(['df-child', 'stateChange', 'push', window.location.href]);\n }\n return ret;\n };\n window.history.pushState = newPushState;\n const newReplaceState = (...args) => {\n const before = window.location.href;\n const ret = oldReplaceState.apply(window.history, args);\n if (window.location.href !== before) {\n this.postMessageToParent(['df-child', 'stateChange', 'replace', window.location.href]);\n }\n return ret;\n };\n window.history.replaceState = newReplaceState;\n }\n createMutationObserver() {\n const body = document.querySelector('body');\n if (!body)\n throw new Error('DFrameContentManager was initialized before the HTML body');\n if (!this.throttledCheckHeight)\n throw new Error('throttledCheckHeight is not defined');\n const observer = new window.MutationObserver(this.throttledCheckHeight);\n observer.observe(body, {\n attributes: false,\n attributeOldValue: false,\n characterData: false,\n characterDataOldValue: false,\n childList: true,\n subtree: true,\n });\n }\n createWindowEventListeners() {\n if (!this.throttledCheckHeight)\n throw new Error('throttledCheckHeight is not defined');\n for (const eventType of windowEventTypes) {\n window.addEventListener(eventType, this.throttledCheckHeight, { passive: true });\n }\n }\n checkHeight() {\n const elements = document.querySelectorAll('[data-iframe-height]');\n let maxWithoutOffset = 0;\n let max = 0;\n for (const element of elements) {\n const dataAttribute = element.getAttribute('data-iframe-height');\n if (dataAttribute === 'false' || dataAttribute === 'no')\n continue;\n const bottomWithoutOffset = element.getBoundingClientRect().bottom +\n parseFloat(getComputedStyle(element).getPropertyValue('margin-bottom'));\n const bottom = dataAttribute ? (bottomWithoutOffset + parseFloat(dataAttribute)) : bottomWithoutOffset;\n if (bottomWithoutOffset > maxWithoutOffset)\n maxWithoutOffset = bottomWithoutOffset;\n if (bottom > max)\n max = bottom;\n }\n max = Math.ceil(max);\n if (maxWithoutOffset > 0 && max !== this.lastHeight) {\n this.postMessageToParent(['df-child', 'height', max]);\n this.lastHeight = max;\n }\n }\n}\n","export const applySearchParams = (reactiveParams, searchParams) => {\n const existingKeys = Object.keys(reactiveParams);\n searchParams.forEach((value, key) => {\n reactiveParams[key] = value;\n existingKeys.splice(existingKeys.indexOf(key), 1);\n });\n existingKeys.forEach(key => {\n delete reactiveParams[key];\n });\n};\n","import DFrameContent from '../DFrameContent.js';\nimport { applySearchParams } from '../vue-reactive/util.js';\nconst dFrameContent = new DFrameContent({\n updateSrc(src, instance) {\n const vIframeOptions = window.vIframeOptions;\n const reactiveParams = vIframeOptions?.reactiveParams;\n if (reactiveParams && typeof reactiveParams === 'object') {\n const srcUrl = new URL(src);\n if (srcUrl.origin === window.location.origin && srcUrl.pathname === window.location.pathname) {\n instance.log('debug', 'applying reactive params', srcUrl.searchParams);\n applySearchParams(reactiveParams, srcUrl.searchParams);\n return;\n }\n }\n const router = vIframeOptions?.router;\n if (router) {\n // @ts-ignore vue-router v2\n const base = router.options?.base ?? router.options.history?.base;\n if (base === null || base === undefined) {\n instance.log('error', 'failed to access base path in router (no router.options.base nor router.options.history.base)', router);\n }\n else {\n const urlPrefix = window.location.origin + base;\n if (!src.startsWith(urlPrefix)) {\n instance.log('info', 'src does not start with base path', src, urlPrefix);\n }\n else {\n let newRoute = src.replace(urlPrefix, '');\n if (!newRoute.startsWith('/'))\n newRoute = '/' + newRoute;\n instance.log('debug', 'applying router.replace', newRoute);\n router.replace(newRoute);\n return;\n }\n }\n }\n instance.log('info', 'falling back to updating window.location.href', src);\n window.location.href = src;\n }\n});\nwindow.dFrameContent = dFrameContent;\nexport default dFrameContent;\n"],"names":["isMouseEventMessage","message","isInitParentMessage","isUpdateSrcMessage","isParentUrlMessage","inIframe","windowEventTypes","DFrameContent","options","__publicField","afCallback","e","level","args","newSrc","listener","notif","msg","partialChildHref","childHref","cb","types","eventType","event","oldReplaceState","newPushState","before","ret","newReplaceState","body","elements","maxWithoutOffset","max","element","dataAttribute","bottomWithoutOffset","bottom","applySearchParams","reactiveParams","searchParams","existingKeys","value","key","dFrameContent","src","instance","vIframeOptions","srcUrl","router","base","_a","_b","urlPrefix","newRoute"],"mappings":"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"}
1
+ {"version":3,"file":"d-frame-content.min.js","sources":["../../lib/utils/url.js","../../lib/messages.js","../../lib/utils/in-iframe.js","../../lib/DFrameContent.js","../../lib/vue-reactive/util.js","../../lib/v-iframe-compat/d-frame-content.ts"],"sourcesContent":["export function getPathDirectory(path) {\n if (path.endsWith('/'))\n return path;\n if (path === '')\n return '/';\n return path.slice(0, path.lastIndexOf('/') + 1);\n}\nexport function getPathWithTrailingSlash(path) {\n if (path.endsWith('/'))\n return path;\n return path + '/';\n}\nexport function getUrlRelativePath(srcUrl, currentUrl) {\n if (currentUrl.origin !== srcUrl.origin)\n return currentUrl.href;\n if (getPathWithTrailingSlash(currentUrl.pathname) === getPathWithTrailingSlash(srcUrl.pathname))\n return '';\n const srcDirectory = getPathDirectory(srcUrl.pathname);\n if (!currentUrl.pathname.startsWith(srcDirectory))\n return currentUrl.pathname;\n return currentUrl.pathname.replace(srcDirectory, '');\n}\n// a URL that can be navigated to or used as a link without running code (no javascript:, data:...)\nexport function isHttpUrl(value, base) {\n if (typeof value !== 'string')\n return false;\n try {\n return ['http:', 'https:'].includes(new URL(value, base).protocol);\n }\n catch {\n return false;\n }\n}\n","import { isHttpUrl } from './utils/url.js';\nexport function isMouseEventMessage(message) {\n return message[0] === 'df-global' && message[1] === 'mouse' && typeof message[2] === 'string';\n}\nexport function isInitChildMessage(message) {\n return message[0] === 'df-child' && message[1] === 'init';\n}\nexport function isHeightMessage(message) {\n // the height is written in the style of the wrapper, anything else than a number would inject css\n return message[0] === 'df-child' && message[1] === 'height' && typeof message[2] === 'number' && Number.isFinite(message[2]) && message[2] >= 0;\n}\nexport function isReinitHeightMessage(message) {\n return message[0] === 'df-child' && message[1] === 'reinit-height';\n}\n// the URL of the child becomes the src of the iframe when it is reloaded: a javascript: URL would run in the parent\nexport function isStateChangeMessage(message) {\n return message[0] === 'df-child' && message[1] === 'stateChange' && (message[2] === 'push' || message[2] === 'replace') && isHttpUrl(message[3]);\n}\nexport function isCustomMessage(message) {\n return message[0] === 'df-child' && message[1] === 'custom';\n}\nexport function isNotifMessage(message) {\n return message[0] === 'df-child' && message[1] === 'notif';\n}\nexport function isAddParentUrlListenerMessage(message) {\n return message[0] === 'df-child' && message[1] === 'addParentUrlListener' && isHttpUrl(message[2]);\n}\nexport function isRemoveParentUrlListenerMessage(message) {\n return message[0] === 'df-child' && message[1] === 'removeParentUrlListener';\n}\nexport function isReadyMessage(message) {\n return message[0] === 'df-child' && message[1] === 'ready';\n}\nexport function isInitParentMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'init';\n}\nexport function isUpdateSrcMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'updateSrc';\n}\nexport function isParentUrlMessage(message) {\n return message[0] === 'df-parent' && message[1] === 'parentUrl';\n}\n","const inIframe = (() => {\n try {\n return window.top !== window.self;\n }\n catch (e) {\n return true;\n }\n})();\nexport default inIframe;\n","import { isInitParentMessage, isMouseEventMessage, isUpdateSrcMessage, isParentUrlMessage } from './messages.js';\nimport inIframe from './utils/in-iframe.js';\nimport { isHttpUrl } from './utils/url.js';\n// prefixed variants are not listed: browsers do not fire them when there is an unprefixed listener\nconst windowEventTypes = [\n 'animationstart',\n 'animationiteration',\n 'animationend',\n 'input',\n 'mouseup',\n 'mousedown',\n 'orientationchange',\n 'afterprint',\n 'beforeprint',\n 'touchstart',\n 'touchend',\n 'touchcancel',\n 'transitionstart',\n 'transitionend',\n 'resize'\n];\nexport default class DFrameContent {\n initialized = false;\n options;\n pendingCheckHeight = false;\n debug = false;\n id;\n throttledCheckHeight;\n lastHeight = 0;\n parentUrlListeners = {};\n parentUrlResults = {};\n constructor(options) {\n this.options = options ?? {};\n if (typeof window === 'undefined')\n return;\n if (window.__d_frame__content) {\n throw new Error('DFrameContent was already initialized');\n }\n window.__d_frame__content = this;\n const afCallback = () => {\n this.checkHeight();\n this.pendingCheckHeight = false;\n };\n this.throttledCheckHeight = () => {\n if (this.pendingCheckHeight)\n return;\n this.pendingCheckHeight = true;\n requestAnimationFrame(afCallback);\n };\n window.addEventListener('message', (e) => this.onMessage(e));\n // simple handshake for initialization\n this.postMessageToParent(['df-child', 'init']);\n }\n log(level, ...args) {\n if (level === 'debug' && !this.debug)\n return;\n if (level === 'debug')\n console.timeLog(`d-frame:${this.id}:content`, ...args);\n if (level === 'info')\n console.info(`d-frame:${this.id}:content`, ...args);\n if (level === 'error')\n console.error(`d-frame:${this.id}:content`, ...args);\n }\n onMessage(e) {\n if (e.source === window.parent && Array.isArray(e.data)) {\n const message = e.data;\n if (isInitParentMessage(message)) {\n this.id = message[2].id;\n this.debug = !!message[2].debug;\n if (this.debug)\n console.time(`d-frame:${this.id}:content`);\n this.log('debug', 'init handshake is ok', message, this.options);\n if (message[2].src && message[2].src !== window.location.href) {\n this.log('error', `src mismatch at init, maybe the frame navigated before the initialization handshake: ${window.location.href} !== ${message[2].src}`);\n }\n if (message[2].resize !== 'no')\n this.initResize();\n if (message[2].syncParams || message[2].syncPath || message[2].stateChangeEvents)\n this.initStateChangeWatcher();\n if (message[2].mouseEvents)\n this.initMouseEvents(message[2].mouseEvents);\n this.initialized = true;\n }\n if (isUpdateSrcMessage(message)) {\n // the parent can be any page, it must not be able to run code in this window (javascript: URL)\n if (!isHttpUrl(message[2], window.location.href)) {\n this.log('error', 'refused to navigate to a src that is not a http(s) URL', message[2]);\n return;\n }\n const newSrc = new URL(message[2], window.location.href).href;\n if (newSrc === window.location.href)\n return;\n this.log('debug', `update src ${window.location.href} -> ${newSrc}`);\n if (this.options.updateSrc) {\n this.options.updateSrc(newSrc, this);\n }\n else {\n this.log('debug', 'no updateSrc method available, falling back to updating window.location.href', newSrc);\n window.location.href = newSrc;\n }\n }\n if (isMouseEventMessage(message) && this.mouseEventTypes.includes(message[2])) {\n this.applySyncedMouseEvent(message);\n }\n if (isParentUrlMessage(message)) {\n if (!isHttpUrl(message[3])) {\n // the result is used as a link, it must not be able to run code\n this.log('error', 'refused a parent URL that is not a http(s) URL', message[3]);\n }\n else if (!this.parentUrlListeners[message[2]]) {\n this.log('error', `received getParentUrlResponse message but no matching callback is registered: ${message[2]}`);\n }\n else {\n this.parentUrlResults[message[2]] = message[3];\n for (const listener of this.parentUrlListeners[message[2]]) {\n listener(message[3]);\n }\n }\n }\n }\n }\n postMessageToParent(message) {\n window.parent.postMessage(message, '*');\n }\n sendNotif(notif) {\n this.postMessageToParent(['df-child', 'notif', notif]);\n }\n sendMessage(msg) {\n this.postMessageToParent(['df-child', 'custom', msg]);\n }\n ready() {\n this.postMessageToParent(['df-child', 'ready']);\n }\n reinitHeight() {\n // the parent forgets the height, the next one must be sent even if it did not change\n this.lastHeight = 0;\n this.postMessageToParent(['df-child', 'reinit-height']);\n }\n // this is not a single-time callback, it can be called multiple times when parent url changes\n addParentUrlListener(partialChildHref, listener) {\n const childHref = new URL(partialChildHref, window.location.href).href;\n if (!inIframe) {\n listener(childHref);\n return;\n }\n if (this.parentUrlListeners[childHref]) {\n // the parent already sends the updates of this URL, the result is known or on its way\n this.parentUrlListeners[childHref].push(listener);\n if (childHref in this.parentUrlResults)\n listener(this.parentUrlResults[childHref]);\n return;\n }\n this.parentUrlListeners[childHref] = [listener];\n this.postMessageToParent(['df-child', 'addParentUrlListener', childHref]);\n }\n removeParentUrlListener(partialChildHref, listener) {\n const childHref = new URL(partialChildHref, window.location.href).href;\n if (this.parentUrlListeners[childHref]) {\n this.parentUrlListeners[childHref] = this.parentUrlListeners[childHref].filter(cb => cb !== listener);\n if (!this.parentUrlListeners[childHref].length) {\n delete this.parentUrlListeners[childHref];\n delete this.parentUrlResults[childHref];\n this.postMessageToParent(['df-child', 'removeParentUrlListener', childHref]);\n }\n }\n }\n currentSyncedMouseEvents = new Set();\n mouseEventTypes = [];\n initMouseEvents(types) {\n this.log('debug', 'initMouseEvents');\n this.mouseEventTypes = types;\n for (const eventType of types) {\n document.addEventListener(eventType, (e) => {\n if (!(e instanceof MouseEvent))\n return;\n this.transmitSyncedMouseEvent(['df-global', 'mouse', eventType, { altKey: e.altKey, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, metaKey: e.metaKey }]);\n }, true);\n }\n }\n transmitSyncedMouseEvent(message) {\n if (this.currentSyncedMouseEvents.has(message[2])) {\n this.currentSyncedMouseEvents.delete(message[2]);\n }\n else {\n this.postMessageToParent(message);\n }\n }\n applySyncedMouseEvent(message) {\n this.currentSyncedMouseEvents.add(message[2]);\n const event = new MouseEvent(message[2], { bubbles: true, cancelable: true, view: window, ...message[3] });\n document.body.dispatchEvent(event);\n }\n initResize() {\n this.log('debug', 'initResize');\n this.checkHeight();\n this.createMutationObserver();\n this.createWindowEventListeners();\n }\n initStateChangeWatcher() {\n this.log('debug', 'initStateChangeWatcher');\n // monkey patch pushState and replaceState to send all state change info to the parent window\n // this is to compensate the lack of event emission by window.history\n const oldReplaceState = window.history.replaceState;\n const newPushState = (...args) => {\n const before = window.location.href;\n // do a replace instead of a push, the push will be done in the parent window if sync-state is activated\n const ret = oldReplaceState.apply(window.history, args);\n if (window.location.href !== before) {\n this.postMessageToParent(['df-child', 'stateChange', 'push', window.location.href]);\n }\n return ret;\n };\n window.history.pushState = newPushState;\n const newReplaceState = (...args) => {\n const before = window.location.href;\n const ret = oldReplaceState.apply(window.history, args);\n if (window.location.href !== before) {\n this.postMessageToParent(['df-child', 'stateChange', 'replace', window.location.href]);\n }\n return ret;\n };\n window.history.replaceState = newReplaceState;\n }\n createMutationObserver() {\n const body = document.querySelector('body');\n if (!body)\n throw new Error('DFrameContentManager was initialized before the HTML body');\n if (!this.throttledCheckHeight)\n throw new Error('throttledCheckHeight is not defined');\n const observer = new window.MutationObserver(this.throttledCheckHeight);\n observer.observe(body, {\n attributes: false,\n attributeOldValue: false,\n characterData: false,\n characterDataOldValue: false,\n childList: true,\n subtree: true,\n });\n }\n createWindowEventListeners() {\n if (!this.throttledCheckHeight)\n throw new Error('throttledCheckHeight is not defined');\n for (const eventType of windowEventTypes) {\n window.addEventListener(eventType, this.throttledCheckHeight, { passive: true });\n }\n }\n checkHeight() {\n const elements = document.querySelectorAll('[data-iframe-height]');\n let maxWithoutOffset = 0;\n let max = 0;\n for (const element of elements) {\n const dataAttribute = element.getAttribute('data-iframe-height');\n if (dataAttribute === 'false' || dataAttribute === 'no')\n continue;\n const bottomWithoutOffset = element.getBoundingClientRect().bottom +\n parseFloat(getComputedStyle(element).getPropertyValue('margin-bottom'));\n const bottom = dataAttribute ? (bottomWithoutOffset + parseFloat(dataAttribute)) : bottomWithoutOffset;\n if (bottomWithoutOffset > maxWithoutOffset)\n maxWithoutOffset = bottomWithoutOffset;\n if (bottom > max)\n max = bottom;\n }\n max = Math.ceil(max);\n if (maxWithoutOffset > 0 && max !== this.lastHeight) {\n this.postMessageToParent(['df-child', 'height', max]);\n this.lastHeight = max;\n }\n }\n}\n","export const applySearchParams = (reactiveParams, searchParams) => {\n searchParams.forEach((value, key) => {\n reactiveParams[key] = value;\n });\n for (const key of Object.keys(reactiveParams)) {\n if (!searchParams.has(key))\n delete reactiveParams[key];\n }\n};\n","import DFrameContent from '../DFrameContent.js'\nimport type { Router } from 'vue-router'\nimport { applySearchParams } from '../vue-reactive/util.js'\n\nexport type VIframeOptions = {\n router?: Router,\n reactiveParams?: Record<string, string> | boolean\n}\n\nconst dFrameContent = new DFrameContent({\n updateSrc (src: string, instance: DFrameContent) {\n const vIframeOptions = (window as Window & { vIframeOptions?: VIframeOptions }).vIframeOptions\n\n const reactiveParams = vIframeOptions?.reactiveParams\n if (reactiveParams && typeof reactiveParams === 'object') {\n const srcUrl = new URL(src)\n if (srcUrl.origin === window.location.origin && srcUrl.pathname === window.location.pathname) {\n instance.log('debug', 'applying reactive params', srcUrl.searchParams)\n applySearchParams(reactiveParams, srcUrl.searchParams)\n return\n }\n }\n\n const router = vIframeOptions?.router\n if (router) {\n // @ts-ignore vue-router v2\n const base = router.options?.base ?? router.options.history?.base\n if (base === null || base === undefined) {\n instance.log('error', 'failed to access base path in router (no router.options.base nor router.options.history.base)', router)\n } else {\n const urlPrefix = window.location.origin + base\n if (!src.startsWith(urlPrefix)) {\n instance.log('info', 'src does not start with base path', src, urlPrefix)\n } else {\n let newRoute = src.replace(urlPrefix, '')\n if (!newRoute.startsWith('/')) newRoute = '/' + newRoute\n instance.log('debug', 'applying router.replace', newRoute)\n router.replace(newRoute)\n return\n }\n }\n }\n\n instance.log('info', 'falling back to updating window.location.href', src)\n window.location.href = src\n }\n});\n\n(window as (Window & { dFrameContent?: DFrameContent })).dFrameContent = dFrameContent\n\nexport default dFrameContent\n"],"names":["isHttpUrl","value","base","isMouseEventMessage","message","isInitParentMessage","isUpdateSrcMessage","isParentUrlMessage","inIframe","windowEventTypes","DFrameContent","options","afCallback","e","level","args","newSrc","listener","notif","msg","partialChildHref","childHref","cb","types","eventType","event","oldReplaceState","newPushState","before","ret","newReplaceState","body","elements","maxWithoutOffset","max","element","dataAttribute","bottomWithoutOffset","bottom","applySearchParams","reactiveParams","searchParams","key","dFrameContent","src","instance","vIframeOptions","srcUrl","router","urlPrefix","newRoute"],"mappings":"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"}
@@ -4,7 +4,7 @@ export type DFrameContentOptions = {
4
4
  };
5
5
  declare global {
6
6
  interface Window {
7
- __d_frame__content: DFrameContent;
7
+ __d_frame__content?: DFrameContent;
8
8
  }
9
9
  }
10
10
  export default class DFrameContent {
@@ -28,6 +28,7 @@ export default class DFrameContent {
28
28
  addParentUrlListener(partialChildHref: string, listener: (result: string) => any): void;
29
29
  removeParentUrlListener(partialChildHref: string, listener: (result: string) => any): void;
30
30
  private currentSyncedMouseEvents;
31
+ private mouseEventTypes;
31
32
  private initMouseEvents;
32
33
  private transmitSyncedMouseEvent;
33
34
  private applySyncedMouseEvent;
@@ -1,35 +1,23 @@
1
1
  import { isInitParentMessage, isMouseEventMessage, isUpdateSrcMessage, isParentUrlMessage } from './messages.js';
2
2
  import inIframe from './utils/in-iframe.js';
3
+ import { isHttpUrl } from './utils/url.js';
4
+ // prefixed variants are not listed: browsers do not fire them when there is an unprefixed listener
3
5
  const windowEventTypes = [
4
6
  'animationstart',
5
- 'webkitAnimationStart',
6
7
  'animationiteration',
7
- 'webkitAnimationIteration',
8
8
  'animationend',
9
- 'webkitAnimationEnd',
10
9
  'input',
11
10
  'mouseup',
12
11
  'mousedown',
13
12
  'orientationchange',
14
13
  'afterprint',
15
14
  'beforeprint',
16
- 'readystatechange',
17
15
  'touchstart',
18
16
  'touchend',
19
17
  'touchcancel',
20
18
  'transitionstart',
21
- 'webkitTransitionStart',
22
- 'MSTransitionStart',
23
- 'oTransitionStart',
24
- 'otransitionstart', 'transitioniteration',
25
- 'webkitTransitionIteration',
26
- 'MSTransitionIteration',
27
- 'oTransitionIteration',
28
- 'otransitioniteration', 'transitionend',
29
- 'webkitTransitionEnd',
30
- 'MSTransitionEnd',
31
- 'oTransitionEnd',
32
- 'otransitionend', 'resize'
19
+ 'transitionend',
20
+ 'resize'
33
21
  ];
34
22
  export default class DFrameContent {
35
23
  initialized = false;
@@ -94,7 +82,12 @@ export default class DFrameContent {
94
82
  this.initialized = true;
95
83
  }
96
84
  if (isUpdateSrcMessage(message)) {
97
- const newSrc = message[2].startsWith('/') ? window.location.origin + message[2] : message[2];
85
+ // the parent can be any page, it must not be able to run code in this window (javascript: URL)
86
+ if (!isHttpUrl(message[2], window.location.href)) {
87
+ this.log('error', 'refused to navigate to a src that is not a http(s) URL', message[2]);
88
+ return;
89
+ }
90
+ const newSrc = new URL(message[2], window.location.href).href;
98
91
  if (newSrc === window.location.href)
99
92
  return;
100
93
  this.log('debug', `update src ${window.location.href} -> ${newSrc}`);
@@ -106,14 +99,19 @@ export default class DFrameContent {
106
99
  window.location.href = newSrc;
107
100
  }
108
101
  }
109
- if (isMouseEventMessage(message)) {
102
+ if (isMouseEventMessage(message) && this.mouseEventTypes.includes(message[2])) {
110
103
  this.applySyncedMouseEvent(message);
111
104
  }
112
105
  if (isParentUrlMessage(message)) {
113
- if (!this.parentUrlListeners[message[2]]) {
106
+ if (!isHttpUrl(message[3])) {
107
+ // the result is used as a link, it must not be able to run code
108
+ this.log('error', 'refused a parent URL that is not a http(s) URL', message[3]);
109
+ }
110
+ else if (!this.parentUrlListeners[message[2]]) {
114
111
  this.log('error', `received getParentUrlResponse message but no matching callback is registered: ${message[2]}`);
115
112
  }
116
113
  else {
114
+ this.parentUrlResults[message[2]] = message[3];
117
115
  for (const listener of this.parentUrlListeners[message[2]]) {
118
116
  listener(message[3]);
119
117
  }
@@ -134,6 +132,8 @@ export default class DFrameContent {
134
132
  this.postMessageToParent(['df-child', 'ready']);
135
133
  }
136
134
  reinitHeight() {
135
+ // the parent forgets the height, the next one must be sent even if it did not change
136
+ this.lastHeight = 0;
137
137
  this.postMessageToParent(['df-child', 'reinit-height']);
138
138
  }
139
139
  // this is not a single-time callback, it can be called multiple times when parent url changes
@@ -143,11 +143,14 @@ export default class DFrameContent {
143
143
  listener(childHref);
144
144
  return;
145
145
  }
146
- if (this.parentUrlResults[childHref]) {
147
- listener(this.parentUrlResults[childHref]);
146
+ if (this.parentUrlListeners[childHref]) {
147
+ // the parent already sends the updates of this URL, the result is known or on its way
148
+ this.parentUrlListeners[childHref].push(listener);
149
+ if (childHref in this.parentUrlResults)
150
+ listener(this.parentUrlResults[childHref]);
151
+ return;
148
152
  }
149
- this.parentUrlListeners[childHref] = this.parentUrlListeners[childHref] ?? [];
150
- this.parentUrlListeners[childHref].push(listener);
153
+ this.parentUrlListeners[childHref] = [listener];
151
154
  this.postMessageToParent(['df-child', 'addParentUrlListener', childHref]);
152
155
  }
153
156
  removeParentUrlListener(partialChildHref, listener) {
@@ -156,13 +159,16 @@ export default class DFrameContent {
156
159
  this.parentUrlListeners[childHref] = this.parentUrlListeners[childHref].filter(cb => cb !== listener);
157
160
  if (!this.parentUrlListeners[childHref].length) {
158
161
  delete this.parentUrlListeners[childHref];
162
+ delete this.parentUrlResults[childHref];
159
163
  this.postMessageToParent(['df-child', 'removeParentUrlListener', childHref]);
160
164
  }
161
165
  }
162
166
  }
163
167
  currentSyncedMouseEvents = new Set();
168
+ mouseEventTypes = [];
164
169
  initMouseEvents(types) {
165
170
  this.log('debug', 'initMouseEvents');
171
+ this.mouseEventTypes = types;
166
172
  for (const eventType of types) {
167
173
  document.addEventListener(eventType, (e) => {
168
174
  if (!(e instanceof MouseEvent))
@@ -1,7 +1,7 @@
1
1
  import { type ParentMessage } from './messages.js';
2
2
  export interface StateChangeAdapter {
3
3
  stateChange(action: 'push' | 'replace', newUrl: URL, element: DFrameElement): void;
4
- onStateChange(callback: () => void): void;
4
+ onStateChange(callback: () => void): () => void;
5
5
  }
6
6
  export declare class DFrameElement extends HTMLElement {
7
7
  get id(): string;
@@ -54,6 +54,7 @@ export declare class DFrameElement extends HTMLElement {
54
54
  private resizeObserver;
55
55
  private boundOnMessage;
56
56
  private mouseEventListeners;
57
+ private stopOnStateChange;
57
58
  private parentUrlListeners;
58
59
  get actualAspectRatio(): number;
59
60
  constructor();
@@ -17,6 +17,7 @@ class WindowStateChangeAdapter {
17
17
  // the idea being that the child makes itself wholly compatible with d-frame and changes its navigation logic to fit it
18
18
  // but the parent should be left as untouched as possible and uses d-frame as a black box (custom element)
19
19
  window.addEventListener('popstate', callback);
20
+ return () => { window.removeEventListener('popstate', callback); };
20
21
  }
21
22
  }
22
23
  // we try to adapt to some of the conventions found out there
@@ -55,7 +56,7 @@ export class DFrameElement extends HTMLElement {
55
56
  }
56
57
  get src() { return this.getAttribute('src'); }
57
58
  set src(value) { this.setAttribute('src', value); }
58
- get fullSrc() { return this.src.startsWith('/') ? (window.location.origin + this.src) : this.src; }
59
+ get fullSrc() { return new URL(this.src, window.location.href).href; }
59
60
  get scrolling() { return this.getAttribute('scrolling'); }
60
61
  set scrolling(value) { this.setAttribute('scrolling', value); }
61
62
  get aspectRatio() {
@@ -108,9 +109,9 @@ export class DFrameElement extends HTMLElement {
108
109
  }
109
110
  set syncPath(value) {
110
111
  if (value !== null)
111
- this.setAttribute('sync-params', value);
112
+ this.setAttribute('sync-path', value);
112
113
  else
113
- this.removeAttribute('sync-params');
114
+ this.removeAttribute('sync-path');
114
115
  }
115
116
  get stateChangeEvents() { return this.hasAttribute('state-change-events'); }
116
117
  set stateChangeEvents(value) {
@@ -169,6 +170,7 @@ export class DFrameElement extends HTMLElement {
169
170
  resizeObserver;
170
171
  boundOnMessage = (e) => this.onMessage(e);
171
172
  mouseEventListeners = {};
173
+ stopOnStateChange;
172
174
  parentUrlListeners = {};
173
175
  get actualAspectRatio() {
174
176
  if (this.aspectRatio !== 'auto')
@@ -233,6 +235,8 @@ export class DFrameElement extends HTMLElement {
233
235
  src: this.currentChildSrc
234
236
  }]);
235
237
  this.childInitialized = true;
238
+ // a new document starts in the iframe, it registers its own parent URL listeners
239
+ this.parentUrlListeners = {};
236
240
  }
237
241
  if (isHeightMessage(message)) {
238
242
  this.resizedHeight = message[2];
@@ -244,6 +248,8 @@ export class DFrameElement extends HTMLElement {
244
248
  }
245
249
  if (isReinitHeightMessage(message) && this.reinitHeight === 'on-message') {
246
250
  this.resizedHeight = undefined;
251
+ // not maintained while the height came from the child
252
+ this.updateAspectRatioHeight();
247
253
  this.updateStyle();
248
254
  }
249
255
  if (isStateChangeMessage(message)) {
@@ -281,7 +287,7 @@ export class DFrameElement extends HTMLElement {
281
287
  this.dispatchEvent(new CustomEvent('ready'));
282
288
  this.updateStyle();
283
289
  }
284
- if (isMouseEventMessage(message)) {
290
+ if (isMouseEventMessage(message) && this.mouseEvents?.includes(message[2])) {
285
291
  this.applySyncedMouseEvent(message);
286
292
  }
287
293
  if (isAddParentUrlListenerMessage(message)) {
@@ -309,7 +315,8 @@ export class DFrameElement extends HTMLElement {
309
315
  this.parsedSyncParams = this.syncParams !== null ? parseSyncParams(this.syncParams || '*') : undefined;
310
316
  }
311
317
  updateSrc() {
312
- if (!this.connected)
318
+ // an empty src is not known yet (<d-frame :src="url">), resolved against the page it would load the parent itself
319
+ if (!this.connected || !this.src)
313
320
  return;
314
321
  for (const [childHref, existingParentUrl] of Object.entries(this.parentUrlListeners)) {
315
322
  const parentUrl = getParentUrl(this.fullSrc, childHref, window.location.href, this.parsedSyncParams, this.syncPath);
@@ -348,7 +355,7 @@ export class DFrameElement extends HTMLElement {
348
355
  this.log('debug', 'height of iframe based on direct attribute: ' + this.height);
349
356
  wrapperHeight = this.height;
350
357
  }
351
- else if (this.aspectRatio !== null) {
358
+ else if (this.aspectRatio !== null && this.aspectRatioHeight) {
352
359
  this.log('debug', 'height of iframe based on aspect ratio: ' + this.aspectRatioHeight);
353
360
  wrapperHeight = this.aspectRatioHeight + 'px';
354
361
  }
@@ -463,7 +470,13 @@ export class DFrameElement extends HTMLElement {
463
470
  /* standard custom element callbacks */
464
471
  connectedCallback() {
465
472
  if (!this.debug) {
466
- const globalDebug = window.localStorage.getItem('debug');
473
+ let globalDebug = null;
474
+ try {
475
+ globalDebug = window.localStorage.getItem('debug');
476
+ }
477
+ catch {
478
+ // storage is blocked in some contexts (sandboxed parent, blocked cookies)
479
+ }
467
480
  if (globalDebug) {
468
481
  const debugParts = globalDebug.split(',');
469
482
  if (debugParts.includes('d-frame') || debugParts.includes('d-frame:' + this.id))
@@ -477,7 +490,13 @@ export class DFrameElement extends HTMLElement {
477
490
  this.log('debug', 'connected');
478
491
  this.connected = true;
479
492
  if (this.adapter.onStateChange) {
480
- this.adapter.onStateChange(() => { this.updateSrc(); });
493
+ this.stopOnStateChange = this.adapter.onStateChange(() => { this.updateSrc(); });
494
+ }
495
+ if (this.aspectRatio !== null && !this.height) {
496
+ // measure synchronously: the observer only fires after the next layout, and the frame
497
+ // would be painted at the height of its loading slot in between
498
+ this.width = this.getBoundingClientRect().width;
499
+ this.updateAspectRatioHeight();
481
500
  }
482
501
  this.resizeObserver.observe(this);
483
502
  window.addEventListener('message', this.boundOnMessage);
@@ -490,8 +509,16 @@ export class DFrameElement extends HTMLElement {
490
509
  }
491
510
  disconnectedCallback() {
492
511
  this.log('debug', 'disconnected');
512
+ this.connected = false;
513
+ // the iframe is reloaded from its src attribute when the element is connected again:
514
+ // it must be the state reached by the child, then the child does the handshake again
515
+ this.childInitialized = false;
516
+ if (this.currentChildSrc)
517
+ this.iframeElement.setAttribute('src', this.currentChildSrc);
493
518
  this.resizeObserver.disconnect();
494
519
  window.removeEventListener('message', this.boundOnMessage);
520
+ this.stopOnStateChange?.();
521
+ this.stopOnStateChange = undefined;
495
522
  for (const [eventType, listener] of Object.entries(this.mouseEventListeners)) {
496
523
  document.removeEventListener(eventType, listener, true);
497
524
  delete this.mouseEventListeners[eventType];
@@ -500,16 +527,22 @@ export class DFrameElement extends HTMLElement {
500
527
  /* reflected attributes */
501
528
  static get observedAttributes() { return ['src', 'aspect-ratio', 'height', 'sync-params', 'sync-path', 'reload']; }
502
529
  attributeChangedCallback(name, oldValue, newValue) {
503
- if (name === 'aspect-ratio')
530
+ if (name === 'aspect-ratio') {
504
531
  this.updateAspectRatioHeight();
532
+ // also when the attribute is removed
533
+ this.updateStyle();
534
+ }
505
535
  // if (name.startsWith('iframe-')) this.updateIframeExtraAttrs()
506
536
  if (!this.connected)
507
537
  return;
508
538
  this.log('debug', 'attribute change', name, oldValue, newValue);
509
539
  if (name === 'src')
510
540
  this.updateSrc();
511
- if (name === 'height')
541
+ if (name === 'height') {
542
+ // the aspect ratio height is not maintained while a height is set
543
+ this.updateAspectRatioHeight();
512
544
  this.updateStyle();
545
+ }
513
546
  if (name === 'sync-params') {
514
547
  this.updateParsedSyncParams();
515
548
  this.updateSrc();
@@ -521,10 +554,6 @@ export class DFrameElement extends HTMLElement {
521
554
  this.log('error', 'reload attribute changed but the iframe has no current src');
522
555
  return;
523
556
  }
524
- if (!this.iframeElement.contentWindow?.location) {
525
- this.log('debug', 'reload attribute changed but no iframe contentWindow, is this a cross domain iframe ?');
526
- return;
527
- }
528
557
  if (!this.iframeLoaded) {
529
558
  this.log('debug', 'reload attribute changed before initial loading was complete, ignore it');
530
559
  return;
@@ -532,7 +561,12 @@ export class DFrameElement extends HTMLElement {
532
561
  this.log('debug', 'reload iframe with current src', this.currentChildSrc);
533
562
  this.childInitialized = false;
534
563
  this.iframeElement.setAttribute('src', this.currentChildSrc);
535
- this.iframeElement.contentWindow.location.reload();
564
+ try {
565
+ this.iframeElement.contentWindow?.location.reload();
566
+ }
567
+ catch {
568
+ // cross-origin child: reload() is forbidden, the src attribute set above already navigates the frame
569
+ }
536
570
  }
537
571
  }
538
572
  }
package/lib/messages.js CHANGED
@@ -1,17 +1,20 @@
1
+ import { isHttpUrl } from './utils/url.js';
1
2
  export function isMouseEventMessage(message) {
2
- return message[0] === 'df-global' && message[1] === 'mouse';
3
+ return message[0] === 'df-global' && message[1] === 'mouse' && typeof message[2] === 'string';
3
4
  }
4
5
  export function isInitChildMessage(message) {
5
6
  return message[0] === 'df-child' && message[1] === 'init';
6
7
  }
7
8
  export function isHeightMessage(message) {
8
- return message[0] === 'df-child' && message[1] === 'height';
9
+ // the height is written in the style of the wrapper, anything else than a number would inject css
10
+ return message[0] === 'df-child' && message[1] === 'height' && typeof message[2] === 'number' && Number.isFinite(message[2]) && message[2] >= 0;
9
11
  }
10
12
  export function isReinitHeightMessage(message) {
11
13
  return message[0] === 'df-child' && message[1] === 'reinit-height';
12
14
  }
15
+ // the URL of the child becomes the src of the iframe when it is reloaded: a javascript: URL would run in the parent
13
16
  export function isStateChangeMessage(message) {
14
- return message[0] === 'df-child' && message[1] === 'stateChange';
17
+ return message[0] === 'df-child' && message[1] === 'stateChange' && (message[2] === 'push' || message[2] === 'replace') && isHttpUrl(message[3]);
15
18
  }
16
19
  export function isCustomMessage(message) {
17
20
  return message[0] === 'df-child' && message[1] === 'custom';
@@ -20,7 +23,7 @@ export function isNotifMessage(message) {
20
23
  return message[0] === 'df-child' && message[1] === 'notif';
21
24
  }
22
25
  export function isAddParentUrlListenerMessage(message) {
23
- return message[0] === 'df-child' && message[1] === 'addParentUrlListener';
26
+ return message[0] === 'df-child' && message[1] === 'addParentUrlListener' && isHttpUrl(message[2]);
24
27
  }
25
28
  export function isRemoveParentUrlListenerMessage(message) {
26
29
  return message[0] === 'df-child' && message[1] === 'removeParentUrlListener';
@@ -3,7 +3,7 @@ const escapeRegExp = (str) => str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
3
3
  export function parseSyncParams(syncParamsStr) {
4
4
  const syncParams = [];
5
5
  for (const syncParam of syncParamsStr.split(',')) {
6
- const [pattern = '*', append] = syncParam.split(':');
6
+ const [pattern = '*', append] = syncParam.trim().split(':');
7
7
  const regexpStr = '^' + escapeRegExp(pattern).replace(/\\\*/g, '(.*)') + '$';
8
8
  syncParams.push({
9
9
  regexp: new RegExp(regexpStr),
@@ -60,8 +60,8 @@ export function getParentUrl(fullSrc, childHref, currentParentHref, syncParams,
60
60
  const childUrl = new URL(childHref);
61
61
  if (syncParams) {
62
62
  // remove existing keys that were deleted from the child
63
- // use toArray instead of iterator as we mutate parentUrl in the loop
64
- for (const key of parentUrl.searchParams.keys().toArray()) {
63
+ // copy the keys as we mutate parentUrl in the loop (not with Iterator.toArray(), missing in Safari < 18.4)
64
+ for (const key of Array.from(parentUrl.searchParams.keys())) {
65
65
  for (const syncParam of syncParams) {
66
66
  if (syncParam.append && !key.startsWith(syncParam.append))
67
67
  continue;
@@ -78,10 +78,12 @@ export function getParentUrl(fullSrc, childHref, currentParentHref, syncParams,
78
78
  for (const syncParam of syncParams) {
79
79
  if (!key.match(syncParam.regexp))
80
80
  continue;
81
- if (srcUrl?.searchParams.get(key) === value)
82
- continue;
83
81
  const parentKey = syncParam.append ? (syncParam.append + key) : key;
84
- parentUrl.searchParams.set(parentKey, value);
82
+ // the value of src is the default one: no need to keep it in the parent, but a previous value must go
83
+ if (srcUrl.searchParams.get(key) === value)
84
+ parentUrl.searchParams.delete(parentKey);
85
+ else
86
+ parentUrl.searchParams.set(parentKey, value);
85
87
  break;
86
88
  }
87
89
  });
@@ -92,8 +94,8 @@ export function getParentUrl(fullSrc, childHref, currentParentHref, syncParams,
92
94
  parentUrl.hash = path;
93
95
  }
94
96
  else if (syncPath.startsWith('/')) {
95
- // a full path is not accepted, only a path child of the base
96
- if (path.startsWith('/'))
97
+ // a full path or another origin is not accepted, only a path child of the base
98
+ if (path.startsWith('/') || childUrl.origin !== srcUrl.origin)
97
99
  return null;
98
100
  if (path === '')
99
101
  parentUrl.pathname = syncPath;
@@ -1,3 +1,4 @@
1
1
  export declare function getPathDirectory(path: string): string;
2
2
  export declare function getPathWithTrailingSlash(path: string): string;
3
3
  export declare function getUrlRelativePath(srcUrl: URL, currentUrl: URL): string;
4
+ export declare function isHttpUrl(value: unknown, base?: string): value is string;
package/lib/utils/url.js CHANGED
@@ -20,3 +20,14 @@ export function getUrlRelativePath(srcUrl, currentUrl) {
20
20
  return currentUrl.pathname;
21
21
  return currentUrl.pathname.replace(srcDirectory, '');
22
22
  }
23
+ // a URL that can be navigated to or used as a link without running code (no javascript:, data:...)
24
+ export function isHttpUrl(value, base) {
25
+ if (typeof value !== 'string')
26
+ return false;
27
+ try {
28
+ return ['http:', 'https:'].includes(new URL(value, base).protocol);
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
@@ -1,5 +1,5 @@
1
1
  export function isVIframeUiNotif(message) {
2
- return message.vIframe === true && message.uiNotification !== undefined;
2
+ return message?.vIframe === true && message.uiNotification !== undefined && message.uiNotification !== null;
3
3
  }
4
4
  export function convertVIframeUiNotif(notif) {
5
5
  if (typeof notif === 'string')
@@ -1,6 +1,6 @@
1
1
  import { onScopeDispose, toRef, reactive } from 'vue';
2
2
  export const useDFrameParentUrls = (dFrameContent, router) => {
3
- console.warn('useDFrameParentUrls is deprecated, it is replaced by useParentUrl from @data-fair/frame/vue-router/use-parent-url.js');
3
+ console.warn('useDFrameParentUrls is deprecated, it is replaced by useParentUrl from @data-fair/frame/lib/vue-router/use-parent-url.js');
4
4
  let parentUrls = reactive({});
5
5
  let listeners = {};
6
6
  const get = (childHref) => {
@@ -4,7 +4,7 @@ declare class VueReactiveDFrameStateChangeAdapter implements StateChangeAdapter
4
4
  reactiveParams: Reactive<Record<string, string>>;
5
5
  constructor(reactiveParams: Reactive<Record<string, string>>);
6
6
  stateChange(action: 'push' | 'replace', newUrl: URL): void;
7
- onStateChange(callback: () => void): void;
7
+ onStateChange(callback: () => void): () => void;
8
8
  }
9
9
  declare const _default: (reactiveParams: Reactive<Record<string, string>>) => VueReactiveDFrameStateChangeAdapter;
10
10
  export default _default;
@@ -9,7 +9,7 @@ class VueReactiveDFrameStateChangeAdapter {
9
9
  applySearchParams(this.reactiveParams, newUrl.searchParams);
10
10
  }
11
11
  onStateChange(callback) {
12
- watch(this.reactiveParams, callback);
12
+ return watch(this.reactiveParams, callback);
13
13
  }
14
14
  }
15
15
  export default (reactiveParams) => new VueReactiveDFrameStateChangeAdapter(reactiveParams);
@@ -1,10 +1,9 @@
1
1
  export const applySearchParams = (reactiveParams, searchParams) => {
2
- const existingKeys = Object.keys(reactiveParams);
3
2
  searchParams.forEach((value, key) => {
4
3
  reactiveParams[key] = value;
5
- existingKeys.splice(existingKeys.indexOf(key), 1);
6
- });
7
- existingKeys.forEach(key => {
8
- delete reactiveParams[key];
9
4
  });
5
+ for (const key of Object.keys(reactiveParams)) {
6
+ if (!searchParams.has(key))
7
+ delete reactiveParams[key];
8
+ }
10
9
  };
@@ -4,7 +4,7 @@ declare class VueRouterDFrameStateChangeAdapter implements StateChangeAdapter {
4
4
  router: Router;
5
5
  constructor(router: Router);
6
6
  stateChange(action: 'push' | 'replace', newUrl: URL, element: DFrameElement): void;
7
- onStateChange(callback: () => void): void;
7
+ onStateChange(callback: () => void): () => void;
8
8
  }
9
9
  declare const _default: (router: Router) => VueRouterDFrameStateChangeAdapter;
10
10
  export default _default;