@microsoft/agents-hosting 0.2.14 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +17 -1
  2. package/dist/src/app/agentApplication.d.ts +16 -4
  3. package/dist/src/app/agentApplication.js +22 -12
  4. package/dist/src/app/agentApplication.js.map +1 -1
  5. package/dist/src/app/agentApplicationBuilder.d.ts +3 -4
  6. package/dist/src/app/agentApplicationBuilder.js +7 -7
  7. package/dist/src/app/agentApplicationBuilder.js.map +1 -1
  8. package/dist/src/app/agentApplicationOptions.d.ts +26 -2
  9. package/dist/src/app/appRoute.d.ts +6 -0
  10. package/dist/src/app/attachmentDownloader.d.ts +18 -0
  11. package/dist/src/app/attachmentDownloader.js +18 -0
  12. package/dist/src/app/attachmentDownloader.js.map +1 -1
  13. package/dist/src/app/conversationUpdateEvents.d.ts +6 -0
  14. package/dist/src/app/index.d.ts +1 -1
  15. package/dist/src/app/index.js +1 -1
  16. package/dist/src/app/index.js.map +1 -1
  17. package/dist/src/app/inputFileDownloader.d.ts +22 -0
  18. package/dist/src/app/oauth/authorization.d.ts +87 -0
  19. package/dist/src/app/oauth/authorization.js +135 -0
  20. package/dist/src/app/oauth/authorization.js.map +1 -0
  21. package/dist/src/app/routeHandler.d.ts +8 -0
  22. package/dist/src/app/routeSelector.d.ts +9 -0
  23. package/dist/src/app/turnState.d.ts +150 -0
  24. package/dist/src/app/turnState.js +125 -0
  25. package/dist/src/app/turnState.js.map +1 -1
  26. package/dist/src/auth/authConfiguration.d.ts +24 -0
  27. package/dist/src/auth/authConfiguration.js.map +1 -1
  28. package/dist/src/auth/request.d.ts +12 -0
  29. package/dist/src/baseAdapter.d.ts +17 -0
  30. package/dist/src/baseAdapter.js +17 -0
  31. package/dist/src/baseAdapter.js.map +1 -1
  32. package/dist/src/cards/cardFactory.d.ts +3 -0
  33. package/dist/src/cards/cardFactory.js +3 -0
  34. package/dist/src/cards/cardFactory.js.map +1 -1
  35. package/dist/src/cards/o365ConnectorCardActionBase.d.ts +8 -0
  36. package/dist/src/oauth/oAuthFlow.d.ts +32 -3
  37. package/dist/src/oauth/oAuthFlow.js +38 -14
  38. package/dist/src/oauth/oAuthFlow.js.map +1 -1
  39. package/dist/src/oauth/userTokenClient.d.ts +2 -2
  40. package/dist/src/oauth/userTokenClient.js +3 -3
  41. package/dist/src/oauth/userTokenClient.js.map +1 -1
  42. package/dist/src/state/agentStatePropertyAccesor.d.ts +1 -1
  43. package/dist/src/state/agentStatePropertyAccesor.js +1 -1
  44. package/dist/src/statusCodes.d.ts +39 -0
  45. package/dist/src/statusCodes.js +39 -0
  46. package/dist/src/statusCodes.js.map +1 -1
  47. package/dist/src/tokenResponseEventName.d.ts +3 -0
  48. package/dist/src/tokenResponseEventName.js +3 -0
  49. package/dist/src/tokenResponseEventName.js.map +1 -1
  50. package/package.json +4 -4
  51. package/src/app/agentApplication.ts +24 -13
  52. package/src/app/agentApplicationBuilder.ts +8 -8
  53. package/src/app/agentApplicationOptions.ts +33 -2
  54. package/src/app/appRoute.ts +7 -0
  55. package/src/app/attachmentDownloader.ts +18 -0
  56. package/src/app/conversationUpdateEvents.ts +6 -0
  57. package/src/app/index.ts +1 -1
  58. package/src/app/inputFileDownloader.ts +24 -0
  59. package/src/app/oauth/authorization.ts +162 -0
  60. package/src/app/routeHandler.ts +8 -0
  61. package/src/app/routeSelector.ts +9 -0
  62. package/src/app/turnState.ts +151 -1
  63. package/src/auth/authConfiguration.ts +32 -1
  64. package/src/auth/request.ts +15 -0
  65. package/src/baseAdapter.ts +18 -0
  66. package/src/cards/cardFactory.ts +3 -0
  67. package/src/cards/o365ConnectorCardActionBase.ts +8 -0
  68. package/src/oauth/oAuthFlow.ts +59 -18
  69. package/src/oauth/userTokenClient.ts +4 -4
  70. package/src/state/agentStatePropertyAccesor.ts +1 -1
  71. package/src/statusCodes.ts +51 -0
  72. package/src/tokenResponseEventName.ts +3 -0
  73. package/dist/src/app/oauth/userIdentity.d.ts +0 -43
  74. package/dist/src/app/oauth/userIdentity.js +0 -54
  75. package/dist/src/app/oauth/userIdentity.js.map +0 -1
  76. package/src/app/oauth/userIdentity.ts +0 -78
