Skip to content

Commit

Permalink
Adding UUIDs to MessageRequests and MessageResponses (#2240)
Browse files Browse the repository at this point in the history
* Adding UUIDs to MessageRequests and MessageResponses

* Committing changefile

* Added in MessageID and MessageUUID types, added in testing for UUID communication and id as numbers communication

* Changing waitForResponse to have input of type MessageUUID

* Updated references to use MessageID and MessageUUID

* Fixed lint error

* Created BaseUUID type

* Added validation for uuid to handleParentMessage

* Only validate UUIDs if there is one

* Added in MessageUUID object

* Testing new changes to UUID

* Added in serialization of objects

* Updated to address PR feedback

* Added comment describing MessageID vs MessageUUID

* Added in helper function

* Removing comment since map.delete was verified to work correctly

* Moved uuid object to interfaces.ts for more general use

* Replaced Function in Map declarations

* Updated to use serialized and deserialized Message Response objects

* Added in unit tests for UUID class

* Fixed lint error

* Updating tests

* Moved uuid object to new file uuidObject.ts, edited changefile verbage

* Added in unit tests for testing callback map deletion and functionality

* Removed unnused import

* Added in logging for when a callbackID fails to be generated

* Fixed a typo in communication spec

* Updated serialization and deserialization and uuid toString function

* Reverted toString() change due to compatibility issues

* Removing unnecessary map clear due to already clearing in uninitialize and removeHandlers

* Fixed an issue with removing message ids

* Reverting uuid back to private

---------

Co-authored-by: Trevor Harris <trharris@microsoft.com>
Co-authored-by: AE ( ͡ಠ ʖ̯ ͡ಠ) <36546967+AE-MS@users.noreply.github.com>
  • Loading branch information
3 people authored and alexneyman-MSFT committed May 17, 2024
1 parent f325c80 commit 3a20fc5
Show file tree
Hide file tree
Showing 12 changed files with 563 additions and 152 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Added `uuid` parameter to `MessageRequest` and `MessageResponse` interfaces",
"packageName": "@microsoft/teams-js",
"email": "jadahiya@microsoft.com",
"dependentChangeType": "patch"
}
267 changes: 190 additions & 77 deletions packages/teams-js/src/internal/communication.ts

Large diffs are not rendered by default.

84 changes: 81 additions & 3 deletions packages/teams-js/src/internal/messageObjects.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,61 @@
import { UUID as MessageUUID } from './uuidObject';

/**
* @internal
* Limited to Microsoft-internal use
*
* MessageIDs represent the legacy number id used for processing MessageRequests and MessageResponses
*/
export type MessageID = number;

/**
* @internal
* Limited to Microsoft-internal use
*/
export interface MessageRequest {
id?: number;
id?: MessageID;
uuid?: MessageUUID;
func: string;
timestamp?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: any[];
apiVersionTag?: string;
isPartialResponse?: boolean;
}

/**
* @internal
* Limited to Microsoft-internal use
*/
export interface SerializedMessageRequest {
id?: MessageID;
uuidAsString?: string;
func: string;
timestamp?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: any[];
apiVersionTag?: string;
}

/**
* @internal
* Limited to Microsoft-internal use
*/
export interface SerializedMessageResponse {
id: MessageID;
uuidAsString?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: any[];
isPartialResponse?: boolean; // If the message is partial, then there will be more future responses for the given message ID.
}

