diff --git a/apps/teams-test-app/e2e-test-data/meeting.json b/apps/teams-test-app/e2e-test-data/meeting.json index 2742b772dc..63e9be7d3e 100644 --- a/apps/teams-test-app/e2e-test-data/meeting.json +++ b/apps/teams-test-app/e2e-test-data/meeting.json @@ -35,12 +35,20 @@ }, { "title": "getMeetingDetailsVerbose API Call - Success", - "version": ">2.22.0", + "version": ">2.22.0 && <= 2.28.0", "type": "callResponse", "boxSelector": "#box_getMeetingDetailsVerbose", "expectedAlertValue": "getMeetingDetails called with shouldGetVerboseDetails: true", "expectedTestAppValue": "{\"details\":{\"scheduledStartTime\":\"testStartTime\",\"joinUrl\":\"testJoinUrl\",\"type\":\"oneOnOneCall\",\"originalCaller\":\"testCallerId\",\"dialedEntity\":\"testDnis\",\"trackingId\":\"testTrackingId\"},\"conversation\":{\"id\":\"testConversationId\"},\"organizer\":{\"id\":\"testOrganizerId\",\"tenantId\":\"testTenantId\"}}" }, + { + "title": "getMeetingDetailsVerbose API Call - Success", + "version": ">2.28.0", + "type": "callResponse", + "boxSelector": "#box_getMeetingDetailsVerbose", + "expectedAlertValue": "getMeetingDetails called with shouldGetVerboseDetails: true", + "expectedTestAppValue": "{\"details\":{\"scheduledStartTime\":\"testStartTime\",\"joinUrl\":\"testJoinUrl\",\"type\":\"oneOnOneCall\",\"originalCallerInfo\":{\"phoneNumber\":\"1234567890\",\"email\":{\"val\":\"calleremail@somedomain.com\"}},\"dialedEntityInfo\":{\"phoneNumber\":\"1234567890\",\"email\":{\"val\":\"calleeemail@somedomain.com\"}},\"trackingId\":\"testTrackingId\",\"callId\":\"testCallId\"},\"conversation\":{\"id\":\"testConversationId\"},\"organizer\":{\"id\":\"testOrganizerId\",\"tenantId\":\"testTenantId\"}}" + }, { "title": "getAuthenticationTokenForAnonymousUser API Call - Success", "type": "callResponse", diff --git a/change/@microsoft-teams-js-4c2cf5b3-6661-4e5f-925b-7a8a49ad9d91.json b/change/@microsoft-teams-js-4c2cf5b3-6661-4e5f-925b-7a8a49ad9d91.json new file mode 100644 index 0000000000..09b008e35c --- /dev/null +++ b/change/@microsoft-teams-js-4c2cf5b3-6661-4e5f-925b-7a8a49ad9d91.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Added three properties to `ICallDetails`, `originalCallerInfo`, `dialedEntityInfo`, and `callId`, created a new type `ICallParticipantIdentifiers`, and deprecated the `originalCaller` and `dialedEntity` properties", + "packageName": "@microsoft/teams-js", + "email": "johnsle@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/teams-js/src/internal/emailAddressValidation.ts b/packages/teams-js/src/internal/emailAddressValidation.ts new file mode 100644 index 0000000000..ba25b61cf7 --- /dev/null +++ b/packages/teams-js/src/internal/emailAddressValidation.ts @@ -0,0 +1,6 @@ +export function validateEmailAddress(emailString: string): void { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(emailString)) { + throw new Error('Input email address does not have the correct format.'); + } +} diff --git a/packages/teams-js/src/public/emailAddress.ts b/packages/teams-js/src/public/emailAddress.ts new file mode 100644 index 0000000000..fbf8c67740 --- /dev/null +++ b/packages/teams-js/src/public/emailAddress.ts @@ -0,0 +1,17 @@ +import { validateEmailAddress } from '../internal/emailAddressValidation'; + +/** + * Represents a validated email. + * + * @hidden + * Hide from docs. + */ +export class EmailAddress { + public constructor(private readonly val: string) { + validateEmailAddress(val); + } + + public toString(): string { + return this.val; + } +} diff --git a/packages/teams-js/src/public/index.ts b/packages/teams-js/src/public/index.ts index 49193f2cf3..b8ee1fcb30 100644 --- a/packages/teams-js/src/public/index.ts +++ b/packages/teams-js/src/public/index.ts @@ -42,6 +42,7 @@ export { } from './interfaces'; export { app } from './app'; export { AppId } from './appId'; +export { EmailAddress } from './emailAddress'; export { appInstallDialog } from './appInstallDialog'; export { barCode } from './barCode'; export { chat, OpenGroupChatRequest, OpenSingleChatRequest } from './chat'; diff --git a/packages/teams-js/src/public/meeting.ts b/packages/teams-js/src/public/meeting.ts index e44aea1dba..9f36169267 100644 --- a/packages/teams-js/src/public/meeting.ts +++ b/packages/teams-js/src/public/meeting.ts @@ -3,6 +3,7 @@ import { doesHandlerExist, registerHandler, removeHandler } from '../internal/ha import { ensureInitialized } from '../internal/internalAPIs'; import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemetry'; import { FrameContexts } from './constants'; +import { EmailAddress } from './emailAddress'; import { ErrorCode, SdkError } from './interfaces'; import { runtime } from './runtime'; @@ -94,6 +95,23 @@ export namespace meeting { type?: T; } + /** + * @hidden + * Hide from docs + * Data structure to represent call participant identifiers + */ + interface ICallParticipantIdentifiers { + /** + * Phone number of a PSTN caller + */ + phoneNumber?: string; + + /** + * Email of a VoIP caller + */ + email?: EmailAddress; + } + /** * @hidden * Hide from docs @@ -101,22 +119,44 @@ export namespace meeting { */ export interface ICallDetails extends IMeetingOrCallDetailsBase { /** + * @deprecated please use {@link ICallDetails.originalCallerInfo} instead + * * @hidden * Phone number of a PSTN caller or email of a VoIP caller */ originalCaller?: string; /** + * @hidden + * Object representing the original caller + */ + originalCallerInfo?: ICallParticipantIdentifiers; + + /** + * @deprecated please use {@link ICallDetails.dialedEntityInfo} instead + * * @hidden * Phone number of a PSTN callee or email of a VoIP callee */ dialedEntity?: never; + /** + * @hidden + * Object representing the entity the caller dialed + */ + dialedEntityInfo?: never; + /** * @hidden * Tracking identifier for grouping related calls */ trackingId?: never; + + /** + * @hidden + * Identifier for the current call + */ + callId?: never; } /** @@ -665,7 +705,7 @@ export namespace meeting { if ( (response.details?.type == CallType.GroupCall || response.details?.type == CallType.OneOnOneCall) && - !response.details.originalCaller + !response.details.originalCallerInfo ) { throw new Error(ErrorCode.NOT_SUPPORTED_ON_PLATFORM.toString()); } diff --git a/packages/teams-js/test/internal/emailAddressValidation.spec.ts b/packages/teams-js/test/internal/emailAddressValidation.spec.ts new file mode 100644 index 0000000000..49d39b3348 --- /dev/null +++ b/packages/teams-js/test/internal/emailAddressValidation.spec.ts @@ -0,0 +1,23 @@ +import { validateEmailAddress } from '../../src/internal/emailAddressValidation'; + +describe('emailAddressValidation', () => { + const invalidEmails = ['@@domain.com', 'firstname lastname@domain.com', 'name@domain']; + invalidEmails.forEach((invalidEmail) => { + it('should throw errors for invalid email addresses', () => { + expect(() => validateEmailAddress(invalidEmail)).toThrow('Input email address does not have the correct format.'); + }); + }); + const validEmails = [ + 'email@domain.com', + 'firstname+lastname@domain.com', + '123@domain.com', + 'name@domain.subdomain.com', + ]; + validEmails.forEach((validEmail) => { + it('should not throw errors for valid email addresses', () => { + expect(() => validateEmailAddress(validEmail)).not.toThrow( + 'Input email address does not have the correct format.', + ); + }); + }); +}); diff --git a/packages/teams-js/test/public/emailAddress.spec.ts b/packages/teams-js/test/public/emailAddress.spec.ts new file mode 100644 index 0000000000..3dae0726e1 --- /dev/null +++ b/packages/teams-js/test/public/emailAddress.spec.ts @@ -0,0 +1,24 @@ +import { EmailAddress } from '../../src/public'; + +describe('emailAddress', () => { + const invalidEmails = ['@@domain.com', 'firstname lastname@domain.com', 'name@domain']; + invalidEmails.forEach((invalidEmail) => { + it('should throw errors for invalid email addresses', () => { + expect(() => new EmailAddress(invalidEmail)).toThrowError( + 'Input email address does not have the correct format.', + ); + }); + }); + const validEmails = [ + 'email@domain.com', + 'firstname+lastname@domain.com', + '123@domain.com', + 'name@domain.subdomain.com', + ]; + validEmails.forEach((validEmail) => { + it('should not throw errors for valid email addresses', () => { + const email = new EmailAddress(validEmail); + expect(email.toString()).toBe(validEmail); + }); + }); +}); diff --git a/packages/teams-js/test/public/meeting.spec.ts b/packages/teams-js/test/public/meeting.spec.ts index c0b615fb40..f603a3f87f 100644 --- a/packages/teams-js/test/public/meeting.spec.ts +++ b/packages/teams-js/test/public/meeting.spec.ts @@ -2,7 +2,7 @@ import { errorLibraryNotInitialized } from '../../src/internal/constants'; import { GlobalVars } from '../../src/internal/globalVars'; import { DOMMessageEvent } from '../../src/internal/interfaces'; import { MessageRequest } from '../../src/internal/messageObjects'; -import { FrameContexts } from '../../src/public'; +import { EmailAddress, FrameContexts } from '../../src/public'; import { app } from '../../src/public/app'; import { ErrorCode, SdkError } from '../../src/public/interfaces'; import { meeting } from '../../src/public/meeting'; @@ -277,7 +277,10 @@ describe('meeting', () => { 'https://teams.microsoft.com/l/meetup-join/19%3ameeting_qwertyuiop[phgfdsasdfghjkjbvcxcvbnmyt1234567890!@#$%^&*(%40thread.v2/0?context=%7b%22Tid%22%3a%2272f988bf-86f1-41af-91ab-2d7cd011db47%22%2c%22Oid%22%3a%226b33ac33-85ae-4995-be29-1d38a77aa8e3%22%7d', type: meeting.CallType.OneOnOneCall, // Verbose details - originalCaller: 'testCallerId', + originalCallerInfo: { + phoneNumber: '1234567890', + email: new EmailAddress('calleremail@somedomain.com'), + }, }; const organizer: meeting.IOrganizer = { id: '8:orgid:6b33ac33-85ae-4995-be29-1d38a77aa8e3', @@ -627,7 +630,10 @@ describe('meeting', () => { 'https://teams.microsoft.com/l/meetup-join/19%3ameeting_qwertyuiop[phgfdsasdfghjkjbvcxcvbnmyt1234567890!@#$%^&*(%40thread.v2/0?context=%7b%22Tid%22%3a%2272f988bf-86f1-41af-91ab-2d7cd011db47%22%2c%22Oid%22%3a%226b33ac33-85ae-4995-be29-1d38a77aa8e3%22%7d', type: meeting.CallType.OneOnOneCall, // Verbose details - originalCaller: 'testCallerId', + originalCallerInfo: { + phoneNumber: '1234567890', + email: new EmailAddress('calleeemail@somedomain.com'), + }, }; const organizer: meeting.IOrganizer = { id: '8:orgid:6b33ac33-85ae-4995-be29-1d38a77aa8e3',