@lti-tool/core 0.12.2 → 0.13.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 (38) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/ltiTool.d.ts +24 -0
  3. package/dist/ltiTool.d.ts.map +1 -1
  4. package/dist/ltiTool.js +34 -1
  5. package/dist/schemas/index.d.ts +1 -0
  6. package/dist/schemas/index.d.ts.map +1 -1
  7. package/dist/schemas/index.js +1 -0
  8. package/dist/schemas/lti13/ags/lineItem.schema.d.ts +3 -3
  9. package/dist/schemas/lti13/deepLinking/contentItem.schema.d.ts +313 -0
  10. package/dist/schemas/lti13/deepLinking/contentItem.schema.d.ts.map +1 -0
  11. package/dist/schemas/lti13/deepLinking/contentItem.schema.js +163 -0
  12. package/dist/schemas/lti13/dynamicRegistration/ltiMessages.schema.d.ts +3 -3
  13. package/dist/schemas/lti13/dynamicRegistration/registrationResponse.schema.d.ts +1 -1
  14. package/dist/schemas/lti13/dynamicRegistration/toolRegistrationPayload.schema.d.ts +2 -2
  15. package/dist/services/ags.service.d.ts +23 -0
  16. package/dist/services/ags.service.d.ts.map +1 -1
  17. package/dist/services/ags.service.js +23 -0
  18. package/dist/services/deepLinking.service.d.ts +61 -0
  19. package/dist/services/deepLinking.service.d.ts.map +1 -0
  20. package/dist/services/deepLinking.service.js +109 -0
  21. package/dist/services/dynamicRegistration.service.d.ts +7 -0
  22. package/dist/services/dynamicRegistration.service.d.ts.map +1 -1
  23. package/dist/services/dynamicRegistration.service.js +7 -0
  24. package/dist/services/nrps.service.d.ts +15 -1
  25. package/dist/services/nrps.service.d.ts.map +1 -1
  26. package/dist/services/nrps.service.js +15 -1
  27. package/dist/services/token.service.d.ts +5 -2
  28. package/dist/services/token.service.d.ts.map +1 -1
  29. package/dist/services/token.service.js +5 -2
  30. package/package.json +1 -1
  31. package/src/ltiTool.ts +44 -1
  32. package/src/schemas/index.ts +9 -0
  33. package/src/schemas/lti13/deepLinking/contentItem.schema.ts +206 -0
  34. package/src/services/ags.service.ts +23 -0
  35. package/src/services/deepLinking.service.ts +128 -0
  36. package/src/services/dynamicRegistration.service.ts +7 -0
  37. package/src/services/nrps.service.ts +15 -1
  38. package/src/services/token.service.ts +5 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lti-tool/core",
3
- "version": "0.12.2",
3
+ "version": "0.13.1",
4
4
  "description": "LTI 1.3 implementation for Node.js",
