diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2206a91decf..43318c273d6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,5 +14,5 @@ ] } }, - "postCreateCommand": "bash -i -c 'nvm install 16.19.0 && nvm use 16.19.0' && npm install @microsoft/rush -g && rush update" + "postCreateCommand": "bash -i -c 'nvm install 20.10.0 && nvm use 20.10.0' && npm install @microsoft/rush -g && rush update" } diff --git a/change-beta/@azure-communication-react-7665db60-acd2-46d1-9f24-fc3d3853e856.json b/change-beta/@azure-communication-react-7665db60-acd2-46d1-9f24-fc3d3853e856.json new file mode 100644 index 00000000000..a9bbfcca78b --- /dev/null +++ b/change-beta/@azure-communication-react-7665db60-acd2-46d1-9f24-fc3d3853e856.json @@ -0,0 +1,9 @@ +{ + "type": "patch", + "area": "fix", + "workstream": "TypeScript noImplicitAny fixes", + "comment": "Fix up noImplicitAny for Chat Composites", + "packageName": "@azure/communication-react", + "email": "3941071+emlynmac@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-communication-react-7665db60-acd2-46d1-9f24-fc3d3853e856.json b/change/@azure-communication-react-7665db60-acd2-46d1-9f24-fc3d3853e856.json new file mode 100644 index 00000000000..a9bbfcca78b --- /dev/null +++ b/change/@azure-communication-react-7665db60-acd2-46d1-9f24-fc3d3853e856.json @@ -0,0 +1,9 @@ +{ + "type": "patch", + "area": "fix", + "workstream": "TypeScript noImplicitAny fixes", + "comment": "Fix up noImplicitAny for Chat Composites", + "packageName": "@azure/communication-react", + "email": "3941071+emlynmac@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/chat-stateful-client/src/index.ts b/packages/chat-stateful-client/src/index.ts index 95da9e4b97c..ac1c69010fc 100644 --- a/packages/chat-stateful-client/src/index.ts +++ b/packages/chat-stateful-client/src/index.ts @@ -9,9 +9,9 @@ export { export type { StatefulChatClient, StatefulChatClientArgs, StatefulChatClientOptions } from './StatefulChatClient'; export type { ChatMessageWithStatus } from './types/ChatMessageWithStatus'; +export { ChatError } from './ChatClientState'; export type { ChatClientState, - ChatError, ChatErrors, ChatThreadClientState, ChatThreadProperties, diff --git a/packages/react-composites/src/composites/CallWithChatComposite/ChatButton/NotificationIcon.tsx b/packages/react-composites/src/composites/CallWithChatComposite/ChatButton/NotificationIcon.tsx index 23481435605..3218b1a2126 100644 --- a/packages/react-composites/src/composites/CallWithChatComposite/ChatButton/NotificationIcon.tsx +++ b/packages/react-composites/src/composites/CallWithChatComposite/ChatButton/NotificationIcon.tsx @@ -17,13 +17,13 @@ export type NotificationIconProps = { export const NotificationIcon = (props: NotificationIconProps): JSX.Element => { const { chatMessagesCount, label } = props; const theme = useTheme(); - const renderNumber = (numberOfMessages): JSX.Element => { + const renderNumber = (numberOfMessages: number): JSX.Element => { if (numberOfMessages < 1) { return <>; } else { const textNumberOfMessages = numberOfMessages < 9 ? numberOfMessages : '9+'; return ( - + {textNumberOfMessages} ); diff --git a/packages/react-composites/src/composites/CallWithChatComposite/ChatButton/useUnreadMessagesTracker.ts b/packages/react-composites/src/composites/CallWithChatComposite/ChatButton/useUnreadMessagesTracker.ts index 37edcaacfe0..30d878d801d 100644 --- a/packages/react-composites/src/composites/CallWithChatComposite/ChatButton/useUnreadMessagesTracker.ts +++ b/packages/react-composites/src/composites/CallWithChatComposite/ChatButton/useUnreadMessagesTracker.ts @@ -58,5 +58,5 @@ export const useUnreadMessagesTracker = (chatAdapter: ChatAdapter, isChatPaneVis * Helper function to determine if the message in the event is a valid one from a user. * Display name is used since system messages will not have one. */ -const validNewChatMessage = (message): boolean => +const validNewChatMessage = (message: ChatMessage): boolean => !!message.senderDisplayName && (message.type === 'text' || message.type === 'html'); diff --git a/packages/react-composites/src/composites/CallWithChatComposite/adapter/TestUtils.ts b/packages/react-composites/src/composites/CallWithChatComposite/adapter/TestUtils.ts index eb4e89f0cce..a423b490c78 100644 --- a/packages/react-composites/src/composites/CallWithChatComposite/adapter/TestUtils.ts +++ b/packages/react-composites/src/composites/CallWithChatComposite/adapter/TestUtils.ts @@ -118,7 +118,7 @@ export type Mutable = { interface MockDeviceManager extends Mutable { // eslint-disable-next-line @typescript-eslint/no-explicit-any - emit(event: any, data?: any); + emit(event: any, data?: any): any; } const createMockDeviceManager = (): MockDeviceManager => { diff --git a/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx b/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx index 15886b7f55d..18ce62f08e6 100644 --- a/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx +++ b/packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx @@ -21,7 +21,7 @@ import { import { ChatMessage } from '@internal/react-components'; import React, { useCallback, useEffect, useMemo } from 'react'; import { useState } from 'react'; -import { AvatarPersona, AvatarPersonaDataCallback } from '../common/AvatarPersona'; +import { AvatarPersona, AvatarPersonaDataCallback, AvatarPersonaProps } from '../common/AvatarPersona'; import { useAdapter } from './adapter/ChatAdapterProvider'; import { ChatCompositeOptions } from './ChatComposite'; import { ChatHeader, getHeaderProps } from './ChatHeader'; @@ -151,7 +151,7 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => { const errorBarProps = usePropsFor(ErrorBar); const onRenderAvatarCallback = useCallback( - (userId, defaultOptions) => { + (userId?: string, defaultOptions?: AvatarPersonaProps) => { return ( { /* @conditional-compile-remove(file-sharing) */ const onRenderFileDownloads = useCallback( - (userId, message: ChatMessage) => ( + (userId: string, message: ChatMessage) => ( <_FileDownloadCards userId={userId} fileMetadata={message.files || []} diff --git a/packages/react-composites/src/composites/ChatComposite/adapter/AzureCommunicationChatAdapter.ts b/packages/react-composites/src/composites/ChatComposite/adapter/AzureCommunicationChatAdapter.ts index 2c10ade0d2b..d75820a2f43 100644 --- a/packages/react-composites/src/composites/ChatComposite/adapter/AzureCommunicationChatAdapter.ts +++ b/packages/react-composites/src/composites/ChatComposite/adapter/AzureCommunicationChatAdapter.ts @@ -332,17 +332,11 @@ export class AzureCommunicationChatAdapter implements ChatAdapter { async downloadAttachments(options: { attachmentUrls: Record }): Promise { return this.asyncTeeErrorToEventEmitter(async () => { if (this.credential === undefined) { - const e = new Error(); - e['target'] = 'ChatThreadClient.getMessage'; - e['innerError'] = new Error('AccessToken is null'); - throw e; + throw new ChatError('ChatThreadClient.getMessage', new Error('AccessToken is null')); } const accessToken = await this.credential.getToken(); if (!accessToken) { - const e = new Error(); - e['target'] = 'ChatThreadClient.getMessage'; - e['innerError'] = new Error('AccessToken is null'); - throw e; + throw new ChatError('ChatThreadClient.getMessage', new Error('AccessToken is null')); } return this.downloadAuthenticatedFile(accessToken.token, options); @@ -359,10 +353,7 @@ export class AzureCommunicationChatAdapter implements ChatAdapter { try { return await fetch(url, { headers }); } catch (err) { - const e = new Error(); - e['target'] = 'ChatThreadClient.getMessage'; - e['innerError'] = err; - throw e; + throw new ChatError('ChatThreadClient.getMessage', err as Error); } } @@ -515,13 +506,13 @@ const convertEventToChatMessage = ( const isChatMessageEditedEvent = ( event: ChatMessageReceivedEvent | ChatMessageEditedEvent | ChatMessageDeletedEvent ): event is ChatMessageEditedEvent => { - return event['editedOn'] !== undefined; + return 'editedOn' in event; }; const isChatMessageDeletedEvent = ( event: ChatMessageReceivedEvent | ChatMessageEditedEvent | ChatMessageDeletedEvent ): event is ChatMessageDeletedEvent => { - return event['deletedOn'] !== undefined; + return 'deletedOn' in event; }; // only text/html message type will be received from event @@ -734,5 +725,5 @@ export async function createAzureCommunicationChatAdapterFromClient( } const isChatError = (e: Error): e is ChatError => { - return e['target'] !== undefined && e['innerError'] !== undefined; + return 'target' in e && e['target'] !== undefined && 'innerError' in e && e['innerError'] !== undefined; }; diff --git a/packages/react-composites/src/composites/ChatComposite/adapter/StubChatClient.ts b/packages/react-composites/src/composites/ChatComposite/adapter/StubChatClient.ts index a8af74d5e92..540ee22472a 100644 --- a/packages/react-composites/src/composites/ChatComposite/adapter/StubChatClient.ts +++ b/packages/react-composites/src/composites/ChatComposite/adapter/StubChatClient.ts @@ -21,14 +21,14 @@ type PublicInterface = { [K in keyof T]: T[K] }; * A public interface compatible stub for ChatClient. */ export class StubChatClient implements PublicInterface { - private threadClient; + private threadClient: ChatThreadClient | undefined; /** * @param threadClient If set, an implementation of ChatThreadClient interface that is returned for *all* calls to * {@getChatThreadClient()}. */ constructor(threadClient?: PublicInterface) { - this.threadClient = threadClient; + this.threadClient = threadClient as ChatThreadClient; } getChatThreadClient(): ChatThreadClient { diff --git a/packages/react-composites/src/composites/common/BaseComposite.tsx b/packages/react-composites/src/composites/common/BaseComposite.tsx index e91f01d2479..ffc3e4b9f0a 100644 --- a/packages/react-composites/src/composites/common/BaseComposite.tsx +++ b/packages/react-composites/src/composites/common/BaseComposite.tsx @@ -93,7 +93,7 @@ export const BaseProvider = ( /** * Before registering fluent icons, we should check DEFAULT_COMPOSITE_ICONS and strip out the key value pairs where value is undefined */ - const iconsToRegister = {}; + const iconsToRegister: { [key: string]: JSX.Element } = {}; Object.entries(DEFAULT_COMPOSITE_ICONS).forEach(([key, value]) => { if (value) { iconsToRegister[key] = value; diff --git a/packages/react-composites/src/mocks/MockChatClient.ts b/packages/react-composites/src/mocks/MockChatClient.ts index e516e2f0da8..f7f9fc10e09 100644 --- a/packages/react-composites/src/mocks/MockChatClient.ts +++ b/packages/react-composites/src/mocks/MockChatClient.ts @@ -21,7 +21,7 @@ export const createStatefulChatClientMock = (threadClient: PublicInterface): ChatClient { - const mockEventHandlersRef = { value: {} }; + const mockEventHandlersRef: { value: { [key: string]: ((e: Event) => void) | undefined | null } } = { value: {} }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const mockChatClient: ChatClient = {} as any; diff --git a/packages/react-composites/src/mocks/useFakeChatAdapters.ts b/packages/react-composites/src/mocks/useFakeChatAdapters.ts index 228404e80f1..7756c6efb9d 100644 --- a/packages/react-composites/src/mocks/useFakeChatAdapters.ts +++ b/packages/react-composites/src/mocks/useFakeChatAdapters.ts @@ -8,7 +8,7 @@ import type { ChatAdapter } from '../composites/ChatComposite/adapter/ChatAdapte import { FakeChatClient, IChatClient, Model } from '@internal/fake-backends'; import { useEffect, useState } from 'react'; -import { ChatClient, ChatParticipant, ChatThreadClient } from '@azure/communication-chat'; +import { ChatClient, ChatParticipant, ChatThreadClient, CreateChatThreadResult } from '@azure/communication-chat'; import { CommunicationTokenCredential, CommunicationUserIdentifier, @@ -89,7 +89,7 @@ export function _useFakeChatAdapters(args: _FakeChatAdapterArgs): _FakeChatAdapt const initializeAdapters = async ( participants: ChatParticipant[], chatClientModel: Model, - thread + thread: CreateChatThreadResult ): Promise => { const remoteAdapters: ChatAdapter[] = []; for (const participant of participants) { @@ -170,11 +170,14 @@ const registerChatThreadClientMethodErrors = ( chatThreadClientMethodErrors?: Partial> ): void => { for (const k in chatThreadClientMethodErrors) { - chatThreadClient[k] = () => { - throw new RestError(chatThreadClientMethodErrors[k].message ?? '', { - code: chatThreadClientMethodErrors[k].code, - statusCode: chatThreadClientMethodErrors[k].statusCode - }); - }; + if (k in chatThreadClient) { + const key = k as keyof Omit; + chatThreadClient[key] = () => { + throw new RestError(chatThreadClientMethodErrors[key]?.message ?? '', { + code: chatThreadClientMethodErrors[key]?.code, + statusCode: chatThreadClientMethodErrors[key]?.statusCode + }); + }; + } } };