mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-18 09:04:44 -04:00
fix(window post message communication): set target origin on creation
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
7f6da650d1
commit
f2fbf9e7ca
5 changed files with 27 additions and 20 deletions
|
@ -7,20 +7,25 @@
|
||||||
import { MotdModal } from './motd-modal'
|
import { MotdModal } from './motd-modal'
|
||||||
import { act, render, screen } from '@testing-library/react'
|
import { act, render, screen } from '@testing-library/react'
|
||||||
import * as fetchMotdModule from './fetch-motd'
|
import * as fetchMotdModule from './fetch-motd'
|
||||||
|
import type { CommonModalProps } from '../modals/common-modal'
|
||||||
import * as CommonModalModule from '../modals/common-modal'
|
import * as CommonModalModule from '../modals/common-modal'
|
||||||
import type { PropsWithChildren } from 'react'
|
import type { PropsWithChildren } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type { CommonModalProps } from '../modals/common-modal'
|
|
||||||
import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n'
|
import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n'
|
||||||
import * as RenderIframeModule from '../../editor-page/renderer-pane/render-iframe'
|
import * as RenderIframeModule from '../../editor-page/renderer-pane/render-iframe'
|
||||||
import { testId } from '../../../utils/test-id'
|
import { testId } from '../../../utils/test-id'
|
||||||
|
import * as UseBaseUrlModule from '../../../hooks/common/use-base-url'
|
||||||
|
|
||||||
jest.mock('./fetch-motd')
|
jest.mock('./fetch-motd')
|
||||||
jest.mock('../modals/common-modal')
|
jest.mock('../modals/common-modal')
|
||||||
jest.mock('../../editor-page/renderer-pane/render-iframe')
|
jest.mock('../../editor-page/renderer-pane/render-iframe')
|
||||||
|
jest.mock('../../../hooks/common/use-base-url')
|
||||||
|
|
||||||
describe('motd modal', () => {
|
describe('motd modal', () => {
|
||||||
beforeAll(mockI18n)
|
beforeAll(async () => {
|
||||||
|
jest.spyOn(UseBaseUrlModule, 'useBaseUrl').mockImplementation(() => 'https://example.org')
|
||||||
|
await mockI18n()
|
||||||
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
jest.resetAllMocks()
|
jest.resetAllMocks()
|
||||||
|
|
|
@ -8,6 +8,7 @@ import type { PropsWithChildren } from 'react'
|
||||||
import React, { createContext, useContext, useEffect, useMemo } from 'react'
|
import React, { createContext, useContext, useEffect, useMemo } from 'react'
|
||||||
import { EditorToRendererCommunicator } from '../../render-page/window-post-message-communicator/editor-to-renderer-communicator'
|
import { EditorToRendererCommunicator } from '../../render-page/window-post-message-communicator/editor-to-renderer-communicator'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
|
import { ORIGIN, useBaseUrl } from '../../../hooks/common/use-base-url'
|
||||||
|
|
||||||
const EditorToRendererCommunicatorContext = createContext<EditorToRendererCommunicator | undefined>(undefined)
|
const EditorToRendererCommunicatorContext = createContext<EditorToRendererCommunicator | undefined>(undefined)
|
||||||
|
|
||||||
|
@ -29,7 +30,12 @@ export const useEditorToRendererCommunicator: () => EditorToRendererCommunicator
|
||||||
* Provides a {@link EditorToRendererCommunicator editor to renderer communicator} for the child components via Context.
|
* Provides a {@link EditorToRendererCommunicator editor to renderer communicator} for the child components via Context.
|
||||||
*/
|
*/
|
||||||
export const EditorToRendererCommunicatorContextProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
export const EditorToRendererCommunicatorContextProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||||
const communicator = useMemo<EditorToRendererCommunicator>(() => new EditorToRendererCommunicator(uuid()), [])
|
const rendererUrl = useBaseUrl(ORIGIN.RENDERER)
|
||||||
|
|
||||||
|
const communicator = useMemo<EditorToRendererCommunicator>(
|
||||||
|
() => new EditorToRendererCommunicator(uuid(), new URL(rendererUrl).origin),
|
||||||
|
[rendererUrl]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentCommunicator = communicator
|
const currentCommunicator = communicator
|
||||||
|
|
|
@ -28,27 +28,27 @@ export const useRendererToEditorCommunicator: () => RendererToEditorCommunicator
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RendererToEditorCommunicatorContextProvider: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
|
export const RendererToEditorCommunicatorContextProvider: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const editorOrigin = useBaseUrl(ORIGIN.EDITOR)
|
const editorUrl = useBaseUrl(ORIGIN.EDITOR)
|
||||||
const uuid = useSingleStringUrlParameter('uuid', undefined)
|
const uuid = useSingleStringUrlParameter('uuid', undefined)
|
||||||
const communicator = useMemo<RendererToEditorCommunicator>(() => {
|
const communicator = useMemo<RendererToEditorCommunicator>(() => {
|
||||||
if (uuid === undefined) {
|
if (uuid === undefined) {
|
||||||
throw new Error('no uuid found in url!')
|
throw new Error('no uuid found in url!')
|
||||||
} else {
|
} else {
|
||||||
return new RendererToEditorCommunicator(uuid)
|
return new RendererToEditorCommunicator(uuid, new URL(editorUrl).origin)
|
||||||
}
|
}
|
||||||
}, [uuid])
|
}, [editorUrl, uuid])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentCommunicator = communicator
|
const currentCommunicator = communicator
|
||||||
|
|
||||||
currentCommunicator.setMessageTarget(window.parent, new URL(editorOrigin).origin)
|
currentCommunicator.setMessageTarget(window.parent)
|
||||||
currentCommunicator.registerEventListener()
|
currentCommunicator.registerEventListener()
|
||||||
currentCommunicator.enableCommunication()
|
currentCommunicator.enableCommunication()
|
||||||
currentCommunicator.sendMessageToOtherSide({
|
currentCommunicator.sendMessageToOtherSide({
|
||||||
type: CommunicationMessageType.RENDERER_READY
|
type: CommunicationMessageType.RENDERER_READY
|
||||||
})
|
})
|
||||||
return () => currentCommunicator?.unregisterEventListener()
|
return () => currentCommunicator?.unregisterEventListener()
|
||||||
}, [communicator, editorOrigin])
|
}, [communicator, editorUrl])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a {@link RendererToEditorCommunicator renderer to editor communicator} for the child components via Context.
|
* Provides a {@link RendererToEditorCommunicator renderer to editor communicator} for the child components via Context.
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { useSendScrollState } from './hooks/use-send-scroll-state'
|
||||||
import { Logger } from '../../../utils/logger'
|
import { Logger } from '../../../utils/logger'
|
||||||
import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change'
|
import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change'
|
||||||
import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute'
|
import { cypressAttribute, cypressId } from '../../../utils/cypress-attribute'
|
||||||
import { ORIGIN, useBaseUrl } from '../../../hooks/common/use-base-url'
|
|
||||||
import { ShowIf } from '../../common/show-if/show-if'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
import { WaitSpinner } from '../../common/wait-spinner/wait-spinner'
|
import { WaitSpinner } from '../../common/wait-spinner/wait-spinner'
|
||||||
import { useExtensionEventEmitter } from '../../markdown-renderer/hooks/use-extension-event-emitter'
|
import { useExtensionEventEmitter } from '../../markdown-renderer/hooks/use-extension-event-emitter'
|
||||||
|
@ -69,7 +68,6 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [rendererReady, setRendererReady] = useState<boolean>(false)
|
const [rendererReady, setRendererReady] = useState<boolean>(false)
|
||||||
const frameReference = useRef<HTMLIFrameElement>(null)
|
const frameReference = useRef<HTMLIFrameElement>(null)
|
||||||
const rendererBaseUrl = useBaseUrl(ORIGIN.RENDERER)
|
|
||||||
const iframeCommunicator = useEditorToRendererCommunicator()
|
const iframeCommunicator = useEditorToRendererCommunicator()
|
||||||
const resetRendererReady = useCallback(() => {
|
const resetRendererReady = useCallback(() => {
|
||||||
log.debug('Reset render status')
|
log.debug('Reset render status')
|
||||||
|
@ -143,9 +141,7 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
log.error('Load triggered without content window')
|
log.error('Load triggered without content window')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const origin = new URL(rendererBaseUrl).origin
|
iframeCommunicator.setMessageTarget(otherWindow)
|
||||||
log.debug(`Set iframecommunicator window with origin ${origin ?? 'undefined'}`)
|
|
||||||
iframeCommunicator.setMessageTarget(otherWindow, origin)
|
|
||||||
iframeCommunicator.enableCommunication()
|
iframeCommunicator.enableCommunication()
|
||||||
iframeCommunicator.sendMessageToOtherSide({
|
iframeCommunicator.sendMessageToOtherSide({
|
||||||
type: CommunicationMessageType.SET_BASE_CONFIGURATION,
|
type: CommunicationMessageType.SET_BASE_CONFIGURATION,
|
||||||
|
@ -155,7 +151,7 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setRendererReady(true)
|
setRendererReady(true)
|
||||||
}, [iframeCommunicator, rendererBaseUrl, rendererType])
|
}, [iframeCommunicator, rendererType])
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffectOnRenderTypeChange(rendererType, onIframeLoad)
|
useEffectOnRenderTypeChange(rendererType, onIframeLoad)
|
||||||
|
|
|
@ -35,13 +35,12 @@ export abstract class WindowPostMessageCommunicator<
|
||||||
MESSAGES extends MessagePayload<RECEIVE_TYPE | SEND_TYPE>
|
MESSAGES extends MessagePayload<RECEIVE_TYPE | SEND_TYPE>
|
||||||
> {
|
> {
|
||||||
private messageTarget?: Window
|
private messageTarget?: Window
|
||||||
private targetOrigin?: string
|
|
||||||
private communicationEnabled: boolean
|
private communicationEnabled: boolean
|
||||||
private readonly emitter: EventEmitter2 = new EventEmitter2()
|
private readonly emitter: EventEmitter2 = new EventEmitter2()
|
||||||
private readonly log: Logger
|
private readonly log: Logger
|
||||||
private readonly boundListener: (event: MessageEvent) => void
|
private readonly boundListener: (event: MessageEvent) => void
|
||||||
|
|
||||||
public constructor(private uuid: string) {
|
public constructor(private readonly uuid: string, private readonly targetOrigin: string) {
|
||||||
this.boundListener = this.handleEvent.bind(this)
|
this.boundListener = this.handleEvent.bind(this)
|
||||||
this.communicationEnabled = false
|
this.communicationEnabled = false
|
||||||
this.log = this.createLogger()
|
this.log = this.createLogger()
|
||||||
|
@ -72,12 +71,10 @@ export abstract class WindowPostMessageCommunicator<
|
||||||
* Messages can be sent as soon as the communication is enabled.
|
* Messages can be sent as soon as the communication is enabled.
|
||||||
*
|
*
|
||||||
* @param otherSide The target {@link Window} that should receive the messages.
|
* @param otherSide The target {@link Window} that should receive the messages.
|
||||||
* @param otherOrigin The origin from the URL of the target. If this isn't correct then the message sending will produce CORS errors.
|
|
||||||
* @see enableCommunication
|
* @see enableCommunication
|
||||||
*/
|
*/
|
||||||
public setMessageTarget(otherSide: Window, otherOrigin: string): void {
|
public setMessageTarget(otherSide: Window): void {
|
||||||
this.messageTarget = otherSide
|
this.messageTarget = otherSide
|
||||||
this.targetOrigin = otherOrigin
|
|
||||||
this.communicationEnabled = false
|
this.communicationEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +83,6 @@ export abstract class WindowPostMessageCommunicator<
|
||||||
*/
|
*/
|
||||||
public unsetMessageTarget(): void {
|
public unsetMessageTarget(): void {
|
||||||
this.messageTarget = undefined
|
this.messageTarget = undefined
|
||||||
this.targetOrigin = undefined
|
|
||||||
this.communicationEnabled = false
|
this.communicationEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +148,10 @@ export abstract class WindowPostMessageCommunicator<
|
||||||
*/
|
*/
|
||||||
protected handleEvent(event: MessageEvent<MessagePayloadWithUuid<RECEIVE_TYPE>>): void {
|
protected handleEvent(event: MessageEvent<MessagePayloadWithUuid<RECEIVE_TYPE>>): void {
|
||||||
if (event.origin !== this.targetOrigin) {
|
if (event.origin !== this.targetOrigin) {
|
||||||
|
this.log.error(
|
||||||
|
`message declined. origin was "${event.origin}" but expected "${String(this.targetOrigin)}"`,
|
||||||
|
event.data
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Optional.ofNullable(event.data)
|
Optional.ofNullable(event.data)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue