UNPKG

@azure/communication-react

Version:

React library for building modern communication user experiences utilizing Azure Communication Services

224 lines • 11.4 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { CallSubscriber } from './CallSubscriber'; import { convertSdkCallToDeclarativeCall, convertSdkIncomingCallToDeclarativeIncomingCall } from './Converter'; import { IncomingCallSubscriber } from './IncomingCallSubscriber'; import { disposeAllViews, disposeAllViewsFromCall } from './StreamUtils'; import { _isTeamsIncomingCall } from './TypeGuards'; import { incomingCallDeclaratify } from './IncomingCallDeclarative'; import { teamsIncomingCallDeclaratify } from './TeamsIncomingCallDeclarative'; /** * ProxyCallAgent proxies CallAgent and saves any returned state in the given context. It will subscribe to all state * updates in the CallAgent and in the contained Calls and RemoteParticipants. When dispose is called it will * unsubscribe from all state updates. */ export class ProxyCallAgentCommon { constructor(context, internalContext) { // Unsubscribe is called when CallAgent is disposed. This should mean no more updating of existing call but we don't // remove any existing state. this.unregisterSubscriber = () => { for (const [_, callSubscriber] of this._callSubscribers.entries()) { callSubscriber.unsubscribe(); } this._callSubscribers.clear(); for (const [_, incomingCallSubscriber] of this._incomingCallSubscribers.entries()) { incomingCallSubscriber.unsubscribe(); } this._incomingCallSubscribers.clear(); this._incomingCalls.clear(); for (const [_, declarativeCall] of this._declarativeCalls.entries()) { declarativeCall.unsubscribe(); } this._declarativeCalls.clear(); }; this.callsUpdated = (event) => { const addedStatefulCall = []; for (const call of event.added) { const statefulCall = this.addCall(call); addedStatefulCall.push(statefulCall); } const removedStatefulCall = []; for (const call of event.removed) { disposeAllViewsFromCall(this._context, this._internalContext, call.id); const callSubscriber = this._callSubscribers.get(call); if (callSubscriber) { callSubscriber.unsubscribe(); this._callSubscribers.delete(call); } this._context.setCallEnded(call.id, call.callEndReason); const declarativeCall = this._declarativeCalls.get(call); if (declarativeCall) { declarativeCall.unsubscribe(); removedStatefulCall.push(declarativeCall); this._declarativeCalls.delete(call); } else { removedStatefulCall.push(this.callDeclaratify(call, this._context)); } } for (const externalCallsUpdatedListener of this._externalCallsUpdatedListeners) { externalCallsUpdatedListener({ added: addedStatefulCall, removed: removedStatefulCall }); } }; this.setIncomingCallEnded = (incomingCallId, callEndReason) => { const incomingCallSubscriber = this._incomingCallSubscribers.get(incomingCallId); if (incomingCallSubscriber) { incomingCallSubscriber.unsubscribe(); this._incomingCallSubscribers.delete(incomingCallId); } this._incomingCalls.delete(incomingCallId); this._context.setIncomingCallEnded(incomingCallId, callEndReason); }; this.incomingCall = ({ incomingCall }) => { // Make sure to not subscribe to the incoming call if we are already subscribed to it. if (!this._incomingCallSubscribers.has(incomingCall.id)) { this._incomingCallSubscribers.set(incomingCall.id, new IncomingCallSubscriber(incomingCall, this.setIncomingCallEnded)); } if (_isTeamsIncomingCall(incomingCall)) { this._incomingCalls.set(incomingCall.id, teamsIncomingCallDeclaratify(incomingCall, this._context)); } else { this._incomingCalls.set(incomingCall.id, incomingCallDeclaratify(incomingCall, this._context)); } this._context.setIncomingCall(convertSdkIncomingCallToDeclarativeIncomingCall(incomingCall)); }; this.addCall = (call) => { var _a; (_a = this._callSubscribers.get(call)) === null || _a === void 0 ? void 0 : _a.unsubscribe(); // For API extentions we need to have the call in the state when we are subscribing as we may want to update the // state during the subscription process in the subscriber so we add the call to state before subscribing. this._context.setCall(convertSdkCallToDeclarativeCall(call)); this._callSubscribers.set(call, new CallSubscriber(call, this._context, this._internalContext)); return this.getOrCreateDeclarativeCall(call); }; this.getOrCreateDeclarativeCall = (call) => { const declarativeCall = this._declarativeCalls.get(call); if (declarativeCall) { return declarativeCall; } const newDeclarativeCall = this.callDeclaratify(call, this._context); this._declarativeCalls.set(call, newDeclarativeCall); return newDeclarativeCall; }; this._context = context; this._internalContext = internalContext; this._callSubscribers = new Map(); this._incomingCallSubscribers = new Map(); this._incomingCalls = new Map(); this._declarativeCalls = new Map(); this._externalCallsUpdatedListeners = new Set(); } /* * We can't directly override get function because it is proxied, * Add a getCommon function and call it in child class */ getCommon(target, prop //eslint-disable-next-line @typescript-eslint/no-explicit-any ) { switch (prop) { case 'startCall': { return this._context.withErrorTeedToState((...args) => { const call = this.startCall(target, args); this.addCall(call); return this.getOrCreateDeclarativeCall(call); }, 'CallAgent.startCall'); } case 'join': { return this._context.withErrorTeedToState((...args) => { const call = this.joinCall(target, args); this.addCall(call); return this.getOrCreateDeclarativeCall(call); }, 'CallAgent.join'); } case 'calls': { return Array.from(this._declarativeCalls.values()); } case 'on': { return (...args) => { // typescript is not smart enough to handle multiple overloads and pull the correct type here so force casting args const event = args[0]; const isCallsUpdated = event === 'callsUpdated'; if (isCallsUpdated) { const listener = args[1]; this._externalCallsUpdatedListeners.add(listener); } else { this.agentSubscribe(target, args); } }; } case 'off': { return (...args) => { // typescript is not smart enough to handle multiple overloads and pull the correct type here so force casting args const event = args[0]; const isCallsUpdated = event === 'callsUpdated'; if (isCallsUpdated) { const listener = args[1]; this._externalCallsUpdatedListeners.delete(listener); } else { this.agentUnsubscribe(target, args); } }; } case 'dispose': { // Wrapping CallAgent.dispose in a callback type (): Promise<void> to accomodate the change of CallAgent.dispose // in calling beta version 1.8.0-beta.1 from callback type (): Promise<void> to (): void const callAgentDisposeAsyncCallbackWrapper = () => __awaiter(this, void 0, void 0, function* () { yield target.dispose(); return Promise.resolve(); }); return () => { return callAgentDisposeAsyncCallbackWrapper().then(() => { this.unsubscribe(); }); }; } /** * This attribute is a special case and doesn't exist on the CallAgent interface. * We need this to be able to return a declarative incoming call object using the call agent. * In a standard headless SDK usage, the right way to get an incoming call is to use the `incomingCall` event. * However, using the declarative layer, the ideal usage would be to: * 1. subscribe to the `onStateChange` event * 2. Get the incoming call from the new state and it's ID * 3. Use `callAgent.incomingCalls` and filter an incoming call ID to get a declarative incoming call object */ case 'incomingCalls': { return Array.from(this._incomingCalls.values()); } default: return Reflect.get(target, prop); } } } /** * @private */ export const clearCallRelatedState = (context, internalContext) => { // Make sure there are no existing call data if creating a new CallAgentDeclarative (if creating a new // CallAgentDeclarative after disposing of the hold one will mean context have old call state). TODO: should we stop // rendering when the previous callAgent is disposed? disposeAllViews(context, internalContext); context.clearCallRelatedState(); internalContext.clearCallRelatedState(); }; //# sourceMappingURL=CallAgentDeclarativeCommon.js.map