5
5
  "keywords": [
6
6
  "lti",
package/src/ltiTool.ts CHANGED
@@ -27,12 +27,14 @@ import {
27
27
  } from './schemas/lti13/ags/lineItem.schema.js';
28
28
  import { type Results, ResultsSchema } from './schemas/lti13/ags/result.schema.js';
29
29
  import { type ScoreSubmission } from './schemas/lti13/ags/scoreSubmission.schema.js';
30
+ import { type DeepLinkingContentItem } from './schemas/lti13/deepLinking/contentItem.schema.js';
30
31
  import { type OpenIDConfiguration } from './schemas/lti13/dynamicRegistration/openIDConfiguration.schema.js';
31
32
  import {
32
33
  type Member,
33
34
  NRPSContextMembershipResponseSchema,
34
35
  } from './schemas/lti13/nrps/contextMembership.schema.js';
35
36
  import { AGSService } from './services/ags.service.js';
37
+ import { DeepLinkingService } from './services/deepLinking.service.js';
36
38
  import { DynamicRegistrationService } from './services/dynamicRegistration.service.js';
37
39
  import { NRPSService } from './services/nrps.service.js';
38
40
  import { createSession } from './services/session.service.js';
@@ -69,6 +71,7 @@ export class LTITool {
69
71
  private tokenService: TokenService;
70
72
  private agsService: AGSService;
71
73
  private nrpsService: NRPSService;
74
+ private deepLinkingService: DeepLinkingService;
72
75
  private dynamicRegistrationService?: DynamicRegistrationService;
73
76
 
74
77
  /**
@@ -96,6 +99,11 @@ export class LTITool {
96
99
  this.config.storage,
97
100
  this.logger,
98
101
  );
102
+ this.deepLinkingService = new DeepLinkingService(
103
+ this.config.keyPair,
104
+ this.logger,
105
+ this.config.security?.keyId ?? 'main',
106
+ );
99
107
  if (this.config.dynamicRegistration) {
100
108
  this.dynamicRegistrationService = new DynamicRegistrationService(
101
109
  this.config.storage,
@@ -474,6 +482,41 @@ export class LTITool {
474
482
  }));
475
483
  }
476
484
 
485
+ /**
486
+ * Creates a Deep Linking response with selected content items.
487
+ * Generates a signed JWT and returns HTML form that auto-submits to the platform.
488
+ *
489
+ * @param session - Active LTI session containing Deep Linking configuration
490
+ * @param contentItems - Array of content items selected by the user
491
+ * @returns HTML string containing auto-submit form
492
+ * @throws {Error} When Deep Linking is not available for the session
493
+ *
494
+ * @example
495
+ * ```typescript
496
+ * const html = await ltiTool.createDeepLinkingResponse(session, [
497
+ * {
498
+ * type: 'ltiResourceLink',
499
+ * title: 'Quiz 1',
500
+ * url: 'https://tool.example.com/quiz/1'
501
+ * }
502
+ * ]);
503
+ * // Render the HTML to return content items to platform
504
+ * ```
505
+ */
506
+ async createDeepLinkingResponse(
507
+ session: LTISession,
508
+ contentItems: DeepLinkingContentItem[],
509
+ ): Promise<string> {
510
+ if (!session) {
511
+ throw new Error('session is required');
512
+ }
513
+ if (!contentItems) {
514
+ throw new Error('contentItems is required');
515
+ }
516
+
517
+ return await this.deepLinkingService.createResponse(session, contentItems);
518
+ }
519
+
477
520
  /**
478
521
  * Fetches and validates the OpenID Connect configuration from an LTI platform during dynamic registration.
479
522
  * Validates that the OIDC endpoint and issuer have matching hostnames for security.
@@ -536,7 +579,7 @@ export class LTITool {
536
579
  dynamicRegistrationForm: DynamicRegistrationForm,
537
580
  ): Promise<string> {
538
581
  if (!this.dynamicRegistrationService) {
539
- throw new Error('Dynmaic registration service is not configured');
582
+ throw new Error('Dynamic registration service is not configured');
540
583
  }
541
584
 
542
585
  return await this.dynamicRegistrationService.completeDynamicRegistration(
@@ -1,4 +1,13 @@
1
1
  export { SessionIdSchema } from './common.schema.js';
2
+ export {
3
+ ContentItemSchema,
4
+ type DeepLinkingContentItem,
5
+ type DeepLinkingFile,
6
+ type DeepLinkingHtml,
7
+ type DeepLinkingImage,
8
+ type DeepLinkingLink,
9
+ type DeepLinkingLtiResourceLink,
10
+ } from './lti13/deepLinking/contentItem.schema.js';
2
11
  export {
3
12
  DynamicRegistrationFormSchema,
4
13
  type DynamicRegistrationForm,
@@ -0,0 +1,206 @@
1
+ import * as z from 'zod';
2
+
3
+ /**
4
+ * Zod schema for base content item properties shared across all content item types.
5
+ * Contains common metadata fields like title, text, icon, and thumbnail.
6
+ *
7
+ * @property title - Optional human-readable title for the content item
8
+ * @property text - Optional descriptive text for the content item
9
+ * @property icon - Optional icon image with URL and dimensions
10
+ * @property thumbnail - Optional thumbnail image with URL and dimensions
11
+ *
12
+ * @see https://www.imsglobal.org/spec/lti-dl/v2p0#content-item-types
13
+ */
14
+ const BaseContentItemSchema = z.object({
15
+ title: z.string().optional(),
16
+ text: z.string().optional(),
17
+ icon: z
18
+ .object({
19
+ url: z.url(),
20
+ width: z.number().optional(),
21
+ height: z.number().optional(),
22
+ })
23
+ .optional(),
24
+ thumbnail: z
25
+ .object({
26
+ url: z.url(),
27
+ width: z.number().optional(),
28
+ height: z.number().optional(),
29
+ })
30
+ .optional(),
31
+ });
32
+
33
+ /**
34
+ * Zod schema for LTI Resource Link content item.
35
+ * Represents a launchable LTI tool resource that can be embedded in the platform.
36
+ *
37
+ * @property type - Always 'ltiResourceLink' for this content type
38
+ * @property url - Optional launch URL for the resource
39
+ * @property custom - Optional custom parameters passed to the tool on launch
40
+ * @property lineItem - Optional gradebook column configuration for this resource
41
+ * @property available - Optional availability window with start/end dates
42
+ * @property submission - Optional submission window with start/end dates
43
+ *
44
+ * @see https://www.imsglobal.org/spec/lti-dl/v2p0#lti-resource-link
45
+ */
46
+ export const LtiResourceLinkSchema = BaseContentItemSchema.extend({
47
+ type: z.literal('ltiResourceLink'),
48
+ url: z.url().optional(),
49
+ custom: z.record(z.string(), z.string()).optional(),
50
+ lineItem: z
51
+ .object({
52
+ scoreMaximum: z.number().min(0),
53
+ label: z.string(),
54
+ resourceId: z.string().optional(),
55
+ tag: z.string().optional(),
56
+ })
57
+ .optional(),
58
+ available: z
59
+ .object({
60
+ startDateTime: z.iso.datetime().optional(),
61
+ endDateTime: z.iso.datetime().optional(),
62
+ })
63
+ .optional(),
64
+ submission: z
65
+ .object({
66
+ startDateTime: z.iso.datetime().optional(),
67
+ endDateTime: z.iso.datetime().optional(),
68
+ })
69
+ .optional(),
70
+ });
71
+
72
+ /**
73
+ * Zod schema for simple link content item.
74
+ * Represents a standard web link that can be opened in various ways.
75
+ *
76
+ * @property type - Always 'link' for this content type
77
+ * @property url - Target URL for the link
78
+ * @property embed - Optional HTML embed code
79
+ * @property window - Optional window configuration for opening the link
80
+ * @property iframe - Optional iframe configuration for embedding the link
81
+ *
82
+ * @see https://www.imsglobal.org/spec/lti-dl/v2p0#link
83
+ */
84
+ export const LinkSchema = BaseContentItemSchema.extend({
85
+ type: z.literal('link'),
86
+ url: z.url(),
87
+ embed: z
88
+ .object({
89
+ html: z.string(),
90
+ })
91
+ .optional(),
92
+ window: z
93
+ .object({
94
+ targetName: z.string().optional(),
95
+ windowFeatures: z.string().optional(),
96
+ })
97
+ .optional(),
98
+ iframe: z
99
+ .object({
100
+ width: z.number().optional(),
101
+ height: z.number().optional(),
102
+ src: z.url(),
103
+ })
104
+ .optional(),
105
+ });
106
+
107
+ /**
108
+ * Zod schema for HTML fragment content item.
109
+ * Represents raw HTML content to be embedded directly in the platform.
110
+ *
111
+ * @property type - Always 'html' for this content type
112
+ * @property html - HTML content to be embedded
113
+ *
114
+ * @see https://www.imsglobal.org/spec/lti-dl/v2p0#html-fragment
115
+ */
116
+ export const HtmlSchema = BaseContentItemSchema.extend({
117
+ type: z.literal('html'),
118
+ html: z.string(),
119
+ });
120
+
121
+ /**
122
+ * Zod schema for file content item.
123
+ * Represents a downloadable file resource.
124
+ *
125
+ * @property type - Always 'file' for this content type
126
+ * @property url - URL to download the file
127
+ * @property mediaType - MIME type of the file
128
+ * @property expiresAt - Optional expiration timestamp for the file URL
129
+ *
130
+ * @see https://www.imsglobal.org/spec/lti-dl/v2p0#file
131
+ */
132
+ export const FileSchema = BaseContentItemSchema.extend({
133
+ type: z.literal('file'),
134
+ url: z.url(),
135
+ mediaType: z.string(),
136
+ expiresAt: z.iso.datetime().optional(),
137
+ });
138
+
139
+ /**
140
+ * Zod schema for image content item.
141
+ * Represents an image resource with optional dimensions.
142
+ *
143
+ * @property type - Always 'image' for this content type
144
+ * @property url - URL to the image
145
+ * @property width - Optional image width in pixels
146
+ * @property height - Optional image height in pixels
147
+ *
148
+ * @see https://www.imsglobal.org/spec/lti-dl/v2p0#image
149
+ */
150
+ export const ImageSchema = BaseContentItemSchema.extend({
151
+ type: z.literal('image'),
152
+ url: z.url(),
153
+ width: z.number().optional(),
154
+ height: z.number().optional(),
155
+ });
156
+
157
+ /**
158
+ * Zod schema for validating any Deep Linking content item type.
159
+ * Uses discriminated union on the 'type' field to determine the specific content item schema.
160
+ * Supports: ltiResourceLink, link, html, file, and image content types.
161
+ *
162
+ * @see https://www.imsglobal.org/spec/lti-dl/v2p0#content-item-types
163
+ */
164
+ export const ContentItemSchema = z.discriminatedUnion('type', [
165
+ LtiResourceLinkSchema,
166
+ LinkSchema,
167
+ HtmlSchema,
168
+ FileSchema,
169
+ ImageSchema,
170
+ ]);
171
+
172
+ /**
173
+ * Type representing a validated LTI Resource Link content item.
174
+ * Used for creating launchable LTI tool resources.
175
+ */
176
+ export type DeepLinkingLtiResourceLink = z.infer<typeof LtiResourceLinkSchema>;
177
+
178
+ /**
179
+ * Type representing a validated simple link content item.
180
+ * Used for creating standard web links.
181
+ */
182
+ export type DeepLinkingLink = z.infer<typeof LinkSchema>;
183
+
184
+ /**
185
+ * Type representing a validated HTML fragment content item.
186
+ * Used for embedding raw HTML content.
187
+ */
188
+ export type DeepLinkingHtml = z.infer<typeof HtmlSchema>;
189
+
190
+ /**
191
+ * Type representing a validated file content item.
192
+ * Used for linking to downloadable files.
193
+ */
194
+ export type DeepLinkingFile = z.infer<typeof FileSchema>;
195
+
196
+ /**
197
+ * Type representing a validated image content item.
198
+ * Used for embedding images.
199
+ */
200
+ export type DeepLinkingImage = z.infer<typeof ImageSchema>;
201
+
202
+ /**
203
+ * Type representing any validated Deep Linking content item.
204
+ * Can be any of: DeepLinkingLtiResourceLink, DeepLinkingLink, DeepLinkingHtml, DeepLinkingFile, or DeepLinkingImage.
205
+ */
206
+ export type DeepLinkingContentItem = z.infer<typeof ContentItemSchema>;
@@ -18,6 +18,13 @@ import type { TokenService } from './token.service.js';
18
18
  * @see https://www.imsglobal.org/spec/lti-ags/v2p0
19
19
  */
20
20
  export class AGSService {
21
+ /**
22
+ * Creates a new AGSService instance.
23
+ *
24
+ * @param tokenService - Token service for obtaining OAuth2 bearer tokens
25
+ * @param storage - Storage adapter for retrieving launch configurations
26
+ * @param logger - Logger instance for debug and error logging
27
+ */
21
28
  constructor(
22
29
  private tokenService: TokenService,
23
30
  private storage: LTIStorage,
@@ -243,6 +250,16 @@ export class AGSService {
243
250
  * @param updateLineItem - Updated line item data including all required fields
244
251
  * @returns Promise resolving to the HTTP response containing the updated line item
245
252
  * @throws {Error} When AGS line item service is not available for the session or update fails
253
+ *
254
+ * @example
255
+ * ```typescript
256
+ * const response = await agsService.updateLineItem(session, {
257
+ * label: 'Quiz 1 (Updated)',
258
+ * scoreMaximum: 100,
259
+ * tag: 'quiz'
260
+ * });
261
+ * const updatedLineItem = await response.json();
262
+ * ```
246
263
  */
247
264
  async updateLineItem(
248
265
  session: LTISession,
@@ -276,6 +293,12 @@ export class AGSService {
276
293
  * @param session - Active LTI session containing AGS line item endpoint configuration
277
294
  * @returns Promise resolving to the HTTP response (typically 204 No Content on success)
278
295
  * @throws {Error} When AGS line item service is not available for the session or deletion fails
296
+ *
297
+ * @example
298
+ * ```typescript
299
+ * const response = await agsService.deleteLineItem(session);
300
+ * console.log('Line item deleted successfully');
301
+ * ```
279
302
  */
280
303
  async deleteLineItem(session: LTISession): Promise<Response> {
281
304
  if (!session.services?.ags?.lineitem) {
@@ -0,0 +1,128 @@
1
+ import { SignJWT } from 'jose';
2
+ import type { BaseLogger } from 'pino';
3
+
4
+ import type { LTISession } from '../interfaces/ltiSession.js';
5
+ import type { DeepLinkingContentItem } from '../schemas/lti13/deepLinking/contentItem.schema.js';
6
+
7
+ /**
8
+ * Deep Linking service for LTI 1.3.
9
+ * Generates signed JWT responses containing selected content items to return to the platform.
10
+ *
11
+ * @see https://www.imsglobal.org/spec/lti-dl/v2p0
12
+ */
13
+ export class DeepLinkingService {
14
+ /**
15
+ * Creates a new DeepLinkingService instance.
16
+ *
17
+ * @param keyPair - RSA key pair for signing client assertion JWTs (must be RS256 compatible)
18
+ * @param logger - Logger instance for debug and error logging
19
+ * @param keyId - Key identifier for JWT header, should match JWKS key ID (defaults to 'main')
20
+ */
21
+ constructor(
22
+ private keyPair: CryptoKeyPair,
23
+ private logger: BaseLogger,
24
+ private keyId = 'main',
25
+ ) {}
26
+
27
+ /**
28
+ * Creates a Deep Linking response with selected content items.
29
+ * Generates a signed JWT and returns an HTML form that auto-submits to the platform.
30
+ *
31
+ * @param session - Active LTI session containing Deep Linking configuration
32
+ * @param contentItems - Array of content items selected by the user
33
+ * @returns Promise resolving to an HTML string containing auto-submit form
34
+ * @throws {Error} When Deep Linking is not available for the session
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const html = await deepLinkingService.createResponse(session, [
39
+ * {
40
+ * type: 'ltiResourceLink',
41
+ * title: 'Quiz 1',
42
+ * url: 'https://tool.example.com/quiz/1'
43
+ * }
44
+ * ]);
45
+ * // Returns HTML form that auto-submits to platform
46
+ * ```
47
+ */
48
+ async createResponse(
49
+ session: LTISession,
50
+ contentItems: DeepLinkingContentItem[],
51
+ ): Promise<string> {
52
+ if (!session.services?.deepLinking) {
53
+ throw new Error('Deep Linking not available for this session');
54
+ }
55
+
56
+ this.logger.debug(
57
+ {
58
+ contentItemCount: contentItems.length,
59
+ returnUrl: session.services.deepLinking.returnUrl,
60
+ },
61
+ 'Creating Deep Linking response',
62
+ );
63
+
64
+ const jwt = await this.createDeepLinkingJWT(session, contentItems);
65
+ return this.generateAutoSubmitForm(session.services.deepLinking.returnUrl, jwt);
66
+ }
67
+
68
+ /**
69
+ * Creates a signed JWT containing the Deep Linking response payload.
70
+ *
71
+ * @param session - Active LTI session with Deep Linking configuration
72
+ * @param contentItems - Array of selected content items
73
+ * @returns Promise resolving to a signed JWT string
74
+ */
75
+ private async createDeepLinkingJWT(
76
+ session: LTISession,
77
+ contentItems: DeepLinkingContentItem[],
78
+ ): Promise<string> {
79
+ const deepLinking = session.services!.deepLinking!;
80
+
81
+ const payload = {
82
+ iss: session.platform.clientId,
83
+ aud: session.platform.issuer,
84
+ exp: Math.floor(Date.now() / 1000) + 600,
85
+ iat: Math.floor(Date.now() / 1000),
86
+ nonce: crypto.randomUUID(),
87
+ 'https://purl.imsglobal.org/spec/lti/claim/message_type': 'LtiDeepLinkingResponse',
88
+ 'https://purl.imsglobal.org/spec/lti/claim/version': '1.3.0',
89
+ 'https://purl.imsglobal.org/spec/lti/claim/deployment_id':
90
+ session.platform.deploymentId,
91
+ 'https://purl.imsglobal.org/spec/lti-dl/claim/content_items': contentItems,
92
+ 'https://purl.imsglobal.org/spec/lti-dl/claim/data': deepLinking.data,
93
+ };
94
+
95
+ return await new SignJWT(payload)
96
+ .setProtectedHeader({
97
+ alg: 'RS256',
98
+ typ: 'JWT',
99
+ kid: this.keyId,
100
+ })
101
+ .sign(this.keyPair.privateKey);
102
+ }
103
+
104
+ /**
105
+ * Generates an HTML form that auto-submits the Deep Linking response to the platform.
106
+ *
107
+ * @param returnUrl - Platform's Deep Linking return URL
108
+ * @param jwt - Signed JWT containing the response
109
+ * @returns HTML string with auto-submit form
110
+ */
111
+ private generateAutoSubmitForm(returnUrl: string, jwt: string): string {
112
+ return `
113
+ <!DOCTYPE html>
114
+ <html>
115
+ <head>
116
+ <title>Returning to platform...</title>
117
+ </head>
118
+ <body>
119
+ <form id="deepLinkingForm" method="POST" action="${returnUrl}">
120
+ <input type="hidden" name="JWT" value="${jwt}" />
121
+ </form>
122
+ <script>
123
+ document.getElementById('deepLinkingForm').submit();
124
+ </script>
125
+ </body>
126
+ </html>`;
127
+ }
128
+ }
@@ -63,6 +63,13 @@ import {
63
63
  * @see https://www.imsglobal.org/spec/lti-dr/v1p0 LTI 1.3 Dynamic Registration specification
64
64
  */
65
65
  export class DynamicRegistrationService {
66
+ /**
67
+ * Creates a new DynamicRegistrationService instance.
68
+ *
69
+ * @param storage - Storage adapter for persisting client and deployment configurations
70
+ * @param dynamicRegistrationConfig - Tool configuration including URLs and service settings
71
+ * @param logger - Logger instance for debug and error logging
72
+ */
66
73
  constructor(
67
74
  private storage: LTIStorage,
68
75
  private dynamicRegistrationConfig: DynamicRegistrationConfig,
@@ -13,6 +13,13 @@ import type { TokenService } from './token.service.js';
13
13
  * @see https://www.imsglobal.org/spec/lti-nrps/v2p0
14
14
  */
15
15
  export class NRPSService {
16
+ /**
17
+ * Creates a new NRPSService instance.
18
+ *
19
+ * @param tokenService - Token service for obtaining OAuth2 bearer tokens
20
+ * @param storage - Storage adapter for retrieving launch configurations
21
+ * @param logger - Logger instance for debug and error logging
22
+ */
16
23
  constructor(
17
24
  private tokenService: TokenService,
18
25
  private storage: LTIStorage,
@@ -24,8 +31,15 @@ export class NRPSService {
24
31
  * Returns raw response that should be parsed by the calling service.
25
32
  *
26
33
  * @param session - Active LTI session containing NRPS service endpoints
27
- * @returns Raw HTTP response containing membership data
34
+ * @returns Promise resolving to the HTTP response containing membership data
28
35
  * @throws {Error} When NRPS is not available for this session or request fails
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const response = await nrpsService.getMembers(session);
40
+ * const data = await response.json();
41
+ * console.log('Course members:', data.members);
42
+ * ```
29
43
  */
30
44
  async getMembers(session: LTISession): Promise<Response> {
31
45
  if (!session.services?.nrps?.membershipUrl) {
@@ -6,6 +6,8 @@ import { SignJWT } from 'jose';
6
6
  *
7
7
  * Implements RFC 7523 (JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants)
8
8
  * as required by LTI 1.3 security framework.
9
+ *
10
+ * @see https://www.rfc-editor.org/rfc/rfc7523
9
11
  */
10
12
  export class TokenService {
11
13
  /**
@@ -24,7 +26,7 @@ export class TokenService {
24
26
  *
25
27
  * @param clientId - OAuth2 client identifier
26
28
  * @param tokenUrl - Platform's token endpoint URL
27
- * @returns Signed JWT client assertion
29
+ * @returns Promise resolving to a signed JWT client assertion string
28
30
  */
29
31
  async createClientAssertion(clientId: string, tokenUrl: string): Promise<string> {
30
32
  return await new SignJWT({
@@ -49,7 +51,8 @@ export class TokenService {
49
51
  * @param clientId - OAuth2 client identifier
50
52
  * @param tokenUrl - Platform's token endpoint URL
51
53
  * @param scope - Requested OAuth2 scope (e.g., AGS score scope)
52
- * @returns Bearer access token for API calls
54
+ * @returns Promise resolving to a bearer access token string for API calls
55
+ * @throws {Error} When the token request fails or response is missing access_token
53
56
  */
54
57
  async getBearerToken(
55
58
  clientId: string,