fix(window post message communication): set target origin on creation

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-11-10 17:24:44 +01:00
parent 7f6da650d1
commit f2fbf9e7ca
5 changed files with 27 additions and 20 deletions

View file

@ -7,20 +7,25 @@
import { MotdModal } from './motd-modal'
import { act, render, screen } from '@testing-library/react'
import * as fetchMotdModule from './fetch-motd'
import type { CommonModalProps } from '../modals/common-modal'
import * as CommonModalModule from '../modals/common-modal'
import type { PropsWithChildren } from 'react'
import React from 'react'
import type { CommonModalProps } from '../modals/common-modal'
import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n'
import * as RenderIframeModule from '../../editor-page/renderer-pane/render-iframe'
import { testId } from '../../../utils/test-id'
import * as UseBaseUrlModule from '../../../hooks/common/use-base-url'
jest.mock('./fetch-motd')
jest.mock('../modals/common-modal')
jest.mock('../../editor-page/renderer-pane/render-iframe')
jest.mock('../../../hooks/common/use-base-url')
describe('motd modal', () => {
beforeAll(mockI18n)
beforeAll(async () => {
jest.spyOn(UseBaseUrlModule, 'useBaseUrl').mockImplementation(() => 'https://example.org')
await mockI18n()
})
afterAll(() => {
jest.resetAllMocks()

View file

@ -8,6 +8,7 @@ import type { PropsWithChildren } from 'react'
import React, { createContext, useContext, useEffect, useMemo } from 'react'
import { EditorToRendererCommunicator } from '../../render-page/window-post-message-communicator/editor-to-renderer-communicator'
import { v4 as uuid } from 'uuid'
import { ORIGIN, useBaseUrl } from '../../../hooks/common/use-base-url'
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.
*/
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(() => {
const currentCommunicator = communicator

View file

@ -28,27 +28,27 @@ export const useRendererToEditorCommunicator: () => RendererToEditorCommunicator
}
export const RendererToEditorCommunicatorContextProvider: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
const editorOrigin = useBaseUrl(ORIGIN.EDITOR)
const editorUrl = useBaseUrl(ORIGIN.EDITOR)
const uuid = useSingleStringUrlParameter('uuid', undefined)
const communicator = useMemo<RendererToEditorCommunicator>(() => {
if (uuid === undefined) {
throw new Error('no uuid found in url!')
} else {
return new RendererToEditorCommunicator(uuid)
return new RendererToEditorCommunicator(uuid, new URL(editorUrl).origin)
}
}, [uuid])
}, [editorUrl, uuid])
useEffect(() => {
const currentCommunicator = communicator
currentCommunicator.setMessageTarget(window.parent, new URL(editorOrigin).origin)
currentCommunicator.setMessageTarget(window.parent)
currentCommunicator.registerEventListener()
currentCommunicator.enableCommunication()
currentCommunicator.sendMessageToOtherSide({
type: CommunicationMessageType.RENDERER_READY
})
return () => currentCommunicator?.unregisterEventListener()
}, [communicator, editorOrigin])
}, [communicator, editorUrl])
/**
* Provides a {@link RendererToEditorCommunicator renderer to editor communicator} for the child components via Context.

View file

@ -23,7 +23,6 @@ import { useSendScrollState } from './hooks/use-send-scroll-state'
import { Logger } from '../../../utils/logger'
import { useEffectOnRenderTypeChange } from './hooks/use-effect-on-render-type-change'
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 { WaitSpinner } from '../../common/wait-spinner/wait-spinner'
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 frameReference = useRef<HTMLIFrameElement>(null)
const rendererBaseUrl = useBaseUrl(ORIGIN.RENDERER)
const iframeCommunicator = useEditorToRendererCommunicator()
const resetRendererReady = useCallback(() => {
log.debug('Reset render status')
@ -143,9 +141,7 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
log.error('Load triggered without content window')
return
}
const origin = new URL(rendererBaseUrl).origin
log.debug(`Set iframecommunicator window with origin ${origin ?? 'undefined'}`)
iframeCommunicator.setMessageTarget(otherWindow, origin)
iframeCommunicator.setMessageTarget(otherWindow)
iframeCommunicator.enableCommunication()
iframeCommunicator.sendMessageToOtherSide({
type: CommunicationMessageType.SET_BASE_CONFIGURATION,
@ -155,7 +151,7 @@ export const RenderIframe: React.FC<RenderIframeProps> = ({
}
})
setRendererReady(true)
}, [iframeCommunicator, rendererBaseUrl, rendererType])
}, [iframeCommunicator, rendererType])
)
useEffectOnRenderTypeChange(rendererType, onIframeLoad)

View file

@ -35,13 +35,12 @@ export abstract class WindowPostMessageCommunicator<
MESSAGES extends MessagePayload<RECEIVE_TYPE | SEND_TYPE>
> {
private messageTarget?: Window
private targetOrigin?: string
private communicationEnabled: boolean
private readonly emitter: EventEmitter2 = new EventEmitter2()
private readonly log: Logger
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.communicationEnabled = false
this.log = this.createLogger()
@ -72,12 +71,10 @@ export abstract class WindowPostMessageCommunicator<
* Messages can be sent as soon as the communication is enabled.
*
* @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
*/
public setMessageTarget(otherSide: Window, otherOrigin: string): void {
public setMessageTarget(otherSide: Window): void {
this.messageTarget = otherSide
this.targetOrigin = otherOrigin
this.communicationEnabled = false
}
@ -86,7 +83,6 @@ export abstract class WindowPostMessageCommunicator<
*/
public unsetMessageTarget(): void {
this.messageTarget = undefined
this.targetOrigin = undefined
this.communicationEnabled = false
}
@ -152,6 +148,10 @@ export abstract class WindowPostMessageCommunicator<
*/
protected handleEvent(event: MessageEvent<MessagePayloadWithUuid<RECEIVE_TYPE>>): void {
if (event.origin !== this.targetOrigin) {
this.log.error(
`message declined. origin was "${event.origin}" but expected "${String(this.targetOrigin)}"`,
event.data
)
return
}
Optional.ofNullable(event.data)