@@ -20,21 +20,47 @@ const TEMP_SCOPE = 'temp'
20
20
 
21
21
  const SSO_SCOPE = 'sso'
22
22
 
23
+ /**
24
+ * Default interface for conversation state.
25
+ * Extend this interface to define custom conversation state properties.
26
+ */
23
27
  export interface DefaultConversationState {}
24
28
 
29
+ /**
30
+ * Default interface for user state.
31
+ * Extend this interface to define custom user state properties.
32
+ */
25
33
  export interface DefaultUserState {}
34
+
35
+ /**
36
+ * Default interface for temporary state that persists only during the current turn.
37
+ * Contains properties used for handling user input, file attachments, and OAuth flows.
38
+ */
26
39
  export interface DefaultTempState {
40
+ /** Current user input text */
27
41
  input: string;
42
+ /** Collection of files attached to the current message */
28
43
  inputFiles: InputFile[];
44
+ /** Last output text generated by the agent */
29
45
  lastOutput: string;
46
+ /** Results from actions performed by the agent */
30
47
  actionOutputs: Record<string, string>;
48
+ /** OAuth tokens keyed by provider ID */
31
49
  authTokens: { [key: string]: string };
50
+ /** Flag to prevent duplicate token exchanges */
32
51
  duplicateTokenExchange?: boolean;
33
52
  }
34
53
 
54
+ /**
55
+ * Default interface for Single Sign-On (SSO) state.
56
+ * Contains properties used for managing OAuth authentication flows.
57
+ */
35
58
  export interface DefaultSSOState {
59
+ /** Whether an OAuth flow has been started */
36
60
  flowStarted: boolean;
61
+ /** The user's access token */
37
62
  userToken: string;
63
+ /** Expiration timestamp for the current flow */
38
64
  flowExpires: number;
39
65
  }
40
66
 
@@ -67,8 +93,11 @@ export interface DefaultSSOState {
67
93
  * }
68
94
  * }
