diff --git a/globals.d.ts b/globals.d.ts index 604799444b..2eb68272b9 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -14,6 +14,7 @@ declare global { registerShortcut: Function; unregisterShortcut: Function; openDialog: Function; + enable: Function; } }; const interfaceConfig: any; diff --git a/react/features/base/conference/reducer.ts b/react/features/base/conference/reducer.ts index a79d338b86..5fa9c24b83 100644 --- a/react/features/base/conference/reducer.ts +++ b/react/features/base/conference/reducer.ts @@ -71,6 +71,7 @@ export interface IJitsiConference { muteParticipant: Function; myLobbyUserId: Function; myUserId: Function; + off: Function; on: Function; removeTrack: Function; replaceTrack: Function; diff --git a/react/features/base/config/configType.ts b/react/features/base/config/configType.ts index 4bda151cb8..86706d1163 100644 --- a/react/features/base/config/configType.ts +++ b/react/features/base/config/configType.ts @@ -316,6 +316,7 @@ export interface IConfig { sdkKey?: string; tileTime?: number; }; + googleApiApplicationClientID?: string; gravatar?: { baseUrl?: string; disabled?: boolean; @@ -366,6 +367,7 @@ export interface IConfig { localSubject?: string; locationURL?: URL; maxFullResolutionParticipants?: number; + microsoftApiApplicationClientID?: string; moderatedRoomServiceUrl?: string; mouseMoveCallbackInterval?: number; noticeMessage?: string; diff --git a/react/features/base/config/reducer.ts b/react/features/base/config/reducer.ts index 82c3130539..43dafb1044 100644 --- a/react/features/base/config/reducer.ts +++ b/react/features/base/config/reducer.ts @@ -77,6 +77,7 @@ export interface IConfigState extends IConfig { analysis?: { obfuscateRoomName?: boolean; }; + disableRemoteControl?: boolean; error?: Error; } diff --git a/react/features/base/connection/reducer.ts b/react/features/base/connection/reducer.ts index 54f056423f..712e9f48a8 100644 --- a/react/features/base/connection/reducer.ts +++ b/react/features/base/connection/reducer.ts @@ -16,6 +16,7 @@ import { ConnectionFailedError } from './actions.any'; export interface IConnectionState { connecting?: any; connection?: { + addFeature: Function; disconnect: Function; getJid: () => string; getLogs: () => Object; diff --git a/react/features/calendar-sync/actions.any.js b/react/features/calendar-sync/actions.any.ts similarity index 88% rename from react/features/calendar-sync/actions.any.js rename to react/features/calendar-sync/actions.any.ts index ac0276d154..ea609f2fd4 100644 --- a/react/features/calendar-sync/actions.any.js +++ b/react/features/calendar-sync/actions.any.ts @@ -1,5 +1,3 @@ -// @flow - import { REFRESH_CALENDAR, SET_CALENDAR_AUTHORIZATION, @@ -19,8 +17,7 @@ import { * isInteractive: boolean * }} */ -export function refreshCalendar( - forcePermission: boolean = false, isInteractive: boolean = true) { +export function refreshCalendar(forcePermission = false, isInteractive = true) { return { type: REFRESH_CALENDAR, forcePermission, @@ -39,7 +36,7 @@ export function refreshCalendar( * authorization: ?string * }} */ -export function setCalendarAuthorization(authorization: ?string) { +export function setCalendarAuthorization(authorization?: string) { return { type: SET_CALENDAR_AUTHORIZATION, authorization diff --git a/react/features/calendar-sync/actions.native.js b/react/features/calendar-sync/actions.native.ts similarity index 84% rename from react/features/calendar-sync/actions.native.js rename to react/features/calendar-sync/actions.native.ts index 1ea63aae84..4fba82c2b5 100644 --- a/react/features/calendar-sync/actions.native.js +++ b/react/features/calendar-sync/actions.native.ts @@ -1,13 +1,15 @@ -// @flow +// @ts-expect-error import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random'; -import type { Dispatch } from 'redux'; import { getDefaultURL } from '../app/functions'; -import { openDialog } from '../base/dialog'; +import { IStore } from '../app/types'; +import { openDialog } from '../base/dialog/actions'; import { refreshCalendar } from './actions'; import { UpdateCalendarEventDialog + + // @ts-ignore } from './components'; import { addLinkToCalendarEntry } from './functions.native'; @@ -35,11 +37,13 @@ export function openUpdateCalendarEventDialog(eventId: string) { * @returns {Function} */ export function updateCalendarEvent(eventId: string) { - return (dispatch: Dispatch, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const defaultUrl = getDefaultURL(getState); const roomName = generateRoomWithoutSeparator(); addLinkToCalendarEntry(getState(), eventId, `${defaultUrl}/${roomName}`) + + // @ts-ignore .finally(() => { dispatch(refreshCalendar(false, false)); }); diff --git a/react/features/calendar-sync/actions.web.js b/react/features/calendar-sync/actions.web.ts similarity index 86% rename from react/features/calendar-sync/actions.web.js rename to react/features/calendar-sync/actions.web.ts index ad3a4e3e8b..4bc82f9211 100644 --- a/react/features/calendar-sync/actions.web.js +++ b/react/features/calendar-sync/actions.web.ts @@ -1,9 +1,11 @@ -// @flow - +// @ts-expect-error import { generateRoomWithoutSeparator } from '@jitsi/js-utils/random'; -import type { Dispatch } from 'redux'; -import { createCalendarConnectedEvent, sendAnalytics } from '../analytics'; +import { createCalendarConnectedEvent } from '../analytics/AnalyticsEvents'; +import { sendAnalytics } from '../analytics/functions'; +import { IStore } from '../app/types'; +// eslint-disable-next-line lines-around-comment +// @ts-ignore import { loadGoogleAPI } from '../google-api'; import { @@ -14,8 +16,8 @@ import { SET_CALENDAR_PROFILE_EMAIL, SET_LOADING_CALENDAR_EVENTS } from './actionTypes'; -import { refreshCalendar, setCalendarEvents } from './actions'; -import { _getCalendarIntegration, isCalendarEnabled } from './functions'; +import { refreshCalendar, setCalendarEvents } from './actions.web'; +import { _getCalendarIntegration, isCalendarEnabled } from './functions.web'; import logger from './logger'; export * from './actions.any'; @@ -26,8 +28,8 @@ export * from './actions.any'; * * @returns {Function} */ -export function bootstrapCalendarIntegration(): Function { - return (dispatch, getState) => { +export function bootstrapCalendarIntegration() { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); if (!isCalendarEnabled(state)) { @@ -63,7 +65,7 @@ export function bootstrapCalendarIntegration(): Function { } return dispatch(integrationToLoad._isSignedIn()) - .then(signedIn => { + .then((signedIn: boolean) => { if (signedIn) { dispatch(setIntegrationReady(integrationType)); dispatch(updateProfile(integrationType)); @@ -114,7 +116,7 @@ export function openUpdateCalendarEventDialog( * msAuthState: Object * }} */ -export function setCalendarAPIAuthState(newState: ?Object) { +export function setCalendarAPIAuthState(newState?: Object) { return { type: SET_CALENDAR_AUTH_STATE, msAuthState: newState @@ -130,7 +132,7 @@ export function setCalendarAPIAuthState(newState: ?Object) { * error: Object * }} */ -export function setCalendarError(error: ?Object) { +export function setCalendarError(error?: Object) { return { type: SET_CALENDAR_ERROR, error @@ -146,7 +148,7 @@ export function setCalendarError(error: ?Object) { * email: string * }} */ -export function setCalendarProfileEmail(newEmail: ?string) { +export function setCalendarProfileEmail(newEmail?: string) { return { type: SET_CALENDAR_PROFILE_EMAIL, email: newEmail @@ -196,8 +198,8 @@ export function setIntegrationReady(integrationType: string) { * signed into. * @returns {Function} */ -export function signIn(calendarType: string): Function { - return (dispatch: Dispatch) => { +export function signIn(calendarType: string) { + return (dispatch: IStore['dispatch']) => { const integration = _getCalendarIntegration(calendarType); if (!integration) { @@ -210,7 +212,7 @@ export function signIn(calendarType: string): Function { .then(() => dispatch(updateProfile(calendarType))) .then(() => dispatch(refreshCalendar())) .then(() => sendAnalytics(createCalendarConnectedEvent())) - .catch(error => { + .catch((error: any) => { logger.error( 'Error occurred while signing into calendar integration', error); @@ -228,10 +230,10 @@ export function signIn(calendarType: string): Function { * @param {string} calendarId - The id of the calendar to use. * @returns {Function} */ -export function updateCalendarEvent(id: string, calendarId: string): Function { - return (dispatch: Dispatch, getState: Function) => { +export function updateCalendarEvent(id: string, calendarId: string) { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { - const { integrationType } = getState()['features/calendar-sync']; + const { integrationType = '' } = getState()['features/calendar-sync']; const integration = _getCalendarIntegration(integrationType); if (!integration) { @@ -240,7 +242,7 @@ export function updateCalendarEvent(id: string, calendarId: string): Function { const { locationURL } = getState()['features/base/connection']; const newRoomName = generateRoomWithoutSeparator(); - let href = locationURL.href; + let href = locationURL?.href ?? ''; href.endsWith('/') || (href += '/'); @@ -275,8 +277,8 @@ export function updateCalendarEvent(id: string, calendarId: string): Function { * should be updated. * @returns {Function} */ -export function updateProfile(calendarType: string): Function { - return (dispatch: Dispatch) => { +export function updateProfile(calendarType: string) { + return (dispatch: IStore['dispatch']) => { const integration = _getCalendarIntegration(calendarType); if (!integration) { @@ -284,7 +286,7 @@ export function updateProfile(calendarType: string): Function { } return dispatch(integration.getCurrentEmail()) - .then(email => { + .then((email: string) => { dispatch(setCalendarProfileEmail(email)); }); }; diff --git a/react/features/calendar-sync/functions.any.js b/react/features/calendar-sync/functions.any.ts similarity index 91% rename from react/features/calendar-sync/functions.any.js rename to react/features/calendar-sync/functions.any.ts index 527b46df3f..6d173aac17 100644 --- a/react/features/calendar-sync/functions.any.js +++ b/react/features/calendar-sync/functions.any.ts @@ -1,8 +1,6 @@ -// @flow - import md5 from 'js-md5'; -import { APP_LINK_SCHEME, parseURIString } from '../base/util'; +import { APP_LINK_SCHEME, parseURIString } from '../base/util/uri'; import { setCalendarEvents } from './actions'; import { MAX_LIST_LENGTH } from './constants'; @@ -16,7 +14,8 @@ const ALLDAY_EVENT_LENGTH = 23 * 60 * 60 * 1000; * @param {Object} entry - The calendar entry. * @returns {boolean} */ -function _isDisplayableCalendarEntry(entry) { +function _isDisplayableCalendarEntry(entry: { allDay: boolean; attendees: Object[]; + endDate: number; startDate: number; }) { // Entries are displayable if: // - Ends in the future (future or ongoing events) // - Is not an all day event and there is only one attendee (these events @@ -45,7 +44,8 @@ export function _updateCalendarEntries(events: Array) { return; } - // eslint-disable-next-line no-invalid-this + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-invalid-this const { dispatch, getState } = this; const knownDomains = getState()['features/base/known-domains']; const entryMap = new Map(); @@ -106,7 +106,7 @@ export function _updateCalendarEntries(events: Array) { * @param {string} negativePattern - The negative pattern. * @returns {string} */ -function _checkPattern(str, positivePattern, negativePattern) { +function _checkPattern(str: string, positivePattern: string, negativePattern: string) { const positiveRegExp = new RegExp(positivePattern, 'gi'); let positiveMatch = positiveRegExp.exec(str); @@ -129,7 +129,7 @@ function _checkPattern(str, positivePattern, negativePattern) { * @private * @returns {CalendarEntry} */ -function _parseCalendarEntry(event, knownDomains) { +function _parseCalendarEntry(event: any, knownDomains: string[]) { if (event) { const url = _getURLFromEvent(event, knownDomains); const startDate = Date.parse(event.startDate); @@ -170,7 +170,8 @@ function _parseCalendarEntry(event, knownDomains) { * @private * @returns {string} */ -function _getURLFromEvent(event, knownDomains) { +function _getURLFromEvent(event: { description: string; location: string; notes: string; title: string; + url: string; }, knownDomains: string[]) { const linkTerminatorPattern = '[^\\s<>$]'; const urlRegExp = `http(s)?://(${knownDomains.join('|')})/${linkTerminatorPattern}+`; diff --git a/react/features/calendar-sync/functions.native.js b/react/features/calendar-sync/functions.native.ts similarity index 83% rename from react/features/calendar-sync/functions.native.js rename to react/features/calendar-sync/functions.native.ts index c2fa6ecd2b..30a06f0e78 100644 --- a/react/features/calendar-sync/functions.native.js +++ b/react/features/calendar-sync/functions.native.ts @@ -1,15 +1,17 @@ -// @flow - import { NativeModules, Platform } from 'react-native'; import RNCalendarEvents from 'react-native-calendar-events'; -import type { Store } from 'redux'; -import { CALENDAR_ENABLED, getFeatureFlag } from '../base/flags'; +import { IReduxState, IStore } from '../app/types'; +import { IStateful } from '../base/app/types'; +import { CALENDAR_ENABLED } from '../base/flags/constants'; +import { getFeatureFlag } from '../base/flags/functions'; +// eslint-disable-next-line lines-around-comment +// @ts-ignore import { getShareInfoText } from '../invite'; -import { setCalendarAuthorization } from './actions'; +import { setCalendarAuthorization } from './actions.native'; import { FETCH_END_DAYS, FETCH_START_DAYS } from './constants'; -import { _updateCalendarEntries } from './functions'; +import { _updateCalendarEntries } from './functions.native'; import logger from './logger'; export * from './functions.any'; @@ -23,10 +25,10 @@ export * from './functions.any'; * @returns {Promise<*>} */ export function addLinkToCalendarEntry( - state: Object, id: string, link: string): Promise { + state: IReduxState, id: string, link: string): Promise { return new Promise((resolve, reject) => { - getShareInfoText(state, link, true).then(shareInfoText => { - RNCalendarEvents.findEventById(id).then(event => { + getShareInfoText(state, link, true).then((shareInfoText: string) => { + RNCalendarEvents.findEventById(id).then((event: any) => { const updateText = event.description ? `${event.description}\n\n${shareInfoText}` @@ -43,6 +45,7 @@ export function addLinkToCalendarEntry( }) }; + // @ts-ignore RNCalendarEvents.saveEvent(event.title, updateObject) .then(resolve, reject); }, reject); @@ -61,7 +64,7 @@ export function addLinkToCalendarEntry( * @returns {boolean} If the app has enabled the calendar feature, {@code true}; * otherwise, {@code false}. */ -export function isCalendarEnabled(stateful: Function | Object) { +export function isCalendarEnabled(stateful: IStateful) { const flag = getFeatureFlag(stateful, CALENDAR_ENABLED); if (typeof flag !== 'undefined') { @@ -85,9 +88,9 @@ export function isCalendarEnabled(stateful: Function | Object) { * @returns {void} */ export function _fetchCalendarEntries( - store: Store<*, *>, + store: IStore, maybePromptForPermission: boolean, - forcePermission: ?boolean) { + forcePermission?: boolean) { const { dispatch, getState } = store; const promptForPermission = (maybePromptForPermission @@ -104,6 +107,8 @@ export function _fetchCalendarEntries( endDate.setDate(endDate.getDate() + FETCH_END_DAYS); RNCalendarEvents.fetchAllEvents( + + // @ts-ignore startDate.getTime(), endDate.getTime(), []) @@ -126,7 +131,7 @@ export function _fetchCalendarEntries( * @private * @returns {Promise} */ -function _ensureCalendarAccess(promptForPermission, dispatch) { +function _ensureCalendarAccess(promptForPermission: boolean | undefined, dispatch: IStore['dispatch']) { return new Promise((resolve, reject) => { RNCalendarEvents.checkPermissions() .then(status => { diff --git a/react/features/calendar-sync/functions.web.js b/react/features/calendar-sync/functions.web.ts similarity index 76% rename from react/features/calendar-sync/functions.web.js rename to react/features/calendar-sync/functions.web.ts index bf20e3c71a..77021737b9 100644 --- a/react/features/calendar-sync/functions.web.js +++ b/react/features/calendar-sync/functions.web.ts @@ -1,12 +1,13 @@ -// @flow - -import { toState } from '../base/redux'; +/* eslint-disable lines-around-comment */ +import { IStore } from '../app/types'; +import { IStateful } from '../base/app/types'; +import { toState } from '../base/redux/functions'; import { clearCalendarIntegration, setCalendarError, setLoadingCalendarEvents -} from './actions'; +} from './actions.web'; export * from './functions.any'; import { CALENDAR_TYPE, @@ -14,10 +15,13 @@ import { FETCH_END_DAYS, FETCH_START_DAYS } from './constants'; -import { _updateCalendarEntries } from './functions'; +import { _updateCalendarEntries } from './functions.web'; import logger from './logger'; +// @ts-ignore import { googleCalendarApi } from './web/googleCalendar'; +// @ts-ignore import { microsoftCalendarApi } from './web/microsoftCalendar'; +/* eslint-enable lines-around-comment */ /** * Determines whether the calendar feature is enabled by the web. @@ -27,7 +31,7 @@ import { microsoftCalendarApi } from './web/microsoftCalendar'; * @returns {boolean} If the app has enabled the calendar feature, {@code true}; * otherwise, {@code false}. */ -export function isCalendarEnabled(stateful: Function | Object) { +export function isCalendarEnabled(stateful: IStateful) { const { enableCalendarIntegration, googleApiApplicationClientID, @@ -37,26 +41,24 @@ export function isCalendarEnabled(stateful: Function | Object) { return Boolean(enableCalendarIntegration && (googleApiApplicationClientID || microsoftApiApplicationClientID)); } -/* eslint-disable no-unused-vars */ /** * Reads the user's calendar and updates the stored entries if need be. * * @param {Object} store - The redux store. - * @param {boolean} maybePromptForPermission - Flag to tell the app if it should + * @param {boolean} _maybePromptForPermission - Flag to tell the app if it should * prompt for a calendar permission if it wasn't granted yet. - * @param {boolean|undefined} forcePermission - Whether to force to re-ask for + * @param {boolean|undefined} _forcePermission - Whether to force to re-ask for * the permission or not. * @private * @returns {void} */ export function _fetchCalendarEntries( - store: Object, - maybePromptForPermission: boolean, - forcePermission: ?boolean) { - /* eslint-enable no-unused-vars */ + store: IStore, + _maybePromptForPermission: boolean, + _forcePermission?: boolean) { const { dispatch, getState } = store; - const { integrationType } = getState()['features/calendar-sync']; + const { integrationType = '' } = getState()['features/calendar-sync']; const integration = _getCalendarIntegration(integrationType); if (!integration) { @@ -69,7 +71,7 @@ export function _fetchCalendarEntries( dispatch(integration.load()) .then(() => dispatch(integration._isSignedIn())) - .then(signedIn => { + .then((signedIn: boolean) => { if (signedIn) { return Promise.resolve(); } @@ -80,13 +82,13 @@ export function _fetchCalendarEntries( }) .then(() => dispatch(integration.getCalendarEntries( FETCH_START_DAYS, FETCH_END_DAYS))) - .then(events => _updateCalendarEntries.call({ + .then((events: Object[]) => _updateCalendarEntries.call({ dispatch, getState }, events)) .then(() => { dispatch(setCalendarError()); - }, error => { + }, (error: any) => { logger.error('Error fetching calendar.', error); if (error.error === ERRORS.AUTH_FAILED) { diff --git a/react/features/calendar-sync/logger.js b/react/features/calendar-sync/logger.ts similarity index 91% rename from react/features/calendar-sync/logger.js rename to react/features/calendar-sync/logger.ts index f252bcf175..2f3266b073 100644 --- a/react/features/calendar-sync/logger.js +++ b/react/features/calendar-sync/logger.ts @@ -1,5 +1,3 @@ -// @flow - import { getLogger } from '../base/logging/functions'; export default getLogger('features/calendar-sync'); diff --git a/react/features/calendar-sync/middleware.js b/react/features/calendar-sync/middleware.ts similarity index 83% rename from react/features/calendar-sync/middleware.js rename to react/features/calendar-sync/middleware.ts index 0eb64fefbc..4ec4de2e88 100644 --- a/react/features/calendar-sync/middleware.js +++ b/react/features/calendar-sync/middleware.ts @@ -1,8 +1,8 @@ -// @flow - -import { SET_CONFIG } from '../base/config'; -import { ADD_KNOWN_DOMAINS } from '../base/known-domains'; -import { MiddlewareRegistry, equals } from '../base/redux'; +import { IStore } from '../app/types'; +import { SET_CONFIG } from '../base/config/actionTypes'; +import { ADD_KNOWN_DOMAINS } from '../base/known-domains/actionTypes'; +import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; +import { equals } from '../base/redux/functions'; import { APP_STATE_CHANGED } from '../mobile/background/actionTypes'; import { REFRESH_CALENDAR } from './actionTypes'; @@ -70,7 +70,7 @@ MiddlewareRegistry.register(store => next => action => { * @private * @returns {void} */ -function _maybeClearAccessStatus(store, { appState }) { +function _maybeClearAccessStatus(store: IStore, { appState }: { appState: string; }) { appState === 'background' && store.dispatch(setCalendarAuthorization(undefined)); } diff --git a/react/features/calendar-sync/reducer.tsx b/react/features/calendar-sync/reducer.tsx index 483eae7597..feabf27dad 100644 --- a/react/features/calendar-sync/reducer.tsx +++ b/react/features/calendar-sync/reducer.tsx @@ -29,7 +29,11 @@ const DEFAULT_STATE = { export interface ICalendarSyncState { authorization?: string; error?: Object; - events: Array; + events: Array<{ + calendarId: string; + id: string; + url: string; + }>; integrationReady: boolean; integrationType?: string; isLoadingEvents?: boolean; diff --git a/react/features/power-monitor/actions.js b/react/features/power-monitor/actions.ts similarity index 88% rename from react/features/power-monitor/actions.js rename to react/features/power-monitor/actions.ts index 7c5bbf5725..558b26b167 100644 --- a/react/features/power-monitor/actions.js +++ b/react/features/power-monitor/actions.ts @@ -1,5 +1,4 @@ -// @flow - +// @ts-expect-error import { Transport } from '../../../modules/transport'; import { @@ -30,7 +29,7 @@ export function suspendDetected() { * type: string * }} */ -export function setTransport(transport: ?Transport) { +export function setTransport(transport?: Transport) { return { type: SET_TRANSPORT, transport diff --git a/react/features/power-monitor/middleware.js b/react/features/power-monitor/middleware.ts similarity index 79% rename from react/features/power-monitor/middleware.js rename to react/features/power-monitor/middleware.ts index 3b9ab49440..332a2988b4 100644 --- a/react/features/power-monitor/middleware.js +++ b/react/features/power-monitor/middleware.ts @@ -1,16 +1,11 @@ -// @flow - - -import { - PostMessageTransportBackend, - Transport -} from '../../../modules/transport'; +// @ts-expect-error +import { PostMessageTransportBackend, Transport } from '../../../modules/transport'; import { CONFERENCE_JOINED, CONFERENCE_LEFT -} from '../base/conference'; -import { MiddlewareRegistry } from '../base/redux'; -import { destroyLocalTracks } from '../base/tracks'; +} from '../base/conference/actionTypes'; +import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; +import { destroyLocalTracks } from '../base/tracks/actions'; import { SUSPEND_DETECTED } from './actionTypes'; import { @@ -18,8 +13,6 @@ import { suspendDetected } from './actions'; -declare var APP: Object; - MiddlewareRegistry.register(store => next => action => { const result = next(action); const { dispatch, getState } = store; @@ -34,7 +27,7 @@ MiddlewareRegistry.register(store => next => action => { }) }); - transport.on('event', event => { + transport.on('event', (event: { event: string; name: string; }) => { if (event && event.name === 'power-monitor' && event.event === 'suspend') { dispatch(suspendDetected()); diff --git a/react/features/remote-control/actions.js b/react/features/remote-control/actions.ts similarity index 83% rename from react/features/remote-control/actions.js rename to react/features/remote-control/actions.ts index b95cd01f87..07895a4177 100644 --- a/react/features/remote-control/actions.js +++ b/react/features/remote-control/actions.ts @@ -1,17 +1,20 @@ -// @flow - +// @ts-ignore import $ from 'jquery'; +import React from 'react'; -import { openDialog } from '../base/dialog'; +import { IStore } from '../app/types'; +import { openDialog } from '../base/dialog/actions'; import { JitsiConferenceEvents } from '../base/lib-jitsi-meet'; +import { pinParticipant } from '../base/participants/actions'; import { getParticipantDisplayName, getPinnedParticipant, - getVirtualScreenshareParticipantByOwnerId, - pinParticipant -} from '../base/participants'; -import { getLocalDesktopTrack, toggleScreensharing } from '../base/tracks'; -import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications'; + getVirtualScreenshareParticipantByOwnerId +} from '../base/participants/functions'; +import { toggleScreensharing } from '../base/tracks/actions'; +import { getLocalDesktopTrack } from '../base/tracks/functions'; +import { showNotification } from '../notifications/actions'; +import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants'; import { isScreenVideoShared } from '../screen-share/functions'; import { @@ -23,6 +26,8 @@ import { SET_RECEIVER_TRANSPORT, SET_REQUESTED_PARTICIPANT } from './actionTypes'; +// eslint-disable-next-line lines-around-comment +// @ts-ignore import { RemoteControlAuthorizationDialog } from './components'; import { DISCO_REMOTE_CONTROL_FEATURE, @@ -43,9 +48,8 @@ import logger from './logger'; /** * Listeners. */ -let permissionsReplyListener, receiverEndpointMessageListener, stopListener; - -declare var APP: Object; +let permissionsReplyListener: Function | undefined, + receiverEndpointMessageListener: Function, stopListener: Function | undefined; /** * Signals that the remote control authorization dialog should be displayed. @@ -72,7 +76,7 @@ export function openRemoteControlAuthorizationDialog(participantId: string) { * @returns {Function} */ export function setRemoteControlActive(active: boolean) { - return (dispatch: Function, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const { active: oldActive } = state['features/remote-control']; const { conference } = state['features/base/conference']; @@ -82,7 +86,7 @@ export function setRemoteControlActive(active: boolean) { type: REMOTE_CONTROL_ACTIVE, active }); - conference.setLocalParticipantProperty('remoteControlSessionStatus', active); + conference?.setLocalParticipantProperty('remoteControlSessionStatus', active); } }; } @@ -95,7 +99,7 @@ export function setRemoteControlActive(active: boolean) { * @returns {Function} */ export function requestRemoteControl(userId: string) { - return (dispatch: Function, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const enabled = isRemoteControlEnabled(state); @@ -110,11 +114,11 @@ export function requestRemoteControl(userId: string) { const { conference } = state['features/base/conference']; - permissionsReplyListener = (participant, event) => { + permissionsReplyListener = (participant: any, event: any) => { dispatch(processPermissionRequestReply(participant.getId(), event)); }; - conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, permissionsReplyListener); + conference?.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, permissionsReplyListener); dispatch({ type: SET_REQUESTED_PARTICIPANT, @@ -140,8 +144,8 @@ export function requestRemoteControl(userId: string) { * @param {EndpointMessage} event - The permission request event. * @returns {Function} */ -export function processPermissionRequestReply(participantId: string, event: Object) { - return (dispatch: Function, getState: Function) => { +export function processPermissionRequestReply(participantId: string, event: any) { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const { action, name, type } = event; const { requestedParticipant } = state['features/remote-control'].controller; @@ -162,11 +166,11 @@ export function processPermissionRequestReply(participantId: string, event: Obje const { conference } = state['features/base/conference']; - stopListener = (participant, stopEvent) => { + stopListener = (participant: any, stopEvent: { name: string; type: string; }) => { dispatch(handleRemoteControlStoppedEvent(participant.getId(), stopEvent)); }; - conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, stopListener); + conference?.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, stopListener); dispatch(resume()); @@ -206,7 +210,9 @@ export function processPermissionRequestReply(participantId: string, event: Obje const virtualScreenshareParticipantId = getVirtualScreenshareParticipantByOwnerId(state, participantId); const pinnedId = pinnedParticipant?.id; + // @ts-ignore if (virtualScreenshareParticipantId && pinnedId !== virtualScreenshareParticipantId) { + // @ts-ignore dispatch(pinParticipant(virtualScreenshareParticipantId)); } else if (!virtualScreenshareParticipantId && pinnedId !== participantId) { dispatch(pinParticipant(participantId)); @@ -226,7 +232,7 @@ export function processPermissionRequestReply(participantId: string, event: Obje * @property {string} type - The function process only events with name REMOTE_CONTROL_MESSAGE_NAME. * @returns {void} */ -export function handleRemoteControlStoppedEvent(participantId: Object, event: Object) { +export function handleRemoteControlStoppedEvent(participantId: Object, event: { name: string; type: string; }) { return (dispatch: Function, getState: Function) => { const state = getState(); const { name, type } = event; @@ -246,8 +252,8 @@ export function handleRemoteControlStoppedEvent(participantId: Object, event: Ob * @param {boolean} notifyRemoteParty - If true a endpoint message to the controlled participant will be sent. * @returns {void} */ -export function stopController(notifyRemoteParty: boolean = false) { - return (dispatch: Function, getState: Function) => { +export function stopController(notifyRemoteParty = false) { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const { controlled } = state['features/remote-control'].controller; @@ -265,7 +271,7 @@ export function stopController(notifyRemoteParty: boolean = false) { logger.log('Stopping remote control controller.'); - conference.off(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, stopListener); + conference?.off(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, stopListener); stopListener = undefined; dispatch(pause()); @@ -289,7 +295,7 @@ export function stopController(notifyRemoteParty: boolean = false) { * @returns {Function} */ export function clearRequest() { - return (dispatch: Function, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const { conference } = getState()['features/base/conference']; dispatch({ @@ -297,7 +303,7 @@ export function clearRequest() { requestedParticipant: undefined }); - conference.off(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, permissionsReplyListener); + conference?.off(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, permissionsReplyListener); permissionsReplyListener = undefined; }; } @@ -313,7 +319,7 @@ export function clearRequest() { * transport: Transport * }} */ -export function setReceiverTransport(transport: Object) { +export function setReceiverTransport(transport?: Object) { return { type: SET_RECEIVER_TRANSPORT, transport @@ -326,7 +332,7 @@ export function setReceiverTransport(transport: Object) { * @returns {Function} */ export function enableReceiver() { - return (dispatch: Function, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const { enabled } = state['features/remote-control'].receiver; @@ -349,7 +355,8 @@ export function enableReceiver() { }); connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true); - receiverEndpointMessageListener = (participant, message) => { + receiverEndpointMessageListener = (participant: any, message: { + action: string; name: string; type: string; }) => { dispatch(endpointMessageReceived(participant.getId(), message)); }; conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, receiverEndpointMessageListener); @@ -401,7 +408,7 @@ export function disableReceiver() { * @param {boolean} [dontNotifyRemoteParty] - If true a endpoint message to the controller participant will be sent. * @returns {Function} */ -export function stopReceiver(dontNotifyLocalParty: boolean = false, dontNotifyRemoteParty: boolean = false) { +export function stopReceiver(dontNotifyLocalParty = false, dontNotifyRemoteParty = false) { return (dispatch: Function, getState: Function) => { const state = getState(); const { receiver } = state['features/remote-control']; @@ -450,8 +457,9 @@ export function stopReceiver(dontNotifyLocalParty: boolean = false, dontNotifyRe * name REMOTE_CONTROL_MESSAGE_NAME. * @returns {Function} */ -export function endpointMessageReceived(participantId: string, message: Object) { - return (dispatch: Function, getState: Function) => { +export function endpointMessageReceived(participantId: string, message: { + action: string; name: string; type: string; }) { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const { action, name, type } = message; if (name !== REMOTE_CONTROL_MESSAGE_NAME) { @@ -472,7 +480,7 @@ export function endpointMessageReceived(participantId: string, message: Object) if (type === EVENTS.stop) { dispatch(stopReceiver(false, true)); } else { // forward the message - transport.sendEvent(message); + transport?.sendEvent(message); } } // else ignore } else { @@ -489,7 +497,7 @@ export function endpointMessageReceived(participantId: string, message: Object) * @returns {Function} */ export function deny(participantId: string) { - return (dispatch: Function, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const { conference } = state['features/base/conference']; @@ -507,7 +515,7 @@ export function deny(participantId: string) { * @returns {Function} */ export function sendStartRequest() { - return (dispatch: Function, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const tracks = state['features/base/tracks']; const track = getLocalDesktopTrack(tracks); @@ -518,7 +526,7 @@ export function sendStartRequest() { return Promise.reject(new Error('Cannot identify screen for the remote control session')); } - return transport.sendRequest({ + return transport?.sendRequest({ name: REMOTE_CONTROL_MESSAGE_NAME, type: REQUESTS.start, sourceId @@ -534,7 +542,7 @@ export function sendStartRequest() { * @returns {Function} */ export function grant(participantId: string) { - return (dispatch: Function, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { dispatch({ type: SET_CONTROLLER, controller: participantId @@ -555,7 +563,7 @@ export function grant(participantId: string) { true, false, { desktopSharingSources: [ 'screen' ] } - )) + )) // @ts-ignore .then(() => dispatch(sendStartRequest())); } @@ -566,7 +574,7 @@ export function grant(participantId: string) { type: EVENTS.permissions, action: PERMISSIONS_ACTIONS.grant })) - .catch(error => { + .catch((error: any) => { logger.error(error); sendRemoteControlEndpointMessage(conference, participantId, { @@ -591,14 +599,16 @@ export function grant(participantId: string) { * @param {Event} event - The mouse event. * @returns {Function} */ -export function mouseClicked(type: string, event: Object) { - return (dispatch: Function, getState: Function) => { +export function mouseClicked(type: string, event: React.MouseEvent) { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const { conference } = state['features/base/conference']; const { controller } = state['features/remote-control']; sendRemoteControlEndpointMessage(conference, controller.controlled, { type, + + // @ts-ignore button: event.which }); }; @@ -610,8 +620,8 @@ export function mouseClicked(type: string, event: Object) { * @param {Event} event - The mouse event. * @returns {Function} */ -export function mouseMoved(event: Object) { - return (dispatch: Function, getState: Function) => { +export function mouseMoved(event: React.MouseEvent) { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const area = getRemoteConrolEventCaptureArea(); if (!area) { @@ -637,8 +647,8 @@ export function mouseMoved(event: Object) { * @param {Event} event - The mouse event. * @returns {Function} */ -export function mouseScrolled(event: Object) { - return (dispatch: Function, getState: Function) => { +export function mouseScrolled(event: { deltaX: number; deltaY: number; }) { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const { conference } = state['features/base/conference']; const { controller } = state['features/remote-control']; @@ -658,8 +668,8 @@ export function mouseScrolled(event: Object) { * @param {Event} event - The key event. * @returns {Function} */ -export function keyPressed(type: string, event: Object) { - return (dispatch: Function, getState: Function) => { +export function keyPressed(type: string, event: React.KeyboardEvent) { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const { conference } = state['features/base/conference']; const { controller } = state['features/remote-control']; @@ -680,7 +690,7 @@ export function keyPressed(type: string, event: Object) { * @returns {Function} */ export function resume() { - return (dispatch: Function, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const area = getRemoteConrolEventCaptureArea(); const state = getState(); const { controller } = state['features/remote-control']; @@ -695,22 +705,22 @@ export function resume() { // FIXME: Once the keyboard shortcuts are using react/redux. APP.keyboardshortcut.enable(false); - area.mousemove(event => { + area.mousemove((event: React.MouseEvent) => { dispatch(mouseMoved(event)); }); - area.mousedown(event => dispatch(mouseClicked(EVENTS.mousedown, event))); - area.mouseup(event => dispatch(mouseClicked(EVENTS.mouseup, event))); - area.dblclick(event => dispatch(mouseClicked(EVENTS.mousedblclick, event))); + area.mousedown((event: React.MouseEvent) => dispatch(mouseClicked(EVENTS.mousedown, event))); + area.mouseup((event: React.MouseEvent) => dispatch(mouseClicked(EVENTS.mouseup, event))); + area.dblclick((event: React.MouseEvent) => dispatch(mouseClicked(EVENTS.mousedblclick, event))); area.contextmenu(() => false); - area[0].onwheel = event => { + area[0].onwheel = (event: any) => { event.preventDefault(); event.stopPropagation(); dispatch(mouseScrolled(event)); return false; }; - $(window).keydown(event => dispatch(keyPressed(EVENTS.keydown, event))); - $(window).keyup(event => dispatch(keyPressed(EVENTS.keyup, event))); + $(window).keydown((event: React.KeyboardEvent) => dispatch(keyPressed(EVENTS.keydown, event))); + $(window).keyup((event: React.KeyboardEvent) => dispatch(keyPressed(EVENTS.keyup, event))); dispatch({ type: CAPTURE_EVENTS, @@ -730,7 +740,7 @@ export function resume() { * @returns {Function} */ export function pause() { - return (dispatch: Function, getState: Function) => { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const { controller } = state['features/remote-control']; const { controlled, isCapturingEvents } = controller; diff --git a/react/features/remote-control/functions.js b/react/features/remote-control/functions.ts similarity index 84% rename from react/features/remote-control/functions.js rename to react/features/remote-control/functions.ts index 46280ecd76..fdce7c31ff 100644 --- a/react/features/remote-control/functions.js +++ b/react/features/remote-control/functions.ts @@ -1,6 +1,9 @@ -// @flow +import React from 'react'; +// @ts-expect-error import VideoLayout from '../../../modules/UI/videolayout/VideoLayout'; +import { IReduxState, IStore } from '../app/types'; +import { IJitsiConference } from '../base/conference/reducer'; import JitsiMeetJS from '../base/lib-jitsi-meet'; import { enableReceiver, stopReceiver } from './actions'; @@ -14,7 +17,7 @@ import logger from './logger'; * @param {*} state - The redux state. * @returns {boolean} - True if the remote control is enabled and false otherwise. */ -export function isRemoteControlEnabled(state: Object) { +export function isRemoteControlEnabled(state: IReduxState) { return !state['features/base/config'].disableRemoteControl && JitsiMeetJS.isDesktopSharingEnabled(); } @@ -27,8 +30,8 @@ export function isRemoteControlEnabled(state: Object) { * @returns {boolean} - True if the message was sent successfully and false otherwise. */ export function sendRemoteControlEndpointMessage( - conference: Object, - to: ?string, + conference: IJitsiConference | undefined, + to: string | undefined, event: Object) { if (!to) { logger.warn('Remote control: Skip sending remote control event. Params:', to); @@ -37,7 +40,7 @@ export function sendRemoteControlEndpointMessage( } try { - conference.sendEndpointMessage(to, { + conference?.sendEndpointMessage(to, { name: REMOTE_CONTROL_MESSAGE_NAME, ...event }); @@ -59,7 +62,7 @@ export function sendRemoteControlEndpointMessage( * @param {Store} store - The redux store. * @returns {void} */ -export function onRemoteControlAPIEvent(event: Object, { getState, dispatch }: Object) { +export function onRemoteControlAPIEvent(event: { type: string; }, { getState, dispatch }: IStore) { switch (event.type) { case EVENTS.supported: logger.log('Remote Control supported.'); @@ -93,7 +96,7 @@ export function getRemoteConrolEventCaptureArea() { * @param {KeyboardEvent} event - The event. * @returns {KEYS} The key that is pressed or undefined. */ -export function getKey(event: Object) { +export function getKey(event: React.KeyboardEvent) { return keyboardEventToKey(event); } @@ -103,7 +106,7 @@ export function getKey(event: Object) { * @param {KeyboardEvent} event - The event. * @returns {Array} With possible values: "shift", "control", "alt", "command". */ -export function getModifiers(event: Object) { +export function getModifiers(event: React.KeyboardEvent) { const modifiers = []; if (event.shiftKey) { diff --git a/react/features/remote-control/keycodes.js b/react/features/remote-control/keycodes.ts similarity index 92% rename from react/features/remote-control/keycodes.js rename to react/features/remote-control/keycodes.ts index ad4716d9dd..fb02638c59 100644 --- a/react/features/remote-control/keycodes.js +++ b/react/features/remote-control/keycodes.ts @@ -1,3 +1,5 @@ +import React from 'react'; + /** * Enumerates the supported keys. * NOTE: The maps represents physical keys on the keyboard, not chars. @@ -145,6 +147,7 @@ const keyCodeToKey = { * Generate codes for digit keys (0-9). */ for (let i = 0; i < 10; i++) { + // @ts-ignore keyCodeToKey[i + 48] = `${i}`; } @@ -154,7 +157,7 @@ for (let i = 0; i < 10; i++) { for (let i = 0; i < 26; i++) { const keyCode = i + 65; - keyCodeToKey[keyCode] = String.fromCharCode(keyCode).toLowerCase(); + keyCodeToKey[keyCode as keyof typeof keyCodeToKey] = String.fromCharCode(keyCode).toLowerCase(); } /** @@ -163,6 +166,6 @@ for (let i = 0; i < 26; i++) { * @param {KeyboardEvent} event - The event. * @returns {KEYS} - The key on the keyboard. */ -export function keyboardEventToKey(event) { - return keyCodeToKey[event.which]; +export function keyboardEventToKey(event: React.KeyboardEvent) { + return keyCodeToKey[event.which as keyof typeof keyCodeToKey]; } diff --git a/react/features/remote-control/logger.js b/react/features/remote-control/logger.ts similarity index 91% rename from react/features/remote-control/logger.js rename to react/features/remote-control/logger.ts index 6b88c538c4..a4ceb438f5 100644 --- a/react/features/remote-control/logger.js +++ b/react/features/remote-control/logger.ts @@ -1,5 +1,3 @@ -// @flow - import { getLogger } from '../base/logging/functions'; export default getLogger('features/remote-control'); diff --git a/react/features/remote-control/middleware.js b/react/features/remote-control/middleware.ts similarity index 86% rename from react/features/remote-control/middleware.js rename to react/features/remote-control/middleware.ts index 8c776d34b8..f160a37984 100644 --- a/react/features/remote-control/middleware.js +++ b/react/features/remote-control/middleware.ts @@ -1,10 +1,10 @@ -// @flow +// @ts-expect-error import { PostMessageTransportBackend, Transport } from '@jitsi/js-utils/transport'; -import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app'; -import { CONFERENCE_JOINED } from '../base/conference'; -import { PARTICIPANT_LEFT } from '../base/participants'; -import { MiddlewareRegistry } from '../base/redux'; +import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes'; +import { CONFERENCE_JOINED } from '../base/conference/actionTypes'; +import { PARTICIPANT_LEFT } from '../base/participants/actionTypes'; +import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; import { clearRequest, setReceiverTransport, setRemoteControlActive, stopController, stopReceiver @@ -51,7 +51,7 @@ MiddlewareRegistry.register(store => next => async action => { if (transport) { // We expect here that even if we receive the supported event earlier // it will be cached and we'll receive it. - transport.on('event', event => { + transport.on('event', (event: { name: string; type: string; }) => { if (event.name === REMOTE_CONTROL_MESSAGE_NAME) { onRemoteControlAPIEvent(event, store); diff --git a/react/features/remote-control/reducer.ts b/react/features/remote-control/reducer.ts index 8f83751b00..669f9fa63c 100644 --- a/react/features/remote-control/reducer.ts +++ b/react/features/remote-control/reducer.ts @@ -34,7 +34,12 @@ export interface IRemoteControlState { receiver: { controller?: string; enabled: boolean; - transport?: Object; + transport?: { + dispose: Function; + on: Function; + sendEvent: Function; + sendRequest: Function; + }; }; } diff --git a/react/features/remote-control/subscriber.js b/react/features/remote-control/subscriber.ts similarity index 89% rename from react/features/remote-control/subscriber.js rename to react/features/remote-control/subscriber.ts index 449d3e950d..28a3fe163a 100644 --- a/react/features/remote-control/subscriber.js +++ b/react/features/remote-control/subscriber.ts @@ -1,12 +1,10 @@ -// @flow - import { getParticipantById, getVirtualScreenshareParticipantByOwnerId, getVirtualScreenshareParticipantOwnerId, isScreenShareParticipant -} from '../base/participants'; -import { StateListenerRegistry } from '../base/redux'; +} from '../base/participants/functions'; +import StateListenerRegistry from '../base/redux/StateListenerRegistry'; import { pause, resume } from './actions'; @@ -15,7 +13,7 @@ import { pause, resume } from './actions'; */ StateListenerRegistry.register( /* selector */ state => { - const { participantId } = state['features/large-video']; + const { participantId = '' } = state['features/large-video']; const { controller } = state['features/remote-control']; const { controlled } = controller;