@lti-tool/core 0.10.0 → 0.11.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @lti-tool/core
2
2
 
3
+ ## 0.11.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 359a3fe: Update dependencies
8
+
9
+ ## 0.11.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 7c87338: Add NRPS implementation for retrieving course membership and user roles
14
+
3
15
  ## 0.10.0
4
16
 
5
17
  ### Minor Changes
package/dist/ltiTool.d.ts CHANGED
@@ -7,6 +7,7 @@ import { type LTI13JwtPayload } from './schemas/index.js';
7
7
  import { type CreateLineItem, type LineItem, type LineItems, type UpdateLineItem } from './schemas/lti13/ags/lineItem.schema.js';
8
8
  import { type Results } from './schemas/lti13/ags/result.schema.js';
9
9
  import { type ScoreSubmission } from './schemas/lti13/ags/scoreSubmission.schema.js';
10
+ import { type Member } from './schemas/lti13/nrps/contextMembership.schema.js';
10
11
  /**
11
12
  * Main LTI 1.3 Tool implementation providing secure authentication, launch verification,
12
13
  * and LTI Advantage services integration.
@@ -37,6 +38,7 @@ export declare class LTITool {
37
38
  private logger;
38
39
  private tokenService;
39
40
  private agsService;
41
+ private nrpsService;
40
42
  /**
41
43
  * Creates a new LTI Tool instance.
42
44
  *
@@ -176,6 +178,22 @@ export declare class LTITool {
176
178
  * @throws {Error} When AGS is not available or deletion fails
177
179
  */
178
180
  deleteLineItem(session: LTISession): Promise<void>;
181
+ /**
182
+ * Retrieves course/context members using Names and Role Provisioning Services (NRPS).
183
+ *
184
+ * @param session - Active LTI session containing NRPS service endpoints
185
+ * @returns Array of members with clean camelCase properties
186
+ * @throws {Error} When NRPS is not available or request fails
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * const members = await ltiTool.getMembers(session);
191
+ * const instructors = members.filter(m =>
192
+ * m.roles.some(role => role.includes('Instructor'))
193
+ * );
194
+ * ```
195
+ */
196
+ getMembers(session: LTISession): Promise<Member[]>;
179
197
  /**
180
198
  * Retrieves all configured LTI client platforms.
181
199
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ltiTool.d.ts","sourceRoot":"","sources":["../src/ltiTool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ltiTool.d.ts","sourceRoot":"","sources":["../src/ltiTool.ts"],"names":[],"mappings":"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"}
package/dist/ltiTool.js CHANGED
@@ -3,7 +3,9 @@ import { AddClientSchema, UpdateClientSchema } from './schemas/client.schema.js'
3
3
  import { HandleLoginParamsSchema, LTI13JwtPayloadSchema, SessionIdSchema, VerifyLaunchParamsSchema, } from './schemas/index.js';
4
4
  import { LineItemSchema, LineItemsSchema, } from './schemas/lti13/ags/lineItem.schema.js';
5
5
  import { ResultsSchema } from './schemas/lti13/ags/result.schema.js';
6
+ import { NRPSContextMembershipResponseSchema, } from './schemas/lti13/nrps/contextMembership.schema.js';
6
7
  import { AGSService } from './services/ags.service.js';
8
+ import { NRPSService } from './services/nrps.service.js';
7
9
  import { createSession } from './services/session.service.js';
8
10
  import { TokenService } from './services/token.service.js';
9
11
  import { getValidLaunchConfig } from './utils/launchConfigValidation.js';
@@ -37,6 +39,7 @@ export class LTITool {
37
39
  logger;
38
40
  tokenService;
39
41
  agsService;
42
+ nrpsService;
40
43
  /**
41
44
  * Creates a new LTI Tool instance.
42
45
  *
@@ -54,6 +57,7 @@ export class LTITool {
54
57
  };
55
58
  this.tokenService = new TokenService(this.config.keyPair, this.config.security?.keyId ?? 'main');
56
59
  this.agsService = new AGSService(this.tokenService, this.config.storage, this.logger);
60
+ this.nrpsService = new NRPSService(this.tokenService, this.config.storage, this.logger);
57
61
  }
58
62
  /**
59
63
  * Handles LTI 1.3 login initiation by generating state/nonce and redirecting to platform auth.
@@ -320,6 +324,43 @@ export class LTITool {
320
324
  }
321
325
  await this.agsService.deleteLineItem(session);
322
326
  }
327
+ /**
328
+ * Retrieves course/context members using Names and Role Provisioning Services (NRPS).
329
+ *
330
+ * @param session - Active LTI session containing NRPS service endpoints
331
+ * @returns Array of members with clean camelCase properties
332
+ * @throws {Error} When NRPS is not available or request fails
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * const members = await ltiTool.getMembers(session);
337
+ * const instructors = members.filter(m =>
338
+ * m.roles.some(role => role.includes('Instructor'))
339
+ * );
340
+ * ```
341
+ */
342
+ async getMembers(session) {
343
+ if (!session) {
344
+ throw new Error('session is required');
345
+ }
346
+ const response = await this.nrpsService.getMembers(session);
347
+ const data = await response.json();
348
+ console.log(data);
349
+ const validated = NRPSContextMembershipResponseSchema.parse(data);
350
+ // Transform to clean camelCase format
351
+ return validated.members.map((member) => ({
352
+ status: member.status,
353
+ name: member.name,
354
+ picture: member.picture,
355
+ givenName: member.given_name,
356
+ familyName: member.family_name,
357
+ middleName: member.middle_name,
358
+ email: member.email,
359
+ userId: member.user_id,
360
+ lisPersonSourcedId: member.lis_person_sourcedid,
361
+ roles: member.roles,
362
+ }));
363
+ }
323
364
  // Client management