69
95
  * ```
96
+ * @template TConversationState - Type for conversation-scoped state
97
+ * @template TUserState - Type for user-scoped state
98
+ * @template TTempState - Type for temporary state that exists only for the current turn
99
+ * @template TSSOState - Type for Single Sign-On (SSO) state
70
100
  */
71
-
72
101
  export class TurnState<
73
102
  TConversationState = DefaultConversationState,
74
103
  TUserState = DefaultUserState,
@@ -80,6 +109,12 @@ export class TurnState<
80
109
  private _loadingPromise?: Promise<boolean>
81
110
  private _stateNotLoadedString = 'TurnState hasn\'t been loaded. Call load() first.'
82
111
 
112
+ /**
113
+ * Gets the conversation-scoped state.
114
+ * This state is shared by all users in the same conversation.
115
+ * @returns The conversation state object
116
+ * @throws Error if state hasn't been loaded
117
+ */
83
118
  public get conversation (): TConversationState {
84
119
  const scope = this.getScope(CONVERSATION_SCOPE)
85
120
  if (!scope) {
@@ -88,6 +123,11 @@ export class TurnState<
88
123
  return scope.value as TConversationState
89
124
  }
90
125
 
126
+ /**
127
+ * Sets the conversation-scoped state.
128
+ * @param value - The new conversation state object
129
+ * @throws Error if state hasn't been loaded
130
+ */
91
131
  public set conversation (value: TConversationState) {
92
132
  const scope = this.getScope(CONVERSATION_SCOPE)
93
133
  if (!scope) {
@@ -96,10 +136,20 @@ export class TurnState<
96
136
  scope.replace(value as Record<string, unknown>)
97
137
  }
98
138
 
139
+ /**
140
+ * Gets whether the state has been loaded from storage
141
+ * @returns True if the state has been loaded, false otherwise
142
+ */
99
143
  public get isLoaded (): boolean {
100
144
  return this._isLoaded
101
145
  }
102
146
 
147
+ /**
148
+ * Gets the temporary state for the current turn.
149
+ * This state is not persisted between turns.
150
+ * @returns The temporary state object
151
+ * @throws Error if state hasn't been loaded
152
+ */
103
153
  public get temp (): TTempState {
104
154
  const scope = this.getScope(TEMP_SCOPE)
105
155
  if (!scope) {
@@ -108,6 +158,11 @@ export class TurnState<
108
158
  return scope.value as TTempState
109
159
  }
110
160
 
161
+ /**
162
+ * Sets the temporary state for the current turn.
163
+ * @param value - The new temporary state object
164
+ * @throws Error if state hasn't been loaded
165
+ */
111
166
  public set temp (value: TTempState) {
112
167
  const scope = this.getScope(TEMP_SCOPE)
113
168
  if (!scope) {
@@ -116,6 +171,12 @@ export class TurnState<
116
171
  scope.replace(value as Record<string, unknown>)
117
172
  }
118
173
 
174
+ /**
175
+ * Gets the user-scoped state.
176
+ * This state is unique to each user and persists across conversations.
177
+ * @returns The user state object
178
+ * @throws Error if state hasn't been loaded
179
+ */
119
180
  public get user (): TUserState {
120
181
  const scope = this.getScope(USER_SCOPE)
121
182
  if (!scope) {
@@ -124,6 +185,11 @@ export class TurnState<
124
185
  return scope.value as TUserState
125
186
  }
126
187
 
188
+ /**
189
+ * Sets the user-scoped state.
190
+ * @param value - The new user state object
191
+ * @throws Error if state hasn't been loaded
192
+ */
127
193
  public set user (value: TUserState) {
128
194
  const scope = this.getScope(USER_SCOPE)
129
195
  if (!scope) {
@@ -132,6 +198,12 @@ export class TurnState<
132
198
  scope.replace(value as Record<string, unknown>)
133
199
  }
134
200
 
201
+ /**
202
+ * Gets the Single Sign-On (SSO) state.
203
+ * This state is used to manage OAuth authentication flows.
204
+ * @returns The SSO state object
205
+ * @throws Error if state hasn't been loaded
206
+ */
135
207
  public get sso (): TSSOState {
136
208
  const scope = this.getScope(SSO_SCOPE)
137
209
  if (!scope) {
@@ -140,6 +212,11 @@ export class TurnState<
140
212
  return scope.value as TSSOState
141
213
  }
142
214
 
215
+ /**
216
+ * Sets the Single Sign-On (SSO) state.
217
+ * @param value - The new SSO state object
218
+ * @throws Error if state hasn't been loaded
219
+ */
143
220
  public set sso (value: TSSOState) {
144
221
  const scope = this.getScope(SSO_SCOPE)
145
222
  if (!scope) {
@@ -148,6 +225,11 @@ export class TurnState<
148
225
  scope.replace(value as Record<string, unknown>)
149
226
  }
150
227
 
228
+ /**
229
+ * Marks the conversation state for deletion.
230
+ * The state will be deleted from storage on the next call to save().
231
+ * @throws Error if state hasn't been loaded
232
+ */
151
233
  public deleteConversationState (): void {
152
234
  const scope = this.getScope(CONVERSATION_SCOPE)
153
235
  if (!scope) {
@@ -156,6 +238,11 @@ export class TurnState<
156
238
  scope.delete()
157
239
  }
158
240
 
241
+ /**
242
+ * Marks the temporary state for deletion.
243
+ * Since temporary state is not persisted, this just clears the in-memory object.
244
+ * @throws Error if state hasn't been loaded
245
+ */
159
246
  public deleteTempState (): void {
160
247
  const scope = this.getScope(TEMP_SCOPE)
161
248
  if (!scope) {
@@ -164,6 +251,11 @@ export class TurnState<
164
251
  scope.delete()
165
252
  }
166
253
 
254
+ /**
255
+ * Marks the user state for deletion.
256
+ * The state will be deleted from storage on the next call to save().
257
+ * @throws Error if state hasn't been loaded
258
+ */
167
259
  public deleteUserState (): void {
168
260
  const scope = this.getScope(USER_SCOPE)
169
261
  if (!scope) {
@@ -172,10 +264,20 @@ export class TurnState<
172
264
  scope.delete()
173
265
  }
174
266
 
267
+ /**
268
+ * Gets a specific state scope by name.
269
+ * @param scope - The name of the scope to retrieve
270
+ * @returns The state entry for the scope, or undefined if not found
271
+ */
175
272
  public getScope (scope: string): TurnStateEntry | undefined {
176
273
  return this._scopes[scope]
177
274
  }
178
275
 
276
+ /**
277
+ * Deletes a value from state by dot-notation path.
278
+ * Format: "scope.property" or just "property" (defaults to temp scope)
279
+ * @param path - The path to the value to delete
280
+ */
179
281
  public deleteValue (path: string): void {
180
282
  const { scope, name } = this.getScopeAndName(path)
181
283
  if (Object.prototype.hasOwnProperty.call(scope.value, name)) {
@@ -183,21 +285,47 @@ export class TurnState<
183
285
  }
184
286
  }
185
287
 
288
+ /**
289
+ * Checks if a value exists in state by dot-notation path.
290
+ * Format: "scope.property" or just "property" (defaults to temp scope)
291
+ * @param path - The path to check
292
+ * @returns True if the value exists, false otherwise
293
+ */
186
294
  public hasValue (path: string): boolean {
187
295
  const { scope, name } = this.getScopeAndName(path)
188
296
  return Object.prototype.hasOwnProperty.call(scope.value, name)
189
297
  }
190
298
 
299
+ /**
300
+ * Gets a value from state by dot-notation path.
301
+ * Format: "scope.property" or just "property" (defaults to temp scope)
302
+ * @template TValue - The type of the value to retrieve
303
+ * @param path - The path to the value
304
+ * @returns The value at the specified path
305
+ */
191
306
  public getValue<TValue = unknown>(path: string): TValue {
192
307
  const { scope, name } = this.getScopeAndName(path)
193
308
  return scope.value[name] as TValue
194
309
  }
195
310
 
311
+ /**
312
+ * Sets a value in state by dot-notation path.
313
+ * Format: "scope.property" or just "property" (defaults to temp scope)
314
+ * @param path - The path to set
315
+ * @param value - The value to set
316
+ */
196
317
  public setValue (path: string, value: unknown): void {
197
318
  const { scope, name } = this.getScopeAndName(path)
198
319
  scope.value[name] = value
199
320
  }
200
321
 
322
+ /**
323
+ * Loads state from storage into memory.
324
+ * @param context - The turn context
325
+ * @param storage - Optional storage provider (if not provided, state will be in-memory only)
326
+ * @param force - If true, forces a reload from storage even if state is already loaded
327
+ * @returns Promise that resolves to true if state was loaded, false if it was already loaded
328
+ */
201
329
  public load (context: TurnContext, storage?: Storage, force: boolean = false): Promise<boolean> {
202
330
  if (this._isLoaded && !force) {
203
331
  return Promise.resolve(false)
@@ -242,6 +370,14 @@ export class TurnState<
242
370
  return this._loadingPromise
243
371
  }
244
372
 
373
+ /**
374
+ * Saves state changes to storage.
375
+ * Only changed scopes will be persisted.
376
+ * @param context - The turn context
377
+ * @param storage - Optional storage provider (if not provided, state changes won't be persisted)
378
+ * @returns Promise that resolves when the save operation is complete
379
+ * @throws Error if state hasn't been loaded
380
+ */
245
381
  public async save (context: TurnContext, storage?: Storage): Promise<void> {
246
382
  if (!this._isLoaded && this._loadingPromise) {
247
383
  await this._loadingPromise
@@ -291,6 +427,13 @@ export class TurnState<
291
427
  }
292
428
  }
293
429
 
430
+ /**
431
+ * Computes the storage keys for each scope based on the turn context.
432
+ * Override this method in derived classes to add or modify storage keys.
433
+ * @param context - The turn context
434
+ * @returns Promise that resolves to a dictionary of scope names to storage keys
435
+ * @protected
436
+ */
294
437
  protected onComputeStorageKeys (context: TurnContext): Promise<Record<string, string>> {
295
438
  const activity = context.activity
296
439
  const channelId = activity?.channelId
@@ -321,6 +464,13 @@ export class TurnState<
321
464
  return Promise.resolve(keys)
322
465
  }
323
466
 
467
+ /**
468
+ * Parses a dot-notation path into scope and property name.
469
+ * If no scope is specified, defaults to the temp scope.
470
+ * @param path - The path to parse (format: "scope.property" or just "property")
471
+ * @returns Object containing the scope entry and property name
472
+ * @private
473
+ */
324
474
  private getScopeAndName (path: string): { scope: TurnStateEntry; name: string } {
325
475
  const parts = path.split('.')
326
476
  if (parts.length > 2) {
@@ -7,13 +7,44 @@
7
7
  * Represents the authentication configuration.
8
8
  */
9
9
  export interface AuthConfiguration {
10
+ /**
11
+ * The tenant ID for the authentication configuration.
12
+ */
10
13
  tenantId?: string
14
+
15
+ /**
16
+ * The client ID for the authentication configuration. Required in production.
17
+ */
11
18
  clientId?: string
19
+
20
+ /**
21
+ * The client secret for the authentication configuration.
22
+ */
12
23
  clientSecret?: string
24
+
25
+ /**
26
+ * The path to the certificate PEM file.
27
+ */
13
28
  certPemFile?: string
29
+
30
+ /**
31
+ * The path to the certificate key file.
32
+ */
14
33
  certKeyFile?: string
34
+
35
+ /**
36
+ * A list of valid issuers for the authentication configuration.
37
+ */
15
38
  issuers: string[]
16
- connectionName?: string,
39
+
40
+ /**
41
+ * The connection name for the authentication configuration.
42
+ */
43
+ connectionName?: string
44
+
45
+ /**
46
+ * The FIC (First-Party Integration Channel) client ID.
47
+ */
17
48
  FICClientId?: string
18
49
  }
19
50
 
@@ -13,8 +13,23 @@ export interface Request<
13
13
  Body extends Record<string, unknown> = Record<string, unknown>,
14
14
  Headers extends Record<string, string[] | string | undefined> = Record<string, string[] | string | undefined>
15
15
  > {
16
+ /**
17
+ * The body of the HTTP request, containing parsed data.
18
+ */
16
19
  body?: Body
20
+
21
+ /**
22
+ * The headers of the HTTP request, represented as key-value pairs.
23
+ */
17
24
  headers: Headers
25
+
26
+ /**
27
+ * The HTTP method of the request (e.g., GET, POST, PUT, DELETE).
28
+ */
18
29
  method?: string
30
+
31
+ /**
32
+ * The user information extracted from a JWT payload, if available.
33
+ */
19
34
  user?: JwtPayload
20
35
  }
@@ -20,6 +20,9 @@ const logger = debug('agents:base-adapter')
20
20
  * Base class for all adapters, providing middleware and error handling capabilities.
21
21
  */
22
22
  export abstract class BaseAdapter {
23
+ /**
24
+ * The middleware set used to process the pipeline of middleware handlers.
25
+ */
23
26
  protected middleware: MiddlewareSet = new MiddlewareSet()
24
27
 
25
28
  private turnError: (context: TurnContext, error: Error) => Promise<void> = async (context: TurnContext, error: Error) => {
@@ -42,7 +45,14 @@ export abstract class BaseAdapter {
42
45
  readonly ConnectorClientKey = Symbol('ConnectorClient')
43
46
  readonly OAuthScopeKey = Symbol('OAuthScope')
44
47
 
48
+ /**
49
+ * The authentication provider used for token management.
50
+ */
45
51
  authProvider: AuthProvider = new MsalTokenProvider()
52
+
53
+ /**
54
+ * The authentication configuration for the adapter.
55
+ */
46
56
  authConfig: AuthConfiguration = { issuers: [] }
47
57
 
48
58
  /**
@@ -103,10 +113,18 @@ export abstract class BaseAdapter {
103
113
  */
104
114
  abstract getAttachment (attachmentId: string, viewId: string): Promise<NodeJS.ReadableStream>
105
115
 
116
+ /**
117
+ * Gets the error handler for the adapter.
118
+ * @returns The current error handler function.
119
+ */
106
120
  get onTurnError (): (context: TurnContext, error: Error) => Promise<void> {
107
121
  return this.turnError
108
122
  }
109
123
 
124
+ /**
125
+ * Sets the error handler for the adapter.
126
+ * @param value - The error handler function to set.
127
+ */
110
128
  set onTurnError (value: (context: TurnContext, error: Error) => Promise<void>) {
111
129
  this.turnError = value
112
130
  }
@@ -21,6 +21,9 @@ import { SigninCard } from './signinCard'
21
21
  * Factory class for creating various types of cards.
22
22
  */
23
23
  export class CardFactory {
24
+ /**
25
+ * The content types supported by the card factory.
26
+ */
24
27
  static contentTypes: any = {
25
28
  adaptiveCard: 'application/vnd.microsoft.card.adaptive',
26
29
  animationCard: 'application/vnd.microsoft.card.animation',
@@ -3,6 +3,14 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ /**
7
+ * Defines the possible types of actions in an O365 connector card.
8
+ *
9
+ * - `ViewAction`: Represents an action to view content.
10
+ * - `OpenUri`: Represents an action to open a URI.
11
+ * - `HttpPOST`: Represents an action to make an HTTP POST request.
12
+ * - `ActionCard`: Represents an action that opens a card with additional actions or inputs.
13
+ */
6
14
  export type O365ConnectorCardActionType = 'ViewAction' | 'OpenUri' | 'HttpPOST' | 'ActionCard'
7
15
 
8
16
  /**
@@ -1,14 +1,13 @@
1
1
  // Copyright (c) Microsoft Corporation. All rights reserved.
2
2
  // Licensed under the MIT License.
3
3
  import { debug } from './../logger'
4
- import { Activity, ActivityTypes, Attachment } from '@microsoft/agents-activity'
4
+ import { ActivityTypes, Attachment } from '@microsoft/agents-activity'
5
5
  import {
6
6
  CardFactory,
7
7
  AgentStatePropertyAccessor,
8
8
  UserState,
9
9
  TurnContext,
10
10
  MessageFactory,
11
- SigningResource,
12
11
  TokenExchangeRequest,
13
12
  UserTokenClient
14
13
  } from '../'
@@ -25,28 +24,72 @@ interface TokenVerifyState {
25
24
  state: string
26
25
  }
27
26
  /**
28
- * Manages the OAuth flow for Teams.
27
+ * Manages the OAuth flow
29
28
  */
30
29
  export class OAuthFlow {
31
- userTokenClient?: UserTokenClient
30
+ /**
31
+ * The user token client used for managing user tokens.
32
+ */
33
+ userTokenClient: UserTokenClient
34
+
35
+ /**
36
+ * The current state of the OAuth flow.
37
+ */
32
38
  state: FlowState | null
39
+
40
+ /**
41
+ * The accessor for managing the flow state in user state.
42
+ */
33
43
  flowStateAccessor: AgentStatePropertyAccessor<FlowState | null>
44
+
45
+ /**
46
+ * The ID of the token exchange request, used to deduplicate requests.
47
+ */
34
48
  tokenExchangeId: string | null = null
49
+
50
+ /**
51
+ * The name of the OAuth connection.
52
+ */
35
53
  absOauthConnectionName: string
54
+
55
+ /**
56
+ * The title of the OAuth card.
57
+ */
58
+ cardTitle: string = 'Sign in'
59
+
60
+ /**
61
+ * The text of the OAuth card.
62
+ */
63
+ cardText: string = 'login'
64
+
36
65
  /**
37
66
  * Creates a new instance of OAuthFlow.
38
67
  * @param userState The user state.
39
68
  */
40
- constructor (userState: UserState, absOauthConnectionName: string, tokenClient?: UserTokenClient) {
41
- this.state = null
69
+ constructor (userState: UserState, absOauthConnectionName: string, tokenClient?: UserTokenClient, cardTitle?: string, cardText?: string) {
70
+ this.state = new FlowState()
42
71
  this.flowStateAccessor = userState.createProperty('flowState')
43
72
  this.absOauthConnectionName = absOauthConnectionName
44
- this.userTokenClient = tokenClient
73
+ this.userTokenClient = tokenClient ?? null!
74
+ this.cardTitle = cardTitle ?? this.cardTitle
75
+ this.cardText = cardText ?? this.cardText
45
76
  }
46
77
 
78
+ /**
79
+ * Retrieves the user token from the user token service.
80
+ * @param context The turn context containing the activity information.
81
+ * @returns A promise that resolves to the user token response.
82
+ * @throws Will throw an error if the channelId or from properties are not set in the activity.
83
+ */
47
84
  public async getUserToken (context: TurnContext): Promise<TokenResponse> {
48
85
  await this.initializeTokenClient(context)
49
- return await this.userTokenClient?.getUserToken(this.absOauthConnectionName, context.activity.channelId!, context.activity.from?.id!)!
86
+ logger.info('Get token from user token service')
87
+ const activity = context.activity
88
+ if (activity.channelId && activity.from && activity.from.id) {
89
+ return await this.userTokenClient.getUserToken(this.absOauthConnectionName, activity.channelId, activity.from.id)
90
+ } else {
91
+ throw new Error('UserTokenService requires channelId and from to be set')
92
+ }
50
93
  }
51
94
 
52
95
  /**
@@ -55,16 +98,14 @@ export class OAuthFlow {
55
98
  * @returns A promise that resolves to the user token.
56
99
  */
57
100
  public async beginFlow (context: TurnContext): Promise<TokenResponse> {
58
- logger.info('Starting OAuth flow')
59
101
  this.state = await this.getUserState(context)
60
-
61
- const authConfig = context.adapter.authConfig
62
102
  if (this.absOauthConnectionName === '') {
63
- throw new Error('connectionName is not set in the auth config, review your environment variables')
103
+ throw new Error('connectionName is not set')
64
104
  }
105
+ logger.info('Starting OAuth flow for connectionName:', this.absOauthConnectionName)
65
106
  await this.initializeTokenClient(context)
66
107
 
67
- const tokenResponse = await this.userTokenClient!.getUserToken(this.absOauthConnectionName, context.activity.channelId!, context.activity.from?.id!)
108
+ const tokenResponse = await this.userTokenClient.getUserToken(this.absOauthConnectionName, context.activity.channelId!, context.activity.from?.id!)
68
109
  if (tokenResponse?.status === TokenRequestStatus.Success) {
69
110
  this.state.flowStarted = false
70
111
  this.state.flowExpires = 0
@@ -73,10 +114,10 @@ export class OAuthFlow {
73
114
  return tokenResponse
74
115
  }
75
116
 
76
- const signingResource: SigningResource = await this.userTokenClient!.getSignInResource(authConfig.clientId!, this.absOauthConnectionName, context.activity)
77
- const oCard: Attachment = CardFactory.oauthCard(this.absOauthConnectionName, 'Sign in', 'login', signingResource)
78
- const cardActivity : Activity = MessageFactory.attachment(oCard)
79
- await context.sendActivity(cardActivity)
117
+ const authConfig = context.adapter.authConfig
118
+ const signingResource = await this.userTokenClient.getSignInResource(authConfig.clientId!, this.absOauthConnectionName, context.activity.getConversationReference(), context.activity.relatesTo)
119
+ const oCard: Attachment = CardFactory.oauthCard(this.absOauthConnectionName, this.cardTitle, this.cardText, signingResource)
120
+ await context.sendActivity(MessageFactory.attachment(oCard))
80
121
  this.state.flowStarted = true
81
122
  this.state.flowExpires = Date.now() + 30000
82
123
  await this.flowStateAccessor.set(context, this.state)
@@ -166,7 +207,7 @@ export class OAuthFlow {
166
207
  }
167
208
 
168
209
  private async initializeTokenClient (context: TurnContext) {
169
- if (this.userTokenClient === undefined) {
210
+ if (this.userTokenClient === undefined || this.userTokenClient === null) {
170
211
  const scope = 'https://api.botframework.com'
171
212
  const accessToken = await context.adapter.authProvider.getAccessToken(context.adapter.authConfig, scope)
172
213
  this.userTokenClient = new UserTokenClient(accessToken)
@@ -3,7 +3,7 @@
3
3
 
4
4
  import axios, { AxiosInstance } from 'axios'
5
5
  import { SigningResource } from './signingResource'
6
- import { Activity } from '@microsoft/agents-activity'
6
+ import { ConversationReference } from '@microsoft/agents-activity'
7
7
  import { debug } from '../logger'
8
8
  import { TokenExchangeRequest } from './tokenExchangeRequest'
9
9
  import { normalizeTokenExchangeState } from '../activityWireCompat'
@@ -86,12 +86,12 @@ export class UserTokenClient {
86
86
  * @param activity The activity.
87
87
  * @returns A promise that resolves to the signing resource.
88
88
  */
89
- async getSignInResource (appId: string, cnxName: string, activity: Activity) : Promise<SigningResource> {
89
+ async getSignInResource (appId: string, cnxName: string, conversationReference: ConversationReference, relatesTo?: ConversationReference) : Promise<SigningResource> {
90
90
  try {
91
91
  const tokenExchangeState = {
92
92
  connectionName: cnxName,
93
- conversation: activity.getConversationReference(),
94
- relatesTo: activity.RelatesTo,
93
+ conversation: conversationReference,
94
+ relatesTo,
95
95
  msAppId: appId
96
96
  }
97
97
  const tokenExchangeStateNormalized = normalizeTokenExchangeState(tokenExchangeState)
@@ -84,7 +84,7 @@ export interface StatePropertyAccessor<T = any> {
84
84
  * - Type checking for properties (when using TypeScript)
85
85
  * - Ensuring properties exist before access
86
86
  *
87
- * Property accessors are created through the AgentState.createProperty() method:
87
+ * Property accessors are created through the {@link AgentState.createProperty} method:
88
88
  *
89
89
  * ```typescript
