@lti-tool/core 0.10.0 → 0.11.0
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 +6 -0
- package/dist/ltiTool.d.ts +18 -0
- package/dist/ltiTool.d.ts.map +1 -1
- package/dist/ltiTool.js +41 -0
- package/dist/schemas/lti13/nrps/contextMembership.schema.d.ts +65 -0
- package/dist/schemas/lti13/nrps/contextMembership.schema.d.ts.map +1 -0
- package/dist/schemas/lti13/nrps/contextMembership.schema.js +47 -0
- package/dist/services/nrps.service.d.ts +28 -0
- package/dist/services/nrps.service.d.ts.map +1 -0
- package/dist/services/nrps.service.js +51 -0
- package/package.json +1 -1
- package/src/ltiTool.ts +51 -0
- package/src/schemas/lti13/nrps/contextMembership.schema.ts +55 -0
- package/src/services/nrps.service.ts +80 -0
package/CHANGELOG.md
CHANGED
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
|
*
|
package/dist/ltiTool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ltiTool.d.ts","sourceRoot":"","sources":["../src/ltiTool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAEL,KAAK,eAAe,EAIrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,SAAS,EAGd,KAAK,cAAc,EACpB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,KAAK,OAAO,EAAiB,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,+CAA+C,CAAC;
|
|
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
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
|
+
}
|