Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/apps/src/activity-sender.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ActivityParams, ConversationReference } from '@microsoft/teams.api';
import {Client as HttpClient } from '@microsoft/teams.common';

import { ActivitySender } from './activity-sender';
import { ApiClient } from './api';

describe('ActivitySender', () => {
let sender: ActivitySender;
Expand All @@ -22,7 +23,10 @@ describe('ActivitySender', () => {
conversation: { id: 'conv-123', conversationType: 'personal' },
};

sender = new ActivitySender(mockHttpClient, undefined as any);
sender = new ActivitySender(
(serviceUrl) => new ApiClient(serviceUrl, mockHttpClient),
undefined as any
);
});

describe('send', () => {
Expand Down
12 changes: 7 additions & 5 deletions packages/apps/src/activity-sender.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ActivityParams, Client, ConversationReference, SentActivity } from '@microsoft/teams.api';
import { Client as HttpClient, ILogger } from '@microsoft/teams.common';
import { ActivityParams, ConversationReference, SentActivity } from '@microsoft/teams.api';
import { ILogger } from '@microsoft/teams.common';

import { ApiClient } from './api';
import { HttpStream } from './http/http-stream';
import { IStreamer, IActivitySender } from './types';

Expand All @@ -10,13 +11,14 @@ import { IStreamer, IActivitySender } from './types';
*/
export class ActivitySender implements IActivitySender {
constructor(
private client: HttpClient,
// ApiClient is serviceUrl-bound, so send/stream need a per-conversation client.
private getApiClient: (serviceUrl: string) => ApiClient,
private logger: ILogger
) { }

async send(activity: ActivityParams, ref: ConversationReference): Promise<SentActivity> {
// Create API client for this conversation's service URL
const api = new Client(ref.serviceUrl, this.client);
const api = this.getApiClient(ref.serviceUrl);

// Merge activity with conversation reference
activity = {
Expand Down Expand Up @@ -48,7 +50,7 @@ export class ActivitySender implements IActivitySender {

createStream(ref: ConversationReference): IStreamer {
// Create API client for this conversation's service URL
const api = new Client(ref.serviceUrl, this.client);
const api = this.getApiClient(ref.serviceUrl);
return new HttpStream(api, ref, this.logger);
}
}
8 changes: 2 additions & 6 deletions packages/apps/src/app.oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ export async function onTokenExchange<TPlugin extends IPlugin>(
});

ctx.userGraph = new GraphClient(
this.client.clone({
token: token.token,
}),
this.client.clone({ token: token.token }),
{ baseUrlRoot: this.graphBaseUrl }
);

Expand Down Expand Up @@ -86,9 +84,7 @@ export async function onVerifyState<TPlugin extends IPlugin>(
});

ctx.userGraph = new GraphClient(
this.client.clone({
token: token.token,
}),
this.client.clone({ token: token.token }),
{ baseUrlRoot: this.graphBaseUrl }
);

Expand Down
17 changes: 11 additions & 6 deletions packages/apps/src/app.process.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Activity, ActivityLike, ConversationReference, InvokeResponse, isInvokeResponse } from '@microsoft/teams.api';
import { Client as GraphClient } from '@microsoft/teams.graph';

import { ApiClient, GraphClient } from './api';
import { ApiClient } from './api';
import { App } from './app';
import { ActivityContext, IActivityContext } from './contexts';
import { IActivityEvent } from './events';
Expand Down Expand Up @@ -38,18 +39,22 @@ export async function $process<TPlugin extends IPlugin>(

try {
userToken = await this.getUserToken(activity.channelId, activity.from.id);
} catch (err) {
} catch {
// noop
}

const client = this.client.clone();
const apiClient = new ApiClient(serviceUrl, this.client.clone({ token: () => this.getBotToken() }), this.options.apiClientSettings);
const apiClient = new ApiClient(
serviceUrl,
this.client.clone({ token: () => this.getBotToken() }),
this.options.apiClientSettings,
this.cloud
);
const userGraph = new GraphClient(
client.clone({ token: () => userToken }),
this.client.clone({ token: () => userToken }),
{ baseUrlRoot: this.graphBaseUrl }
);
const appGraph = new GraphClient(
client.clone({ token: () => this.getAppGraphToken(activity.conversation.tenantId ?? 'common') }),
this.client.clone({ token: () => this.getAppGraphToken(activity.conversation?.tenantId ?? 'common') }),
{ baseUrlRoot: this.graphBaseUrl }
);

Expand Down
42 changes: 21 additions & 21 deletions packages/apps/src/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,31 +68,29 @@ describe('App', () => {
});

it('should acquire bot token via TokenManager', async () => {
const mockAcquireToken = jest.fn().mockResolvedValue({
accessToken: mockBotToken,
await app.stop();
app = new TestApp({
httpServerAdapter: new TestAdapter(),
clientId: 'test-client-id',
tenantId: 'test-tenant-id',
token: jest.fn().mockResolvedValue(mockBotToken),
});

// @ts-expect-error - accessing private method for testing
jest.spyOn(app.tokenManager, 'getConfidentialClient').mockReturnValue({
acquireTokenByClientCredential: mockAcquireToken,
} as any);

const token = await app.testGetBotToken();

expect(token).toBeInstanceOf(JsonWebToken);
expect(token?.toString()).toBe(mockBotToken);
});

it('should acquire graph token via TokenManager', async () => {
const mockAcquireToken = jest.fn().mockResolvedValue({
accessToken: mockGraphToken,
await app.stop();
app = new TestApp({
httpServerAdapter: new TestAdapter(),
clientId: 'test-client-id',
tenantId: 'test-tenant-id',
token: jest.fn().mockResolvedValue(mockGraphToken),
});

// @ts-expect-error - accessing private method for testing
jest.spyOn(app.tokenManager, 'getConfidentialClient').mockReturnValue({
acquireTokenByClientCredential: mockAcquireToken,
} as any);

const token = await app.testGetAppGraphToken();

expect(token).toBeInstanceOf(JsonWebToken);
Expand All @@ -112,16 +110,18 @@ describe('App', () => {
});

it('should not prefetch tokens on start', async () => {
const mockAcquireToken = jest.fn();

// @ts-expect-error - accessing private method for testing
jest.spyOn(app.tokenManager, 'getConfidentialClient').mockReturnValue({
acquireTokenByClientCredential: mockAcquireToken,
} as any);
await app.stop();
const token = jest.fn().mockResolvedValue(mockBotToken);
app = new TestApp({
httpServerAdapter: new TestAdapter(),
clientId: 'test-client-id',
tenantId: 'test-tenant-id',
token,
});

await app.start();

expect(mockAcquireToken).not.toHaveBeenCalled();
expect(token).not.toHaveBeenCalled();
});
});

Expand Down
Loading
Loading