90
90
  * // Create a property accessor for a user profile
@@ -4,17 +4,68 @@
4
4
  */
5
5
 
6
6
  export enum StatusCodes {
7
+ /**
8
+ * The request has succeeded.
9
+ */
7
10
  OK = 200,
11
+
12
+ /**
13
+ * The request has been fulfilled and resulted in a new resource being created.
14
+ */
8
15
  CREATED = 201,
16
+
17
+ /**
18
+ * Indicates multiple options for the resource that the client may follow.
19
+ */
9
20
  MULTIPLE_CHOICES = 300,
21
+
22
+ /**
23
+ * The server cannot or will not process the request due to a client error.
24
+ */
10
25
  BAD_REQUEST = 400,
26
+
27
+ /**
28
+ * The request requires user authentication.
29
+ */
11
30
  UNAUTHORIZED = 401,
31
+
32
+ /**
33
+ * The requested resource could not be found.
34
+ */
12
35
  NOT_FOUND = 404,
36
+
37
+ /**
38
+ * The request method is not allowed for the requested resource.
39
+ */
13
40
  METHOD_NOT_ALLOWED = 405,
41
+
42
+ /**
43
+ * The request could not be completed due to a conflict with the current state of the resource.
44
+ */
14
45
  CONFLICT = 409,
46
+
47
+ /**
48
+ * The server does not meet one of the preconditions specified by the client.
49
+ */
15
50
  PRECONDITION_FAILED = 412,
51
+
52
+ /**
53
+ * The client should switch to a different protocol.
54
+ */
16
55
  UPGRADE_REQUIRED = 426,
56
+
57
+ /**
58
+ * The server encountered an unexpected condition that prevented it from fulfilling the request.
59
+ */
17
60
  INTERNAL_SERVER_ERROR = 500,
61
+
62
+ /**
63
+ * The server does not support the functionality required to fulfill the request.
64
+ */
18
65
  NOT_IMPLEMENTED = 501,
66
+
67
+ /**
68
+ * The server received an invalid response from the upstream server.
69
+ */
19
70
  BAD_GATEWAY = 502,
20
71
  }