diff --git a/frontend/app/common/types.ts b/frontend/app/common/types.ts index 98f6ef34..e5c3d193 100644 --- a/frontend/app/common/types.ts +++ b/frontend/app/common/types.ts @@ -1,4 +1,4 @@ -export interface User { +export type User = { name: string; id: string; picture: string; @@ -7,15 +7,10 @@ export interface User { block: boolean; verified: boolean; email_subscription?: boolean; -} +}; /** data which is used on user-info page */ -export interface UserInfo { - id: User['id']; - name: string | ''; - isDefaultPicture: boolean; - picture: string; -} +export type UserInfo = Pick; export interface BlockedUser { id: string; diff --git a/frontend/app/common/user-info-settings.ts b/frontend/app/common/user-info-settings.ts index 79400215..357a8810 100644 --- a/frontend/app/common/user-info-settings.ts +++ b/frontend/app/common/user-info-settings.ts @@ -4,8 +4,6 @@ import type { UserInfo } from './types'; export const userInfo: UserInfo = parseQuery(); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isDefaultPicture = ((userInfo.isDefaultPicture as any) as string) !== '1'; export const id = userInfo.id; export const name = userInfo.name; export const picture = userInfo.picture; diff --git a/frontend/app/components/auth-panel/auth-panel.tsx b/frontend/app/components/auth-panel/auth-panel.tsx index 19134f87..c36bbcb6 100644 --- a/frontend/app/components/auth-panel/auth-panel.tsx +++ b/frontend/app/components/auth-panel/auth-panel.tsx @@ -75,9 +75,11 @@ class AuthPanelComponent extends Component { toggleUserInfoVisibility = () => { const { user } = this.props; - if (window.parent && user) { - postMessage({ isUserInfoShown: true, user }); + if (user === null) { + return; } + + postMessage({ isUserInfoShown: true, user }); }; renderAuthorized = (user: User) => { diff --git a/frontend/app/components/dropdown/dropdown.tsx b/frontend/app/components/dropdown/dropdown.tsx index 8fbf36ee..70af3682 100644 --- a/frontend/app/components/dropdown/dropdown.tsx +++ b/frontend/app/components/dropdown/dropdown.tsx @@ -4,6 +4,7 @@ import b from 'bem-react-helper'; import { Theme } from 'common/types'; import { sleep } from 'utils/sleep'; import { Button } from 'components/button'; +import { parseMessage } from 'utils/postMessage'; type Props = RenderableProps<{ title: string; @@ -133,12 +134,14 @@ export class Dropdown extends Component { }); } - receiveMessage(e: { data: string | object }) { + receiveMessage(event: MessageEvent<{ clickOutside?: boolean }>) { try { - const data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data; + const data = parseMessage(event); + + if (!data || !data.clickOutside || !this.state.isActive) { + return; + } - if (!data.clickOutside) return; - if (!this.state.isActive) return; this.setState( { contentTranslateX: 0, diff --git a/frontend/app/components/root/root.tsx b/frontend/app/components/root/root.tsx index 3c45fbfc..3c305e3e 100644 --- a/frontend/app/components/root/root.tsx +++ b/frontend/app/components/root/root.tsx @@ -4,7 +4,7 @@ import b from 'bem-react-helper'; import { IntlShape, useIntl, FormattedMessage, defineMessages } from 'react-intl'; import clsx from 'clsx'; -import type { Sorting } from 'common/types'; +import type { Sorting, Theme } from 'common/types'; import type { StoreState } from 'store'; import { COMMENT_NODE_CLASSNAME_PREFIX, @@ -39,7 +39,7 @@ import { ConnectedComment as Comment } from 'components/comment/connected-commen import { uploadImage, getPreview } from 'common/api'; import { isUserAnonymous } from 'utils/isUserAnonymous'; import { bindActions } from 'utils/actionBinder'; -import { postMessage } from 'utils/postMessage'; +import { postMessage, parseMessage } from 'utils/postMessage'; import { useActions } from 'hooks/useAction'; import { setCollapse } from 'store/thread/actions'; import { logout } from 'components/auth/auth.api'; @@ -177,13 +177,10 @@ export class Root extends Component { } }; - onMessage(event: { data: string | object }) { - if (!event.data) { - return; - } - + onMessage(event: MessageEvent<{ theme?: Theme }>) { try { - const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; + const data = parseMessage(event); + if (data.theme && THEMES.includes(data.theme)) { this.props.setTheme(data.theme); } diff --git a/frontend/app/embed.ts b/frontend/app/embed.ts index 504c6d1a..4cd970fc 100644 --- a/frontend/app/embed.ts +++ b/frontend/app/embed.ts @@ -1,5 +1,6 @@ import type { UserInfo, Theme } from 'common/types'; import { BASE_URL, NODE_ID, COMMENT_NODE_CLASSNAME_PREFIX } from 'common/constants.config'; +import { parseMessage, ParentMessage } from 'utils/postMessage'; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); @@ -199,9 +200,7 @@ function createInstance(config: typeof window.remark_config) { this.closeEl.innerHTML = '✖'; this.closeEl.onclick = () => this.close(); } - const queryUserInfo = `${query}&page=user-info&&id=${user.id}&name=${user.name}&picture=${ - user.picture || '' - }&isDefaultPicture=${user.isDefaultPicture || 0}`; + const queryUserInfo = `${query}&page=user-info&&id=${user.id}&name=${user.name}&picture=${user.picture || ''}`; const iframe = createFrame({ host: BASE_URL, query: queryUserInfo, height: '100%', margin: '0' }); this.node.appendChild(iframe); this.iframe = iframe; @@ -265,9 +264,10 @@ function createInstance(config: typeof window.remark_config) { }, }; - function receiveMessages(event: { data?: string }): void { + function receiveMessages(event: MessageEvent): void { try { - const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; + const data = parseMessage(event); + if (data.remarkIframeHeight) { iframe.style.height = `${data.remarkIframeHeight}px`; } @@ -278,7 +278,7 @@ function createInstance(config: typeof window.remark_config) { if (Object.prototype.hasOwnProperty.call(data, 'isUserInfoShown')) { if (data.isUserInfoShown) { - userInfo.init(data.user || {}); + userInfo.init(data.user); } else { userInfo.close(); } @@ -297,25 +297,25 @@ function createInstance(config: typeof window.remark_config) { if (hash.indexOf(`#${COMMENT_NODE_CLASSNAME_PREFIX}`) === 0) { if (e) e.preventDefault(); - iframe.contentWindow!.postMessage(JSON.stringify({ hash }), '*'); + iframe.contentWindow!.postMessage({ hash }, '*'); } } function postTitleToIframe(title: string) { if (iframe.contentWindow) { - iframe.contentWindow.postMessage(JSON.stringify({ title }), '*'); + iframe.contentWindow.postMessage({ title }, '*'); } } function postClickOutsideToIframe(e: MouseEvent) { if (iframe.contentWindow && !iframe.contains(e.target as Node)) { - iframe.contentWindow.postMessage(JSON.stringify({ clickOutside: true }), '*'); + iframe.contentWindow.postMessage({ clickOutside: true }, '*'); } } function changeTheme(theme: Theme) { if (iframe.contentWindow) { - iframe.contentWindow.postMessage(JSON.stringify({ theme }), '*'); + iframe.contentWindow.postMessage({ theme }, '*'); } } diff --git a/frontend/app/utils/postMessage.ts b/frontend/app/utils/postMessage.ts index f0a849db..da58dde1 100644 --- a/frontend/app/utils/postMessage.ts +++ b/frontend/app/utils/postMessage.ts @@ -1,22 +1,42 @@ -import { User } from 'common/types'; +import type { Theme, UserInfo } from 'common/types'; -export type Message = - | { inited: true } - | { - isUserInfoShown: true; - user: User; - } - | { isUserInfoShown: false } - | { scrollTo: number } - | { remarkIframeHeight: number }; +export type ParentMessage = { + inited?: true; + scrollTo?: number; + remarkIframeHeight?: number; +} & ( + | { isUserInfoShown: true; user: UserInfo } + | { isUserInfoShown: false; user?: never } + | { isUserInfoShown?: never; user?: never } +); + +export type ChildMessage = { + theme?: Theme; +}; + +type AllMessages = ParentMessage & ChildMessage; /** * Sends message to parent window * * @returns request success of fail */ -export function postMessage(data: Message): boolean { +export function postMessage(data: AllMessages): boolean { if (!window.parent || window.parent === window) return false; - window.parent.postMessage(JSON.stringify(data), '*'); + window.parent.postMessage(data, '*'); return true; } + +/** + * Parses data from post message that was received in iframe + * + * @param evt post message event + * @returns + */ +export function parseMessage({ data }: MessageEvent): T { + if (typeof data !== 'object' || data === null || Array.isArray(data)) { + return {} as T; + } + + return data as T; +} diff --git a/frontend/templates/iframe.ejs b/frontend/templates/iframe.ejs index 13dc9a82..4ff54472 100644 --- a/frontend/templates/iframe.ejs +++ b/frontend/templates/iframe.ejs @@ -134,7 +134,7 @@ setInterval(function () { if (document.body.offsetHeight !== lastHeight && document.body.offsetHeight > 22) { lastHeight = document.body.offsetHeight; - window.parent.postMessage(JSON.stringify({ remarkIframeHeight: lastHeight }), '*'); + window.parent.postMessage({ remarkIframeHeight: lastHeight }, '*'); } }, 200); @@ -142,7 +142,9 @@ function receiveMessages(event) { try { - const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; + const data = event.data; + const isObj = typeof data === 'object' && data !== null && !Array.isArray(data); + if (!isObj) return; const isItHash = data.hash && data.hash.indexOf('#') === 0; if (isItHash) { @@ -170,7 +172,7 @@ return c; }, {}); - window.parent.postMessage(JSON.stringify({ inited: true }), '*'); + window.parent.postMessage({ inited: true }, '*');