diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 173d8dca..064a20db 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,13 +1,31 @@ +const COPYRIGHT_HEADER = `/* +Copyright %%CURRENT_YEAR%% New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +`; + module.exports = { plugins: ["matrix-org"], extends: [ - "prettier", "plugin:matrix-org/react", "plugin:matrix-org/a11y", "plugin:matrix-org/typescript", + "prettier", ], parserOptions: { - ecmaVersion: 2018, + ecmaVersion: "latest", sourceType: "module", project: ["./tsconfig.json"], }, @@ -15,27 +33,11 @@ module.exports = { browser: true, node: true, }, - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - }, rules: { - "jsx-a11y/media-has-caption": ["off"], + "matrix-org/require-copyright-header": ["error", COPYRIGHT_HEADER], + "jsx-a11y/media-has-caption": "off", + "deprecate/import": "off", // Disabled because it crashes the linter }, - overrides: [ - { - files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}"], - extends: [ - "plugin:matrix-org/typescript", - "plugin:matrix-org/react", - "prettier", - ], - rules: { - // We're aiming to convert this code to strict mode - "@typescript-eslint/no-non-null-assertion": "off", - }, - }, - ], settings: { react: { version: "detect", diff --git a/package.json b/package.json index 007bd32f..957464c4 100644 --- a/package.json +++ b/package.json @@ -104,11 +104,13 @@ "eslint": "^8.14.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^9.0.0", + "eslint-plugin-deprecate": "^0.8.2", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-matrix-org": "^0.4.0", + "eslint-plugin-matrix-org": "^1.2.1", "eslint-plugin-react": "^7.29.4", "eslint-plugin-react-hooks": "^4.5.0", + "eslint-plugin-unicorn": "^48.0.1", "i18next-parser": "^6.6.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.2.2", @@ -118,6 +120,7 @@ "sass": "^1.42.1", "storybook-builder-vite": "^0.1.12", "typescript": "^5.1.6", + "typescript-eslint-language-service": "^5.0.5", "vite": "^4.2.0", "vite-plugin-html-template": "^1.1.0", "vite-plugin-svgr": "^3.2.0" diff --git a/src/App.tsx b/src/App.tsx index 00890f31..6708beb1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Suspense, useEffect, useState } from "react"; +import { FC, Suspense, useEffect, useState } from "react"; import { BrowserRouter as Router, Switch, @@ -43,7 +43,7 @@ interface BackgroundProviderProps { children: JSX.Element; } -const BackgroundProvider = ({ children }: BackgroundProviderProps) => { +const BackgroundProvider: FC = ({ children }) => { const { pathname } = useLocation(); useEffect(() => { @@ -63,7 +63,7 @@ interface AppProps { history: History; } -export default function App({ history }: AppProps) { +export const App: FC = ({ history }) => { const [loaded, setLoaded] = useState(false); useEffect(() => { @@ -116,4 +116,4 @@ export default function App({ history }: AppProps) { ); -} +}; diff --git a/src/Banner.tsx b/src/Banner.tsx index fcc68a3b..87ce8a96 100644 --- a/src/Banner.tsx +++ b/src/Banner.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ReactNode } from "react"; +import { FC, ReactNode } from "react"; import styles from "./Banner.module.css"; @@ -22,6 +22,6 @@ interface Props { children: ReactNode; } -export const Banner = ({ children }: Props) => { +export const Banner: FC = ({ children }) => { return
{children}
; }; diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index f7ceea9f..c1ee6338 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -82,7 +82,8 @@ export type SetClientParams = { const ClientContext = createContext(undefined); -export const useClientState = () => useContext(ClientContext); +export const useClientState = (): ClientState | undefined => + useContext(ClientContext); export function useClient(): { client?: MatrixClient; @@ -408,8 +409,8 @@ export interface Session { tempPassword?: string; } -const clearSession = () => localStorage.removeItem("matrix-auth-store"); -const saveSession = (s: Session) => +const clearSession = (): void => localStorage.removeItem("matrix-auth-store"); +const saveSession = (s: Session): void => localStorage.setItem("matrix-auth-store", JSON.stringify(s)); const loadSession = (): Session | undefined => { const data = localStorage.getItem("matrix-auth-store"); @@ -423,4 +424,5 @@ const loadSession = (): Session | undefined => { const clientIsDisconnected = ( syncState: SyncState, syncData?: ISyncStateData -) => syncState === "ERROR" && syncData?.error?.name === "ConnectionError"; +): boolean => + syncState === "ERROR" && syncData?.error?.name === "ConnectionError"; diff --git a/src/DisconnectedBanner.tsx b/src/DisconnectedBanner.tsx index 6ec5d5ac..06369cc5 100644 --- a/src/DisconnectedBanner.tsx +++ b/src/DisconnectedBanner.tsx @@ -15,22 +15,22 @@ limitations under the License. */ import classNames from "classnames"; -import { HTMLAttributes, ReactNode } from "react"; +import { FC, HTMLAttributes, ReactNode } from "react"; import { useTranslation } from "react-i18next"; import styles from "./DisconnectedBanner.module.css"; import { ValidClientState, useClientState } from "./ClientContext"; -interface DisconnectedBannerProps extends HTMLAttributes { +interface Props extends HTMLAttributes { children?: ReactNode; className?: string; } -export function DisconnectedBanner({ +export const DisconnectedBanner: FC = ({ children, className, ...rest -}: DisconnectedBannerProps) { +}) => { const { t } = useTranslation(); const clientState = useClientState(); let shouldShowBanner = false; @@ -50,4 +50,4 @@ export function DisconnectedBanner({ )} ); -} +}; diff --git a/src/E2EEBanner.tsx b/src/E2EEBanner.tsx index 774f3582..7b173605 100644 --- a/src/E2EEBanner.tsx +++ b/src/E2EEBanner.tsx @@ -15,13 +15,14 @@ limitations under the License. */ import { Trans } from "react-i18next"; +import { FC } from "react"; import { Banner } from "./Banner"; import styles from "./E2EEBanner.module.css"; import { ReactComponent as LockOffIcon } from "./icons/LockOff.svg"; import { useEnableE2EE } from "./settings/useSetting"; -export const E2EEBanner = () => { +export const E2EEBanner: FC = () => { const [e2eeEnabled] = useEnableE2EE(); if (e2eeEnabled) return null; diff --git a/src/Facepile.tsx b/src/Facepile.tsx index 7ed995ce..08b460a9 100644 --- a/src/Facepile.tsx +++ b/src/Facepile.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { HTMLAttributes } from "react"; +import { FC, HTMLAttributes } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { useTranslation } from "react-i18next"; @@ -30,14 +30,14 @@ interface Props extends HTMLAttributes { size?: Size | number; } -export function Facepile({ +export const Facepile: FC = ({ className, client, members, max = 3, size = Size.XS, ...rest -}: Props) { +}) => { const { t } = useTranslation(); const displayedMembers = members.slice(0, max); @@ -63,4 +63,4 @@ export function Facepile({ })} ); -} +}; diff --git a/src/FullScreenView.tsx b/src/FullScreenView.tsx index 6ac134b8..10fe01f4 100644 --- a/src/FullScreenView.tsx +++ b/src/FullScreenView.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ReactNode, useCallback, useEffect } from "react"; +import { FC, ReactNode, useCallback, useEffect } from "react"; import { useLocation } from "react-router-dom"; import classNames from "classnames"; import { Trans, useTranslation } from "react-i18next"; @@ -32,7 +32,10 @@ interface FullScreenViewProps { children: ReactNode; } -export function FullScreenView({ className, children }: FullScreenViewProps) { +export const FullScreenView: FC = ({ + className, + children, +}) => { return (
@@ -46,13 +49,13 @@ export function FullScreenView({ className, children }: FullScreenViewProps) {
); -} +}; interface ErrorViewProps { error: Error; } -export function ErrorView({ error }: ErrorViewProps) { +export const ErrorView: FC = ({ error }) => { const location = useLocation(); const { t } = useTranslation(); @@ -95,9 +98,9 @@ export function ErrorView({ error }: ErrorViewProps) { )} ); -} +}; -export function CrashView() { +export const CrashView: FC = () => { const { t } = useTranslation(); const onReload = useCallback(() => { @@ -126,9 +129,9 @@ export function CrashView() { ); -} +}; -export function LoadingView() { +export const LoadingView: FC = () => { const { t } = useTranslation(); return ( @@ -136,4 +139,4 @@ export function LoadingView() {

{t("Loading…")}

); -} +}; diff --git a/src/Header.tsx b/src/Header.tsx index aea3da71..b080bec0 100644 --- a/src/Header.tsx +++ b/src/Header.tsx @@ -33,13 +33,13 @@ interface HeaderProps extends HTMLAttributes { className?: string; } -export function Header({ children, className, ...rest }: HeaderProps) { +export const Header: FC = ({ children, className, ...rest }) => { return (
{children}
); -} +}; interface LeftNavProps extends HTMLAttributes { children: ReactNode; @@ -47,12 +47,12 @@ interface LeftNavProps extends HTMLAttributes { hideMobile?: boolean; } -export function LeftNav({ +export const LeftNav: FC = ({ children, className, hideMobile, ...rest -}: LeftNavProps) { +}) => { return (
); -} +}; interface RightNavProps extends HTMLAttributes { children?: ReactNode; @@ -74,12 +74,12 @@ interface RightNavProps extends HTMLAttributes { hideMobile?: boolean; } -export function RightNav({ +export const RightNav: FC = ({ children, className, hideMobile, ...rest -}: RightNavProps) { +}) => { return (
); -} +}; interface HeaderLogoProps { className?: string; } -export function HeaderLogo({ className }: HeaderLogoProps) { +export const HeaderLogo: FC = ({ className }) => { const { t } = useTranslation(); return ( @@ -111,7 +111,7 @@ export function HeaderLogo({ className }: HeaderLogoProps) { ); -} +}; interface RoomHeaderInfoProps { id: string; diff --git a/src/ListBox.tsx b/src/ListBox.tsx index b7ec7c72..b025f483 100644 --- a/src/ListBox.tsx +++ b/src/ListBox.tsx @@ -14,7 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MutableRefObject, PointerEvent, useCallback, useRef } from "react"; +import { + MutableRefObject, + PointerEvent, + ReactNode, + useCallback, + useRef, +} from "react"; import { useListBox, useOption, AriaListBoxOptions } from "@react-aria/listbox"; import { ListState } from "@react-stately/list"; import { Node } from "@react-types/shared"; @@ -35,7 +41,7 @@ export function ListBox({ className, listBoxRef, ...rest -}: ListBoxProps) { +}: ListBoxProps): ReactNode { const ref = useRef(null); const listRef = listBoxRef ?? ref; @@ -66,7 +72,7 @@ interface OptionProps { item: Node; } -function Option({ item, state, className }: OptionProps) { +function Option({ item, state, className }: OptionProps): ReactNode { const ref = useRef(null); const { optionProps, isSelected, isFocused, isDisabled } = useOption( { key: item.key }, diff --git a/src/Menu.tsx b/src/Menu.tsx index c1e8e40b..932c19d4 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Key, useRef, useState } from "react"; +import { Key, ReactNode, useRef, useState } from "react"; import { AriaMenuOptions, useMenu, useMenuItem } from "@react-aria/menu"; import { TreeState, useTreeState } from "@react-stately/tree"; import { mergeProps } from "@react-aria/utils"; @@ -37,7 +37,7 @@ export function Menu({ onClose, label, ...rest -}: MenuProps) { +}: MenuProps): ReactNode { const state = useTreeState({ ...rest, selectionMode: "none" }); const menuRef = useRef(null); const { menuProps } = useMenu(rest, state, menuRef); @@ -68,7 +68,12 @@ interface MenuItemProps { onClose: () => void; } -function MenuItem({ item, state, onAction, onClose }: MenuItemProps) { +function MenuItem({ + item, + state, + onAction, + onClose, +}: MenuItemProps): ReactNode { const ref = useRef(null); const { menuItemProps } = useMenuItem( { diff --git a/src/Modal.tsx b/src/Modal.tsx index b644abe4..9fdd53b9 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ReactNode, useCallback } from "react"; +import { FC, ReactNode, useCallback } from "react"; import { AriaDialogProps } from "@react-types/dialog"; import { useTranslation } from "react-i18next"; import { @@ -36,7 +36,7 @@ import { useMediaQuery } from "./useMediaQuery"; import { Glass } from "./Glass"; // TODO: Support tabs -export interface ModalProps extends AriaDialogProps { +export interface Props extends AriaDialogProps { title: string; children: ReactNode; className?: string; @@ -58,14 +58,14 @@ export interface ModalProps extends AriaDialogProps { * A modal, taking the form of a drawer / bottom sheet on touchscreen devices, * and a dialog box on desktop. */ -export function Modal({ +export const Modal: FC = ({ title, children, className, open, onDismiss, ...rest -}: ModalProps) { +}) => { const { t } = useTranslation(); // Empirically, Chrome on Android can end up not matching (hover: none), but // still matching (pointer: coarse) :/ @@ -140,4 +140,4 @@ export function Modal({ ); } -} +}; diff --git a/src/SequenceDiagramViewerPage.tsx b/src/SequenceDiagramViewerPage.tsx index 9fb66f94..defa1809 100644 --- a/src/SequenceDiagramViewerPage.tsx +++ b/src/SequenceDiagramViewerPage.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useCallback, useState } from "react"; +import { FC, useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; import { @@ -30,7 +30,7 @@ interface DebugLog { remoteUserIds: string[]; } -export function SequenceDiagramViewerPage() { +export const SequenceDiagramViewerPage: FC = () => { const { t } = useTranslation(); usePageTitle(t("Inspector")); @@ -69,4 +69,4 @@ export function SequenceDiagramViewerPage() { )}
); -} +}; diff --git a/src/TranslatedError.ts b/src/TranslatedError.ts index 62960f04..595672cc 100644 --- a/src/TranslatedError.ts +++ b/src/TranslatedError.ts @@ -37,5 +37,7 @@ class TranslatedErrorImpl extends TranslatedError {} // i18next-parser can't detect calls to a constructor, so we expose a bare // function instead -export const translatedError = (messageKey: string, t: typeof i18n.t) => - new TranslatedErrorImpl(messageKey, t); +export const translatedError = ( + messageKey: string, + t: typeof i18n.t +): TranslatedError => new TranslatedErrorImpl(messageKey, t); diff --git a/src/UrlParams.ts b/src/UrlParams.ts index 90713c3b..0b230ae8 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -134,7 +134,7 @@ class ParamParser { private fragmentParams: URLSearchParams; private queryParams: URLSearchParams; - constructor(search: string, hash: string) { + public constructor(search: string, hash: string) { this.queryParams = new URLSearchParams(search); const fragmentQueryStart = hash.indexOf("?"); @@ -146,18 +146,18 @@ class ParamParser { // Normally, URL params should be encoded in the fragment so as to avoid // leaking them to the server. However, we also check the normal query // string for backwards compatibility with versions that only used that. - getParam(name: string): string | null { + public getParam(name: string): string | null { return this.fragmentParams.get(name) ?? this.queryParams.get(name); } - getAllParams(name: string): string[] { + public getAllParams(name: string): string[] { return [ ...this.fragmentParams.getAll(name), ...this.queryParams.getAll(name), ]; } - getFlagParam(name: string, defaultValue = false): boolean { + public getFlagParam(name: string, defaultValue = false): boolean { const param = this.getParam(name); return param === null ? defaultValue : param !== "false"; } diff --git a/src/UserMenu.tsx b/src/UserMenu.tsx index 515e71f0..44427287 100644 --- a/src/UserMenu.tsx +++ b/src/UserMenu.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useCallback, useMemo } from "react"; +import { FC, ReactNode, useCallback, useMemo } from "react"; import { Item } from "@react-stately/collections"; import { useLocation } from "react-router-dom"; import { useTranslation } from "react-i18next"; @@ -31,7 +31,7 @@ import { ReactComponent as LogoutIcon } from "./icons/Logout.svg"; import { Body } from "./typography/Typography"; import styles from "./UserMenu.module.css"; -interface UserMenuProps { +interface Props { preventNavigation: boolean; isAuthenticated: boolean; isPasswordlessUser: boolean; @@ -41,7 +41,7 @@ interface UserMenuProps { onAction: (value: string) => void; } -export function UserMenu({ +export const UserMenu: FC = ({ preventNavigation, isAuthenticated, isPasswordlessUser, @@ -49,7 +49,7 @@ export function UserMenu({ displayName, avatarUrl, onAction, -}: UserMenuProps) { +}) => { const { t } = useTranslation(); const location = useLocation(); @@ -123,7 +123,7 @@ export function UserMenu({ { // eslint-disable-next-line @typescript-eslint/no-explicit-any - (props: any) => ( + (props: any): ReactNode => ( {items.map(({ key, icon: Icon, label, dataTestid }) => ( @@ -141,4 +141,4 @@ export function UserMenu({ } ); -} +}; diff --git a/src/UserMenuContainer.tsx b/src/UserMenuContainer.tsx index 359f75f6..c629ecea 100644 --- a/src/UserMenuContainer.tsx +++ b/src/UserMenuContainer.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useCallback, useState } from "react"; +import { FC, useCallback, useState } from "react"; import { useHistory, useLocation } from "react-router-dom"; import { useClientLegacy } from "./ClientContext"; @@ -26,7 +26,7 @@ interface Props { preventNavigation?: boolean; } -export function UserMenuContainer({ preventNavigation = false }: Props) { +export const UserMenuContainer: FC = ({ preventNavigation = false }) => { const location = useLocation(); const history = useHistory(); const { client, logout, authenticated, passwordlessUser } = useClientLegacy(); @@ -83,4 +83,4 @@ export function UserMenuContainer({ preventNavigation = false }: Props) { )} ); -} +}; diff --git a/src/analytics/AnalyticsNotice.tsx b/src/analytics/AnalyticsNotice.tsx index 544de61a..7b1d3fb5 100644 --- a/src/analytics/AnalyticsNotice.tsx +++ b/src/analytics/AnalyticsNotice.tsx @@ -1,3 +1,19 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import { FC } from "react"; import { Trans } from "react-i18next"; diff --git a/src/analytics/PosthogAnalytics.ts b/src/analytics/PosthogAnalytics.ts index 9d4f3f5c..a0316736 100644 --- a/src/analytics/PosthogAnalytics.ts +++ b/src/analytics/PosthogAnalytics.ts @@ -117,7 +117,7 @@ export class PosthogAnalytics { return this.internalInstance; } - constructor(private readonly posthog: PostHog) { + private constructor(private readonly posthog: PostHog) { const posthogConfig: PosthogSettings = { project_api_key: Config.get().posthog?.api_key, api_host: Config.get().posthog?.api_host, @@ -183,7 +183,7 @@ export class PosthogAnalytics { return properties; }; - private registerSuperProperties(properties: Properties) { + private registerSuperProperties(properties: Properties): void { if (this.enabled) { this.posthog.register(properties); } @@ -202,7 +202,7 @@ export class PosthogAnalytics { eventName: string, properties: Properties, options?: CaptureOptions - ) { + ): void { if (!this.enabled) { return; } @@ -213,7 +213,7 @@ export class PosthogAnalytics { return this.enabled; } - setAnonymity(anonymity: Anonymity): void { + private setAnonymity(anonymity: Anonymity): void { // Update this.anonymity. // To update the anonymity typically you want to call updateAnonymityFromSettings // to ensure this value is in step with the user's settings. @@ -236,7 +236,9 @@ export class PosthogAnalytics { .join(""); } - private async identifyUser(analyticsIdGenerator: () => string) { + private async identifyUser( + analyticsIdGenerator: () => string + ): Promise { if (this.anonymity == Anonymity.Pseudonymous && this.enabled) { // Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows // different devices to send the same ID. @@ -271,7 +273,7 @@ export class PosthogAnalytics { } } - async getAnalyticsId() { + private async getAnalyticsId(): Promise { const client: MatrixClient = window.matrixclient; let accountAnalyticsId; if (widget) { @@ -291,7 +293,9 @@ export class PosthogAnalytics { return null; } - async hashedEcAnalyticsId(accountAnalyticsId: string): Promise { + private async hashedEcAnalyticsId( + accountAnalyticsId: string + ): Promise { const client: MatrixClient = window.matrixclient; const posthogIdMaterial = "ec" + accountAnalyticsId + client.getUserId(); const bufferForPosthogId = await crypto.subtle.digest( @@ -304,7 +308,7 @@ export class PosthogAnalytics { .join(""); } - async setAccountAnalyticsId(analyticsID: string) { + private async setAccountAnalyticsId(analyticsID: string): Promise { if (!widget) { const client = window.matrixclient; @@ -335,7 +339,7 @@ export class PosthogAnalytics { this.updateAnonymityAndIdentifyUser(optInAnalytics); } - private updateSuperProperties() { + private updateSuperProperties(): void { // Update super properties in posthog with our platform (app version, platform). // These properties will be subsequently passed in every event. // diff --git a/src/analytics/PosthogEvents.ts b/src/analytics/PosthogEvents.ts index 97e25b6f..89c1ee50 100644 --- a/src/analytics/PosthogEvents.ts +++ b/src/analytics/PosthogEvents.ts @@ -36,18 +36,22 @@ export class CallEndedTracker { maxParticipantsCount: 0, }; - cacheStartCall(time: Date) { + public cacheStartCall(time: Date): void { this.cache.startTime = time; } - cacheParticipantCountChanged(count: number) { + public cacheParticipantCountChanged(count: number): void { this.cache.maxParticipantsCount = Math.max( count, this.cache.maxParticipantsCount ); } - track(callId: string, callParticipantsNow: number, sendInstantly: boolean) { + public track( + callId: string, + callParticipantsNow: number, + sendInstantly: boolean + ): void { PosthogAnalytics.instance.trackEvent( { eventName: "CallEnded", @@ -67,7 +71,7 @@ interface CallStarted extends IPosthogEvent { } export class CallStartedTracker { - track(callId: string) { + public track(callId: string): void { PosthogAnalytics.instance.trackEvent({ eventName: "CallStarted", callId: callId, @@ -86,19 +90,19 @@ export class SignupTracker { signupEnd: new Date(0), }; - cacheSignupStart(time: Date) { + public cacheSignupStart(time: Date): void { this.cache.signupStart = time; } - getSignupEndTime() { + public getSignupEndTime(): Date { return this.cache.signupEnd; } - cacheSignupEnd(time: Date) { + public cacheSignupEnd(time: Date): void { this.cache.signupEnd = time; } - track() { + public track(): void { PosthogAnalytics.instance.trackEvent({ eventName: "Signup", signupDuration: Date.now() - this.cache.signupStart.getTime(), @@ -112,7 +116,7 @@ interface Login extends IPosthogEvent { } export class LoginTracker { - track() { + public track(): void { PosthogAnalytics.instance.trackEvent({ eventName: "Login", }); @@ -127,7 +131,7 @@ interface MuteMicrophone { } export class MuteMicrophoneTracker { - track(targetIsMute: boolean, callId: string) { + public track(targetIsMute: boolean, callId: string): void { PosthogAnalytics.instance.trackEvent({ eventName: "MuteMicrophone", targetMuteState: targetIsMute ? "mute" : "unmute", @@ -143,7 +147,7 @@ interface MuteCamera { } export class MuteCameraTracker { - track(targetIsMute: boolean, callId: string) { + public track(targetIsMute: boolean, callId: string): void { PosthogAnalytics.instance.trackEvent({ eventName: "MuteCamera", targetMuteState: targetIsMute ? "mute" : "unmute", @@ -158,7 +162,7 @@ interface UndecryptableToDeviceEvent { } export class UndecryptableToDeviceEventTracker { - track(callId: string) { + public track(callId: string): void { PosthogAnalytics.instance.trackEvent({ eventName: "UndecryptableToDeviceEvent", callId, @@ -174,7 +178,7 @@ interface QualitySurveyEvent { } export class QualitySurveyEventTracker { - track(callId: string, feedbackText: string, stars: number) { + public track(callId: string, feedbackText: string, stars: number): void { PosthogAnalytics.instance.trackEvent({ eventName: "QualitySurvey", callId, @@ -190,7 +194,7 @@ interface CallDisconnectedEvent { } export class CallDisconnectedEventTracker { - track(reason?: DisconnectReason) { + public track(reason?: DisconnectReason): void { PosthogAnalytics.instance.trackEvent({ eventName: "CallDisconnected", reason, diff --git a/src/analytics/PosthogSpanProcessor.ts b/src/analytics/PosthogSpanProcessor.ts index effa6436..98a6dbbc 100644 --- a/src/analytics/PosthogSpanProcessor.ts +++ b/src/analytics/PosthogSpanProcessor.ts @@ -39,9 +39,9 @@ const maxRejoinMs = 2 * 60 * 1000; // 2 minutes * Span processor that extracts certain metrics from spans to send to PostHog */ export class PosthogSpanProcessor implements SpanProcessor { - async forceFlush(): Promise {} + public async forceFlush(): Promise {} - onStart(span: Span): void { + public onStart(span: Span): void { // Hack: Yield to allow attributes to be set before processing Promise.resolve().then(() => { switch (span.name) { @@ -55,7 +55,7 @@ export class PosthogSpanProcessor implements SpanProcessor { }); } - onEnd(span: ReadableSpan): void { + public onEnd(span: ReadableSpan): void { switch (span.name) { case "matrix.groupCallMembership": this.onGroupCallMembershipEnd(span); @@ -157,7 +157,7 @@ export class PosthogSpanProcessor implements SpanProcessor { /** * Shutdown the processor. */ - shutdown(): Promise { + public shutdown(): Promise { return Promise.resolve(); } } diff --git a/src/analytics/RageshakeSpanProcessor.ts b/src/analytics/RageshakeSpanProcessor.ts index b5b055f2..670a77c3 100644 --- a/src/analytics/RageshakeSpanProcessor.ts +++ b/src/analytics/RageshakeSpanProcessor.ts @@ -1,4 +1,20 @@ -import { Attributes } from "@opentelemetry/api"; +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { AttributeValue, Attributes } from "@opentelemetry/api"; import { hrTimeToMicroseconds } from "@opentelemetry/core"; import { SpanProcessor, @@ -6,7 +22,21 @@ import { Span, } from "@opentelemetry/sdk-trace-base"; -const dumpAttributes = (attr: Attributes) => +const dumpAttributes = ( + attr: Attributes +): { + key: string; + type: + | "string" + | "number" + | "bigint" + | "boolean" + | "symbol" + | "undefined" + | "object" + | "function"; + value: AttributeValue | undefined; +}[] => Object.entries(attr).map(([key, value]) => ({ key, type: typeof value, @@ -20,13 +50,13 @@ const dumpAttributes = (attr: Attributes) => export class RageshakeSpanProcessor implements SpanProcessor { private readonly spans: ReadableSpan[] = []; - async forceFlush(): Promise {} + public async forceFlush(): Promise {} - onStart(span: Span): void { + public onStart(span: Span): void { this.spans.push(span); } - onEnd(): void {} + public onEnd(): void {} /** * Dumps the spans collected so far as Jaeger-compatible JSON. @@ -110,5 +140,5 @@ export class RageshakeSpanProcessor implements SpanProcessor { }); } - async shutdown(): Promise {} + public async shutdown(): Promise {} } diff --git a/src/auth/RegisterPage.tsx b/src/auth/RegisterPage.tsx index c49d9b43..807fdbdd 100644 --- a/src/auth/RegisterPage.tsx +++ b/src/auth/RegisterPage.tsx @@ -68,7 +68,7 @@ export const RegisterPage: FC = () => { if (password !== passwordConfirmation) return; - const submit = async () => { + const submit = async (): Promise => { setRegistering(true); const recaptchaResponse = await execute(); @@ -183,7 +183,7 @@ export const RegisterPage: FC = () => { required name="password" type="password" - onChange={(e: ChangeEvent) => + onChange={(e: ChangeEvent): void => setPassword(e.target.value) } value={password} @@ -197,7 +197,7 @@ export const RegisterPage: FC = () => { required type="password" name="passwordConfirmation" - onChange={(e: ChangeEvent) => + onChange={(e: ChangeEvent): void => setPasswordConfirmation(e.target.value) } value={passwordConfirmation} diff --git a/src/auth/useInteractiveLogin.ts b/src/auth/useInteractiveLogin.ts index 07dec12d..28288fee 100644 --- a/src/auth/useInteractiveLogin.ts +++ b/src/auth/useInteractiveLogin.ts @@ -21,8 +21,12 @@ import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix"; import { initClient } from "../matrix-utils"; import { Session } from "../ClientContext"; -export const useInteractiveLogin = () => - useCallback< +export function useInteractiveLogin(): ( + homeserver: string, + username: string, + password: string +) => Promise<[MatrixClient, Session]> { + return useCallback< ( homeserver: string, username: string, @@ -41,8 +45,8 @@ export const useInteractiveLogin = () => }, password, }), - stateUpdated: (...args) => {}, - requestEmailToken: (...args): Promise<{ sid: string }> => { + stateUpdated: (): void => {}, + requestEmailToken: (): Promise<{ sid: string }> => { return Promise.resolve({ sid: "" }); }, }); @@ -69,6 +73,6 @@ export const useInteractiveLogin = () => false ); /* eslint-enable camelcase */ - return [client, session]; }, []); +} diff --git a/src/auth/useInteractiveRegistration.ts b/src/auth/useInteractiveRegistration.ts index a4863671..70ce0c2f 100644 --- a/src/auth/useInteractiveRegistration.ts +++ b/src/auth/useInteractiveRegistration.ts @@ -72,7 +72,7 @@ export const useInteractiveRegistration = (): { password, auth: auth || undefined, }), - stateUpdated: (nextStage, status) => { + stateUpdated: (nextStage, status): void => { if (status.error) { throw new Error(status.error); } @@ -88,7 +88,7 @@ export const useInteractiveRegistration = (): { }); } }, - requestEmailToken: (...args) => { + requestEmailToken: (): Promise<{ sid: string }> => { return Promise.resolve({ sid: "dummy" }); }, }); diff --git a/src/auth/useRecaptcha.ts b/src/auth/useRecaptcha.ts index 647370da..a77d41ee 100644 --- a/src/auth/useRecaptcha.ts +++ b/src/auth/useRecaptcha.ts @@ -34,7 +34,11 @@ interface RecaptchaPromiseRef { reject: (error: Error) => void; } -export const useRecaptcha = (sitekey?: string) => { +export function useRecaptcha(sitekey?: string): { + execute: () => Promise; + reset: () => void; + recaptchaId: string; +} { const { t } = useTranslation(); const [recaptchaId] = useState(() => randomString(16)); const promiseRef = useRef(); @@ -42,7 +46,7 @@ export const useRecaptcha = (sitekey?: string) => { useEffect(() => { if (!sitekey) return; - const onRecaptchaLoaded = () => { + const onRecaptchaLoaded = (): void => { if (!document.getElementById(recaptchaId)) return; window.grecaptcha.render(recaptchaId, { @@ -90,11 +94,11 @@ export const useRecaptcha = (sitekey?: string) => { }); promiseRef.current = { - resolve: (value) => { + resolve: (value): void => { resolve(value); observer.disconnect(); }, - reject: (error) => { + reject: (error): void => { reject(error); observer.disconnect(); }, @@ -119,4 +123,4 @@ export const useRecaptcha = (sitekey?: string) => { }, []); return { execute, reset, recaptchaId }; -}; +} diff --git a/src/button/Button.tsx b/src/button/Button.tsx index 3d269dc2..d1476dbf 100644 --- a/src/button/Button.tsx +++ b/src/button/Button.tsx @@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -import { forwardRef } from "react"; +import { FC, forwardRef } from "react"; import { PressEvent } from "@react-types/shared"; import classNames from "classnames"; import { useButton } from "@react-aria/button"; @@ -135,14 +135,11 @@ export const Button = forwardRef( } ); -export function MicButton({ - muted, - ...rest -}: { +export const MicButton: FC<{ muted: boolean; // TODO: add all props for ); -} +}; -export function VideoButton({ - muted, - ...rest -}: { +export const VideoButton: FC<{ muted: boolean; // TODO: add all props for ); -} +}; -export function ScreenshareButton({ - enabled, - className, - ...rest -}: { +export const ScreenshareButton: FC<{ enabled: boolean; className?: string; // TODO: add all props for ); -} +}; -export function HangupButton({ - className, - ...rest -}: { +export const HangupButton: FC<{ className?: string; // TODO: add all props for ); -} +}; -export function SettingsButton({ - className, - ...rest -}: { +export const SettingsButton: FC<{ className?: string; // TODO: add all props for ); -} +}; interface AudioButtonProps extends Omit { /** @@ -248,7 +232,7 @@ interface AudioButtonProps extends Omit { volume: number; } -export function AudioButton({ volume, ...rest }: AudioButtonProps) { +export const AudioButton: FC = ({ volume, ...rest }) => { const { t } = useTranslation(); return ( @@ -258,16 +242,16 @@ export function AudioButton({ volume, ...rest }: AudioButtonProps) { ); -} +}; interface FullscreenButtonProps extends Omit { fullscreen?: boolean; } -export function FullscreenButton({ +export const FullscreenButton: FC = ({ fullscreen, ...rest -}: FullscreenButtonProps) { +}) => { const { t } = useTranslation(); const Icon = fullscreen ? FullscreenExit : Fullscreen; const label = fullscreen ? t("Exit full screen") : t("Full screen"); @@ -279,4 +263,4 @@ export function FullscreenButton({ ); -} +}; diff --git a/src/button/CopyButton.tsx b/src/button/CopyButton.tsx index 91c8e750..b44b919d 100644 --- a/src/button/CopyButton.tsx +++ b/src/button/CopyButton.tsx @@ -16,6 +16,7 @@ limitations under the License. import { useTranslation } from "react-i18next"; import useClipboard from "react-use-clipboard"; +import { FC } from "react"; import { ReactComponent as CheckIcon } from "../icons/Check.svg"; import { ReactComponent as CopyIcon } from "../icons/Copy.svg"; @@ -28,14 +29,15 @@ interface Props { variant?: ButtonVariant; copiedMessage?: string; } -export function CopyButton({ + +export const CopyButton: FC = ({ value, children, className, variant, copiedMessage, ...rest -}: Props) { +}) => { const { t } = useTranslation(); const [isCopied, setCopied] = useClipboard(value, { successDuration: 3000 }); @@ -62,4 +64,4 @@ export function CopyButton({ )} ); -} +}; diff --git a/src/button/LinkButton.tsx b/src/button/LinkButton.tsx index 3392935d..2a94b150 100644 --- a/src/button/LinkButton.tsx +++ b/src/button/LinkButton.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { HTMLAttributes } from "react"; +import { FC, HTMLAttributes } from "react"; import { Link } from "react-router-dom"; import classNames from "classnames"; import * as H from "history"; @@ -34,14 +34,14 @@ interface Props extends HTMLAttributes { className?: string; } -export function LinkButton({ +export const LinkButton: FC = ({ children, to, size, variant, className, ...rest -}: Props) { +}) => { return ( ); -} +}; diff --git a/src/home/CallList.tsx b/src/home/CallList.tsx index ac501e41..ef1722ca 100644 --- a/src/home/CallList.tsx +++ b/src/home/CallList.tsx @@ -18,6 +18,7 @@ import { Link } from "react-router-dom"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { Room } from "matrix-js-sdk/src/models/room"; +import { FC } from "react"; import { CopyButton } from "../button"; import { Avatar, Size } from "../Avatar"; @@ -31,7 +32,8 @@ interface CallListProps { rooms: GroupCallRoom[]; client: MatrixClient; } -export function CallList({ rooms, client }: CallListProps) { + +export const CallList: FC = ({ rooms, client }) => { return ( <>
@@ -54,7 +56,7 @@ export function CallList({ rooms, client }: CallListProps) {
); -} +}; interface CallTileProps { name: string; avatarUrl: string; @@ -62,7 +64,8 @@ interface CallTileProps { participants: RoomMember[]; client: MatrixClient; } -function CallTile({ name, avatarUrl, room }: CallTileProps) { + +const CallTile: FC = ({ name, avatarUrl, room }) => { const roomSharedKey = useRoomSharedKey(room.roomId); return ( @@ -92,4 +95,4 @@ function CallTile({ name, avatarUrl, room }: CallTileProps) { />
); -} +}; diff --git a/src/home/HomePage.tsx b/src/home/HomePage.tsx index a78cb8bd..779a7168 100644 --- a/src/home/HomePage.tsx +++ b/src/home/HomePage.tsx @@ -15,6 +15,7 @@ limitations under the License. */ import { useTranslation } from "react-i18next"; +import { FC } from "react"; import { useClientState } from "../ClientContext"; import { ErrorView, LoadingView } from "../FullScreenView"; @@ -22,7 +23,7 @@ import { UnauthenticatedView } from "./UnauthenticatedView"; import { RegisteredView } from "./RegisteredView"; import { usePageTitle } from "../usePageTitle"; -export function HomePage() { +export const HomePage: FC = () => { const { t } = useTranslation(); usePageTitle(t("Home")); @@ -39,4 +40,4 @@ export function HomePage() { ); } -} +}; diff --git a/src/home/JoinExistingCallModal.tsx b/src/home/JoinExistingCallModal.tsx index 35672295..d8ed9fce 100644 --- a/src/home/JoinExistingCallModal.tsx +++ b/src/home/JoinExistingCallModal.tsx @@ -16,6 +16,7 @@ limitations under the License. import { PressEvent } from "@react-types/shared"; import { useTranslation } from "react-i18next"; +import { FC } from "react"; import { Modal } from "../Modal"; import { Button } from "../button"; @@ -28,7 +29,11 @@ interface Props { onJoin: (e: PressEvent) => void; } -export function JoinExistingCallModal({ onJoin, open, onDismiss }: Props) { +export const JoinExistingCallModal: FC = ({ + onJoin, + open, + onDismiss, +}) => { const { t } = useTranslation(); return ( @@ -42,4 +47,4 @@ export function JoinExistingCallModal({ onJoin, open, onDismiss }: Props) { ); -} +}; diff --git a/src/home/RegisteredView.tsx b/src/home/RegisteredView.tsx index 5552eda3..219060e3 100644 --- a/src/home/RegisteredView.tsx +++ b/src/home/RegisteredView.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useState, useCallback, FormEvent, FormEventHandler } from "react"; +import { useState, useCallback, FormEvent, FormEventHandler, FC } from "react"; import { useHistory } from "react-router-dom"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { randomString } from "matrix-js-sdk/src/randomstring"; @@ -48,7 +48,7 @@ interface Props { client: MatrixClient; } -export function RegisteredView({ client }: Props) { +export const RegisteredView: FC = ({ client }) => { const [loading, setLoading] = useState(false); const [error, setError] = useState(); const [optInAnalytics] = useOptInAnalytics(); @@ -72,7 +72,7 @@ export function RegisteredView({ client }: Props) { ? sanitiseRoomNameInput(roomNameData) : ""; - async function submit() { + async function submit(): Promise { setError(undefined); setLoading(true); @@ -176,4 +176,4 @@ export function RegisteredView({ client }: Props) { /> ); -} +}; diff --git a/src/home/UnauthenticatedView.tsx b/src/home/UnauthenticatedView.tsx index 8cd64c14..8f78e26a 100644 --- a/src/home/UnauthenticatedView.tsx +++ b/src/home/UnauthenticatedView.tsx @@ -73,7 +73,7 @@ export const UnauthenticatedView: FC = () => { const roomName = sanitiseRoomNameInput(data.get("callName") as string); const displayName = data.get("displayName") as string; - async function submit() { + async function submit(): Promise { setError(undefined); setLoading(true); const recaptchaResponse = await execute(); diff --git a/src/home/useGroupCallRooms.ts b/src/home/useGroupCallRooms.ts index 51633d8e..6e50dda0 100644 --- a/src/home/useGroupCallRooms.ts +++ b/src/home/useGroupCallRooms.ts @@ -31,7 +31,7 @@ export interface GroupCallRoom { } const tsCache: { [index: string]: number } = {}; -function getLastTs(client: MatrixClient, r: Room) { +function getLastTs(client: MatrixClient, r: Room): number { if (tsCache[r.roomId]) { return tsCache[r.roomId]; } @@ -82,7 +82,7 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] { const [rooms, setRooms] = useState([]); useEffect(() => { - function updateRooms() { + function updateRooms(): void { if (!client.groupCallEventHandler) { return; } diff --git a/src/initializer.tsx b/src/initializer.tsx index 9a8152dd..d2bbb076 100644 --- a/src/initializer.tsx +++ b/src/initializer.tsx @@ -35,11 +35,11 @@ enum LoadState { class DependencyLoadStates { // TODO: decide where olm should be initialized (see TODO comment below) // olm: LoadState = LoadState.None; - config: LoadState = LoadState.None; - sentry: LoadState = LoadState.None; - openTelemetry: LoadState = LoadState.None; + public config: LoadState = LoadState.None; + public sentry: LoadState = LoadState.None; + public openTelemetry: LoadState = LoadState.None; - allDepsAreLoaded() { + public allDepsAreLoaded(): boolean { return !Object.values(this).some((s) => s !== LoadState.Loaded); } } @@ -52,7 +52,7 @@ export class Initializer { return Initializer.internalInstance?.isInitialized; } - public static initBeforeReact() { + public static initBeforeReact(): void { // this maybe also needs to return a promise in the future, // if we have to do async inits before showing the loading screen // but this should be avioded if possible @@ -126,9 +126,9 @@ export class Initializer { return Initializer.internalInstance.initPromise; } - loadStates = new DependencyLoadStates(); + private loadStates = new DependencyLoadStates(); - initStep(resolve: (value: void | PromiseLike) => void) { + private initStep(resolve: (value: void | PromiseLike) => void): void { // TODO: Olm is initialized with the client currently (see `initClient()` and `olm.ts`) // we need to decide if we want to init it here or keep it in initClient // if (this.loadStates.olm === LoadState.None) { diff --git a/src/input/AvatarInputField.tsx b/src/input/AvatarInputField.tsx index 8a069718..4a561f6d 100644 --- a/src/input/AvatarInputField.tsx +++ b/src/input/AvatarInputField.tsx @@ -64,7 +64,7 @@ export const AvatarInputField = forwardRef( useEffect(() => { const currentInput = fileInputRef.current; - const onChange = (e: Event) => { + const onChange = (e: Event): void => { const inputEvent = e as unknown as ChangeEvent; if (inputEvent.target.files && inputEvent.target.files.length > 0) { setObjUrl(URL.createObjectURL(inputEvent.target.files[0])); @@ -76,7 +76,7 @@ export const AvatarInputField = forwardRef( currentInput.addEventListener("change", onChange); - return () => { + return (): void => { currentInput?.removeEventListener("change", onChange); }; }); diff --git a/src/input/StarRatingInput.tsx b/src/input/StarRatingInput.tsx index 3017a117..14000859 100644 --- a/src/input/StarRatingInput.tsx +++ b/src/input/StarRatingInput.tsx @@ -41,8 +41,8 @@ export function StarRatingInput({ return (
setHover(index)} - onMouseLeave={() => setHover(rating)} + onMouseEnter={(): void => setHover(index)} + onMouseLeave={(): void => setHover(rating)} key={index} > { + onChange={(_ev): void => { setRating(index); onChange(index); }} diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index 1605edf3..ddd23b9d 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -52,7 +52,7 @@ export interface MediaDevices { function useObservableState( observable: Observable | undefined, startWith: T -) { +): T { const [state, setState] = useState(startWith); useEffect(() => { // observable state doesn't run in SSR @@ -207,7 +207,8 @@ export const MediaDevicesProvider: FC = ({ children }) => { ); }; -export const useMediaDevices = () => useContext(MediaDevicesContext); +export const useMediaDevices = (): MediaDevices => + useContext(MediaDevicesContext); /** * React hook that requests for the media devices context to be populated with @@ -215,7 +216,10 @@ export const useMediaDevices = () => useContext(MediaDevicesContext); * default because it may involve requesting additional permissions from the * user. */ -export const useMediaDeviceNames = (context: MediaDevices, enabled = true) => +export const useMediaDeviceNames = ( + context: MediaDevices, + enabled = true +): void => useEffect(() => { if (enabled) { context.startUsingDeviceNames(); diff --git a/src/livekit/openIDSFU.ts b/src/livekit/openIDSFU.ts index d10f56fb..3e6779f5 100644 --- a/src/livekit/openIDSFU.ts +++ b/src/livekit/openIDSFU.ts @@ -43,13 +43,13 @@ export type OpenIDClientParts = Pick< export function useOpenIDSFU( client: OpenIDClientParts, rtcSession: MatrixRTCSession -) { +): SFUConfig | undefined { const [sfuConfig, setSFUConfig] = useState(undefined); const activeFocus = useActiveFocus(rtcSession); useEffect(() => { - (async () => { + (async (): Promise => { const sfuConfig = activeFocus ? await getSFUConfigWithOpenID(client, activeFocus) : undefined; diff --git a/src/livekit/options.ts b/src/livekit/options.ts index 7f9695f1..00f19b12 100644 --- a/src/livekit/options.ts +++ b/src/livekit/options.ts @@ -1,3 +1,19 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import { AudioPresets, DefaultReconnectPolicy, diff --git a/src/livekit/useECConnectionState.ts b/src/livekit/useECConnectionState.ts index e9298eb7..8e0d44ac 100644 --- a/src/livekit/useECConnectionState.ts +++ b/src/livekit/useECConnectionState.ts @@ -119,7 +119,7 @@ export function useECConnectionState( `SFU config changed! URL was ${currentSFUConfig.current?.url} now ${sfuConfig?.url}` ); - (async () => { + (async (): Promise => { setSwitchingFocus(true); await livekitRoom?.disconnect(); setIsInDoConnect(true); diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index d6d8394b..361f08c2 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -156,7 +156,7 @@ export function useLiveKit( useEffect(() => { // Sync the requested devices with LiveKit's devices if (room !== undefined && connectionState === ConnectionState.Connected) { - const syncDevice = (kind: MediaDeviceKind, device: MediaDevice) => { + const syncDevice = (kind: MediaDeviceKind, device: MediaDevice): void => { const id = device.selectedId; if (id !== undefined && room.getActiveDevice(kind) !== id) { room diff --git a/src/main.tsx b/src/main.tsx index 0ec2bfae..452935c1 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -26,7 +26,7 @@ import { createBrowserHistory } from "history"; import "./index.css"; import { setLogLevel as setLKLogLevel } from "livekit-client"; -import App from "./App"; +import { App } from "./App"; import { init as initRageshake } from "./settings/rageshake"; import { Initializer } from "./initializer"; diff --git a/src/matrix-utils.ts b/src/matrix-utils.ts index a03b4bc4..2bb1734a 100644 --- a/src/matrix-utils.ts +++ b/src/matrix-utils.ts @@ -40,7 +40,7 @@ export const fallbackICEServerAllowed = import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true"; export class CryptoStoreIntegrityError extends Error { - constructor() { + public constructor() { super("Crypto store data was expected, but none was found"); } } @@ -52,13 +52,13 @@ const SYNC_STORE_NAME = "element-call-sync"; // (It's a good opportunity to make the database names consistent.) const CRYPTO_STORE_NAME = "element-call-crypto"; -function waitForSync(client: MatrixClient) { +function waitForSync(client: MatrixClient): Promise { return new Promise((resolve, reject) => { const onSync = ( state: SyncState, _old: SyncState | null, data?: ISyncStateData - ) => { + ): void => { if (state === "PREPARED") { client.removeListener(ClientEvent.Sync, onSync); resolve(); @@ -108,7 +108,7 @@ export async function initClient( // Chrome supports it. (It bundles them fine in production mode.) workerFactory: import.meta.env.DEV ? undefined - : () => new IndexedDBWorker(), + : (): Worker => new IndexedDBWorker(), }); } else if (localStorage) { baseOpts.store = new MemoryStore({ localStorage }); @@ -307,7 +307,7 @@ export async function createRoom( // Wait for the room to arrive await new Promise((resolve, reject) => { - const onRoom = async (room: Room) => { + const onRoom = async (room: Room): Promise => { if (room.roomId === (await createPromise).room_id) { resolve(); cleanUp(); @@ -318,7 +318,7 @@ export async function createRoom( cleanUp(); }); - const cleanUp = () => { + const cleanUp = (): void => { client.off(ClientEvent.Room, onRoom); }; client.on(ClientEvent.Room, onRoom); diff --git a/src/otel/OTelCall.ts b/src/otel/OTelCall.ts index c7a7fb1a..c050116e 100644 --- a/src/otel/OTelCall.ts +++ b/src/otel/OTelCall.ts @@ -44,7 +44,7 @@ export class OTelCall { OTelCallAbstractMediaStreamSpan >(); - constructor( + public constructor( public userId: string, public deviceId: string, public call: MatrixCall, @@ -60,7 +60,7 @@ export class OTelCall { } } - public dispose() { + public dispose(): void { this.call.peerConn?.removeEventListener( "connectionstatechange", this.onCallConnectionStateChanged diff --git a/src/otel/OTelCallAbstractMediaStreamSpan.ts b/src/otel/OTelCallAbstractMediaStreamSpan.ts index aa77051d..66d9d7af 100644 --- a/src/otel/OTelCallAbstractMediaStreamSpan.ts +++ b/src/otel/OTelCallAbstractMediaStreamSpan.ts @@ -1,3 +1,19 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import opentelemetry, { Span } from "@opentelemetry/api"; import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport"; @@ -14,8 +30,8 @@ export abstract class OTelCallAbstractMediaStreamSpan { public readonly span; public constructor( - readonly oTel: ElementCallOpenTelemetry, - readonly callSpan: Span, + protected readonly oTel: ElementCallOpenTelemetry, + protected readonly callSpan: Span, protected readonly type: string ) { const ctx = opentelemetry.trace.setSpan( @@ -32,7 +48,7 @@ export abstract class OTelCallAbstractMediaStreamSpan { this.span = oTel.tracer.startSpan(this.type, options, ctx); } - protected upsertTrackSpans(tracks: TrackStats[]) { + protected upsertTrackSpans(tracks: TrackStats[]): void { let prvTracks: TrackId[] = [...this.trackSpans.keys()]; tracks.forEach((t) => { if (!this.trackSpans.has(t.id)) { diff --git a/src/otel/OTelCallFeedMediaStreamSpan.ts b/src/otel/OTelCallFeedMediaStreamSpan.ts index 6023fa65..e3f0a2f7 100644 --- a/src/otel/OTelCallFeedMediaStreamSpan.ts +++ b/src/otel/OTelCallFeedMediaStreamSpan.ts @@ -1,3 +1,19 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import { Span } from "@opentelemetry/api"; import { CallFeedStats, @@ -10,9 +26,9 @@ import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSp export class OTelCallFeedMediaStreamSpan extends OTelCallAbstractMediaStreamSpan { private readonly prev: { isAudioMuted: boolean; isVideoMuted: boolean }; - constructor( - readonly oTel: ElementCallOpenTelemetry, - readonly callSpan: Span, + public constructor( + protected readonly oTel: ElementCallOpenTelemetry, + protected readonly callSpan: Span, callFeed: CallFeedStats ) { const postFix = diff --git a/src/otel/OTelCallMediaStreamTrackSpan.ts b/src/otel/OTelCallMediaStreamTrackSpan.ts index 935e22fc..9351f723 100644 --- a/src/otel/OTelCallMediaStreamTrackSpan.ts +++ b/src/otel/OTelCallMediaStreamTrackSpan.ts @@ -1,3 +1,19 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import opentelemetry, { Span } from "@opentelemetry/api"; @@ -8,8 +24,8 @@ export class OTelCallMediaStreamTrackSpan { private prev: TrackStats; public constructor( - readonly oTel: ElementCallOpenTelemetry, - readonly streamSpan: Span, + protected readonly oTel: ElementCallOpenTelemetry, + protected readonly streamSpan: Span, data: TrackStats ) { const ctx = opentelemetry.trace.setSpan( diff --git a/src/otel/OTelCallTransceiverMediaStreamSpan.ts b/src/otel/OTelCallTransceiverMediaStreamSpan.ts index 97006cd8..7aeb3d6e 100644 --- a/src/otel/OTelCallTransceiverMediaStreamSpan.ts +++ b/src/otel/OTelCallTransceiverMediaStreamSpan.ts @@ -1,3 +1,19 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import { Span } from "@opentelemetry/api"; import { TrackStats, @@ -13,9 +29,9 @@ export class OTelCallTransceiverMediaStreamSpan extends OTelCallAbstractMediaStr currentDirection: string; }; - constructor( - readonly oTel: ElementCallOpenTelemetry, - readonly callSpan: Span, + public constructor( + protected readonly oTel: ElementCallOpenTelemetry, + protected readonly callSpan: Span, stats: TransceiverStats ) { super(oTel, callSpan, `matrix.call.transceiver.${stats.mid}`); diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index b60d5aa9..777aa5f6 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -62,7 +62,7 @@ export class OTelGroupCallMembership { }; private readonly speakingSpans = new Map>(); - constructor(private groupCall: GroupCall, client: MatrixClient) { + public constructor(private groupCall: GroupCall, client: MatrixClient) { const clientId = client.getUserId(); if (clientId) { this.myUserId = clientId; @@ -76,14 +76,14 @@ export class OTelGroupCallMembership { this.groupCall.on(GroupCallEvent.CallsChanged, this.onCallsChanged); } - dispose() { + public dispose(): void { this.groupCall.removeListener( GroupCallEvent.CallsChanged, this.onCallsChanged ); } - public onJoinCall() { + public onJoinCall(): void { if (!ElementCallOpenTelemetry.instance) return; if (this.callMembershipSpan !== undefined) { logger.warn("Call membership span is already started"); @@ -114,7 +114,7 @@ export class OTelGroupCallMembership { this.callMembershipSpan?.addEvent("matrix.joinCall"); } - public onLeaveCall() { + public onLeaveCall(): void { if (this.callMembershipSpan === undefined) { logger.warn("Call membership span is already ended"); return; @@ -127,7 +127,7 @@ export class OTelGroupCallMembership { this.groupCallContext = undefined; } - public onUpdateRoomState(event: MatrixEvent) { + public onUpdateRoomState(event: MatrixEvent): void { if ( !event || (!event.getType().startsWith("m.call") && @@ -142,7 +142,7 @@ export class OTelGroupCallMembership { ); } - public onCallsChanged = (calls: CallsByUserAndDevice) => { + public onCallsChanged(calls: CallsByUserAndDevice): void { for (const [userId, userCalls] of calls.entries()) { for (const [deviceId, call] of userCalls.entries()) { if (!this.callsByCallId.has(call.callId)) { @@ -179,9 +179,9 @@ export class OTelGroupCallMembership { this.callsByCallId.delete(callTrackingInfo.call.callId); } } - }; + } - public onCallStateChange(call: MatrixCall, newState: CallState) { + public onCallStateChange(call: MatrixCall, newState: CallState): void { const callTrackingInfo = this.callsByCallId.get(call.callId); if (!callTrackingInfo) { logger.error(`Got call state change for unknown call ID ${call.callId}`); @@ -193,7 +193,7 @@ export class OTelGroupCallMembership { }); } - public onSendEvent(call: MatrixCall, event: VoipEvent) { + public onSendEvent(call: MatrixCall, event: VoipEvent): void { const eventType = event.eventType as string; if ( !eventType.startsWith("m.call") && @@ -220,7 +220,7 @@ export class OTelGroupCallMembership { } } - public onReceivedVoipEvent(event: MatrixEvent) { + public onReceivedVoipEvent(event: MatrixEvent): void { // These come straight from CallEventHandler so don't have // a call already associated (in principle we could receive // events for calls we don't know about). @@ -251,37 +251,41 @@ export class OTelGroupCallMembership { }); } - public onToggleMicrophoneMuted(newValue: boolean) { + public onToggleMicrophoneMuted(newValue: boolean): void { this.callMembershipSpan?.addEvent("matrix.toggleMicMuted", { "matrix.microphone.muted": newValue, }); } - public onSetMicrophoneMuted(setMuted: boolean) { + public onSetMicrophoneMuted(setMuted: boolean): void { this.callMembershipSpan?.addEvent("matrix.setMicMuted", { "matrix.microphone.muted": setMuted, }); } - public onToggleLocalVideoMuted(newValue: boolean) { + public onToggleLocalVideoMuted(newValue: boolean): void { this.callMembershipSpan?.addEvent("matrix.toggleVidMuted", { "matrix.video.muted": newValue, }); } - public onSetLocalVideoMuted(setMuted: boolean) { + public onSetLocalVideoMuted(setMuted: boolean): void { this.callMembershipSpan?.addEvent("matrix.setVidMuted", { "matrix.video.muted": setMuted, }); } - public onToggleScreensharing(newValue: boolean) { + public onToggleScreensharing(newValue: boolean): void { this.callMembershipSpan?.addEvent("matrix.setVidMuted", { "matrix.screensharing.enabled": newValue, }); } - public onSpeaking(member: RoomMember, deviceId: string, speaking: boolean) { + public onSpeaking( + member: RoomMember, + deviceId: string, + speaking: boolean + ): void { if (speaking) { // Ensure that there's an audio activity span for this speaker let deviceMap = this.speakingSpans.get(member); @@ -311,7 +315,7 @@ export class OTelGroupCallMembership { } } - public onCallError(error: CallError, call: MatrixCall) { + public onCallError(error: CallError, call: MatrixCall): void { const callTrackingInfo = this.callsByCallId.get(call.callId); if (!callTrackingInfo) { logger.error(`Got error for unknown call ID ${call.callId}`); @@ -321,17 +325,19 @@ export class OTelGroupCallMembership { callTrackingInfo.span.recordException(error); } - public onGroupCallError(error: GroupCallError) { + public onGroupCallError(error: GroupCallError): void { this.callMembershipSpan?.recordException(error); } - public onUndecryptableToDevice(event: MatrixEvent) { + public onUndecryptableToDevice(event: MatrixEvent): void { this.callMembershipSpan?.addEvent("matrix.toDevice.undecryptable", { "sender.userId": event.getSender(), }); } - public onCallFeedStatsReport(report: GroupCallStatsReport) { + public onCallFeedStatsReport( + report: GroupCallStatsReport + ): void { if (!ElementCallOpenTelemetry.instance) return; let call: OTelCall | undefined; const callId = report.report?.callId; @@ -362,7 +368,7 @@ export class OTelGroupCallMembership { public onConnectionStatsReport( statsReport: GroupCallStatsReport - ) { + ): void { this.buildCallStatsSpan( OTelStatsReportType.ConnectionReport, statsReport.report @@ -371,7 +377,7 @@ export class OTelGroupCallMembership { public onByteSentStatsReport( statsReport: GroupCallStatsReport - ) { + ): void { this.buildCallStatsSpan( OTelStatsReportType.ByteSentReport, statsReport.report @@ -431,7 +437,7 @@ export class OTelGroupCallMembership { public onSummaryStatsReport( statsReport: GroupCallStatsReport - ) { + ): void { if (!ElementCallOpenTelemetry.instance) return; const type = OTelStatsReportType.SummaryReport; diff --git a/src/otel/ObjectFlattener.ts b/src/otel/ObjectFlattener.ts index 652f9056..715517d2 100644 --- a/src/otel/ObjectFlattener.ts +++ b/src/otel/ObjectFlattener.ts @@ -45,9 +45,9 @@ export class ObjectFlattener { return flatObject; } - static flattenSummaryStatsReportObject( + public static flattenSummaryStatsReportObject( statsReport: GroupCallStatsReport - ) { + ): Attributes { const flatObject = {}; ObjectFlattener.flattenObjectRecursive( statsReport.report, diff --git a/src/otel/otel.ts b/src/otel/otel.ts index d4629591..c6a1b971 100644 --- a/src/otel/otel.ts +++ b/src/otel/otel.ts @@ -36,7 +36,7 @@ export class ElementCallOpenTelemetry { private otlpExporter?: OTLPTraceExporter; public readonly rageshakeProcessor?: RageshakeSpanProcessor; - static globalInit(): void { + public static globalInit(): void { const config = Config.get(); // we always enable opentelemetry in general. We only enable the OTLP // collector if a URL is defined (and in future if another setting is defined) @@ -55,11 +55,11 @@ export class ElementCallOpenTelemetry { } } - static get instance(): ElementCallOpenTelemetry { + public static get instance(): ElementCallOpenTelemetry { return sharedInstance; } - constructor( + private constructor( collectorUrl: string | undefined, rageshakeUrl: string | undefined ) { diff --git a/src/profile/useProfile.ts b/src/profile/useProfile.ts index 2281bb1e..571c3835 100644 --- a/src/profile/useProfile.ts +++ b/src/profile/useProfile.ts @@ -38,7 +38,11 @@ type ProfileSaveCallback = ({ removeAvatar: boolean; }) => Promise; -export function useProfile(client: MatrixClient | undefined) { +interface UseProfile extends ProfileLoadState { + saveProfile: ProfileSaveCallback; +} + +export function useProfile(client: MatrixClient | undefined): UseProfile { const [{ success, loading, displayName, avatarUrl, error }, setState] = useState(() => { let user: User | undefined = undefined; @@ -59,7 +63,7 @@ export function useProfile(client: MatrixClient | undefined) { const onChangeUser = ( _event: MatrixEvent | undefined, { displayName, avatarUrl }: User - ) => { + ): void => { setState({ success: false, loading: false, diff --git a/src/room/CallEndedView.tsx b/src/room/CallEndedView.tsx index 6050b9e9..72567618 100644 --- a/src/room/CallEndedView.tsx +++ b/src/room/CallEndedView.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { FC, FormEventHandler, useCallback, useState } from "react"; +import { FC, FormEventHandler, ReactNode, useCallback, useState } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { Trans, useTranslation } from "react-i18next"; import { useHistory } from "react-router-dom"; @@ -148,7 +148,7 @@ export const CallEndedView: FC = ({
); - const renderBody = () => { + const renderBody = (): ReactNode => { if (leaveError) { return ( <> diff --git a/src/room/GroupCallInspector.tsx b/src/room/GroupCallInspector.tsx index d3df2c5b..68f27add 100644 --- a/src/room/GroupCallInspector.tsx +++ b/src/room/GroupCallInspector.tsx @@ -28,7 +28,8 @@ import { useContext, Dispatch, SetStateAction, - ReactNode, + FC, + PropsWithChildren, } from "react"; import ReactJson, { CollapsedFieldProps } from "react-json-view"; import mermaid from "mermaid"; @@ -72,11 +73,11 @@ const defaultCollapsedFields = [ "content", ]; -function shouldCollapse({ name }: CollapsedFieldProps) { +function shouldCollapse({ name }: CollapsedFieldProps): boolean { return name ? defaultCollapsedFields.includes(name) : false; } -function getUserName(userId: string) { +function getUserName(userId: string): string { const match = userId.match(/@([^:]+):/); return match && match.length > 0 @@ -84,7 +85,7 @@ function getUserName(userId: string) { : userId.replace(/\W/g, ""); } -function formatContent(type: string, content: CallEventContent) { +function formatContent(type: string, content: CallEventContent): string { if (type === "m.call.hangup") { return `callId: ${content.call_id.slice(-4)} reason: ${ content.reason @@ -123,7 +124,7 @@ const dateFormatter = new Intl.DateTimeFormat([], { fractionalSecondDigits: 3, }); -function formatTimestamp(timestamp: number | Date) { +function formatTimestamp(timestamp: number | Date): string { return dateFormatter.format(timestamp); } @@ -145,11 +146,9 @@ export const InspectorContext = [InspectorContextState, Dispatch>] >(undefined); -export function InspectorContextProvider({ +export const InspectorContextProvider: FC> = ({ children, -}: { - children: ReactNode; -}) { +}) => { // We take the tuple of [currentState, setter] and stick // it straight into the context for other things to call // the setState method... this feels like a fairly severe @@ -161,7 +160,7 @@ export function InspectorContextProvider({ {children} ); -} +}; type CallEventContent = { ["m.calls"]: { @@ -192,13 +191,13 @@ interface SequenceDiagramViewerProps { events: SequenceDiagramMatrixEvent[]; } -export function SequenceDiagramViewer({ +export const SequenceDiagramViewer: FC = ({ localUserId, remoteUserIds, selectedUserId, onSelectUserId, events, -}: SequenceDiagramViewerProps) { +}) => { const mermaidElRef = useRef(null); useEffect(() => { @@ -232,7 +231,7 @@ export function SequenceDiagramViewer({ className={styles.selectInput} label="Remote User" selectedKey={selectedUserId} - onSelectionChange={(key) => onSelectUserId(key.toString())} + onSelectionChange={(key): void => onSelectUserId(key.toString())} > {remoteUserIds.map((userId) => ( {userId} @@ -243,7 +242,7 @@ export function SequenceDiagramViewer({ ); -} +}; function reducer( state: InspectorContextState, @@ -254,7 +253,7 @@ function reducer( callStateEvent?: MatrixEvent; memberStateEvents?: MatrixEvent[]; } -) { +): InspectorContextState { switch (action.type) { case RoomStateEvent.Events: { const { event, callStateEvent, memberStateEvents } = action; @@ -380,7 +379,7 @@ function useGroupCallState( }); useEffect(() => { - function onUpdateRoomState(event?: MatrixEvent) { + function onUpdateRoomState(event?: MatrixEvent): void { const callStateEvent = groupCall.room.currentState.getStateEvents( "org.matrix.msc3401.call", groupCall.groupCallId @@ -400,13 +399,13 @@ function useGroupCallState( otelGroupCallMembership?.onUpdateRoomState(event); } - function onReceivedVoipEvent(event: MatrixEvent) { + function onReceivedVoipEvent(event: MatrixEvent): void { dispatch({ type: ClientEvent.ReceivedVoipEvent, event }); otelGroupCallMembership?.onReceivedVoipEvent(event); } - function onSendVoipEvent(event: VoipEvent, call: MatrixCall) { + function onSendVoipEvent(event: VoipEvent, call: MatrixCall): void { dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event }); otelGroupCallMembership?.onSendEvent(call, event); @@ -416,19 +415,19 @@ function useGroupCallState( newState: CallState, _: CallState, call: MatrixCall - ) { + ): void { otelGroupCallMembership?.onCallStateChange(call, newState); } - function onCallError(error: CallError, call: MatrixCall) { + function onCallError(error: CallError, call: MatrixCall): void { otelGroupCallMembership.onCallError(error, call); } - function onGroupCallError(error: GroupCallError) { + function onGroupCallError(error: GroupCallError): void { otelGroupCallMembership.onGroupCallError(error); } - function onUndecryptableToDevice(event: MatrixEvent) { + function onUndecryptableToDevice(event: MatrixEvent): void { dispatch({ type: ClientEvent.ReceivedVoipEvent, event }); Sentry.captureMessage("Undecryptable to-device Event"); @@ -478,12 +477,12 @@ interface GroupCallInspectorProps { show: boolean; } -export function GroupCallInspector({ +export const GroupCallInspector: FC = ({ client, groupCall, otelGroupCallMembership, show, -}: GroupCallInspectorProps) { +}) => { const [currentTab, setCurrentTab] = useState("sequence-diagrams"); const [selectedUserId, setSelectedUserId] = useState(); const state = useGroupCallState(client, groupCall, otelGroupCallMembership); @@ -506,10 +505,12 @@ export function GroupCallInspector({ className={styles.inspector} >
- - +
{currentTab === "sequence-diagrams" && state.localUserId && @@ -539,4 +540,4 @@ export function GroupCallInspector({ )} ); -} +}; diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index ab96926a..2c409e5f 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useHistory } from "react-router-dom"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { Room, isE2EESupported } from "livekit-client"; @@ -64,14 +64,14 @@ interface Props { rtcSession: MatrixRTCSession; } -export function GroupCallView({ +export const GroupCallView: FC = ({ client, isPasswordlessUser, confineToRoom, preload, hideHeader, rtcSession, -}: Props) { +}) => { const memberships = useMatrixRTCSessionMemberships(rtcSession); const isJoined = useMatrixRTCSessionJoinState(rtcSession); @@ -135,7 +135,9 @@ export function GroupCallView({ useEffect(() => { if (widget && preload) { // In preload mode, wait for a join action before entering - const onJoin = async (ev: CustomEvent) => { + const onJoin = async ( + ev: CustomEvent + ): Promise => { // XXX: I think this is broken currently - LiveKit *won't* request // permissions and give you device names unless you specify a kind, but // here we want all kinds of devices. This needs a fix in livekit-client @@ -247,9 +249,11 @@ export function GroupCallView({ useEffect(() => { if (widget && isJoined) { - const onHangup = async (ev: CustomEvent) => { + const onHangup = async ( + ev: CustomEvent + ): Promise => { leaveRTCSession(rtcSession); - await widget!.api.transport.reply(ev.detail, {}); + widget!.api.transport.reply(ev.detail, {}); widget!.api.setAlwaysOnScreen(false); }; widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup); @@ -388,7 +392,7 @@ export function GroupCallView({ client={client} matrixInfo={matrixInfo} muteStates={muteStates} - onEnter={() => enterRTCSession(rtcSession)} + onEnter={(): void => enterRTCSession(rtcSession)} confineToRoom={confineToRoom} hideHeader={hideHeader} participatingMembers={participatingMembers} @@ -397,4 +401,4 @@ export function GroupCallView({ ); } -} +}; diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 707eb5d8..979aa2e6 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -27,7 +27,16 @@ import { ConnectionState, Room, Track } from "livekit-client"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { Room as MatrixRoom } from "matrix-js-sdk/src/models/room"; -import { Ref, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { + FC, + ReactNode, + Ref, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { useTranslation } from "react-i18next"; import useMeasure from "react-use-measure"; import { logger } from "matrix-js-sdk/src/logger"; @@ -91,7 +100,7 @@ export interface ActiveCallProps e2eeConfig?: E2EEConfig; } -export function ActiveCall(props: ActiveCallProps) { +export const ActiveCall: FC = (props) => { const sfuConfig = useOpenIDSFU(props.client, props.rtcSession); const { livekitRoom, connState } = useLiveKit( props.muteStates, @@ -112,7 +121,7 @@ export function ActiveCall(props: ActiveCallProps) { ); -} +}; export interface InCallViewProps { client: MatrixClient; @@ -128,7 +137,7 @@ export interface InCallViewProps { onShareClick: (() => void) | null; } -export function InCallView({ +export const InCallView: FC = ({ client, matrixInfo, rtcSession, @@ -140,7 +149,7 @@ export function InCallView({ otelGroupCallMembership, connState, onShareClick, -}: InCallViewProps) { +}) => { const { t } = useTranslation(); usePreventScroll(); useWakeLock(); @@ -211,13 +220,13 @@ export function InCallView({ useEffect(() => { if (widget) { - const onTileLayout = async (ev: CustomEvent) => { + const onTileLayout = (ev: CustomEvent): void => { setLayout("grid"); - await widget!.api.transport.reply(ev.detail, {}); + widget!.api.transport.reply(ev.detail, {}); }; - const onSpotlightLayout = async (ev: CustomEvent) => { + const onSpotlightLayout = (ev: CustomEvent): void => { setLayout("spotlight"); - await widget!.api.transport.reply(ev.detail, {}); + widget!.api.transport.reply(ev.detail, {}); }; widget.lazyActions.on(ElementWidgetActions.TileLayout, onTileLayout); @@ -296,7 +305,7 @@ export function InCallView({ disableAnimations={prefersReducedMotion || isSafari} layoutStates={layoutStates} > - {(props) => ( + {(props): ReactNode => ( ); -} +}; function findMatrixMember( room: MatrixRoom, diff --git a/src/room/RageshakeRequestModal.tsx b/src/room/RageshakeRequestModal.tsx index ed9acbcb..74debd34 100644 --- a/src/room/RageshakeRequestModal.tsx +++ b/src/room/RageshakeRequestModal.tsx @@ -17,7 +17,7 @@ limitations under the License. import { FC, useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { Modal, ModalProps } from "../Modal"; +import { Modal, Props as ModalProps } from "../Modal"; import { Button } from "../button"; import { FieldRow, ErrorMessage } from "../input/Input"; import { useSubmitRageshake } from "../settings/submit-rageshake"; @@ -52,8 +52,8 @@ export const RageshakeRequestModal: FC = ({