/**
* @internal
* Limited to Microsoft-internal use
*/
export interface MessageResponse {
id: number;
id: MessageID;
uuid?: MessageUUID;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: any[];
isPartialResponse?: boolean; // If the message is partial, then there will be more future responses for the given message ID.
Expand All @@ -34,6 +73,45 @@ export interface MessageResponse {
* merged. However, it's a journey.
*/
export interface MessageRequestWithRequiredProperties extends MessageRequest {
id: number;
id: MessageID;
uuid: MessageUUID;
timestamp: number;
}

export const serializeMessageRequest = (message: MessageRequest): SerializedMessageRequest => {
const { uuid, ...restOfMessage } = message;
const uuidAsString = uuid?.toString();
const request: SerializedMessageRequest = {
...restOfMessage,
uuidAsString: uuidAsString,
};
return request;
};

export const deserializeMessageRequest = (message: SerializedMessageRequest): MessageRequest => {
const { uuidAsString, ...restOfMessage } = message;
const request: MessageRequest = {
...restOfMessage,
uuid: uuidAsString ? new MessageUUID(uuidAsString) : undefined,
};
return request;
};

export const deserializeMessageResponse = (serializedResponse: SerializedMessageResponse): MessageResponse => {
const { uuidAsString, ...restOfResponse } = serializedResponse;
const messageResponse: MessageResponse = {
...restOfResponse,
uuid: uuidAsString ? new MessageUUID(uuidAsString) : undefined,
};
return messageResponse;
};

export const serializeMessageResponse = (response: MessageResponse): SerializedMessageResponse => {
const { uuid, ...restOfResponse } = response;
const uuidAsString = uuid?.toString();
const messageResponse: SerializedMessageResponse = {
...restOfResponse,
uuidAsString: uuidAsString,
};
return messageResponse;
};
17 changes: 17 additions & 0 deletions packages/teams-js/src/internal/uuidObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { generateGUID, validateUuid } from './utils';

/**
* @internal
* Limited to Microsoft-internal use
*
* UUID object
*/
export class UUID {
public constructor(private readonly uuid: string = generateGUID()) {
validateUuid(uuid);
}

public toString(): string {
return this.uuid;
}
}
142 changes: 141 additions & 1 deletion packages/teams-js/test/internal/communication.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as communication from '../../src/internal/communication';
import { GlobalVars } from '../../src/internal/globalVars';
import { MessageRequest } from '../../src/internal/messageObjects';
import { NestedAppAuthMessageEventNames, NestedAppAuthRequest } from '../../src/internal/nestedAppAuthUtils';
import { ApiName, ApiVersionNumber, getApiVersionTag } from '../../src/internal/telemetry';
import { UUID } from '../../src/internal/uuidObject';
import { FrameContexts } from '../../src/public';
import { app } from '../../src/public/app';
import { MessageRequest, Utils } from '../utils';
import { Utils } from '../utils';

const testApiVersion = getApiVersionTag(ApiVersionNumber.V_1, 'mockedApiName' as ApiName);

Expand Down Expand Up @@ -1609,4 +1611,142 @@ describe('Testing communication', () => {
});
});
});
describe('UUID tests', () => {
let utils: Utils = new Utils();

beforeEach(() => {
// Set a mock window for testing
utils = new Utils();
utils.mockWindow.parent = undefined;
app._initialize(utils.mockWindow);
communication.Communication.parentWindow = undefined;
GlobalVars.isFramelessWindow = false;
});
afterEach(() => {
communication.uninitializeCommunication();
});
afterAll(() => {
GlobalVars.isFramelessWindow = false;
});
describe('postMessage', () => {
it('should delete callback correctly with uuid', async () => {
app._initialize(utils.mockWindow);
communication.initializeCommunication(undefined, testApiVersion);
let callbackWasCalled = 0;
const initializeMessage = utils.findInitializeMessageOrThrow();
let message = utils.findMessageByFunc('initialize');

if (message) {
expect(message.id).toBe(0);
}

await utils.respondToMessage(initializeMessage);

communication.sendMessageToParent(testApiVersion, 'testAction', () => {
callbackWasCalled++;
});
message = utils.findMessageByFunc('testAction');

if (message) {
expect(message.id).toBe(1);
}

await utils.respondToMessage({ id: message?.id, uuid: message?.uuid, func: 'testAction' }, false);

expect(callbackWasCalled).toBe(1);

//Call respondToMessageAgain, this should not call the callback since it should be deleted and callbackWasCalled should be still 1
await utils.respondToMessage({ id: message?.id, uuid: message?.uuid, func: 'testAction' }, false);

expect(callbackWasCalled).toBe(1);

communication.sendMessageToParent(testApiVersion, 'testAction2', () => {
callbackWasCalled++;
});

message = utils.findMessageByFunc('testAction2');

if (message) {
expect(message.id).toBe(2);
}

await utils.respondToMessage({ id: message?.id, uuid: message?.uuid, func: 'testAction2' }, false);
expect(callbackWasCalled).toBe(2);
});

it('should delete callback correctly with number id only', async () => {
app._initialize(utils.mockWindow);
communication.initializeCommunication(undefined, testApiVersion);
let callbackWasCalled = 0;
const initializeMessage = utils.findInitializeMessageOrThrow();
let message = utils.findMessageByFunc('initialize');

if (message) {
expect(message.id).toBe(0);
}

await utils.respondToMessage(initializeMessage);

communication.sendMessageToParent(testApiVersion, 'testAction', () => {
callbackWasCalled++;
});
message = utils.findMessageByFunc('testAction');

if (message) {
expect(message.id).toBe(1);
}

await utils.respondToMessage({ id: message?.id, func: 'testAction' }, false);

expect(callbackWasCalled).toBe(1);

//Call respondToMessageAgain, this should not call the callback since it should be deleted and callbackWasCalled should be still 1
await utils.respondToMessage({ id: message?.id, func: 'testAction' }, false);

expect(callbackWasCalled).toBe(1);

communication.sendMessageToParent(testApiVersion, 'testAction2', () => {
callbackWasCalled++;
});

message = utils.findMessageByFunc('testAction2');

if (message) {
expect(message.id).toBe(2);
}

await utils.respondToMessage({ id: message?.id, func: 'testAction2' }, false);
expect(callbackWasCalled).toBe(2);
});

it('should not call callback with invalid uuid', async () => {
app._initialize(utils.mockWindow);
communication.initializeCommunication(undefined, testApiVersion);
let callbackWasCalled = 0;
const initializeMessage = utils.findInitializeMessageOrThrow();

await utils.respondToMessage(initializeMessage);

communication.sendMessageToParent(testApiVersion, 'testAction', () => {
callbackWasCalled++;
});

const message = utils.findMessageByFunc('testAction');

if (message) {
expect(message.id).toBe(1);
}

await utils.respondToMessage({ id: message?.id, uuid: new UUID(), func: 'testAction' }, false);

//Since uuid is set but is not the same value as the message request, message should not process and message should not call callback
expect(callbackWasCalled).toBe(0);

//Respond with correct set of message uuid and now callback should be called
await utils.respondToMessage({ id: message?.id, uuid: message?.uuid, func: 'testAction' }, false);

expect(callbackWasCalled).toBe(1);
});
});
});
});

0 comments on commit 3a20fc5

Please sign in to comment.