@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 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
- ContentArea: "ContentArea";
30
- RichTextEditor: "RichTextEditor";
31
- CourseNavigation: "CourseNavigation";
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
- ContentArea: "ContentArea";
57
- RichTextEditor: "RichTextEditor";
58
- CourseNavigation: "CourseNavigation";
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
- ContentArea: "ContentArea";
83
- RichTextEditor: "RichTextEditor";
84
- CourseNavigation: "CourseNavigation";
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;;;;;;;;;;;;;;;;;;;iBAa/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"}
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(['ContentArea', 'RichTextEditor', 'CourseNavigation']))
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
- ContentArea: "ContentArea";
54
- RichTextEditor: "RichTextEditor";
55
- CourseNavigation: "CourseNavigation";
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiBrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC"}
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
- ContentArea: "ContentArea";
25
- RichTextEditor: "RichTextEditor";
26
- CourseNavigation: "CourseNavigation";
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
- ContentArea: "ContentArea";
84
- RichTextEditor: "RichTextEditor";
85
- CourseNavigation: "CourseNavigation";
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;;;;;;;;;;;;;;;;;;;;;;;;;;;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
+ {"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;IAiCjF;;;;;;;;;;;;;OAaG;IACG,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;IA4BvD;;;;;;;;;;;;;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"}
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 response = await fetch(`${session.services.ags.lineitem}/scores`, {
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
- // cleanse the results URL
88
- // we cannot include a search / query param
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;IAoDlB;;;;;;OAMG;IACG,yBAAyB,CAC7B,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,6BAA6B,GAAG,SAAS,CAAC;IAQrD;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAoBrB;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IAoBnB;;;;;;OAMG;IACH,OAAO,CAAC,wBAAwB;YAkClB,mCAAmC;IAgBjD,OAAO,CAAC,0BAA0B;CAiDnC"}
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
- messages.push({
201
- type: 'LtiDeepLinkingRequest',
202
- target_link_uri: deepLinkingUri,
203
- label: 'Content Selection',
204
- placements: ['ContentArea'], // Focus on content area only
205
- supported_types: ['ltiResourceLink'], // Standard content selection
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,CAuG1E"}
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: agsEndpoint.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lti-tool/core",
3
- "version": "0.13.2",
3
+ "version": "0.14.1",
4
4
  "description": "LTI 1.3 implementation for Node.js",
5
5
  "keywords": [
6
6
  "lti",
@@ -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(z.enum(['ContentArea', 'RichTextEditor', 'CourseNavigation']))
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 response = await fetch(`${session.services.ags.lineitem}/scores`, {
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
- // cleanse the results URL
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(resultsUrl, {
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
- messages.push({
269
- type: 'LtiDeepLinkingRequest' as const,
270
- target_link_uri: deepLinkingUri,
271
- label: 'Content Selection',
272
- placements: ['ContentArea' as const], // Focus on content area only
273
- supported_types: ['ltiResourceLink' as const], // Standard content selection
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(selectedServices: string[]): ToolRegistrationPayload {
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(selectedServices, deepLinkingUri);
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: agsEndpoint.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
  }