diff --git a/change-beta/@azure-communication-react-0f94f856-0d7e-4f92-9e16-2c58149052ac.json b/change-beta/@azure-communication-react-0f94f856-0d7e-4f92-9e16-2c58149052ac.json new file mode 100644 index 00000000000..8f869a12865 --- /dev/null +++ b/change-beta/@azure-communication-react-0f94f856-0d7e-4f92-9e16-2c58149052ac.json @@ -0,0 +1,9 @@ +{ + "type": "patch", + "area": "fix", + "workstream": "", + "comment": "Add option to callAdapter and callWithChatAdapter dispose methods to not dispose of the callAgent", + "packageName": "@azure/communication-react", + "email": "2684369+JamesBurnside@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change-beta/@azure-communication-react-7c38ebea-7a13-4fee-8d1d-0cfca4f07642.json b/change-beta/@azure-communication-react-7c38ebea-7a13-4fee-8d1d-0cfca4f07642.json new file mode 100644 index 00000000000..2bb31d5e26b --- /dev/null +++ b/change-beta/@azure-communication-react-7c38ebea-7a13-4fee-8d1d-0cfca4f07642.json @@ -0,0 +1,9 @@ +{ + "type": "minor", + "area": "fix", + "workstream": "", + "comment": "Update callAdapter.dispose and callWithChatAdapter.dispose to be async to match underlying behavior", + "packageName": "@azure/communication-react", + "email": "2684369+JamesBurnside@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-communication-react-0f94f856-0d7e-4f92-9e16-2c58149052ac.json b/change/@azure-communication-react-0f94f856-0d7e-4f92-9e16-2c58149052ac.json new file mode 100644 index 00000000000..8f869a12865 --- /dev/null +++ b/change/@azure-communication-react-0f94f856-0d7e-4f92-9e16-2c58149052ac.json @@ -0,0 +1,9 @@ +{ + "type": "patch", + "area": "fix", + "workstream": "", + "comment": "Add option to callAdapter and callWithChatAdapter dispose methods to not dispose of the callAgent", + "packageName": "@azure/communication-react", + "email": "2684369+JamesBurnside@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-communication-react-7c38ebea-7a13-4fee-8d1d-0cfca4f07642.json b/change/@azure-communication-react-7c38ebea-7a13-4fee-8d1d-0cfca4f07642.json new file mode 100644 index 00000000000..2bb31d5e26b --- /dev/null +++ b/change/@azure-communication-react-7c38ebea-7a13-4fee-8d1d-0cfca4f07642.json @@ -0,0 +1,9 @@ +{ + "type": "minor", + "area": "fix", + "workstream": "", + "comment": "Update callAdapter.dispose and callWithChatAdapter.dispose to be async to match underlying behavior", + "packageName": "@azure/communication-react", + "email": "2684369+JamesBurnside@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/communication-react/review/beta/communication-react.api.md b/packages/communication-react/review/beta/communication-react.api.md index 8eb13fa5436..5d5f62be84b 100644 --- a/packages/communication-react/review/beta/communication-react.api.md +++ b/packages/communication-react/review/beta/communication-react.api.md @@ -1183,7 +1183,10 @@ export interface CallSurveyImprovementSuggestions { } // @public -export interface CallWithChatAdapter extends CallWithChatAdapterManagement, AdapterState, Disposable_2, CallWithChatAdapterSubscriptions { +export interface CallWithChatAdapter extends CallWithChatAdapterManagement, AdapterState, CallWithChatAdapterSubscriptions { + dispose(options?: { + doNotDisposeCallAgent?: boolean; + }): Promise; } // @public @@ -2087,7 +2090,10 @@ export type ClientState = CallClientState & ChatClientState; export type Common = Pick>; // @public -export interface CommonCallAdapter extends AdapterState, Disposable_2, CallAdapterCallOperations, CallAdapterDeviceManagement, CallAdapterSubscribers { +export interface CommonCallAdapter extends AdapterState, CallAdapterCallOperations, CallAdapterDeviceManagement, CallAdapterSubscribers { + dispose(options?: { + doNotDisposeCallAgent?: boolean; + }): Promise; // @deprecated joinCall(microphoneOn?: boolean): void; joinCall(options?: JoinCallOptions): void; diff --git a/packages/communication-react/review/stable/communication-react.api.md b/packages/communication-react/review/stable/communication-react.api.md index 4f57b9d4546..b7d4ca03c8c 100644 --- a/packages/communication-react/review/stable/communication-react.api.md +++ b/packages/communication-react/review/stable/communication-react.api.md @@ -970,7 +970,10 @@ export interface CallSurveyImprovementSuggestions { } // @public -export interface CallWithChatAdapter extends CallWithChatAdapterManagement, AdapterState, Disposable_2, CallWithChatAdapterSubscriptions { +export interface CallWithChatAdapter extends CallWithChatAdapterManagement, AdapterState, CallWithChatAdapterSubscriptions { + dispose(options?: { + doNotDisposeCallAgent?: boolean; + }): Promise; } // @public @@ -1805,7 +1808,10 @@ export type ClientState = CallClientState & ChatClientState; export type Common = Pick>; // @public -export interface CommonCallAdapter extends AdapterState, Disposable_2, CallAdapterCallOperations, CallAdapterDeviceManagement, CallAdapterSubscribers { +export interface CommonCallAdapter extends AdapterState, CallAdapterCallOperations, CallAdapterDeviceManagement, CallAdapterSubscribers { + dispose(options?: { + doNotDisposeCallAgent?: boolean; + }): Promise; // @deprecated joinCall(microphoneOn?: boolean): void; joinCall(options?: JoinCallOptions): void; diff --git a/packages/react-composites/src/composites/CallComposite/MockCallAdapter.ts b/packages/react-composites/src/composites/CallComposite/MockCallAdapter.ts index 40c6dec15de..4066b13566b 100644 --- a/packages/react-composites/src/composites/CallComposite/MockCallAdapter.ts +++ b/packages/react-composites/src/composites/CallComposite/MockCallAdapter.ts @@ -59,7 +59,7 @@ export class _MockCallAdapter implements CallAdapter { getState(): CallAdapterState { return this.state; } - dispose(): void { + dispose(): Promise { throw Error('dispose not implemented'); } joinCall(): Call | undefined { diff --git a/packages/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.ts b/packages/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.ts index d6d87ef5711..ffc44018167 100644 --- a/packages/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.ts +++ b/packages/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.ts @@ -625,10 +625,12 @@ export class AzureCommunicationCallAdapter { this.resetDiagnosticsForwarder(); this.callClient.offStateChange(this.onClientStateChange); - this.callAgent.dispose(); + if (!options?.doNotDisposeCallAgent) { + await this.callAgent.dispose(); + } } public async queryCameras(): Promise { @@ -1921,7 +1923,7 @@ function useAzureCommunicationCallAdapterGeneric< if (beforeDisposeRef.current) { await beforeDisposeRef.current(adapterRef.current); } - adapterRef.current.dispose(); + await adapterRef.current.dispose(); adapterRef.current = undefined; } let newAdapter: Adapter | undefined = undefined; @@ -2015,7 +2017,7 @@ function useAzureCommunicationCallAdapterGeneric< if (beforeDisposeRef.current) { await beforeDisposeRef.current(adapterRef.current); } - adapterRef.current.dispose(); + await adapterRef.current.dispose(); adapterRef.current = undefined; } })(); diff --git a/packages/react-composites/src/composites/CallComposite/adapter/CallAdapter.ts b/packages/react-composites/src/composites/CallComposite/adapter/CallAdapter.ts index 8baf65f8200..fed6407e402 100644 --- a/packages/react-composites/src/composites/CallComposite/adapter/CallAdapter.ts +++ b/packages/react-composites/src/composites/CallComposite/adapter/CallAdapter.ts @@ -35,7 +35,7 @@ import { AddPhoneNumberOptions } from '@azure/communication-calling'; import { DtmfTone } from '@azure/communication-calling'; import { CommunicationIdentifier } from '@azure/communication-common'; import type { CommunicationUserIdentifier, PhoneNumberIdentifier } from '@azure/communication-common'; -import type { AdapterState, Disposable, AdapterError, AdapterErrors } from '../../common/adapters'; +import type { AdapterState, AdapterError, AdapterErrors } from '../../common/adapters'; /* @conditional-compile-remove(breakout-rooms) */ import type { AdapterNotifications } from '../../common/adapters'; import { @@ -1106,10 +1106,13 @@ export interface CallAdapterCallManagement extends CallAdapterCallOperations { */ export interface CommonCallAdapter extends AdapterState, - Disposable, CallAdapterCallOperations, CallAdapterDeviceManagement, CallAdapterSubscribers { + /** + * Dispose of the adapter. This performs cleanup of resources. + */ + dispose(options?: { doNotDisposeCallAgent?: boolean }): Promise; /** * Join the call with microphone initially on/off. * @deprecated Use joinCall(options?:JoinCallOptions) instead. diff --git a/packages/react-composites/src/composites/CallWithChatComposite/adapter/AzureCommunicationCallWithChatAdapter.ts b/packages/react-composites/src/composites/CallWithChatComposite/adapter/AzureCommunicationCallWithChatAdapter.ts index d992339c041..343f1fcde09 100644 --- a/packages/react-composites/src/composites/CallWithChatComposite/adapter/AzureCommunicationCallWithChatAdapter.ts +++ b/packages/react-composites/src/composites/CallWithChatComposite/adapter/AzureCommunicationCallWithChatAdapter.ts @@ -418,14 +418,14 @@ export class AzureCommunicationCallWithChatAdapter implements CallWithChatAdapte return this.context.getState(); } /** Dispose of the current CallWithChatAdapter. */ - public dispose(): void { + public async dispose(options?: { doNotDisposeCallAgent?: boolean }): Promise { this.isAdapterDisposed = true; if (this.chatAdapter) { this.chatAdapter.offStateChange(this.onChatStateChange); this.chatAdapter.dispose(); } this.callAdapter.offStateChange(this.onCallStateChange); - this.callAdapter.dispose(); + await this.callAdapter.dispose(options); } /** Remove a participant from the Call only. */ public async removeParticipant(userId: string | CommunicationIdentifier): Promise { @@ -1288,7 +1288,7 @@ export const useAzureCommunicationCallWithChatAdapter = ( if (beforeDisposeRef.current) { await beforeDisposeRef.current(adapterRef.current); } - adapterRef.current.dispose(); + await adapterRef.current.dispose(); adapterRef.current = undefined; } if (creatingAdapterRef.current) { @@ -1338,7 +1338,7 @@ export const useAzureCommunicationCallWithChatAdapter = ( if (beforeDisposeRef.current) { await beforeDisposeRef.current(adapterRef.current); } - adapterRef.current.dispose(); + await adapterRef.current.dispose(); adapterRef.current = undefined; } })(); diff --git a/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatAdapter.ts b/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatAdapter.ts index e11aa98927a..834f7c35a37 100644 --- a/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatAdapter.ts +++ b/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatAdapter.ts @@ -25,7 +25,7 @@ import { } from '../../ChatComposite'; import { ResourceDetails } from '../../ChatComposite'; import { CallWithChatAdapterState } from '../state/CallWithChatAdapterState'; -import type { AdapterError, AdapterState, Disposable } from '../../common/adapters'; +import type { AdapterError, AdapterState } from '../../common/adapters'; import { AudioDeviceInfo, Call, @@ -595,8 +595,12 @@ export type ChatInitializedListener = (event: { adapter: CallWithChatAdapter }) export interface CallWithChatAdapter extends CallWithChatAdapterManagement, AdapterState, - Disposable, - CallWithChatAdapterSubscriptions {} + CallWithChatAdapterSubscriptions { + /** + * Dispose of the adapter. This performs cleanup of resources. + */ + dispose(options?: { doNotDisposeCallAgent?: boolean }): Promise; +} /** * Events fired off by the {@link CallWithChatAdapter}. diff --git a/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatBackedCallAdapter.ts b/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatBackedCallAdapter.ts index e25f049c4e1..13df9ea9bf1 100644 --- a/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatBackedCallAdapter.ts +++ b/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatBackedCallAdapter.ts @@ -85,7 +85,8 @@ export class CallWithChatBackedCallAdapter implements CallAdapter { }; public getState = (): CallAdapterState => callAdapterStateFromCallWithChatAdapterState(this.callWithChatAdapter.getState()); - public dispose = (): void => this.callWithChatAdapter.dispose(); + public dispose = (options?: { doNotDisposeCallAgent?: boolean }): Promise => + this.callWithChatAdapter.dispose(options); public joinCall = (options?: boolean | JoinCallOptions): Call | undefined => { if (typeof options === 'boolean') { return this.callWithChatAdapter.joinCall(options); diff --git a/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatBackedChatAdapter.ts b/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatBackedChatAdapter.ts index 25c21e638f7..cd4744d0aae 100644 --- a/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatBackedChatAdapter.ts +++ b/packages/react-composites/src/composites/CallWithChatComposite/adapter/CallWithChatBackedChatAdapter.ts @@ -67,7 +67,7 @@ export class CallWithChatBackedChatAdapter implements ChatAdapter { await this.callWithChatAdapter.removeParticipant(userId); public loadPreviousChatMessages = async (messagesToLoad: number): Promise => await this.callWithChatAdapter.loadPreviousChatMessages(messagesToLoad); - public dispose = (): void => this.callWithChatAdapter.dispose(); + public dispose = (): Promise => this.callWithChatAdapter.dispose(); public onStateChange = (handler: (state: ChatAdapterState) => void): void => { const convertedHandler = (state: CallWithChatAdapterState): void => { diff --git a/packages/react-composites/tests/app/call/LiveApp.tsx b/packages/react-composites/tests/app/call/LiveApp.tsx index 5f80bb15823..9bc235e8b72 100644 --- a/packages/react-composites/tests/app/call/LiveApp.tsx +++ b/packages/react-composites/tests/app/call/LiveApp.tsx @@ -21,7 +21,9 @@ export function LiveApp(props: { queryArgs: QueryArgs }): JSX.Element { setCallAdapter(wrapAdapterForTests(await createCallAdapterWithCredentials(queryArgs))); })(); - return () => callAdapter && callAdapter.dispose(); + return () => { + callAdapter && callAdapter.dispose(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [queryArgs]); diff --git a/packages/react-composites/tests/app/lib/MockCallAdapter.ts b/packages/react-composites/tests/app/lib/MockCallAdapter.ts index 90c696b3b52..3043984f016 100644 --- a/packages/react-composites/tests/app/lib/MockCallAdapter.ts +++ b/packages/react-composites/tests/app/lib/MockCallAdapter.ts @@ -46,7 +46,7 @@ export class MockCallAdapter implements CallAdapter { allowUnsupportedBrowserVersion(): void { throw Error('allowWithUnsupportedBrowserVersion not implemented'); } - dispose(): void { + dispose(): Promise { throw Error('dispose not implemented'); } joinCall(): Call | undefined { diff --git a/samples/CallWithChat/src/app/views/CallScreen.tsx b/samples/CallWithChat/src/app/views/CallScreen.tsx index 16fc7f247aa..6ac528a49a1 100644 --- a/samples/CallWithChat/src/app/views/CallScreen.tsx +++ b/samples/CallWithChat/src/app/views/CallScreen.tsx @@ -219,7 +219,7 @@ export const CallScreen = (props: CallScreenProps): JSX.Element => { // This ensures the service knows the user intentionally left the call if the user // closed the browser tab during an active call. useEffect(() => { - const disposeAdapter = (): void => adapter?.dispose(); + const disposeAdapter = async (): Promise => await adapter?.dispose(); window.addEventListener('beforeunload', disposeAdapter); return () => window.removeEventListener('beforeunload', disposeAdapter); }, [adapter]); diff --git a/samples/Calling/src/app/views/CallCompositeContainer.tsx b/samples/Calling/src/app/views/CallCompositeContainer.tsx index bd87b7094e3..77296a379e5 100644 --- a/samples/Calling/src/app/views/CallCompositeContainer.tsx +++ b/samples/Calling/src/app/views/CallCompositeContainer.tsx @@ -54,7 +54,7 @@ export const CallCompositeContainer = (props: CallCompositeContainerProps): JSX. // This ensures the service knows the user intentionally left the call if the user // closed the browser tab during an active call. useEffect(() => { - const disposeAdapter = (): void => adapter?.dispose(); + const disposeAdapter = async (): Promise => await adapter?.dispose(); window.addEventListener('beforeunload', disposeAdapter); return () => window.removeEventListener('beforeunload', disposeAdapter); }, [adapter]);