324
365
  /**
325
366
  * Retrieves all configured LTI client platforms.
@@ -0,0 +1,65 @@
1
+ import * as z from 'zod';
2
+ /**
3
+ * Schema for individual member in NRPS response
4
+ */
5
+ export declare const NRPSMemberResponseSchema: z.ZodObject<{
6
+ status: z.ZodString;
7
+ name: z.ZodString;
8
+ picture: z.ZodOptional<z.ZodURL>;
9
+ given_name: z.ZodOptional<z.ZodString>;
10
+ family_name: z.ZodOptional<z.ZodString>;
11
+ middle_name: z.ZodOptional<z.ZodString>;
12
+ email: z.ZodOptional<z.ZodString>;
13
+ user_id: z.ZodString;
14
+ lis_person_sourcedid: z.ZodOptional<z.ZodString>;
15
+ roles: z.ZodArray<z.ZodString>;
16
+ }, z.core.$strip>;
17
+ /**
18
+ * Schema for context information in NRPS response
19
+ */
20
+ export declare const NRPSContextResponseSchema: z.ZodObject<{
21
+ id: z.ZodString;
22
+ label: z.ZodString;
23
+ title: z.ZodString;
24
+ }, z.core.$strip>;
25
+ /**
26
+ * Schema for full NRPS context membership response
27
+ */
28
+ export declare const NRPSContextMembershipResponseSchema: z.ZodObject<{
29
+ id: z.ZodURL;
30
+ context: z.ZodObject<{
31
+ id: z.ZodString;
32
+ label: z.ZodString;
33
+ title: z.ZodString;
34
+ }, z.core.$strip>;
35
+ members: z.ZodArray<z.ZodObject<{
36
+ status: z.ZodString;
37
+ name: z.ZodString;
38
+ picture: z.ZodOptional<z.ZodURL>;
39
+ given_name: z.ZodOptional<z.ZodString>;
40
+ family_name: z.ZodOptional<z.ZodString>;
41
+ middle_name: z.ZodOptional<z.ZodString>;
42
+ email: z.ZodOptional<z.ZodString>;
43
+ user_id: z.ZodString;
44
+ lis_person_sourcedid: z.ZodOptional<z.ZodString>;
45
+ roles: z.ZodArray<z.ZodString>;
46
+ }, z.core.$strip>>;
47
+ }, z.core.$strip>;
48
+ /**
49
+ * Clean public API schemas (camelCase for JS/TS consumers)
50
+ */
51
+ export declare const MemberSchema: z.ZodObject<{
52
+ status: z.ZodString;
53
+ name: z.ZodString;
54
+ picture: z.ZodOptional<z.ZodURL>;
55
+ givenName: z.ZodOptional<z.ZodString>;
56
+ familyName: z.ZodOptional<z.ZodString>;
57
+ middleName: z.ZodOptional<z.ZodString>;
58
+ email: z.ZodOptional<z.ZodString>;
59
+ userId: z.ZodString;
60
+ lisPersonSourcedId: z.ZodOptional<z.ZodString>;
61
+ roles: z.ZodArray<z.ZodString>;
62
+ }, z.core.$strip>;
63
+ export type Member = z.infer<typeof MemberSchema>;
64
+ export type Context = z.infer<typeof NRPSContextResponseSchema>;
65
+ //# sourceMappingURL=contextMembership.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextMembership.schema.d.ts","sourceRoot":"","sources":["../../../../src/schemas/lti13/nrps/contextMembership.schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB;;GAEG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;iBAWnC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,yBAAyB;;;;iBAIpC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;iBAI9C,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;iBAWvB,CAAC;AAGH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
@@ -0,0 +1,47 @@
1
+ import * as z from 'zod';
2
+ /**
3
+ * Schema for individual member in NRPS response
4
+ */
5
+ export const NRPSMemberResponseSchema = z.object({
6
+ status: z.string(),
7
+ name: z.string(),
8
+ picture: z.url().optional(),
9
+ given_name: z.string().optional(),
10
+ family_name: z.string().optional(),
11
+ middle_name: z.string().optional(),
12
+ email: z.string().optional(), // Platforms don't force email regexp conformance
13
+ user_id: z.string(),
14
+ lis_person_sourcedid: z.string().optional(),
15
+ roles: z.array(z.string()),
16
+ });
17
+ /**
18
+ * Schema for context information in NRPS response
19
+ */
20
+ export const NRPSContextResponseSchema = z.object({
21
+ id: z.string(),
22
+ label: z.string(),
23
+ title: z.string(),
24
+ });
25
+ /**
26
+ * Schema for full NRPS context membership response
27
+ */
28
+ export const NRPSContextMembershipResponseSchema = z.object({
29
+ id: z.url(),
30
+ context: NRPSContextResponseSchema,
31
+ members: z.array(NRPSMemberResponseSchema),
32
+ });
33
+ /**
34
+ * Clean public API schemas (camelCase for JS/TS consumers)
35
+ */
36
+ export const MemberSchema = z.object({
37
+ status: z.string(),
38
+ name: z.string(),
39
+ picture: z.url().optional(),
40
+ givenName: z.string().optional(),
41
+ familyName: z.string().optional(),
42
+ middleName: z.string().optional(),
43
+ email: z.string().optional(), // Platforms don't force email regexp conformance
44
+ userId: z.string(),
45
+ lisPersonSourcedId: z.string().optional(),
46
+ roles: z.array(z.string()),
47
+ });
@@ -0,0 +1,28 @@
1
+ import type { BaseLogger } from 'pino';
2
+ import type { LTISession } from '../interfaces/ltiSession.js';
3
+ import type { LTIStorage } from '../interfaces/ltiStorage.js';
4
+ import type { TokenService } from './token.service.js';
5
+ /**
6
+ * Names and Role Provisioning Services (NRPS) implementation for LTI 1.3.
7
+ * Provides methods to retrieve course membership and user information from the platform.
8
+ *
9
+ * @see https://www.imsglobal.org/spec/lti-nrps/v2p0
10
+ */
11
+ export declare class NRPSService {
12
+ private tokenService;
13
+ private storage;
14
+ private logger;
15
+ constructor(tokenService: TokenService, storage: LTIStorage, logger: BaseLogger);
16
+ /**
17
+ * Retrieves all members (users) in the current course/context from the platform.
18
+ * Returns raw response that should be parsed by the calling service.
19
+ *
20
+ * @param session - Active LTI session containing NRPS service endpoints
21
+ * @returns Raw HTTP response containing membership data
22
+ * @throws {Error} When NRPS is not available for this session or request fails
23
+ */
24
+ getMembers(session: LTISession): Promise<Response>;
25
+ private getNRPSToken;
26
+ private validateNRPSResponse;
27
+ }
28
+ //# sourceMappingURL=nrps.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nrps.service.d.ts","sourceRoot":"","sources":["../../src/services/nrps.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAG9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;GAKG;AACH,qBAAa,WAAW;IAEpB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,MAAM;gBAFN,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,UAAU;IAG5B;;;;;;;OAOG;IACG,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;YAsB1C,YAAY;YAeZ,oBAAoB;CAanC"}
@@ -0,0 +1,51 @@
1
+ import { getValidLaunchConfig } from '../utils/launchConfigValidation.js';
2
+ /**
3
+ * Names and Role Provisioning Services (NRPS) implementation for LTI 1.3.
4
+ * Provides methods to retrieve course membership and user information from the platform.
5
+ *
6
+ * @see https://www.imsglobal.org/spec/lti-nrps/v2p0
7
+ */
8
+ export class NRPSService {
9
+ tokenService;
10
+ storage;
11
+ logger;
12
+ constructor(tokenService, storage, logger) {
13
+ this.tokenService = tokenService;
14
+ this.storage = storage;
15
+ this.logger = logger;
16
+ }
17
+ /**
18
+ * Retrieves all members (users) in the current course/context from the platform.
19
+ * Returns raw response that should be parsed by the calling service.
20
+ *
21
+ * @param session - Active LTI session containing NRPS service endpoints
22
+ * @returns Raw HTTP response containing membership data
23
+ * @throws {Error} When NRPS is not available for this session or request fails
24
+ */
25
+ async getMembers(session) {
26
+ if (!session.services?.nrps?.membershipUrl) {
27
+ throw new Error('NRPS not available for this session');
28
+ }
29
+ const token = await this.getNRPSToken(session, 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly');
30
+ const response = await fetch(session.services.nrps.membershipUrl, {
31
+ method: 'GET',
32
+ headers: {
33
+ Authorization: `Bearer ${token}`,
34
+ Accept: 'application/vnd.ims.lti-nrps.v2.membershipcontainer+json',
35
+ },
36
+ });
37
+ await this.validateNRPSResponse(response, 'get members');
38
+ return response;
39
+ }
40
+ async getNRPSToken(session, scope) {
41
+ const launchConfig = await getValidLaunchConfig(this.storage, session.platform.issuer, session.platform.clientId, session.platform.deploymentId);
42
+ return this.tokenService.getBearerToken(session.platform.clientId, launchConfig.tokenUrl, scope);
43
+ }
44
+ async validateNRPSResponse(response, operation) {
45
+ if (!response.ok) {
46
+ const error = await response.json();
47
+ this.logger.error({ error, status: response.status, statusText: response.statusText }, `NRPS ${operation} failed`);
48
+ throw new Error(`NRPS ${operation} failed: ${response.statusText} ${error}`);
49
+ }
50
+ }
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lti-tool/core",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "LTI 1.3 implementation for Node.js",
5
5
  "keywords": [
6
6
  "lti",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "jose": "^6.1.0",
40
- "zod": "^4.1.11"
40
+ "zod": "^4.1.12"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "pino": "^9.11.0"
@@ -48,6 +48,6 @@
48
48
  }
49
49
  },
50
50
  "devDependencies": {
51
- "pino": "^9.12.0"
51
+ "pino": "^10.1.0"
52
52
  }
53
53
  }
