@microsoft/agents-hosting 0.2.14 → 0.4.1

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 (84) hide show
  1. package/README.md +17 -1
  2. package/dist/src/app/agentApplication.d.ts +27 -15
  3. package/dist/src/app/agentApplication.js +36 -31
  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 +128 -15
  24. package/dist/src/app/turnState.js +114 -16
  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/storage/fileStorage.d.ts +9 -0
  48. package/dist/src/storage/fileStorage.js +62 -0
  49. package/dist/src/storage/fileStorage.js.map +1 -0
  50. package/dist/src/storage/index.d.ts +1 -0
  51. package/dist/src/storage/index.js +1 -0
  52. package/dist/src/storage/index.js.map +1 -1
  53. package/dist/src/tokenResponseEventName.d.ts +3 -0
  54. package/dist/src/tokenResponseEventName.js +3 -0
  55. package/dist/src/tokenResponseEventName.js.map +1 -1
  56. package/package.json +4 -4
  57. package/src/app/agentApplication.ts +36 -32
  58. package/src/app/agentApplicationBuilder.ts +8 -8
  59. package/src/app/agentApplicationOptions.ts +33 -2
  60. package/src/app/appRoute.ts +7 -0
  61. package/src/app/attachmentDownloader.ts +18 -0
  62. package/src/app/conversationUpdateEvents.ts +6 -0
  63. package/src/app/index.ts +1 -1
  64. package/src/app/inputFileDownloader.ts +24 -0
  65. package/src/app/oauth/authorization.ts +162 -0
  66. package/src/app/routeHandler.ts +8 -0
  67. package/src/app/routeSelector.ts +9 -0
  68. package/src/app/turnState.ts +129 -33
  69. package/src/auth/authConfiguration.ts +32 -1
  70. package/src/auth/request.ts +15 -0
  71. package/src/baseAdapter.ts +18 -0
  72. package/src/cards/cardFactory.ts +3 -0
  73. package/src/cards/o365ConnectorCardActionBase.ts +8 -0
  74. package/src/oauth/oAuthFlow.ts +59 -18
  75. package/src/oauth/userTokenClient.ts +4 -4
  76. package/src/state/agentStatePropertyAccesor.ts +1 -1
  77. package/src/statusCodes.ts +51 -0
  78. package/src/storage/fileStorage.ts +59 -0
  79. package/src/storage/index.ts +1 -0
  80. package/src/tokenResponseEventName.ts +3 -0
  81. package/dist/src/app/oauth/userIdentity.d.ts +0 -43
  82. package/dist/src/app/oauth/userIdentity.js +0 -54
  83. package/dist/src/app/oauth/userIdentity.js.map +0 -1
  84. package/src/app/oauth/userIdentity.ts +0 -78
@@ -18,24 +18,25 @@ const USER_SCOPE = 'user'
18
18
 
19
19
  const TEMP_SCOPE = 'temp'
20
20
 
21
- const SSO_SCOPE = 'sso'
22
-
21
+ /**
22
+ * Default interface for conversation state.
23
+ * Extend this interface to define custom conversation state properties.
24
+ */
23
25
  export interface DefaultConversationState {}
24
26
 
27
+ /**
28
+ * Default interface for user state.
29
+ * Extend this interface to define custom user state properties.
30
+ */
25
31
  export interface DefaultUserState {}
32
+
33
+ /**
34
+ * Default interface for temporary state that persists only during the current turn.
35
+ * Contains properties used for handling user input, file attachments, and OAuth flows.
36
+ */
26
37
  export interface DefaultTempState {
27
- input: string;
38
+ /** Collection of files attached to the current message */
28
39
  inputFiles: InputFile[];
29
- lastOutput: string;
30
- actionOutputs: Record<string, string>;
31
- authTokens: { [key: string]: string };
32
- duplicateTokenExchange?: boolean;
33
- }
34
-
35
- export interface DefaultSSOState {
36
- flowStarted: boolean;
37
- userToken: string;
38
- flowExpires: number;
39
40
  }
40
41
 
41
42
  /**
@@ -67,19 +68,27 @@ export interface DefaultSSOState {
67
68
  * }
68
69
  * }
69
70
  * ```
71
+ * @template TConversationState - Type for conversation-scoped state
72
+ * @template TUserState - Type for user-scoped state
73
+ * @template TTempState - Type for temporary state that exists only for the current turn
74
+ * @template TSSOState - Type for Single Sign-On (SSO) state
70
75
  */
71
-
72
76
  export class TurnState<
73
77
  TConversationState = DefaultConversationState,
74
78
  TUserState = DefaultUserState,
