From 1621f73c6959c3697c58687174beb5d647e24567 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 22 Nov 2022 14:23:30 +0100 Subject: [PATCH] refactor(ui/player): create player context for webplayer components --- .../app/components/Session/PlayerContent.js | 70 +++++++++ frontend/app/components/Session/WebPlayer.js | 139 ------------------ frontend/app/components/Session/WebPlayer.tsx | 127 ++++++++++++++++ .../app/components/Session/playerContext.ts | 12 ++ .../components/Session_/Console/Console.js | 18 --- .../Session_/Console/ConsoleContent.js | 123 ---------------- .../Console/ConsoleRow/ConsoleRow.tsx | 48 ------ .../Session_/Console/ConsoleRow/index.ts | 1 - .../Session_/Console/console.module.css | 38 ----- .../components/Session_/Inspector/index.js | 6 +- .../Session_/LongTasks/LongTasks.js | 8 +- .../app/components/Session_/Player/Player.js | 110 ++++++-------- .../components/Session_/Profiler/Profiler.js | 4 +- frontend/app/player/create.ts | 8 +- 14 files changed, 271 insertions(+), 441 deletions(-) create mode 100644 frontend/app/components/Session/PlayerContent.js delete mode 100644 frontend/app/components/Session/WebPlayer.js create mode 100644 frontend/app/components/Session/WebPlayer.tsx create mode 100644 frontend/app/components/Session/playerContext.ts delete mode 100644 frontend/app/components/Session_/Console/Console.js delete mode 100644 frontend/app/components/Session_/Console/ConsoleContent.js delete mode 100644 frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx delete mode 100644 frontend/app/components/Session_/Console/ConsoleRow/index.ts delete mode 100644 frontend/app/components/Session_/Console/console.module.css diff --git a/frontend/app/components/Session/PlayerContent.js b/frontend/app/components/Session/PlayerContent.js new file mode 100644 index 000000000..8983ca5fc --- /dev/null +++ b/frontend/app/components/Session/PlayerContent.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { + connectPlayer, +} from 'Player'; +import PlayerBlock from '../Session_/PlayerBlock'; +import styles from '../Session_/session.module.css'; +import { countDaysFrom } from 'App/date'; +import cn from 'classnames'; +import RightBlock from './RightBlock'; + +function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, hasError }) { + const sessionDays = countDaysFrom(session.startedAt); + return ( +
+ {hasError ? ( +
+
+
+ {sessionDays > 2 ? 'Session not found.' : 'This session is still being processed.'} +
+
+ {sessionDays > 2 + ? 'Please check your data retention policy.' + : 'Please check it again in a few minutes.'} +
+
+
+ ) : ( +
+
+
+ +
+
+ {activeTab !== '' && ( + + )} +
+ )} +
+ ); +} + +function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) { + return ( + !live && + !fullscreen && + ); +} + +export default connectPlayer((state) => ({ + showEvents: !state.showEvents, + hasError: state.error, +}))(PlayerContent); diff --git a/frontend/app/components/Session/WebPlayer.js b/frontend/app/components/Session/WebPlayer.js deleted file mode 100644 index 6e71d3ea7..000000000 --- a/frontend/app/components/Session/WebPlayer.js +++ /dev/null @@ -1,139 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; -import { Loader, Modal } from 'UI'; -import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; -import { fetchList } from 'Duck/integrations'; -import { PlayerProvider, injectNotes, connectPlayer, init as initPlayer, clean as cleanPlayer, Controls } from 'Player'; -import cn from 'classnames'; -import RightBlock from './RightBlock'; -import withLocationHandlers from 'HOCs/withLocationHandlers'; -import { useStore } from 'App/mstore' -import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; -import PlayerBlock from '../Session_/PlayerBlock'; -import styles from '../Session_/session.module.css'; -import { countDaysFrom } from 'App/date'; -import ReadNote from '../Session_/Player/Controls/components/ReadNote'; -import { fetchList as fetchMembers } from 'Duck/member'; - -const TABS = { - EVENTS: 'User Steps', - HEATMAPS: 'Click Map', -}; - -const InitLoader = connectPlayer((state) => ({ - loading: !state.initialized, -}))(Loader); - -const PlayerContentConnected = connectPlayer((state) => ({ - showEvents: !state.showEvents, - hasError: state.error, -}))(PlayerContent); - -function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, hasError }) { - const sessionDays = countDaysFrom(session.startedAt); - return ( -
- {hasError ? ( -
-
-
{sessionDays > 2 ? 'Session not found.' : 'This session is still being processed.'}
-
{sessionDays > 2 ? 'Please check your data retention policy.' : 'Please check it again in a few minutes.'}
-
-
- ) : ( -
-
-
- -
-
- {activeTab !== '' && } -
- )} -
- ); -} - -function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) { - return !live && !fullscreen && ; -} - -function WebPlayer(props) { - const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props; - const { notesStore } = useStore() - const [activeTab, setActiveTab] = useState(''); - const [showNoteModal, setShowNote] = useState(false) - const [noteItem, setNoteItem] = useState(null) - - useEffect(() => { - fetchList('issues'); - initPlayer(session, jwt); - props.fetchMembers() - - notesStore.fetchSessionNotes(session.sessionId).then(r => { - injectNotes(r) - const note = props.query.get('note'); - if (note) { - Controls.pause() - setNoteItem(notesStore.getNoteById(parseInt(note, 10), r)) - setShowNote(true) - } - }) - - const jumptTime = props.query.get('jumpto'); - if (jumptTime) { - Controls.jump(parseInt(jumptTime)); - } - - return () => cleanPlayer(); - }, [session.sessionId]); - - // LAYOUT (TODO: local layout state - useContext or something..) - useEffect( - () => () => { - toggleFullscreen(false); - closeBottomBlock(); - }, - [] - ); - - const onNoteClose = () => {setShowNote(false); Controls.togglePlay()} - return ( - - - - - - {showNoteModal ? ( - m.id === noteItem?.userId)?.email || ''} - note={noteItem} - onClose={onNoteClose} - notFound={!noteItem} - /> - ) : null} - - - - ); -} - -export default connect( - (state) => ({ - session: state.getIn(['sessions', 'current']), - jwt: state.get('jwt'), - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - showEvents: state.get('showEvents'), - members: state.getIn(['members', 'list']), - }), - { - toggleFullscreen, - closeBottomBlock, - fetchList, - fetchMembers, - } -)(withLocationHandlers()(WebPlayer)); diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx new file mode 100644 index 000000000..811d6b9ba --- /dev/null +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -0,0 +1,127 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { Modal } from 'UI'; +import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; +import { fetchList } from 'Duck/integrations'; +import { + PlayerProvider, + createWebPlayer, +} from 'Player'; + +import withLocationHandlers from 'HOCs/withLocationHandlers'; +import { useStore } from 'App/mstore'; +import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; +import ReadNote from '../Session_/Player/Controls/components/ReadNote'; +import { fetchList as fetchMembers } from 'Duck/member'; +import PlayerContent from './PlayerContent' +import { + IPlayerContext, + PlayerContext, + defaultContextValue +} from './playerContext' + +const TABS = { + EVENTS: 'User Steps', + HEATMAPS: 'Click Map', +}; + +function WebPlayer(props: any) { + const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props; + const { notesStore } = useStore(); + const [activeTab, setActiveTab] = useState(''); + const [showNoteModal, setShowNote] = useState(false); + const [noteItem, setNoteItem] = useState(null); + const [contextValue, setContextValue] = useState(defaultContextValue) + + useEffect(() => { + fetchList('issues'); + const [WebPlayerInst, PlayerStore] = createWebPlayer(session, jwt); + setContextValue({ player: WebPlayerInst, store: PlayerStore }) + + // initPlayer(session, jwt); TODOPlayer + props.fetchMembers(); + + notesStore.fetchSessionNotes(session.sessionId).then((r) => { + // WebPlayerInst.injectNotes(r); + // PlayerStore.update({ notes: r }) + const note = props.query.get('note'); + if (note) { + WebPlayerInst.pause(); + setNoteItem(notesStore.getNoteById(parseInt(note, 10), r)); + setShowNote(true); + } + }); + + const jumptTime = props.query.get('jumpto'); + if (jumptTime) { + WebPlayerInst.jump(parseInt(jumptTime)); + } + + return () => WebPlayerInst.clean(); + }, [session.sessionId]); + + // LAYOUT (TODO: local layout state - useContext or something..) + useEffect( + () => () => { + toggleFullscreen(false); + closeBottomBlock(); + }, + [] + ); + + const onNoteClose = () => { + setShowNote(false); + contextValue.player.togglePlay(); + }; + + if (!contextValue.player) return null; + + return ( + + + <> + + {/* @ts-ignore */} + + + {showNoteModal ? ( + ) => m.id === noteItem?.userId)?.email || ''} + note={noteItem} + onClose={onNoteClose} + notFound={!noteItem} + /> + ) : null} + + + + + ); +} + +export default connect( + (state: any) => ({ + session: state.getIn(['sessions', 'current']), + jwt: state.get('jwt'), + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + showEvents: state.get('showEvents'), + members: state.getIn(['members', 'list']), + }), + { + toggleFullscreen, + closeBottomBlock, + fetchList, + fetchMembers, + } +)(withLocationHandlers()(WebPlayer)); diff --git a/frontend/app/components/Session/playerContext.ts b/frontend/app/components/Session/playerContext.ts new file mode 100644 index 000000000..7068b982f --- /dev/null +++ b/frontend/app/components/Session/playerContext.ts @@ -0,0 +1,12 @@ +import { createContext } from 'react'; +import { + IWebPlayer, + IStore +} from 'Player' + +export interface IPlayerContext { + player: IWebPlayer + store: IStore, +} +export const defaultContextValue: IPlayerContext = { player: undefined, store: undefined} +export const PlayerContext = createContext(defaultContextValue); diff --git a/frontend/app/components/Session_/Console/Console.js b/frontend/app/components/Session_/Console/Console.js deleted file mode 100644 index 3c4a3752c..000000000 --- a/frontend/app/components/Session_/Console/Console.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import { connectPlayer, jump } from 'Player'; -import ConsoleContent from './ConsoleContent'; - -@connectPlayer(state => ({ - logs: state.logList, - // time: state.time, - livePlay: state.livePlay, - listNow: state.logListNow, -})) -export default class Console extends React.PureComponent { - render() { - const { logs, time, listNow } = this.props; - return ( - - ); - } -} diff --git a/frontend/app/components/Session_/Console/ConsoleContent.js b/frontend/app/components/Session_/Console/ConsoleContent.js deleted file mode 100644 index a7482b69e..000000000 --- a/frontend/app/components/Session_/Console/ConsoleContent.js +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { getRE } from 'App/utils'; -import { Icon, NoContent, Tabs, Input } from 'UI'; -import { jump } from 'Player'; -import { LEVEL } from 'Types/session/log'; -import Autoscroll from '../Autoscroll'; -import BottomBlock from '../BottomBlock'; -import stl from './console.module.css'; -import ConsoleRow from './ConsoleRow'; -// import { Duration } from 'luxon'; - -const ALL = 'ALL'; -const INFO = 'INFO'; -const WARNINGS = 'WARNINGS'; -const ERRORS = 'ERRORS'; - -const LEVEL_TAB = { - [LEVEL.INFO]: INFO, - [LEVEL.LOG]: INFO, - [LEVEL.WARNING]: WARNINGS, - [LEVEL.ERROR]: ERRORS, - [LEVEL.EXCEPTION]: ERRORS, -}; - -const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab })); - -// eslint-disable-next-line complexity -const getIconProps = (level) => { - switch (level) { - case LEVEL.INFO: - case LEVEL.LOG: - return { - name: 'console/info', - color: 'blue2', - }; - case LEVEL.WARN: - case LEVEL.WARNING: - return { - name: 'console/warning', - color: 'red2', - }; - case LEVEL.ERROR: - return { - name: 'console/error', - color: 'red', - }; - } - return null; -}; - -function renderWithNL(s = '') { - if (typeof s !== 'string') return ''; - return s.split('\n').map((line, i) =>
{line}
); -} - -export default class ConsoleContent extends React.PureComponent { - state = { - filter: '', - activeTab: ALL, - }; - onTabClick = (activeTab) => this.setState({ activeTab }); - onFilterChange = ({ target: { value } }) => this.setState({ filter: value }); - - render() { - const { logs, isResult, additionalHeight, logsNow } = this.props; - const time = logsNow.length > 0 ? logsNow[logsNow.length - 1].time : undefined; - const { filter, activeTab, currentError } = this.state; - const filterRE = getRE(filter, 'i'); - const filtered = logs.filter(({ level, value }) => - activeTab === ALL - ? filterRE.test(value) - : filterRE.test(value) && LEVEL_TAB[level] === activeTab - ); - - const lastIndex = filtered.filter((item) => item.time <= time).length - 1; - - return ( - <> - - -
- Console - -
- -
- - - - No Data - - } - size="small" - show={filtered.length === 0} - > - - {filtered.map((l) => ( - - ))} - - - -
- - ); - } -} diff --git a/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx deleted file mode 100644 index c87ff3f9c..000000000 --- a/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useState } from 'react'; -import cn from 'classnames'; -import stl from '../console.module.css'; -import { Icon } from 'UI'; -import JumpButton from 'Shared/DevTools/JumpButton'; - -interface Props { - log: any; - iconProps: any; - jump?: any; - renderWithNL?: any; -} -function ConsoleRow(props: Props) { - const { log, iconProps, jump, renderWithNL } = props; - const [expanded, setExpanded] = useState(false); - const lines = log.value.split('\n').filter((l: any) => !!l); - const canExpand = lines.length > 1; - return ( -
setExpanded(!expanded)} - > -
- -
- {/*
- {Duration.fromMillis(log.time).toFormat('mm:ss.SSS')} -
*/} -
-
- {canExpand && ( - - )} - {renderWithNL(lines.pop())} -
- {canExpand && expanded && lines.map((l: any) =>
{l}
)} -
- jump(log.time)} /> -
- ); -} - -export default ConsoleRow; diff --git a/frontend/app/components/Session_/Console/ConsoleRow/index.ts b/frontend/app/components/Session_/Console/ConsoleRow/index.ts deleted file mode 100644 index c9140d748..000000000 --- a/frontend/app/components/Session_/Console/ConsoleRow/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ConsoleRow'; diff --git a/frontend/app/components/Session_/Console/console.module.css b/frontend/app/components/Session_/Console/console.module.css deleted file mode 100644 index 2da78f540..000000000 --- a/frontend/app/components/Session_/Console/console.module.css +++ /dev/null @@ -1,38 +0,0 @@ - -.message { - overflow-x: auto; - margin-left: 10px; - font-size: 13px; - overflow-x: auto; - &::-webkit-scrollbar { - height: 2px; - } -} - -.line { - font-family: 'Menlo', 'monaco', 'consolas', monospace; - /* margin-top: -1px; ??? */ - display: flex; - align-items: flex-start; - border-bottom: solid thin $gray-light-shade; - &:hover { - background-coor: $active-blue !important; - } -} - -.timestamp { - -} - -.activeRow { - background-color: $teal-light !important; -} - -.icon { - padding-top: 4px; - margin-right: 7px; -} - -.inactiveRow { - opacity: 0.5; -} \ No newline at end of file diff --git a/frontend/app/components/Session_/Inspector/index.js b/frontend/app/components/Session_/Inspector/index.js index f76834fee..38d44149a 100644 --- a/frontend/app/components/Session_/Inspector/index.js +++ b/frontend/app/components/Session_/Inspector/index.js @@ -53,10 +53,10 @@ export default function Inspector () { if (!doc) return null; return ( - +
markElement(null) } className={stl.wrapper}> - ); -} \ No newline at end of file +} diff --git a/frontend/app/components/Session_/LongTasks/LongTasks.js b/frontend/app/components/Session_/LongTasks/LongTasks.js index fd3b4cc17..2fa80fd01 100644 --- a/frontend/app/components/Session_/LongTasks/LongTasks.js +++ b/frontend/app/components/Session_/LongTasks/LongTasks.js @@ -40,12 +40,12 @@ export default class GraphQL extends React.PureComponent { const { filter, current } = this.state; const filterRE = getRE(filter, 'i'); const filtered = list - .filter(({ containerType, context, containerName = "", containerId = "", containerSrc="" }) => - filterRE.test(containerName) || + .filter(({ containerType, context, containerName = "", containerId = "", containerSrc="" }) => + filterRE.test(containerName) || filterRE.test(containerId) || filterRE.test(containerSrc) || filterRE.test(CONTEXTS[ context ]) || - filterRE.test(CONTAINER_TYPES[ containerType ])); + filterRE.test(CONTAINER_TYPES[ containerType ])); const lastIndex = filtered.filter(item => item.time <= time).length - 1; return ( @@ -64,7 +64,7 @@ export default class GraphQL extends React.PureComponent { - Learn more + Learn more about Long Tasks API } diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index 48d881c29..ea3640847 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -20,21 +20,13 @@ import { OVERVIEW, } from 'Duck/components/player'; import NetworkPanel from 'Shared/DevTools/NetworkPanel'; -import Console from '../Console/Console'; import StackEvents from '../StackEvents/StackEvents'; import Storage from '../Storage'; -import Profiler from '../Profiler'; import { ConnectedPerformance } from '../Performance'; import GraphQL from '../GraphQL'; import Exceptions from '../Exceptions/Exceptions'; import LongTasks from '../LongTasks'; import Inspector from '../Inspector'; -import { - attach as attachPlayer, - Controls as PlayerControls, - scale as scalePlayerScreen, - connectPlayer, -} from 'Player'; import Controls from './Controls'; import Overlay from './Overlay'; import stl from './player.module.css'; @@ -42,70 +34,47 @@ import { updateLastPlayedSession } from 'Duck/sessions'; import OverviewPanel from '../OverviewPanel'; import ConsolePanel from 'Shared/DevTools/ConsolePanel'; import ProfilerPanel from 'Shared/DevTools/ProfilerPanel'; +import { PlayerContext } from 'App/components/Session/playerContext'; -@connectPlayer((state) => ({ - live: state.live, -})) -@connect( - (state) => { - const isAssist = window.location.pathname.includes('/assist/'); - return { - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - nextId: state.getIn(['sessions', 'nextId']), - sessionId: state.getIn(['sessions', 'current', 'sessionId']), - closedLive: - !!state.getIn(['sessions', 'errors']) || - (isAssist && !state.getIn(['sessions', 'current', 'live'])), - }; - }, - { - hideTargetDefiner, +function Player(props) { + const { + className, + bottomBlockIsActive, + fullscreen, fullscreenOff, - updateLastPlayedSession, - } -) -export default class Player extends React.PureComponent { - screenWrapper = React.createRef(); + nextId, + closedLive, + bottomBlock, + activeTab, + } = props; + const playerContext = React.useContext(PlayerContext) + const screenWrapper = React.useRef(); - componentDidUpdate(prevProps) { - if ( - [prevProps.bottomBlock, this.props.bottomBlock].includes(NONE) || - prevProps.fullscreen !== this.props.fullscreen - ) { - scalePlayerScreen(); + React.useEffect(() => { + props.updateLastPlayedSession(props.sessionId); + if (!props.closedLive) { + const parentElement = findDOMNode(screenWrapper.current); //TODO: good architecture + playerContext.player.attach(parentElement); } - } - componentDidMount() { - this.props.updateLastPlayedSession(this.props.sessionId); - if (this.props.closedLive) return; + }, []) - const parentElement = findDOMNode(this.screenWrapper.current); //TODO: good architecture - attachPlayer(parentElement); - } + React.useEffect(() => { + playerContext.player.scale(); + }, [props.bottomBlock, props.fullscreen, playerContext.player]) - render() { - const { - className, - bottomBlockIsActive, - fullscreen, - fullscreenOff, - nextId, - closedLive, - bottomBlock, - activeTab, - } = this.props; + if (!playerContext.player) return null; - const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw'; - return ( -
{fullscreen && }
- -
+ +
{!fullscreen && !!bottomBlock && (
@@ -125,8 +94,25 @@ export default class Player extends React.PureComponent { {bottomBlock === INSPECTOR && }
)} - +
- ); - } + ) } + +export default connect((state) => { + const isAssist = window.location.pathname.includes('/assist/'); + return { + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + nextId: state.getIn(['sessions', 'nextId']), + sessionId: state.getIn(['sessions', 'current', 'sessionId']), + closedLive: + !!state.getIn(['sessions', 'errors']) || + (isAssist && !state.getIn(['sessions', 'current', 'live'])), + }; + }, + { + hideTargetDefiner, + fullscreenOff, + updateLastPlayedSession, + } +)(Player) diff --git a/frontend/app/components/Session_/Profiler/Profiler.js b/frontend/app/components/Session_/Profiler/Profiler.js index 398560a3d..9b9e6e42c 100644 --- a/frontend/app/components/Session_/Profiler/Profiler.js +++ b/frontend/app/components/Session_/Profiler/Profiler.js @@ -33,7 +33,7 @@ export default class Profiler extends React.PureComponent { return ( - } @@ -55,7 +55,7 @@ export default class Profiler extends React.PureComponent { /> - +export type IWebPlayer = WebPlayer +export type IWebPlayerStore = Store -export function createWebPlayer(session, wrapStore?: (s:WebPlayerStore) => WebPlayerStore): [WebPlayer, WebPlayerStore] { +export function createWebPlayer(session: Record, wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { let store: WebPlayerStore = new SimpleStore({ ...Player.INITIAL_STATE, ...MM_INITIAL_STATE, @@ -20,7 +22,7 @@ export function createWebPlayer(session, wrapStore?: (s:WebPlayerStore) => WebPl } -export function createLiveWebPlayer(session, config: RTCIceServer[], wrapStore?: (s:WebPlayerStore) => WebPlayerStore): [WebPlayer, WebPlayerStore] { +export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { let store: WebPlayerStore = new SimpleStore({ ...Player.INITIAL_STATE, ...MM_INITIAL_STATE, @@ -30,4 +32,4 @@ export function createLiveWebPlayer(session, config: RTCIceServer[], wrapStore?: } const player = new WebPlayer(store, session, config, true) return [player, store] -} \ No newline at end of file +}