package/src/ltiTool.ts CHANGED
@@ -24,7 +24,12 @@ import {
24
24
  } from './schemas/lti13/ags/lineItem.schema.js';
25
25
  import { type Results, ResultsSchema } from './schemas/lti13/ags/result.schema.js';
26
26
  import { type ScoreSubmission } from './schemas/lti13/ags/scoreSubmission.schema.js';
27
+ import {
28
+ type Member,
29
+ NRPSContextMembershipResponseSchema,
30
+ } from './schemas/lti13/nrps/contextMembership.schema.js';
27
31
  import { AGSService } from './services/ags.service.js';
32
+ import { NRPSService } from './services/nrps.service.js';
28
33
  import { createSession } from './services/session.service.js';
29
34
  import { TokenService } from './services/token.service.js';
30
35
  import { getValidLaunchConfig } from './utils/launchConfigValidation.js';
@@ -58,6 +63,7 @@ export class LTITool {
58
63
  private logger: Logger;
59
64
  private tokenService: TokenService;
60
65
  private agsService: AGSService;
66
+ private nrpsService: NRPSService;
61
67
 
62
68
  /**
63
69
  * Creates a new LTI Tool instance.
@@ -79,6 +85,11 @@ export class LTITool {
79
85
  this.config.security?.keyId ?? 'main',
80
86
  );
81
87
  this.agsService = new AGSService(this.tokenService, this.config.storage, this.logger);
88
+ this.nrpsService = new NRPSService(
89
+ this.tokenService,
90
+ this.config.storage,
91
+ this.logger,
92
+ );
82
93
  }
83
94
 
84
95
  /**
@@ -411,6 +422,46 @@ export class LTITool {
411
422
  await this.agsService.deleteLineItem(session);
412
423
  }
413
424
 
425
+ /**
426
+ * Retrieves course/context members using Names and Role Provisioning Services (NRPS).
427
+ *
428
+ * @param session - Active LTI session containing NRPS service endpoints
429
+ * @returns Array of members with clean camelCase properties
430
+ * @throws {Error} When NRPS is not available or request fails
431
+ *
432
+ * @example
433
+ * ```typescript
434
+ * const members = await ltiTool.getMembers(session);
435
+ * const instructors = members.filter(m =>
436
+ * m.roles.some(role => role.includes('Instructor'))
437
+ * );
438
+ * ```
439
+ */
440
+ async getMembers(session: LTISession): Promise<Member[]> {
441
+ if (!session) {
442
+ throw new Error('session is required');
443
+ }
444
+
445
+ const response = await this.nrpsService.getMembers(session);
446
+ const data = await response.json();
447
+ console.log(data);
448
+ const validated = NRPSContextMembershipResponseSchema.parse(data);
449
+
450
+ // Transform to clean camelCase format
451
+ return validated.members.map((member) => ({
452
+ status: member.status,
453
+ name: member.name,
454
+ picture: member.picture,
455
+ givenName: member.given_name,
456
+ familyName: member.family_name,
457
+ middleName: member.middle_name,
458
+ email: member.email,
459
+ userId: member.user_id,
460
+ lisPersonSourcedId: member.lis_person_sourcedid,
461
+ roles: member.roles,
462
+ }));
463
+ }
464
+
414
465
  // Client management
415
466
 
416
467
  /**
@@ -0,0 +1,55 @@
1
+ import * as z from 'zod';
2
+
3
+ /**
4
+ * Schema for individual member in NRPS response
5
+ */
6
+ export const NRPSMemberResponseSchema = z.object({
7
+ status: z.string(),
8
+ name: z.string(),
9
+ picture: z.url().optional(),
10
+ given_name: z.string().optional(),
11
+ family_name: z.string().optional(),
12
+ middle_name: z.string().optional(),
13
+ email: z.string().optional(), // Platforms don't force email regexp conformance
14
+ user_id: z.string(),
15
+ lis_person_sourcedid: z.string().optional(),
16
+ roles: z.array(z.string()),
17
+ });
18
+
19
+ /**
20
+ * Schema for context information in NRPS response
21
+ */
22
+ export const NRPSContextResponseSchema = z.object({
23
+ id: z.string(),
24
+ label: z.string(),
25
+ title: z.string(),
26
+ });
27
+
28
+ /**
29
+ * Schema for full NRPS context membership response
30
+ */
31
+ export const NRPSContextMembershipResponseSchema = z.object({
32
+ id: z.url(),
33
+ context: NRPSContextResponseSchema,
34
+ members: z.array(NRPSMemberResponseSchema),
35
+ });
36
+
37
+ /**
38
+ * Clean public API schemas (camelCase for JS/TS consumers)
39
+ */
40
+ export const MemberSchema = z.object({
41
+ status: z.string(),
42
+ name: z.string(),
43
+ picture: z.url().optional(),
44
+ givenName: z.string().optional(),
45
+ familyName: z.string().optional(),
46
+ middleName: z.string().optional(),
47
+ email: z.string().optional(), // Platforms don't force email regexp conformance
48
+ userId: z.string(),
49
+ lisPersonSourcedId: z.string().optional(),
50
+ roles: z.array(z.string()),
51
+ });
52
+
53
+ // Export clean types for public API
54
+ export type Member = z.infer<typeof MemberSchema>;
55
+ export type Context = z.infer<typeof NRPSContextResponseSchema>;
@@ -0,0 +1,80 @@
1
+ import type { BaseLogger } from 'pino';
2
+
3
+ import type { LTISession } from '../interfaces/ltiSession.js';
4
+ import type { LTIStorage } from '../interfaces/ltiStorage.js';
5
+ import { getValidLaunchConfig } from '../utils/launchConfigValidation.js';
6
+
7
+ import type { TokenService } from './token.service.js';
8
+
9
+ /**
10
+ * Names and Role Provisioning Services (NRPS) implementation for LTI 1.3.
11
+ * Provides methods to retrieve course membership and user information from the platform.
12
+ *
13
+ * @see https://www.imsglobal.org/spec/lti-nrps/v2p0
14
+ */
15
+ export class NRPSService {
16
+ constructor(
17
+ private tokenService: TokenService,
18
+ private storage: LTIStorage,
19
+ private logger: BaseLogger,
20
+ ) {}
21
+
22
+ /**
23
+ * Retrieves all members (users) in the current course/context from the platform.
24
+ * Returns raw response that should be parsed by the calling service.
25
+ *
26
+ * @param session - Active LTI session containing NRPS service endpoints
27
+ * @returns Raw HTTP response containing membership data
28
+ * @throws {Error} When NRPS is not available for this session or request fails
29
+ */
30
+ async getMembers(session: LTISession): Promise<Response> {
31
+ if (!session.services?.nrps?.membershipUrl) {
32
+ throw new Error('NRPS not available for this session');
33
+ }
34
+
35
+ const token = await this.getNRPSToken(
36
+ session,
37
+ 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly',
38
+ );
39
+
40
+ const response = await fetch(session.services.nrps.membershipUrl, {
41
+ method: 'GET',
42
+ headers: {
43
+ Authorization: `Bearer ${token}`,
44
+ Accept: 'application/vnd.ims.lti-nrps.v2.membershipcontainer+json',
45
+ },
46
+ });
47
+
48
+ await this.validateNRPSResponse(response, 'get members');
49
+ return response;
50
+ }
51
+
52
+ private async getNRPSToken(session: LTISession, scope: string): Promise<string> {
53
+ const launchConfig = await getValidLaunchConfig(
54
+ this.storage,
55
+ session.platform.issuer,
56
+ session.platform.clientId,
57
+ session.platform.deploymentId,
58
+ );
59
+
60
+ return this.tokenService.getBearerToken(
61
+ session.platform.clientId,
62
+ launchConfig.tokenUrl,
63
+ scope,
64
+ );
65
+ }
66
+
67
+ private async validateNRPSResponse(
68
+ response: Response,
69
+ operation: string,
70
+ ): Promise<void> {
71
+ if (!response.ok) {
72
+ const error = await response.json();
73
+ this.logger.error(
74
+ { error, status: response.status, statusText: response.statusText },
75
+ `NRPS ${operation} failed`,
76
+ );
77
+ throw new Error(`NRPS ${operation} failed: ${response.statusText} ${error}`);
78
+ }
79
+ }
80
+ }