@lti-tool/core 0.13.2 → 0.14.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 +12 -0
- package/dist/schemas/lti13/dynamicRegistration/ltiMessages.schema.d.ts +15 -9
- package/dist/schemas/lti13/dynamicRegistration/ltiMessages.schema.d.ts.map +1 -1
- package/dist/schemas/lti13/dynamicRegistration/ltiMessages.schema.js +7 -1
- package/dist/schemas/lti13/dynamicRegistration/registrationResponse.schema.d.ts +5 -3
- package/dist/schemas/lti13/dynamicRegistration/registrationResponse.schema.d.ts.map +1 -1
- package/dist/schemas/lti13/dynamicRegistration/toolRegistrationPayload.schema.d.ts +10 -6
- package/dist/schemas/lti13/dynamicRegistration/toolRegistrationPayload.schema.d.ts.map +1 -1
- package/dist/services/ags.service.d.ts.map +1 -1
- package/dist/services/ags.service.js +4 -7
- package/dist/services/dynamicRegistration.service.d.ts +12 -0
- package/dist/services/dynamicRegistration.service.d.ts.map +1 -1
- package/dist/services/dynamicRegistration.service.js +68 -11
- package/dist/services/dynamicRegistrationHandlers/moodle.js +1 -1
- package/dist/services/session.service.d.ts.map +1 -1
- package/dist/services/session.service.js +7 -2
- package/package.json +1 -1
- package/src/schemas/lti13/dynamicRegistration/ltiMessages.schema.ts +9 -1
- package/src/services/ags.service.ts +4 -7
- package/src/services/dynamicRegistration.service.ts +85 -10
- package/src/services/dynamicRegistrationHandlers/moodle.ts +1 -1
- package/src/services/session.service.ts +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @lti-tool/core
|
|
2
2
|
|
|
3
|
+
## 0.14.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 25534f8: Fix score and results endpoint to use a cleansed ags line item endpoint without search params.
|
|
8
|
+
|
|
9
|
+
## 0.14.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- e9141eb: Support additional canvas deep linking placements
|
|
14
|
+
|
|
3
15
|
## 0.13.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -26,9 +26,11 @@ declare const LTIDeepLinkingMessageSchema: z.ZodObject<{
|
|
|
26
26
|
target_link_uri: z.ZodOptional<z.ZodURL>;
|
|
27
27
|
label: z.ZodOptional<z.ZodString>;
|
|
28
28
|
placements: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
editor_button: "editor_button";
|
|
30
|
+
assignment_selection: "assignment_selection";
|
|
31
|
+
link_selection: "link_selection";
|
|
32
|
+
module_index_menu_modal: "module_index_menu_modal";
|
|
33
|
+
module_menu_modal: "module_menu_modal";
|
|
32
34
|
}>>>;
|
|
33
35
|
supported_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
34
36
|
file: "file";
|
|
@@ -53,9 +55,11 @@ export declare const LTIMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
53
55
|
target_link_uri: z.ZodOptional<z.ZodURL>;
|
|
54
56
|
label: z.ZodOptional<z.ZodString>;
|
|
55
57
|
placements: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
editor_button: "editor_button";
|
|
59
|
+
assignment_selection: "assignment_selection";
|
|
60
|
+
link_selection: "link_selection";
|
|
61
|
+
module_index_menu_modal: "module_index_menu_modal";
|
|
62
|
+
module_menu_modal: "module_menu_modal";
|
|
59
63
|
}>>>;
|
|
60
64
|
supported_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
61
65
|
file: "file";
|
|
@@ -79,9 +83,11 @@ export declare const LTIMessagesArraySchema: z.ZodArray<z.ZodDiscriminatedUnion<
|
|
|
79
83
|
target_link_uri: z.ZodOptional<z.ZodURL>;
|
|
80
84
|
label: z.ZodOptional<z.ZodString>;
|
|
81
85
|
placements: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
editor_button: "editor_button";
|
|
87
|
+
assignment_selection: "assignment_selection";
|
|
88
|
+
link_selection: "link_selection";
|
|
89
|
+
module_index_menu_modal: "module_index_menu_modal";
|
|
90
|
+
module_menu_modal: "module_menu_modal";
|
|
85
91
|
}>>>;
|
|
86
92
|
supported_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
87
93
|
file: "file";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ltiMessages.schema.d.ts","sourceRoot":"","sources":["../../../../src/schemas/lti13/dynamicRegistration/ltiMessages.schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB;;;;GAIG;AACH,QAAA,MAAM,4BAA4B;;iBAEhC,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,2BAA2B
|
|
1
|
+
{"version":3,"file":"ltiMessages.schema.d.ts","sourceRoot":"","sources":["../../../../src/schemas/lti13/dynamicRegistration/ltiMessages.schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB;;;;GAIG;AACH,QAAA,MAAM,4BAA4B;;iBAEhC,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;iBAqB/B,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;2BAG3B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;4BAA4B,CAAC;AAEhE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC1D,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAClF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
|
|
@@ -26,7 +26,13 @@ const LTIDeepLinkingMessageSchema = z.object({
|
|
|
26
26
|
target_link_uri: z.url().optional(),
|
|
27
27
|
label: z.string().optional(),
|
|
28
28
|
placements: z
|
|
29
|
-
.array(z.enum([
|
|
29
|
+
.array(z.enum([
|
|
30
|
+
'editor_button',
|
|
31
|
+
'assignment_selection',
|
|
32
|
+
'link_selection',
|
|
33
|
+
'module_index_menu_modal',
|
|
34
|
+
'module_menu_modal',
|
|
35
|
+
]))
|
|
30
36
|
.optional(),
|
|
31
37
|
supported_types: z
|
|
32
38
|
.array(z.enum(['ltiResourceLink', 'file', 'html', 'link', 'image']))
|
|
@@ -50,9 +50,11 @@ export declare const RegistrationResponseSchema: z.ZodObject<{
|
|
|
50
50
|
target_link_uri: z.ZodOptional<z.ZodURL>;
|
|
51
51
|
label: z.ZodOptional<z.ZodString>;
|
|
52
52
|
placements: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
editor_button: "editor_button";
|
|
54
|
+
assignment_selection: "assignment_selection";
|
|
55
|
+
link_selection: "link_selection";
|
|
56
|
+
module_index_menu_modal: "module_index_menu_modal";
|
|
57
|
+
module_menu_modal: "module_menu_modal";
|
|
56
58
|
}>>>;
|
|
57
59
|
supported_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
58
60
|
file: "file";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registrationResponse.schema.d.ts","sourceRoot":"","sources":["../../../../src/schemas/lti13/dynamicRegistration/registrationResponse.schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AA0BzB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,0BAA0B
|
|
1
|
+
{"version":3,"file":"registrationResponse.schema.d.ts","sourceRoot":"","sources":["../../../../src/schemas/lti13/dynamicRegistration/registrationResponse.schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AA0BzB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiBrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC"}
|
|
@@ -21,9 +21,11 @@ declare const LTIToolConfigurationSchema: z.ZodObject<{
|
|
|
21
21
|
target_link_uri: z.ZodOptional<z.ZodURL>;
|
|
22
22
|
label: z.ZodOptional<z.ZodString>;
|
|
23
23
|
placements: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
editor_button: "editor_button";
|
|
25
|
+
assignment_selection: "assignment_selection";
|
|
26
|
+
link_selection: "link_selection";
|
|
27
|
+
module_index_menu_modal: "module_index_menu_modal";
|
|
28
|
+
module_menu_modal: "module_menu_modal";
|
|
27
29
|
}>>>;
|
|
28
30
|
supported_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
29
31
|
file: "file";
|
|
@@ -80,9 +82,11 @@ export declare const ToolRegistrationPayloadSchema: z.ZodObject<{
|
|
|
80
82
|
target_link_uri: z.ZodOptional<z.ZodURL>;
|
|
81
83
|
label: z.ZodOptional<z.ZodString>;
|
|
82
84
|
placements: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
editor_button: "editor_button";
|
|
86
|
+
assignment_selection: "assignment_selection";
|
|
87
|
+
link_selection: "link_selection";
|
|
88
|
+
module_index_menu_modal: "module_index_menu_modal";
|
|
89
|
+
module_menu_modal: "module_menu_modal";
|
|
86
90
|
}>>>;
|
|
87
91
|
supported_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
88
92
|
file: "file";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolRegistrationPayload.schema.d.ts","sourceRoot":"","sources":["../../../../src/schemas/lti13/dynamicRegistration/toolRegistrationPayload.schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAIzB;;;;;;;;;GASG;AACH,QAAA,MAAM,0BAA0B
|
|
1
|
+
{"version":3,"file":"toolRegistrationPayload.schema.d.ts","sourceRoot":"","sources":["../../../../src/schemas/lti13/dynamicRegistration/toolRegistrationPayload.schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAIzB;;;;;;;;;GASG;AACH,QAAA,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAM9B,CAAC;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAYxC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AACpF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ags.service.d.ts","sourceRoot":"","sources":["../../src/services/ags.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;AAC9D,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACf,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AAGtF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;GAKG;AACH,qBAAa,UAAU;IASnB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,MAAM;IAVhB;;;;;;OAMG;gBAEO,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,UAAU;IAG5B;;;;;;;;;;;;;;;;;;OAkBG;IACG,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"ags.service.d.ts","sourceRoot":"","sources":["../../src/services/ags.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;AAC9D,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACf,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AAGtF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;GAKG;AACH,qBAAa,UAAU;IASnB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,MAAM;IAVhB;;;;;;OAMG;gBAEO,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,UAAU;IAG5B;;;;;;;;;;;;;;;;;;OAkBG;IACG,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC;IAkCjF;;;;;;;;;;;;;OAaG;IACG,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwBvD;;;;;;;;;;;;;OAaG;IACG,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;IAsB3D;;;;;;;;;;;;;OAaG;IACG,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;IAsBzD;;;;;;;;;;;;;;;;;;;OAmBG;IACG,cAAc,CAClB,OAAO,EAAE,UAAU,EACnB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,QAAQ,CAAC;IAuBpB;;;;;;;;;;;;;;;;;OAiBG;IACG,cAAc,CAClB,OAAO,EAAE,UAAU,EACnB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,QAAQ,CAAC;IAuBpB;;;;;;;;;;;;OAYG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;YAqB9C,WAAW;YAeX,mBAAmB;CAalC"}
|
|
@@ -54,7 +54,8 @@ export class AGSService {
|
|
|
54
54
|
activityProgress: score.activityProgress,
|
|
55
55
|
gradingProgress: score.gradingProgress,
|
|
56
56
|
};
|
|
57
|
-
const
|
|
57
|
+
const agsScoreEndpoint = `${session.services.ags.lineitem}/scores`;
|
|
58
|
+
const response = await fetch(agsScoreEndpoint, {
|
|
58
59
|
method: 'POST',
|
|
59
60
|
headers: {
|
|
60
61
|
Authorization: `Bearer ${token}`,
|
|
@@ -84,12 +85,8 @@ export class AGSService {
|
|
|
84
85
|
throw new Error('AGS line item not available for this session');
|
|
85
86
|
}
|
|
86
87
|
const token = await this.getAGSToken(session, 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly');
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const lineItemUrl = new URL(session.services.ags.lineitem);
|
|
90
|
-
lineItemUrl.search = '';
|
|
91
|
-
const resultsUrl = `${lineItemUrl.toString()}/results`;
|
|
92
|
-
const response = await fetch(resultsUrl, {
|
|
88
|
+
const resultsEndpoint = `${session.services.ags.lineitem}/results`;
|
|
89
|
+
const response = await fetch(resultsEndpoint, {
|
|
93
90
|
method: 'GET',
|
|
94
91
|
headers: {
|
|
95
92
|
Authorization: `Bearer ${token}`,
|
|
@@ -97,12 +97,23 @@ export declare class DynamicRegistrationService {
|
|
|
97
97
|
* @returns Session data if valid and not expired, undefined otherwise
|
|
98
98
|
*/
|
|
99
99
|
verifyRegistrationSession(sessionToken: string): Promise<LTIDynamicRegistrationSession | undefined>;
|
|
100
|
+
/**
|
|
101
|
+
* Builds Canvas-specific deep linking messages for the 5 common placements.
|
|
102
|
+
* Creates separate messages for editor, module menu, assignments, modules page, and link selection.
|
|
103
|
+
*
|
|
104
|
+
* @param deepLinkingUri - URI where deep linking requests should be sent
|
|
105
|
+
* @param toolName - Display name of the tool
|
|
106
|
+
* @returns Array of Canvas deep linking message configurations
|
|
107
|
+
*/
|
|
108
|
+
private buildCanvasDeepLinkingMessages;
|
|
100
109
|
/**
|
|
101
110
|
* Builds array of LTI message types based on selected services during registration.
|
|
102
111
|
* Always includes ResourceLinkRequest, conditionally adds DeepLinkingRequest.
|
|
103
112
|
*
|
|
104
113
|
* @param selectedServices - Array of service names selected by administrator
|
|
105
114
|
* @param deepLinkingUri - URI where deep linking requests should be sent
|
|
115
|
+
* @param platformFamily - Platform family code (e.g., 'canvas', 'moodle')
|
|
116
|
+
* @param toolName - Display name of the tool
|
|
106
117
|
* @returns Array of LTI message configurations for the registration payload
|
|
107
118
|
*/
|
|
108
119
|
private buildMessages;
|
|
@@ -119,6 +130,7 @@ export declare class DynamicRegistrationService {
|
|
|
119
130
|
* Combines tool configuration, selected services, and OAuth parameters into LTI 1.3 registration format.
|
|
120
131
|
*
|
|
121
132
|
* @param selectedServices - Array of service names selected by administrator
|
|
133
|
+
* @param platformFamily - Platform family code (e.g., 'canvas', 'moodle')
|
|
122
134
|
* @returns Complete registration payload ready for platform submission
|
|
123
135
|
*/
|
|
124
136
|
private buildRegistrationPayload;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamicRegistration.service.d.ts","sourceRoot":"","sources":["../../src/services/dynamicRegistration.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,gDAAgD,CAAC;AACpG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uEAAuE,CAAC;AAErH,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,oEAAoE,CAAC;AAC5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oEAAoE,CAAC;AAS9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAAa,0BAA0B;IASnC,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,yBAAyB;IACjC,OAAO,CAAC,MAAM;IAVhB;;;;;;OAMG;gBAEO,OAAO,EAAE,UAAU,EACnB,yBAAyB,EAAE,yBAAyB,EACpD,MAAM,EAAE,UAAU;IAG5B;;;;;;;OAOG;IACG,0BAA0B,CAC9B,mBAAmB,EAAE,mBAAmB,GACvC,OAAO,CAAC,mBAAmB,CAAC;IAkC/B;;;;;;;;OAQG;IACG,2BAA2B,CAC/B,mBAAmB,EAAE,mBAAmB,EACxC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC;IAsClB;;;;;;;OAOG;IACG,2BAA2B,CAC/B,uBAAuB,EAAE,uBAAuB,GAC/C,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"dynamicRegistration.service.d.ts","sourceRoot":"","sources":["../../src/services/dynamicRegistration.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,gDAAgD,CAAC;AACpG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uEAAuE,CAAC;AAErH,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,oEAAoE,CAAC;AAC5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oEAAoE,CAAC;AAS9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAAa,0BAA0B;IASnC,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,yBAAyB;IACjC,OAAO,CAAC,MAAM;IAVhB;;;;;;OAMG;gBAEO,OAAO,EAAE,UAAU,EACnB,yBAAyB,EAAE,yBAAyB,EACpD,MAAM,EAAE,UAAU;IAG5B;;;;;;;OAOG;IACG,0BAA0B,CAC9B,mBAAmB,EAAE,mBAAmB,GACvC,OAAO,CAAC,mBAAmB,CAAC;IAkC/B;;;;;;;;OAQG;IACG,2BAA2B,CAC/B,mBAAmB,EAAE,mBAAmB,EACxC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC;IAsClB;;;;;;;OAOG;IACG,2BAA2B,CAC/B,uBAAuB,EAAE,uBAAuB,GAC/C,OAAO,CAAC,MAAM,CAAC;IA2DlB;;;;;;OAMG;IACG,yBAAyB,CAC7B,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,6BAA6B,GAAG,SAAS,CAAC;IAQrD;;;;;;;OAOG;IACH,OAAO,CAAC,8BAA8B;IA2CtC;;;;;;;;;OASG;IACH,OAAO,CAAC,aAAa;IA0BrB;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IAoBnB;;;;;;;OAOG;IACH,OAAO,CAAC,wBAAwB;YA0ClB,mCAAmC;IAgBjD,OAAO,CAAC,0BAA0B;CAiDnC"}
|
|
@@ -140,8 +140,10 @@ export class DynamicRegistrationService {
|
|
|
140
140
|
if (!session) {
|
|
141
141
|
throw new Error('Invalid or expired session');
|
|
142
142
|
}
|
|
143
|
+
// Extract platform family from session
|
|
144
|
+
const platformFamily = session.openIdConfiguration['https://purl.imsglobal.org/spec/lti-platform-configuration'].product_family_code;
|
|
143
145
|
// 1. build payload
|
|
144
|
-
const toolRegistrationPayload = this.buildRegistrationPayload(dynamicRegistrationForm.services ?? []);
|
|
146
|
+
const toolRegistrationPayload = this.buildRegistrationPayload(dynamicRegistrationForm.services ?? [], platformFamily);
|
|
145
147
|
// 2. Post request to Moodle
|
|
146
148
|
const registrationResponse = await postRegistrationToMoodle(session.openIdConfiguration.registration_endpoint, toolRegistrationPayload, this.logger, session.registrationToken);
|
|
147
149
|
// 3. save to storage
|
|
@@ -185,25 +187,79 @@ export class DynamicRegistrationService {
|
|
|
185
187
|
}
|
|
186
188
|
return session;
|
|
187
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Builds Canvas-specific deep linking messages for the 5 common placements.
|
|
192
|
+
* Creates separate messages for editor, module menu, assignments, modules page, and link selection.
|
|
193
|
+
*
|
|
194
|
+
* @param deepLinkingUri - URI where deep linking requests should be sent
|
|
195
|
+
* @param toolName - Display name of the tool
|
|
196
|
+
* @returns Array of Canvas deep linking message configurations
|
|
197
|
+
*/
|
|
198
|
+
buildCanvasDeepLinkingMessages(deepLinkingUri, toolName) {
|
|
199
|
+
return [
|
|
200
|
+
{
|
|
201
|
+
type: 'LtiDeepLinkingRequest',
|
|
202
|
+
target_link_uri: deepLinkingUri,
|
|
203
|
+
label: toolName,
|
|
204
|
+
placements: ['editor_button'],
|
|
205
|
+
supported_types: ['ltiResourceLink'],
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: 'LtiDeepLinkingRequest',
|
|
209
|
+
target_link_uri: deepLinkingUri,
|
|
210
|
+
label: toolName,
|
|
211
|
+
placements: ['module_menu_modal'],
|
|
212
|
+
supported_types: ['ltiResourceLink'],
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
type: 'LtiDeepLinkingRequest',
|
|
216
|
+
target_link_uri: deepLinkingUri,
|
|
217
|
+
label: toolName,
|
|
218
|
+
placements: ['assignment_selection'],
|
|
219
|
+
supported_types: ['ltiResourceLink'],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
type: 'LtiDeepLinkingRequest',
|
|
223
|
+
target_link_uri: deepLinkingUri,
|
|
224
|
+
label: toolName,
|
|
225
|
+
placements: ['module_index_menu_modal'],
|
|
226
|
+
supported_types: ['ltiResourceLink'],
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
type: 'LtiDeepLinkingRequest',
|
|
230
|
+
target_link_uri: deepLinkingUri,
|
|
231
|
+
label: toolName,
|
|
232
|
+
placements: ['link_selection'],
|
|
233
|
+
supported_types: ['ltiResourceLink'],
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
}
|
|
188
237
|
/**
|
|
189
238
|
* Builds array of LTI message types based on selected services during registration.
|
|
190
239
|
* Always includes ResourceLinkRequest, conditionally adds DeepLinkingRequest.
|
|
191
240
|
*
|
|
192
241
|
* @param selectedServices - Array of service names selected by administrator
|
|
193
242
|
* @param deepLinkingUri - URI where deep linking requests should be sent
|
|
243
|
+
* @param platformFamily - Platform family code (e.g., 'canvas', 'moodle')
|
|
244
|
+
* @param toolName - Display name of the tool
|
|
194
245
|
* @returns Array of LTI message configurations for the registration payload
|
|
195
246
|
*/
|
|
196
|
-
buildMessages(selectedServices, deepLinkingUri) {
|
|
247
|
+
buildMessages(selectedServices, deepLinkingUri, platformFamily, toolName) {
|
|
197
248
|
const messages = [];
|
|
198
249
|
messages.push({ type: 'LtiResourceLinkRequest' });
|
|
199
250
|
if (selectedServices?.includes('deep_linking')) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
251
|
+
if (platformFamily.toLowerCase() === 'canvas') {
|
|
252
|
+
messages.push(...this.buildCanvasDeepLinkingMessages(deepLinkingUri, toolName));
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
messages.push({
|
|
256
|
+
type: 'LtiDeepLinkingRequest',
|
|
257
|
+
target_link_uri: deepLinkingUri,
|
|
258
|
+
label: toolName,
|
|
259
|
+
placements: ['editor_button'],
|
|
260
|
+
supported_types: ['ltiResourceLink'],
|
|
261
|
+
});
|
|
262
|
+
}
|
|
207
263
|
}
|
|
208
264
|
return messages;
|
|
209
265
|
}
|
|
@@ -229,15 +285,16 @@ export class DynamicRegistrationService {
|
|
|
229
285
|
* Combines tool configuration, selected services, and OAuth parameters into LTI 1.3 registration format.
|
|
230
286
|
*
|
|
231
287
|
* @param selectedServices - Array of service names selected by administrator
|
|
288
|
+
* @param platformFamily - Platform family code (e.g., 'canvas', 'moodle')
|
|
232
289
|
* @returns Complete registration payload ready for platform submission
|
|
233
290
|
*/
|
|
234
|
-
buildRegistrationPayload(selectedServices) {
|
|
291
|
+
buildRegistrationPayload(selectedServices, platformFamily) {
|
|
235
292
|
const config = this.dynamicRegistrationConfig;
|
|
236
293
|
const deepLinkingUri = config.deepLinkingUri || `${config.url}/lti/deep-linking`;
|
|
237
294
|
const jwksUri = config.jwksUri || `${config.url}/lti/jwks`;
|
|
238
295
|
const launchUri = config.launchUri || `${config.url}/lti/launch`;
|
|
239
296
|
const loginUri = config.loginUri || `${config.url}/lti/login`;
|
|
240
|
-
const messages = this.buildMessages(selectedServices, deepLinkingUri);
|
|
297
|
+
const messages = this.buildMessages(selectedServices, deepLinkingUri, platformFamily, config.name);
|
|
241
298
|
const scopes = this.buildScopes(selectedServices);
|
|
242
299
|
const toolRegistrationPayload = {
|
|
243
300
|
application_type: 'web',
|
|
@@ -142,7 +142,7 @@ export async function postRegistrationToMoodle(registrationEndpoint, registratio
|
|
|
142
142
|
if (!response.ok) {
|
|
143
143
|
const errorText = await response.json();
|
|
144
144
|
logger.error({ errorText }, 'lti dynamic registration error');
|
|
145
|
-
throw new Error(errorText);
|
|
145
|
+
throw new Error(JSON.stringify(errorText));
|
|
146
146
|
}
|
|
147
147
|
const data = await response.json();
|
|
148
148
|
logger.debug({ data }, 'Registration response');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.service.d.ts","sourceRoot":"","sources":["../../src/services/session.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D;;;;;;GAMG;AAEH,wBAAgB,aAAa,CAAC,eAAe,EAAE,eAAe,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"session.service.d.ts","sourceRoot":"","sources":["../../src/services/session.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D;;;;;;GAMG;AAEH,wBAAgB,aAAa,CAAC,eAAe,EAAE,eAAe,GAAG,UAAU,CA4G1E"}
|
|
@@ -20,9 +20,14 @@ export function createSession(lti13JwtPayload) {
|
|
|
20
20
|
const isAdmin = roles.some((role) => role.includes('Administrator'));
|
|
21
21
|
const services = {};
|
|
22
22
|
if (agsEndpoint) {
|
|
23
|
+
let lineItemUrl;
|
|
24
|
+
if (agsEndpoint.lineitem) {
|
|
25
|
+
const url = new URL(agsEndpoint.lineitem);
|
|
26
|
+
lineItemUrl = `${url.origin}${url.pathname}`; // quirk: moodle adds a url search param
|
|
27
|
+
}
|
|
23
28
|
services.ags = {
|
|
24
|
-
lineitem:
|
|
25
|
-
lineitems: agsEndpoint.lineitems,
|
|
29
|
+
lineitem: lineItemUrl,
|
|
30
|
+
lineitems: agsEndpoint.lineitems, // quirk: keep the moodle url search param
|
|
26
31
|
scopes: agsEndpoint.scope || [],
|
|
27
32
|
};
|
|
28
33
|
}
|
package/package.json
CHANGED
|
@@ -28,7 +28,15 @@ const LTIDeepLinkingMessageSchema = z.object({
|
|
|
28
28
|
target_link_uri: z.url().optional(),
|
|
29
29
|
label: z.string().optional(),
|
|
30
30
|
placements: z
|
|
31
|
-
.array(
|
|
31
|
+
.array(
|
|
32
|
+
z.enum([
|
|
33
|
+
'editor_button',
|
|
34
|
+
'assignment_selection',
|
|
35
|
+
'link_selection',
|
|
36
|
+
'module_index_menu_modal',
|
|
37
|
+
'module_menu_modal',
|
|
38
|
+
]),
|
|
39
|
+
)
|
|
32
40
|
.optional(),
|
|
33
41
|
supported_types: z
|
|
34
42
|
.array(z.enum(['ltiResourceLink', 'file', 'html', 'link', 'image']))
|
|
@@ -70,7 +70,8 @@ export class AGSService {
|
|
|
70
70
|
gradingProgress: score.gradingProgress,
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
const
|
|
73
|
+
const agsScoreEndpoint = `${session.services.ags.lineitem}/scores`;
|
|
74
|
+
const response = await fetch(agsScoreEndpoint, {
|
|
74
75
|
method: 'POST',
|
|
75
76
|
headers: {
|
|
76
77
|
Authorization: `Bearer ${token}`,
|
|
@@ -107,13 +108,9 @@ export class AGSService {
|
|
|
107
108
|
'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly',
|
|
108
109
|
);
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
// we cannot include a search / query param
|
|
112
|
-
const lineItemUrl = new URL(session.services.ags.lineitem);
|
|
113
|
-
lineItemUrl.search = '';
|
|
114
|
-
const resultsUrl = `${lineItemUrl.toString()}/results`;
|
|
111
|
+
const resultsEndpoint = `${session.services.ags.lineitem}/results`;
|
|
115
112
|
|
|
116
|
-
const response = await fetch(
|
|
113
|
+
const response = await fetch(resultsEndpoint, {
|
|
117
114
|
method: 'GET',
|
|
118
115
|
headers: {
|
|
119
116
|
Authorization: `Bearer ${token}`,
|
|
@@ -189,9 +189,16 @@ export class DynamicRegistrationService {
|
|
|
189
189
|
throw new Error('Invalid or expired session');
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
// Extract platform family from session
|
|
193
|
+
const platformFamily =
|
|
194
|
+
session.openIdConfiguration[
|
|
195
|
+
'https://purl.imsglobal.org/spec/lti-platform-configuration'
|
|
196
|
+
].product_family_code;
|
|
197
|
+
|
|
192
198
|
// 1. build payload
|
|
193
199
|
const toolRegistrationPayload = this.buildRegistrationPayload(
|
|
194
200
|
dynamicRegistrationForm.services ?? [],
|
|
201
|
+
platformFamily,
|
|
195
202
|
);
|
|
196
203
|
|
|
197
204
|
// 2. Post request to Moodle
|
|
@@ -249,29 +256,88 @@ export class DynamicRegistrationService {
|
|
|
249
256
|
return session;
|
|
250
257
|
}
|
|
251
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Builds Canvas-specific deep linking messages for the 5 common placements.
|
|
261
|
+
* Creates separate messages for editor, module menu, assignments, modules page, and link selection.
|
|
262
|
+
*
|
|
263
|
+
* @param deepLinkingUri - URI where deep linking requests should be sent
|
|
264
|
+
* @param toolName - Display name of the tool
|
|
265
|
+
* @returns Array of Canvas deep linking message configurations
|
|
266
|
+
*/
|
|
267
|
+
private buildCanvasDeepLinkingMessages(
|
|
268
|
+
deepLinkingUri: string,
|
|
269
|
+
toolName: string,
|
|
270
|
+
): LTIMessage[] {
|
|
271
|
+
return [
|
|
272
|
+
{
|
|
273
|
+
type: 'LtiDeepLinkingRequest' as const,
|
|
274
|
+
target_link_uri: deepLinkingUri,
|
|
275
|
+
label: toolName,
|
|
276
|
+
placements: ['editor_button' as const],
|
|
277
|
+
supported_types: ['ltiResourceLink' as const],
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
type: 'LtiDeepLinkingRequest' as const,
|
|
281
|
+
target_link_uri: deepLinkingUri,
|
|
282
|
+
label: toolName,
|
|
283
|
+
placements: ['module_menu_modal' as const],
|
|
284
|
+
supported_types: ['ltiResourceLink' as const],
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
type: 'LtiDeepLinkingRequest' as const,
|
|
288
|
+
target_link_uri: deepLinkingUri,
|
|
289
|
+
label: toolName,
|
|
290
|
+
placements: ['assignment_selection' as const],
|
|
291
|
+
supported_types: ['ltiResourceLink' as const],
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
type: 'LtiDeepLinkingRequest' as const,
|
|
295
|
+
target_link_uri: deepLinkingUri,
|
|
296
|
+
label: toolName,
|
|
297
|
+
placements: ['module_index_menu_modal' as const],
|
|
298
|
+
supported_types: ['ltiResourceLink' as const],
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
type: 'LtiDeepLinkingRequest' as const,
|
|
302
|
+
target_link_uri: deepLinkingUri,
|
|
303
|
+
label: toolName,
|
|
304
|
+
placements: ['link_selection' as const],
|
|
305
|
+
supported_types: ['ltiResourceLink' as const],
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
|
|
252
310
|
/**
|
|
253
311
|
* Builds array of LTI message types based on selected services during registration.
|
|
254
312
|
* Always includes ResourceLinkRequest, conditionally adds DeepLinkingRequest.
|
|
255
313
|
*
|
|
256
314
|
* @param selectedServices - Array of service names selected by administrator
|
|
257
315
|
* @param deepLinkingUri - URI where deep linking requests should be sent
|
|
316
|
+
* @param platformFamily - Platform family code (e.g., 'canvas', 'moodle')
|
|
317
|
+
* @param toolName - Display name of the tool
|
|
258
318
|
* @returns Array of LTI message configurations for the registration payload
|
|
259
319
|
*/
|
|
260
320
|
private buildMessages(
|
|
261
321
|
selectedServices: string[],
|
|
262
322
|
deepLinkingUri: string,
|
|
323
|
+
platformFamily: string,
|
|
324
|
+
toolName: string,
|
|
263
325
|
): LTIMessage[] {
|
|
264
|
-
const messages = [];
|
|
326
|
+
const messages: LTIMessage[] = [];
|
|
265
327
|
messages.push({ type: 'LtiResourceLinkRequest' as const });
|
|
266
328
|
|
|
267
329
|
if (selectedServices?.includes('deep_linking')) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
330
|
+
if (platformFamily.toLowerCase() === 'canvas') {
|
|
331
|
+
messages.push(...this.buildCanvasDeepLinkingMessages(deepLinkingUri, toolName));
|
|
332
|
+
} else {
|
|
333
|
+
messages.push({
|
|
334
|
+
type: 'LtiDeepLinkingRequest' as const,
|
|
335
|
+
target_link_uri: deepLinkingUri,
|
|
336
|
+
label: toolName,
|
|
337
|
+
placements: ['editor_button' as const],
|
|
338
|
+
supported_types: ['ltiResourceLink' as const],
|
|
339
|
+
});
|
|
340
|
+
}
|
|
275
341
|
}
|
|
276
342
|
|
|
277
343
|
return messages;
|
|
@@ -309,9 +375,13 @@ export class DynamicRegistrationService {
|
|
|
309
375
|
* Combines tool configuration, selected services, and OAuth parameters into LTI 1.3 registration format.
|
|
310
376
|
*
|
|
311
377
|
* @param selectedServices - Array of service names selected by administrator
|
|
378
|
+
* @param platformFamily - Platform family code (e.g., 'canvas', 'moodle')
|
|
312
379
|
* @returns Complete registration payload ready for platform submission
|
|
313
380
|
*/
|
|
314
|
-
private buildRegistrationPayload(
|
|
381
|
+
private buildRegistrationPayload(
|
|
382
|
+
selectedServices: string[],
|
|
383
|
+
platformFamily: string,
|
|
384
|
+
): ToolRegistrationPayload {
|
|
315
385
|
const config = this.dynamicRegistrationConfig;
|
|
316
386
|
|
|
317
387
|
const deepLinkingUri = config.deepLinkingUri || `${config.url}/lti/deep-linking`;
|
|
@@ -319,7 +389,12 @@ export class DynamicRegistrationService {
|
|
|
319
389
|
const launchUri = config.launchUri || `${config.url}/lti/launch`;
|
|
320
390
|
const loginUri = config.loginUri || `${config.url}/lti/login`;
|
|
321
391
|
|
|
322
|
-
const messages = this.buildMessages(
|
|
392
|
+
const messages = this.buildMessages(
|
|
393
|
+
selectedServices,
|
|
394
|
+
deepLinkingUri,
|
|
395
|
+
platformFamily,
|
|
396
|
+
config.name,
|
|
397
|
+
);
|
|
323
398
|
const scopes = this.buildScopes(selectedServices);
|
|
324
399
|
|
|
325
400
|
const toolRegistrationPayload: ToolRegistrationPayload = {
|
|
@@ -173,7 +173,7 @@ export async function postRegistrationToMoodle(
|
|
|
173
173
|
if (!response.ok) {
|
|
174
174
|
const errorText = await response.json();
|
|
175
175
|
logger.error({ errorText }, 'lti dynamic registration error');
|
|
176
|
-
throw new Error(errorText);
|
|
176
|
+
throw new Error(JSON.stringify(errorText));
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
const data = await response.json();
|
|
@@ -31,9 +31,14 @@ export function createSession(lti13JwtPayload: LTI13JwtPayload): LTISession {
|
|
|
31
31
|
|
|
32
32
|
const services: Record<string, unknown> = {};
|
|
33
33
|
if (agsEndpoint) {
|
|
34
|
+
let lineItemUrl: string | undefined;
|
|
35
|
+
if (agsEndpoint.lineitem) {
|
|
36
|
+
const url = new URL(agsEndpoint.lineitem);
|
|
37
|
+
lineItemUrl = `${url.origin}${url.pathname}`; // quirk: moodle adds a url search param
|
|
38
|
+
}
|
|
34
39
|
services.ags = {
|
|
35
|
-
lineitem:
|
|
36
|
-
lineitems: agsEndpoint.lineitems,
|
|
40
|
+
lineitem: lineItemUrl,
|
|
41
|
+
lineitems: agsEndpoint.lineitems, // quirk: keep the moodle url search param
|
|
37
42
|
scopes: agsEndpoint.scope || [],
|
|
38
43
|
};
|
|
39
44
|
}
|