75
- TTempState = DefaultTempState,
76
- TSSOState = DefaultSSOState
79
+ TTempState = DefaultTempState
77
80
  > implements AppMemory {
78
81
  private _scopes: Record<string, TurnStateEntry> = {}
79
82
  private _isLoaded = false
80
83
  private _loadingPromise?: Promise<boolean>
81
84
  private _stateNotLoadedString = 'TurnState hasn\'t been loaded. Call load() first.'
82
85
 
86
+ /**
87
+ * Gets the conversation-scoped state.
88
+ * This state is shared by all users in the same conversation.
89
+ * @returns The conversation state object
90
+ * @throws Error if state hasn't been loaded
91
+ */
83
92
  public get conversation (): TConversationState {
84
93
  const scope = this.getScope(CONVERSATION_SCOPE)
85
94
  if (!scope) {
@@ -88,6 +97,11 @@ export class TurnState<
88
97
  return scope.value as TConversationState
89
98
  }
90
99
 
100
+ /**
101
+ * Sets the conversation-scoped state.
102
+ * @param value - The new conversation state object
103
+ * @throws Error if state hasn't been loaded
104
+ */
91
105
  public set conversation (value: TConversationState) {
92
106
  const scope = this.getScope(CONVERSATION_SCOPE)
93
107
  if (!scope) {
@@ -96,10 +110,20 @@ export class TurnState<
96
110
  scope.replace(value as Record<string, unknown>)
97
111
  }
98
112
 
113
+ /**
114
+ * Gets whether the state has been loaded from storage
115
+ * @returns True if the state has been loaded, false otherwise
116
+ */
99
117
  public get isLoaded (): boolean {
100
118
  return this._isLoaded
101
119
  }
102
120
 
121
+ /**
122
+ * Gets the temporary state for the current turn.
123
+ * This state is not persisted between turns.
124
+ * @returns The temporary state object
125
+ * @throws Error if state hasn't been loaded
126
+ */
103
127
  public get temp (): TTempState {
104
128
  const scope = this.getScope(TEMP_SCOPE)
105
129
  if (!scope) {
@@ -108,6 +132,11 @@ export class TurnState<
108
132
  return scope.value as TTempState
109
133
  }
110
134
 
135
+ /**
136
+ * Sets the temporary state for the current turn.
137
+ * @param value - The new temporary state object
138
+ * @throws Error if state hasn't been loaded
139
+ */
111
140
  public set temp (value: TTempState) {
112
141
  const scope = this.getScope(TEMP_SCOPE)
113
142
  if (!scope) {
@@ -116,6 +145,12 @@ export class TurnState<
116
145
  scope.replace(value as Record<string, unknown>)
117
146
  }
118
147
 
148
+ /**
149
+ * Gets the user-scoped state.
150
+ * This state is unique to each user and persists across conversations.
151
+ * @returns The user state object
152
+ * @throws Error if state hasn't been loaded
153
+ */
119
154
  public get user (): TUserState {
120
155
  const scope = this.getScope(USER_SCOPE)
121
156
  if (!scope) {
@@ -124,6 +159,11 @@ export class TurnState<
124
159
  return scope.value as TUserState
125
160
  }
126
161
 
162
+ /**
163
+ * Sets the user-scoped state.
164
+ * @param value - The new user state object
165
+ * @throws Error if state hasn't been loaded
166
+ */
127
167
  public set user (value: TUserState) {
128
168
  const scope = this.getScope(USER_SCOPE)
129
169
  if (!scope) {
@@ -132,22 +172,11 @@ export class TurnState<
132
172
  scope.replace(value as Record<string, unknown>)
133
173
  }
134
174
 
135
- public get sso (): TSSOState {
136
- const scope = this.getScope(SSO_SCOPE)
137
- if (!scope) {
138
- throw new Error(this._stateNotLoadedString)
139
- }
140
- return scope.value as TSSOState
141
- }
142
-
143
- public set sso (value: TSSOState) {
144
- const scope = this.getScope(SSO_SCOPE)
145
- if (!scope) {
146
- throw new Error(this._stateNotLoadedString)
147
- }
148
- scope.replace(value as Record<string, unknown>)
149
- }
150
-
175
+ /**
176
+ * Marks the conversation state for deletion.
177
+ * The state will be deleted from storage on the next call to save().
178
+ * @throws Error if state hasn't been loaded
179
+ */
151
180
  public deleteConversationState (): void {
152
181
  const scope = this.getScope(CONVERSATION_SCOPE)
153
182
  if (!scope) {
@@ -156,6 +185,11 @@ export class TurnState<
156
185
  scope.delete()
157
186
  }
158
187
 
188
+ /**
189
+ * Marks the temporary state for deletion.
190
+ * Since temporary state is not persisted, this just clears the in-memory object.
191
+ * @throws Error if state hasn't been loaded
192
+ */
159
193
  public deleteTempState (): void {
160
194
  const scope = this.getScope(TEMP_SCOPE)
161
195
  if (!scope) {
@@ -164,6 +198,11 @@ export class TurnState<
164
198
  scope.delete()
165
199
  }
166
200
 
201
+ /**
202
+ * Marks the user state for deletion.
203
+ * The state will be deleted from storage on the next call to save().
204
+ * @throws Error if state hasn't been loaded
205
+ */
167
206
  public deleteUserState (): void {
168
207
  const scope = this.getScope(USER_SCOPE)
169
208
  if (!scope) {
@@ -172,10 +211,20 @@ export class TurnState<
172
211
  scope.delete()
173
212
  }
174
213
 
214
+ /**
215
+ * Gets a specific state scope by name.
216
+ * @param scope - The name of the scope to retrieve
217
+ * @returns The state entry for the scope, or undefined if not found
218
+ */
175
219
  public getScope (scope: string): TurnStateEntry | undefined {
176
220
  return this._scopes[scope]
177
221
  }
178
222
 
223
+ /**
224
+ * Deletes a value from state by dot-notation path.
225
+ * Format: "scope.property" or just "property" (defaults to temp scope)
226
+ * @param path - The path to the value to delete
227
+ */
179
228
  public deleteValue (path: string): void {
180
229
  const { scope, name } = this.getScopeAndName(path)
181
230
  if (Object.prototype.hasOwnProperty.call(scope.value, name)) {
@@ -183,21 +232,47 @@ export class TurnState<
183
232
  }
184
233
  }
185
234
 
235
+ /**
236
+ * Checks if a value exists in state by dot-notation path.
237
+ * Format: "scope.property" or just "property" (defaults to temp scope)
238
+ * @param path - The path to check
239
+ * @returns True if the value exists, false otherwise
240
+ */
186
241
  public hasValue (path: string): boolean {
187
242
  const { scope, name } = this.getScopeAndName(path)
188
243
  return Object.prototype.hasOwnProperty.call(scope.value, name)
189
244
  }
190
245
 
246
+ /**
247
+ * Gets a value from state by dot-notation path.
248
+ * Format: "scope.property" or just "property" (defaults to temp scope)
249
+ * @template TValue - The type of the value to retrieve
250
+ * @param path - The path to the value
251
+ * @returns The value at the specified path
252
+ */
191
253
  public getValue<TValue = unknown>(path: string): TValue {
192
254
  const { scope, name } = this.getScopeAndName(path)
193
255
  return scope.value[name] as TValue
194
256
  }
195
257
 
258
+ /**
259
+ * Sets a value in state by dot-notation path.
260
+ * Format: "scope.property" or just "property" (defaults to temp scope)
261
+ * @param path - The path to set
262
+ * @param value - The value to set
263
+ */
196
264
  public setValue (path: string, value: unknown): void {
197
265
  const { scope, name } = this.getScopeAndName(path)
198
266
  scope.value[name] = value
199
267
  }
200
268
 
269
+ /**
270
+ * Loads state from storage into memory.
271
+ * @param context - The turn context
272
+ * @param storage - Optional storage provider (if not provided, state will be in-memory only)
273
+ * @param force - If true, forces a reload from storage even if state is already loaded
274
+ * @returns Promise that resolves to true if state was loaded, false if it was already loaded
275
+ */
201
276
  public load (context: TurnContext, storage?: Storage, force: boolean = false): Promise<boolean> {
202
277
  if (this._isLoaded && !force) {
203
278
  return Promise.resolve(false)
@@ -242,6 +317,14 @@ export class TurnState<
242
317
  return this._loadingPromise
243
318
  }
244
319
 
320
+ /**
321
+ * Saves state changes to storage.
322
+ * Only changed scopes will be persisted.
323
+ * @param context - The turn context
324
+ * @param storage - Optional storage provider (if not provided, state changes won't be persisted)
325
+ * @returns Promise that resolves when the save operation is complete
326
+ * @throws Error if state hasn't been loaded
327
+ */
245
328
  public async save (context: TurnContext, storage?: Storage): Promise<void> {
246
329
  if (!this._isLoaded && this._loadingPromise) {
247
330
  await this._loadingPromise
@@ -291,6 +374,13 @@ export class TurnState<
291
374
  }
292
375
  }
293
376
 
377
+ /**
378
+ * Computes the storage keys for each scope based on the turn context.
379
+ * Override this method in derived classes to add or modify storage keys.
380
+ * @param context - The turn context
381
+ * @returns Promise that resolves to a dictionary of scope names to storage keys
382
+ * @protected
383
+ */
294
384
  protected onComputeStorageKeys (context: TurnContext): Promise<Record<string, string>> {
295
385
  const activity = context.activity
296
386
  const channelId = activity?.channelId
@@ -317,10 +407,16 @@ export class TurnState<
317
407
  const keys: Record<string, string> = {}
318
408
  keys[CONVERSATION_SCOPE] = `${channelId}/${agentId}/conversations/${conversationId}`
319
409
  keys[USER_SCOPE] = `${channelId}/${agentId}/users/${userId}`
320
- keys[SSO_SCOPE] = `${channelId}/${agentId}/sso`
321
410
  return Promise.resolve(keys)
322
411
  }
323
412
 
413
+ /**
414
+ * Parses a dot-notation path into scope and property name.
415
+ * If no scope is specified, defaults to the temp scope.
416
+ * @param path - The path to parse (format: "scope.property" or just "property")
417
+ * @returns Object containing the scope entry and property name
418
+ * @private
419
+ */
324
420
  private getScopeAndName (path: string): { scope: TurnStateEntry; name: string } {
325
421
  const parts = path.split('.')
326
422
  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
  }