diff --git a/src/App/App.js b/src/App/App.js index 437332f63..4906a5921 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -5,9 +5,9 @@ const React = require('react'); const { useTranslation } = require('react-i18next'); const { useCore } = require('stremio/core'); const { Router } = require('stremio-router'); -const { Shell, Chromecast, ServicesProvider, GamepadProvider } = require('stremio/services'); +const { Chromecast, ServicesProvider, GamepadProvider } = require('stremio/services'); const { NotFound } = require('stremio/routes'); -const { FullscreenProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, useShell, useBinaryState, useProfile, withCoreSuspender, onFileDrop } = require('stremio/common'); +const { FullscreenProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, useBinaryState, useProfile, withCoreSuspender, onFileDrop, usePlatform } = require('stremio/common'); const ServicesToaster = require('./ServicesToaster'); const DeepLinkHandler = require('./DeepLinkHandler'); const SearchParamsHandler = require('./SearchParamsHandler'); @@ -24,14 +24,13 @@ const App = () => { const core = useCore(); const profile = useProfile(); const { i18n } = useTranslation(); - const shell = useShell(); + const { shell } = usePlatform(); const [gamepadSupportEnabled, setGamepadSupportEnabled] = React.useState(false); const onPathNotMatch = React.useCallback(() => { return NotFound; }, []); const services = React.useMemo(() => { return { - shell: new Shell(), chromecast: new Chromecast(), }; }, []); @@ -99,11 +98,9 @@ const App = () => { } }; services.chromecast.on('stateChanged', onChromecastStateChange); - services.shell.start(); services.chromecast.start(); window.services = services; return () => { - services.shell.stop(); services.chromecast.stop(); services.chromecast.off('stateChanged', onChromecastStateChange); }; @@ -143,10 +140,10 @@ const App = () => { setGamepadSupportEnabled(profile.settings.gamepadSupport); } - if (profile.settings?.quitOnClose && shell.windowClosed) { + if (profile.settings?.quitOnClose && shell.state.windowClosed) { shell.send('quit'); } - }, [profile.settings, shell.windowClosed]); + }, [profile.settings, shell.state.windowClosed]); React.useEffect(() => { const onWindowFocus = () => { @@ -187,33 +184,31 @@ const App = () => { return ( - - - - - - - { - shortcutModalOpen && - } - { - gamepadModalOpen && - } - - - - - - - - - - - + + + + + + { + shortcutModalOpen && + } + { + gamepadModalOpen && + } + + + + + + + + + + ); }; diff --git a/src/App/UpdaterBanner/UpdaterBanner.tsx b/src/App/UpdaterBanner/UpdaterBanner.tsx index 3d421deb5..6d3bfd932 100644 --- a/src/App/UpdaterBanner/UpdaterBanner.tsx +++ b/src/App/UpdaterBanner/UpdaterBanner.tsx @@ -1,8 +1,7 @@ import React, { useEffect } from 'react'; import Icon from '@stremio/stremio-icons/react'; import { useTranslation } from 'react-i18next'; -import { useServices } from 'stremio/services'; -import { useBinaryState, useShell } from 'stremio/common'; +import { useBinaryState, usePlatform } from 'stremio/common'; import { Button, Transition } from 'stremio/components'; import styles from './UpdaterBanner.less'; @@ -12,19 +11,18 @@ type Props = { const UpdaterBanner = ({ className }: Props) => { const { t } = useTranslation(); - const { shell } = useServices(); - const shellTransport = useShell(); + const { shell } = usePlatform(); const [visible, show, hide] = useBinaryState(false); const onInstallClick = () => { - shellTransport.send('autoupdater-notif-clicked'); + shell.send('autoupdater-notif-clicked'); }; useEffect(() => { - shell.transport && shell.transport.on('autoupdater-show-notif', show); + shell.on('autoupdater-show-notif', show); return () => { - shell.transport && shell.transport.off('autoupdater-show-notif', show); + shell.off('autoupdater-show-notif', show); }; }, []); diff --git a/src/common/Fullscreen/FullscreenProvider.tsx b/src/common/Fullscreen/FullscreenProvider.tsx index 5e4aecccb..c164dfd8b 100644 --- a/src/common/Fullscreen/FullscreenProvider.tsx +++ b/src/common/Fullscreen/FullscreenProvider.tsx @@ -4,8 +4,8 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { withCoreSuspender } from '../CoreSuspender'; import onShortcut from '../Shortcuts/onShortcut'; import useSettings from '../useSettings'; -import useShell, { type WindowVisibility } from '../useShell'; import FullscreenContext, { type FullscreenContextValue } from './FullscreenContext'; +import { usePlatform } from '../Platform'; type Props = { children: React.ReactNode, @@ -15,7 +15,7 @@ const hasWebkitFullscreen = typeof HTMLVideoElement !== 'undefined' && typeof HTMLVideoElement.prototype.webkitEnterFullscreen === 'function'; const FullscreenProvider = ({ children }: Props) => { - const shell = useShell(); + const { shell } = usePlatform(); const [settings] = useSettings(); const escExitFullscreen = settings.escExitFullscreen; diff --git a/src/common/Platform/Platform.tsx b/src/common/Platform/Platform.tsx index 86ca999c6..bb22d7f99 100644 --- a/src/common/Platform/Platform.tsx +++ b/src/common/Platform/Platform.tsx @@ -1,10 +1,12 @@ import React, { createContext, useContext } from 'react'; import { WHITELISTED_HOSTS } from 'stremio/common/CONSTANTS'; import { name, isMobile } from './device'; +import useShell from './shell/useShell'; interface PlatformContext { name: string; isMobile: boolean; + shell: Shell; openExternal: (url: string) => void; } @@ -15,6 +17,8 @@ type Props = { }; const PlatformProvider = ({ children }: Props) => { + const shell = useShell(); + const openExternal = (url: string) => { try { const { hostname } = new URL(url); @@ -30,7 +34,7 @@ const PlatformProvider = ({ children }: Props) => { }; return ( - + {children} ); diff --git a/src/types/global.d.ts b/src/common/Platform/shell/global.d.ts similarity index 87% rename from src/types/global.d.ts rename to src/common/Platform/shell/global.d.ts index 3e6e4acff..db2132f38 100644 --- a/src/types/global.d.ts +++ b/src/common/Platform/shell/global.d.ts @@ -3,7 +3,6 @@ type QtTransportMessage = { }; interface QtTransport { - send: (message: string) => void, onmessage: (message: QtTransportMessage) => void, } @@ -22,8 +21,8 @@ interface Chrome { } declare global { - var qt: Qt | undefined; + var qt: Qt; var chrome: Chrome | undefined; } -export { }; +export {}; diff --git a/src/common/Platform/shell/shell.d.ts b/src/common/Platform/shell/shell.d.ts new file mode 100644 index 000000000..3d8d47e23 --- /dev/null +++ b/src/common/Platform/shell/shell.d.ts @@ -0,0 +1,27 @@ +type WindowVisibility = { + visible: boolean; + visibility: number; + isFullscreen: boolean; +}; + +type WindowState = { + state: number; +}; + +type MediaStatus = { + paused: boolean; +}; + +interface Shell { + active: boolean, + state: ShellState, + on: (name: string, listener: (arg: any) => void) => void; + off: (name: string, listener: (arg: any) => void) => void; + send: (method: string, ...args: (string | number | object)[]) => void; +} + +type ShellState = { + version: string | null; + windowClosed: boolean; + windowHidden: boolean; +}; diff --git a/src/common/Platform/shell/useShell.ts b/src/common/Platform/shell/useShell.ts new file mode 100644 index 000000000..cad232540 --- /dev/null +++ b/src/common/Platform/shell/useShell.ts @@ -0,0 +1,124 @@ +import { useEffect, useState } from 'react'; +import EventEmitter from 'eventemitter3'; + +const IPC = globalThis?.chrome?.webview; +const LEGACY_IPC = globalThis?.qt?.webChannelTransport; +if (LEGACY_IPC) LEGACY_IPC.onmessage = () => { /* empty */ }; + +const events = new EventEmitter(); + +enum ShellEventType { + SIGNAL = 1, + INIT = 3, + INVOKE_METHOD = 6, +} + +type ShellEvent = { + id: number; + type: ShellEventType; +}; + +type ShellEventInit = ShellEvent & { + data: { + transport: { + properties: string[][], + } + }; +}; + +type ShellEventSignal = ShellEvent & { + args: string[]; +}; + +type ShellMessage = { + data: string; +}; + +const useShell = (): Shell => { + const [state, setState] = useState({ + version: null, + windowClosed: false, + windowHidden: false, + }); + + const on = (name: string, listener: (arg: any) => void) => events.on(name, listener); + const off = (name: string, listener: (arg: any) => void) => events.off(name, listener); + + const send = (method: string, ...args: (string | number | object)[]) => { + try { + IPC?.postMessage(JSON.stringify({ + id: 0, + type: ShellEventType.INVOKE_METHOD, + args: [method, ...args], + })); + } catch (e) { + console.error('Shell', 'Failed to send event', e); + } + }; + + useEffect(() => { + const onWindowVisibilityChanged = (data: WindowVisibility) => { + setState((state) => ({ + ...state, + windowClosed: data.visible === false && data.visibility === 0, + })); + }; + + const onWindowStateChanged = (data: WindowState) => { + setState((state) => ({ + ...state, + windowHidden: data.state === 9, + })); + }; + + on('win-visibility-changed', onWindowVisibilityChanged); + on('win-state-changed', onWindowStateChanged); + + return () => { + off('win-visibility-changed', onWindowVisibilityChanged); + off('win-state-changed', onWindowStateChanged); + }; + }, []); + + useEffect(() => { + IPC?.postMessage(JSON.stringify({ + id: 0, + type: ShellEventType.INIT, + })); + + const onMessage = (message: ShellMessage) => { + try { + const event = JSON.parse(message.data) as ShellEvent; + + if (event.type === ShellEventType.INIT) { + const { data } = event as ShellEventInit; + const [, [,,, version]] = data.transport.properties; + + setState((state) => ({ ...state, version })); + send('app-ready'); + } + + if (event.type === ShellEventType.SIGNAL) { + const { args } = event as ShellEventSignal; + const [methodName, methodArg] = args; + events.emit(methodName, methodArg); + } + } catch (e) { + console.error('Shell', 'Failed to handle event', e); + } + }; + + IPC?.addEventListener('message', onMessage); + return () => IPC?.removeEventListener('message', onMessage); + }, []); + + return { + active: !!IPC, + send, + on, + off, + state, + }; +}; + +export default useShell; diff --git a/src/common/index.js b/src/common/index.js index c27b37178..b99b88c2f 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -22,7 +22,6 @@ const useNotifications = require('./useNotifications'); const useOnScrollToBottom = require('./useOnScrollToBottom'); const useProfile = require('./useProfile'); const { default: useSettings } = require('./useSettings'); -const { default: useShell } = require('./useShell'); const useStreamingServer = require('./useStreamingServer'); const { default: useTimeout } = require('./useTimeout'); const { default: usePlayUrl } = require('./usePlayUrl'); @@ -63,7 +62,6 @@ module.exports = { useOnScrollToBottom, useProfile, useSettings, - useShell, useStreamingServer, useTimeout, usePlayUrl, diff --git a/src/common/useShell.ts b/src/common/useShell.ts deleted file mode 100644 index 3ecffe88e..000000000 --- a/src/common/useShell.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { useEffect, useState } from 'react'; -import EventEmitter from 'eventemitter3'; - -const SHELL_EVENT_OBJECT = 'transport'; -const transport = globalThis?.chrome?.webview; -const events = new EventEmitter(); - -enum ShellEventType { - SIGNAL = 1, - INVOKE_METHOD = 6, -} - -type ShellEvent = { - id: number; - type: ShellEventType; - object: string; - args: string[]; -}; - -export type WindowVisibility = { - visible: boolean; - visibility: number; - isFullscreen: boolean; -}; - -export type WindowState = { - state: number; -}; - -export type MediaStatus = { - paused: boolean; -}; - -const createId = () => Math.floor(Math.random() * 9999) + 1; - -const useShell = () => { - const [windowClosed, setWindowClosed] = useState(false); - const [windowHidden, setWindowHidden] = useState(false); - - const on = (name: string, listener: (arg: any) => void) => { - events.on(name, listener); - }; - - const off = (name: string, listener: (arg: any) => void) => { - events.off(name, listener); - }; - - const send = (method: string, ...args: (string | number | object)[]) => { - try { - transport?.postMessage(JSON.stringify({ - id: createId(), - type: ShellEventType.INVOKE_METHOD, - object: SHELL_EVENT_OBJECT, - method: 'onEvent', - args: [method, ...args], - })); - } catch (e) { - console.error('Shell', 'Failed to send event', e); - } - }; - - useEffect(() => { - const onWindowVisibilityChanged = (data: WindowVisibility) => { - setWindowClosed(data.visible === false && data.visibility === 0); - }; - - const onWindowStateChanged = (data: WindowState) => { - setWindowHidden(data.state === 9); - }; - - on('win-visibility-changed', onWindowVisibilityChanged); - on('win-state-changed', onWindowStateChanged); - - return () => { - off('win-visibility-changed', onWindowVisibilityChanged); - off('win-state-changed', onWindowStateChanged); - }; - }, []); - - useEffect(() => { - if (!transport) return; - - const onMessage = ({ data }: { data: string }) => { - try { - const { type, args } = JSON.parse(data) as ShellEvent; - if (type === ShellEventType.SIGNAL) { - const [methodName, methodArg] = args; - events.emit(methodName, methodArg); - } - } catch (e) { - console.error('Shell', 'Failed to handle event', e); - } - }; - - transport.addEventListener('message', onMessage); - return () => transport.removeEventListener('message', onMessage); - }, []); - - return { - active: !!transport, - send, - on, - off, - windowClosed, - windowHidden, - }; -}; - -export default useShell; diff --git a/src/index.js b/src/index.js index 74d39601d..f93589176 100755 --- a/src/index.js +++ b/src/index.js @@ -18,7 +18,7 @@ const { initReactI18next } = require('react-i18next'); const stremioTranslations = require('stremio-translations'); const App = require('./App'); const { CoreProvider } = require('./core'); -const { FileDropProvider } = require('./common'); +const { FileDropProvider, PlatformProvider } = require('./common'); const translations = Object.fromEntries(Object.entries(stremioTranslations()).map(([key, value]) => [key, { translation: value @@ -42,11 +42,13 @@ const appInfo = { const root = ReactDOM.createRoot(document.getElementById('app')); root.render( - - - - - + + + + + + + ); if (process.env.NODE_ENV === 'production' && process.env.SERVICE_WORKER_DISABLED !== 'true' && process.env.SERVICE_WORKER_DISABLED !== true && 'serviceWorker' in navigator) { diff --git a/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js b/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js index 1980b4943..d1b9112e5 100644 --- a/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js +++ b/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js @@ -5,12 +5,12 @@ const PropTypes = require('prop-types'); const classnames = require('classnames'); const debounce = require('lodash.debounce'); const { useRouteFocused } = require('stremio-router'); -const { useServices } = require('stremio/services'); +const { usePlatform } = require('stremio/common'); const { Slider } = require('stremio/components'); const styles = require('./styles'); const VolumeSlider = ({ className, volume, onVolumeChangeRequested, muted }) => { - const { shell } = useServices(); + const { shell } = usePlatform(); const disabled = volume === null || isNaN(volume); const routeFocused = useRouteFocused(); const [slidingVolume, setSlidingVolume] = React.useState(null); diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index de0cd1ca0..20b5f0ce8 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -10,7 +10,7 @@ const { useRouteFocused } = require('stremio-router'); const { useCore } = require('stremio/core'); const { useServices, useGamepad } = require('stremio/services'); const { useContentGamepadNavigation } = require('stremio/services/GamepadNavigation'); -const { useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, useShell, usePlatform, onShortcut } = require('stremio/common'); +const { useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, usePlatform, onShortcut } = require('stremio/common'); const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components'); const BufferingLoader = require('./BufferingLoader'); const VolumeChangeIndicator = require('./VolumeChangeIndicator'); @@ -42,7 +42,6 @@ const Player = ({ urlParams, queryParams }) => { const { t } = useTranslation(); const services = useServices(); const core = useCore(); - const shell = useShell(); const gamepad = useGamepad(); const forceTranscoding = React.useMemo(() => { return queryParams.has('forceTranscoding'); @@ -424,7 +423,7 @@ const Player = ({ urlParams, queryParams }) => { seriesInfo: player.seriesInfo, }, { chromecastTransport: services.chromecast.active ? services.chromecast.transport : null, - shellTransport: services.shell.active ? services.shell.transport : null, + shellTransport: platform.shell.active ? platform.shell : null, }); } }, [streamingServer.baseUrl, player.selected, player.stream, streamSubtitles, forceTranscoding, casting]); @@ -536,10 +535,10 @@ const Player = ({ urlParams, queryParams }) => { }, []); React.useEffect(() => { - if (settings.pauseOnMinimize && (shell.windowClosed || shell.windowHidden)) { + if (settings.pauseOnMinimize && (platform.shell.state.windowClosed || platform.shell.state.windowHidden)) { onPauseRequested(); } - }, [settings.pauseOnMinimize, shell.windowClosed, shell.windowHidden]); + }, [settings.pauseOnMinimize, platform.shell.state.windowClosed, platform.shell.state.windowHidden]); useMediaSession(video.state, player, fullscreen, onPlayRequested, onPauseRequested, onNextVideoRequested); @@ -565,8 +564,8 @@ const Player = ({ urlParams, queryParams }) => { break; } }; - shell.on('media-key', onMediaKey); - return () => shell.off('media-key', onMediaKey); + platform.shell.on('media-key', onMediaKey); + return () => platform.shell.off('media-key', onMediaKey); }, [video.state.paused, player.nextVideo, onPlayRequested, onPauseRequested, onNextVideoRequested]); onShortcut('seekForward', (combo) => { diff --git a/src/routes/Player/useMediaSession.ts b/src/routes/Player/useMediaSession.ts index 7af09bd3e..28b562959 100644 --- a/src/routes/Player/useMediaSession.ts +++ b/src/routes/Player/useMediaSession.ts @@ -1,6 +1,5 @@ import { useEffect } from 'react'; -import { useShell } from 'stremio/common'; -import { MediaStatus } from 'stremio/common/useShell'; +import { usePlatform } from 'stremio/common'; const useMediaSession = ( videoState: VideoState, @@ -10,7 +9,7 @@ const useMediaSession = ( onPauseRequested: () => void, onNextVideoRequested: () => void, ) => { - const shell = useShell(); + const { shell } = usePlatform(); useEffect(() => { if (!('audioSession' in navigator)) return; diff --git a/src/routes/Settings/Info/Info.tsx b/src/routes/Settings/Info/Info.tsx index a0a8e2236..18a8dcb51 100644 --- a/src/routes/Settings/Info/Info.tsx +++ b/src/routes/Settings/Info/Info.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useServices } from 'stremio/services'; +import { usePlatform } from 'stremio/common'; import { Option, Section } from '../components'; import styles from './Info.less'; @@ -9,7 +9,7 @@ type Props = { }; const Info = ({ streamingServer }: Props) => { - const { shell } = useServices(); + const { shell } = usePlatform(); const { t } = useTranslation(); const settings = useMemo(() => ( @@ -38,10 +38,10 @@ const Info = ({ streamingServer }: Props) => { } { - typeof shell?.transport?.props?.shellVersion === 'string' && + typeof shell.state.version === 'string' && } diff --git a/src/routes/Settings/Interface/Interface.tsx b/src/routes/Settings/Interface/Interface.tsx index 330d41931..19d2efb2f 100644 --- a/src/routes/Settings/Interface/Interface.tsx +++ b/src/routes/Settings/Interface/Interface.tsx @@ -1,5 +1,5 @@ import React, { forwardRef } from 'react'; -import { useServices } from 'stremio/services'; +import { usePlatform } from 'stremio/common'; import { MultiselectMenu, Toggle } from 'stremio/components'; import { Section, Option } from '../components'; import useInterfaceOptions from './useInterfaceOptions'; @@ -9,7 +9,7 @@ type Props = { }; const Interface = forwardRef(({ profile }: Props, ref) => { - const { shell } = useServices(); + const { shell } = usePlatform(); const { interfaceLanguageSelect, diff --git a/src/routes/Settings/Menu/Menu.tsx b/src/routes/Settings/Menu/Menu.tsx index 5bc5208f1..d085a7da9 100644 --- a/src/routes/Settings/Menu/Menu.tsx +++ b/src/routes/Settings/Menu/Menu.tsx @@ -1,10 +1,9 @@ import React, { useMemo } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; -import { useServices } from 'stremio/services'; +import { usePlatform } from 'stremio/common'; import { Button } from 'stremio/components'; import { SECTIONS } from '../constants'; -import { usePlatform } from 'stremio/common'; import styles from './Menu.less'; type Props = { @@ -15,7 +14,7 @@ type Props = { const Menu = ({ selected, streamingServer, onSelect }: Props) => { const { t } = useTranslation(); - const { shell } = useServices(); + const { shell } = usePlatform(); const platform = usePlatform(); const settings = useMemo(() => ( @@ -55,9 +54,9 @@ const Menu = ({ selected, streamingServer, onSelect }: Props) => { } { - typeof shell?.transport?.props?.shellVersion === 'string' && -
- {t('SETTINGS_SHELL_VERSION')}: {shell.transport.props.shellVersion} + typeof shell.state.version === 'string' && +
+ {t('SETTINGS_SHELL_VERSION')}: {shell.state.version}
}
diff --git a/src/routes/Settings/Player/Player.tsx b/src/routes/Settings/Player/Player.tsx index 213757cc3..f45cf142c 100644 --- a/src/routes/Settings/Player/Player.tsx +++ b/src/routes/Settings/Player/Player.tsx @@ -1,16 +1,15 @@ import React, { forwardRef } from 'react'; import { ColorInput, MultiselectMenu, Toggle } from 'stremio/components'; -import { useServices } from 'stremio/services'; +import { usePlatform } from 'stremio/common'; import { Category, Option, Section } from '../components'; import usePlayerOptions from './usePlayerOptions'; -import { usePlatform } from 'stremio/common'; type Props = { profile: Profile, }; const Player = forwardRef(({ profile }: Props, ref) => { - const { shell } = useServices(); + const { shell } = usePlatform(); const platform = usePlatform(); const { diff --git a/src/services/ServicesContext/types.d.ts b/src/services/ServicesContext/types.d.ts index 82bba1378..3860d3a03 100644 --- a/src/services/ServicesContext/types.d.ts +++ b/src/services/ServicesContext/types.d.ts @@ -1,4 +1,3 @@ type ServicesContext = { - shell: any, chromecast: any, }; diff --git a/src/services/Shell/Shell.d.ts b/src/services/Shell/Shell.d.ts deleted file mode 100644 index b1bcca069..000000000 --- a/src/services/Shell/Shell.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -type ShellTransportProps = { - shellVersion: string, -}; - -type ShellTransport = { - props: ShellTransportProps, -}; - -interface ShellService { - transport: ShellTransport, -} diff --git a/src/services/Shell/Shell.js b/src/services/Shell/Shell.js deleted file mode 100644 index 85d2dafd2..000000000 --- a/src/services/Shell/Shell.js +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (C) 2017-2023 Smart code 203358507 - -const EventEmitter = require('eventemitter3'); -const ShellTransport = require('./ShellTransport'); - -function Shell() { - let active = false; - let error = null; - let starting = false; - let transport = null; - - const events = new EventEmitter(); - - function onStateChanged() { - events.emit('stateChanged'); - } - - Object.defineProperties(this, { - active: { - configurable: false, - enumerable: true, - get: function() { - return active; - } - }, - error: { - configurable: false, - enumerable: true, - get: function() { - return error; - } - }, - starting: { - configurable: false, - enumerable: true, - get: function() { - return starting; - } - }, - transport: { - configurable: false, - enumerable: true, - get: function() { - return transport; - } - } - }); - - this.start = function() { - if (active || error instanceof Error || starting) { - return; - } - - active = false; - starting = true; - - try { - transport = new ShellTransport(); - active = true; - error = null; - starting = false; - onStateChanged(); - } catch (e) { - console.error(e); - active = false; - error = new Error('Failed to initialize shell transport', { cause: e }); - starting = false; - onStateChanged(); - transport = null; - } - - onStateChanged(); - }; - this.stop = function() { - active = false; - error = null; - starting = false; - onStateChanged(); - }; - this.on = function(name, listener) { - events.on(name, listener); - }; - this.off = function(name, listener) { - events.off(name, listener); - }; -} - -module.exports = Shell; diff --git a/src/services/Shell/ShellTransport.js b/src/services/Shell/ShellTransport.js deleted file mode 100644 index 0dcf52d6d..000000000 --- a/src/services/Shell/ShellTransport.js +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (C) 2017-2023 Smart code 203358507 - -const EventEmitter = require('eventemitter3'); - -const QtMsgTypes = { - signal: 1, - propertyUpdate: 2, - init: 3, - idle: 4, - debug: 5, - invokeMethod: 6, - connectToSignal: 7, - disconnectFromSignal: 8, - setProperty: 9, - response: 10, -}; -const QtObjId = 'transport'; // the ID of our transport object - -function ShellTransport() { - const events = new EventEmitter(); - - this.props = {}; - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const shell = this; - const transport = window.qt && window.qt.webChannelTransport; - if (!transport) throw 'no viable transport found (qt.webChannelTransport)'; - - let id = 0; - function send(msg) { - msg.id = id++; - transport.send(JSON.stringify(msg)); - } - - transport.onmessage = function (message) { - const msg = JSON.parse(message.data); - if (msg.id === 0) { - const obj = msg.data[QtObjId]; - - obj.properties.slice(1).forEach(function (prop) { - shell.props[prop[1]] = prop[3]; - }); - if (typeof shell.props.shellVersion === 'string') { - shell.shellVersionArr = ( - shell.props.shellVersion.match(/(\d+)\.(\d+)\.(\d+)/) || [] - ) - .slice(1, 4) - .map(Number); - } - events.emit('received-props', shell.props); - - obj.signals.forEach(function (sig) { - send({ - type: QtMsgTypes.connectToSignal, - object: QtObjId, - signal: sig[1], - }); - }); - - const onEvent = obj.methods.filter(function (x) { - return x[0] === 'onEvent'; - })[0]; - - shell.send = function (ev, args) { - send({ - type: QtMsgTypes.invokeMethod, - object: QtObjId, - method: onEvent[1], - args: [ev, args || {}], - }); - }; - - shell.send('app-ready', {}); // signal that we're ready to take events - } - - if (msg.object === QtObjId && msg.type === QtMsgTypes.signal) - events.emit(msg.args[0], msg.args[1]); - }; - send({ type: QtMsgTypes.init }); - - this.on = function(name, listener) { - events.on(name, listener); - }; - this.off = function(name, listener) { - events.off(name, listener); - }; - this.removeAllListeners = function() { - events.removeAllListeners(); - }; -} - -module.exports = ShellTransport; diff --git a/src/services/Shell/index.js b/src/services/Shell/index.js deleted file mode 100644 index 990fc1e37..000000000 --- a/src/services/Shell/index.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (C) 2017-2023 Smart code 203358507 - -const Shell = require('./Shell'); - -module.exports = Shell; diff --git a/src/services/index.js b/src/services/index.js index 2a4c1543c..6fc75c3bf 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -3,13 +3,11 @@ const Chromecast = require('./Chromecast'); const { ServicesProvider, useServices } = require('./ServicesContext'); const { GamepadProvider, useGamepad } = require('./GamepadContext'); -const Shell = require('./Shell'); module.exports = { Chromecast, ServicesProvider, useServices, - Shell, GamepadProvider, useGamepad, };