Skip to content

Commit 32c0387

Browse files
committedMay 26, 2024·
feat: add observer to listen iframe href change
1 parent 37cea4a commit 32c0387

File tree

8 files changed

+64
-47
lines changed

8 files changed

+64
-47
lines changed
 

‎.eslintrc.cjs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = {
1111
plugins: ['react-refresh', 'mobx'],
1212
rules: {
1313
'@typescript-eslint/ban-ts-comment': 'off',
14+
'@typescript-eslint/no-explicit-any': 'off',
1415
'react-refresh/only-export-components': [
1516
'warn',
1617
{allowConstantExport: true},

‎src/components/devices/device-screen.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {Device} from '@/domain/models'
22
import {observer} from 'mobx-react-lite'
33
import {X} from 'lucide-react'
44
import {appStore} from '@/store/app-store'
5-
import {merge} from '@/utils'
5+
import {getIframeId, merge} from '@/utils'
66
import {useToggle} from '@uidotdev/usehooks'
77

88
type Props = {
@@ -52,6 +52,7 @@ export const DeviceScreen = observer(({src, device: device}: Props) => {
5252
}}>
5353
{src ? (
5454
<iframe
55+
id={getIframeId(device.id)}
5556
src={src}
5657
sandbox={`allow-scripts allow-forms allow-same-origin allow-presentation allow-orientation-lock allow-modals allow-popups allow-popups-to-escape-sandbox allow-pointer-lock`}
5758
allow='web-share'

‎src/hooks/use-app-listeners.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {appStore} from '@/store/app-store'
22
import {appUIStore} from '@/store/app-ui-store'
3+
import {syncLocationStore} from '@/store/sync-location-store'
34
import {useEffect} from 'react'
45

56
export const useAppListeners = () => {
@@ -20,7 +21,7 @@ export const useAppListeners = () => {
2021
}, [])
2122

2223
useEffect(() => {
23-
appUIStore.syncLocation(devices)
24+
syncLocationStore.initialize(appStore.devices)
2425
}, [devices])
2526

2627
return {}

‎src/store/app-ui-store.ts

-45
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import {Device} from '@/domain/models'
2-
import {appStore} from '@/store/app-store'
31
import {autorun, makeAutoObservable} from 'mobx'
42
import {WheelEvent as ReactWheelEvent, RefObject} from 'react'
53

@@ -110,49 +108,6 @@ export class AppUIStore {
110108
const zoomAmount = -(event.deltaY / ZOOM_SENSITIVITY)
111109
this.setZoom(Math.max(Math.min(this.zoom + zoomAmount, 2), 0.2))
112110
}
113-
114-
private iframeListenUrlChange(
115-
iframe: HTMLIFrameElement,
116-
callback: (url: string) => void,
117-
) {
118-
let lastDispatched: string
119-
let isFirstLoad: boolean = true
120-
121-
const getUrl = () => iframe?.contentWindow?.location.href ?? ''
122-
123-
const getUrlHandler = () => {
124-
const href = getUrl()
125-
126-
if (lastDispatched !== href && !isFirstLoad) {
127-
console.log('dispatch')
128-
callback(href)
129-
lastDispatched = href
130-
} else {
131-
// prevent to dispatch when it's still the initial page
132-
isFirstLoad = false
133-
lastDispatched = href
134-
}
135-
}
136-
137-
let intervalId: NodeJS.Timeout
138-
139-
iframe.contentWindow?.addEventListener('load', () => {
140-
// clear interval if there's some
141-
if (intervalId) clearInterval(intervalId)
142-
intervalId = setInterval(getUrlHandler, 100)
143-
})
144-
}
145-
146-
syncLocation(devices: Device[]) {
147-
devices.forEach(device => {
148-
const selector = `#screen-${device.id} iframe`
149-
const iframe = document.querySelector<HTMLIFrameElement>(selector)
150-
151-
if (!iframe) return
152-
153-
this.iframeListenUrlChange(iframe, url => appStore.setUrl(url))
154-
})
155-
}
156111
}
157112

158113
export const appUIStore = new AppUIStore()

‎src/store/sync-location-store.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {Device} from '@/domain/models'
2+
import {getIframeBody, getIframeHref, getIframeId} from '@/utils'
3+
import {makeAutoObservable} from 'mobx'
4+
5+
export class SyncLocationStore {
6+
lastDispatched: string = ''
7+
8+
constructor() {
9+
makeAutoObservable(this)
10+
}
11+
12+
private iframeListenUrlChange(
13+
iframe: HTMLIFrameElement,
14+
callback: (url: string) => void,
15+
) {
16+
iframe.contentWindow?.addEventListener('load', () => {
17+
let oldHref = getIframeHref(iframe)
18+
const body = getIframeBody(iframe)
19+
20+
const observer = new MutationObserver(() => {
21+
const newHref = getIframeHref(iframe)
22+
23+
if (oldHref !== newHref) {
24+
oldHref = newHref
25+
callback(newHref)
26+
}
27+
})
28+
29+
if (!body) return
30+
observer.observe(body, {childList: true, subtree: true})
31+
})
32+
}
33+
34+
initialize(devices: Device[]) {
35+
devices.forEach(device => {
36+
const selector = `#${getIframeId(device.id)}`
37+
const iframe = document.querySelector<HTMLIFrameElement>(selector)
38+
39+
if (!iframe) return
40+
41+
this.iframeListenUrlChange(iframe, url => console.log(url))
42+
})
43+
}
44+
}
45+
46+
export const syncLocationStore = new SyncLocationStore()

‎src/utils/iframe.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const getIframeHref = (iframe: HTMLIFrameElement): string => {
2+
return iframe.contentWindow?.location.href || ''
3+
}
4+
5+
export const getIframeBody = (
6+
iframe: HTMLIFrameElement,
7+
): HTMLElement | undefined => {
8+
return iframe.contentDocument?.body
9+
}

‎src/utils/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export * from './merge-classes'
2+
export * from './iframe'
3+
export * from './screen'

‎src/utils/screen.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const getScreenId = (id: string) => `screen-${id}`
2+
export const getIframeId = (id: string) => `screen-iframe-${id}`

0 commit comments

Comments
 (0)
Please sign in to comment.