diff --git a/change/@microsoft-teams-js-fc4a8f34-92ca-48dc-9c74-c84d8eed6a89.json b/change/@microsoft-teams-js-fc4a8f34-92ca-48dc-9c74-c84d8eed6a89.json new file mode 100644 index 0000000000..5b8b7b14c6 --- /dev/null +++ b/change/@microsoft-teams-js-fc4a8f34-92ca-48dc-9c74-c84d8eed6a89.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Added optional `FeatureSet` field to `AppEligibilityInformation` interface", + "packageName": "@microsoft/teams-js", + "email": "email not defined", + "dependentChangeType": "patch" +} diff --git a/packages/teams-js/src/private/copilot/eligibility.ts b/packages/teams-js/src/private/copilot/eligibility.ts index e42d56b719..50791d0a52 100644 --- a/packages/teams-js/src/private/copilot/eligibility.ts +++ b/packages/teams-js/src/private/copilot/eligibility.ts @@ -80,7 +80,9 @@ function isEligibilityInfoValid(eligibilityInfo: AppEligibilityInformation): boo eligibilityInfo.userClassification === undefined || eligibilityInfo.isCopilotEligible === undefined || eligibilityInfo.isCopilotEnabledRegion === undefined || - eligibilityInfo.isOptedOutByAdmin === undefined + eligibilityInfo.isOptedOutByAdmin === undefined || + (eligibilityInfo.featureSet && + (eligibilityInfo.featureSet.serverFeatures === undefined || eligibilityInfo.featureSet.uxFeatures === undefined)) ) { return false; } diff --git a/packages/teams-js/src/public/interfaces.ts b/packages/teams-js/src/public/interfaces.ts index ab607a1864..8f10629bb5 100644 --- a/packages/teams-js/src/public/interfaces.ts +++ b/packages/teams-js/src/public/interfaces.ts @@ -1132,6 +1132,11 @@ export interface AppEligibilityInformation { * A user will be in at most one cohort. */ cohort: Cohort | null; + /** + * Feature Sets + * If this property is undefined, it indicates that the host is an older version that doesn't support this property. + */ + featureSet?: FeatureSet; /** * Indicates that the user is eligible for Microsoft Entra ID Authenticated Copilot experience. */ @@ -1150,6 +1155,22 @@ export interface AppEligibilityInformation { userClassification: UserClassification | null; } +/** + * @hidden + * @beta + * Represents the feature set available to the user. + */ +export interface FeatureSet { + /** + * Server Feature set + */ + serverFeatures: ReadonlyArray; + /** + * UX Feature set + */ + uxFeatures: ReadonlyArray; +} + /** * @hidden * diff --git a/packages/teams-js/test/private/copilot.spec.ts b/packages/teams-js/test/private/copilot.spec.ts index 4de25d9e4b..6d89749d24 100644 --- a/packages/teams-js/test/private/copilot.spec.ts +++ b/packages/teams-js/test/private/copilot.spec.ts @@ -16,6 +16,7 @@ const mockedAppEligibilityInformation = { persona: Persona.Student, eduType: EduType.HigherEducation, }, + featureSet: { serverFeatures: ['feature1', 'feature2'], uxFeatures: ['feature3'] }, }; const mockedAppEligibilityInformationUserClassificationNull = { @@ -188,6 +189,24 @@ describe('copilot', () => { return expect(promise).resolves.toEqual(mockedAppEligibilityInformation); }); + it(`should not throw if featureSet in response is undefined - with context ${frameContext}`, async () => { + await utils.initializeWithContext(frameContext); + utils.setRuntimeConfig(copilotRuntimeConfig); + + const promise = copilot.eligibility.getEligibilityInfo(); + const message = utils.findMessageByFunc('copilot.eligibility.getEligibilityInfo'); + const mockedAppEligibilityInformationWithUndefinedFeatureSet = { + ...mockedAppEligibilityInformation, + featureSet: undefined, + }; + expect(message).not.toBeNull(); + if (message) { + utils.respondToMessage(message, mockedAppEligibilityInformationWithUndefinedFeatureSet); + } + + return expect(promise).resolves.toEqual(mockedAppEligibilityInformationWithUndefinedFeatureSet); + }); + it(`should throw error if host returns error - with context ${frameContext}`, async () => { await utils.initializeWithContext(frameContext); utils.setRuntimeConfig(copilotRuntimeConfig); @@ -319,6 +338,50 @@ describe('copilot', () => { await expect(promise).rejects.toThrowError('Error deserializing eligibility information'); }); + + it('getEligibilityInfo should throw if AppEligibilityInformation.featureSet.serverFeatures is undefined', async () => { + await utils.initializeWithContext(FrameContexts.content); + utils.setRuntimeConfig(copilotRuntimeConfig); + + const mockedInvalidAppEligibilityInformationWithInvalidUxFeatures = { + ...mockedAppEligibilityInformation, + featureSet: { + serverFeatures: undefined, + uxFeatures: [], + }, + }; + + const promise = copilot.eligibility.getEligibilityInfo(); + const message = utils.findMessageByFunc('copilot.eligibility.getEligibilityInfo'); + expect(message).not.toBeNull(); + if (message) { + utils.respondToMessage(message, mockedInvalidAppEligibilityInformationWithInvalidUxFeatures); + } + + await expect(promise).rejects.toThrowError('Error deserializing eligibility information'); + }); + + it('getEligibilityInfo should throw if AppEligibilityInformation.featureSet.uxFeatures is undefined', async () => { + await utils.initializeWithContext(FrameContexts.content); + utils.setRuntimeConfig(copilotRuntimeConfig); + + const mockedInvalidAppEligibilityInformationWithInvalidUxFeatures = { + ...mockedAppEligibilityInformation, + featureSet: { + serverFeatures: [], + uxFeatures: undefined, + }, + }; + + const promise = copilot.eligibility.getEligibilityInfo(); + const message = utils.findMessageByFunc('copilot.eligibility.getEligibilityInfo'); + expect(message).not.toBeNull(); + if (message) { + utils.respondToMessage(message, mockedInvalidAppEligibilityInformationWithInvalidUxFeatures); + } + + await expect(promise).rejects.toThrowError('Error deserializing eligibility information'); + }); }); }); });