lti-v1.0-node-library 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jaakko Rajala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # lti-v1.0-node-library
2
+
3
+ TypeScript library for LTI 1.0/1.1 integration with LMS platforms, including Basic Outcomes grade submission. Focused on LTI 1.0/1.1 support for platforms like older versions of A-Plus that don't support LTI 1.3.
4
+
5
+ ## Quick Start
6
+
7
+ ### Installation
8
+
9
+ ```bash
10
+ npm install lti-v1.0-node-library
11
+ ```
12
+
13
+ **Note:** The examples below use Express.js, but you can adapt the library to work with other Node.js frameworks. The library uses Express types for convenience, but the core functionality is framework-agnostic.
14
+
15
+ ### Express.js Example
16
+
17
+ ```typescript
18
+ import express, { Request, Response } from 'express';
19
+ import session from 'express-session';
20
+ import { handleLti10Launch, submitGrade } from 'lti-v1.0-node-library';
21
+
22
+ const app = express();
23
+ app.use(express.urlencoded({ extended: true }));
24
+ // SESSION_SECRET is for Express session encryption (separate from LTI consumer secret)
25
+ app.use(session({
26
+ secret: process.env.SESSION_SECRET || 'your-session-secret',
27
+ resave: true,
28
+ saveUninitialized: true
29
+ }));
30
+
31
+ // Set LTI environment variables (required for LTI authentication)
32
+ // CONSUMER_KEY and CONSUMER_SECRET authenticate communication with your LMS
33
+ process.env.CONSUMER_KEY = 'your-consumer-key';
34
+ process.env.CONSUMER_SECRET = 'your-consumer-secret';
35
+
36
+ // LTI launch endpoint
37
+ app.post('/lti-launch', (req: Request, res: Response) => {
38
+ handleLti10Launch(req, res, {
39
+ onSuccess: (ltiData, userInfo, outcomeService) => {
40
+ // User is logged in, session data is stored in req.session
41
+ res.redirect('/app');
42
+ }
43
+ });
44
+ });
45
+
46
+ // Grade submission endpoint
47
+ app.post('/submit-grade', async (req: Request, res: Response) => {
48
+ const outcomeService = req.session.outcomeService;
49
+ if (!outcomeService) {
50
+ return res.status(400).json({ error: 'Outcome service not available' });
51
+ }
52
+
53
+ const result = await submitGrade(outcomeService, req.body.grade, req.body.maxScore);
54
+ res.json(result);
55
+ });
56
+ ```
57
+
58
+ ## Configuration
59
+
60
+ ### Environment Variables
61
+
62
+ **Required:**
63
+
64
+ ```bash
65
+ CONSUMER_KEY=your-consumer-key-from-lms
66
+ CONSUMER_SECRET=your-consumer-secret-from-lms
67
+ ```
68
+
69
+ **Why these are needed:**
70
+
71
+ - **`CONSUMER_KEY`**: A unique identifier for your LTI tool in the LMS. The LMS sends this key with each launch request to identify which tool is being launched. It must match exactly what you configure in your LMS.
72
+
73
+ - **`CONSUMER_SECRET`**: A shared secret used for OAuth 1.0 signature verification. The LMS signs launch requests using this secret, and your tool verifies the signature to ensure the request is authentic and hasn't been tampered with. It's also used when submitting grades back to the LMS.
74
+
75
+ **Security Note:** These credentials authenticate communication between your LMS and your LTI tool. Keep them secure and never commit them to version control. Use environment variables or a secure secrets manager.
76
+
77
+ ### LMS Configuration (A-Plus Example)
78
+
79
+ 1. **Launch URL**: `http://your-server:port/lti-launch`
80
+ 2. **Consumer Key**: Must match your `CONSUMER_KEY` environment variable exactly
81
+ 3. **Consumer Secret**: Must match your `CONSUMER_SECRET` environment variable exactly
82
+ 4. **Privacy Notice URL**: `http://your-server:port/privacy` (mandatory for external services)
83
+ 5. **Enable**: "Send grades back to LMS" option (required for grade submission)
84
+
85
+ ### Privacy Notice URL
86
+
87
+ **Required for external services:** A link to your service's privacy notice is mandatory for LTI tools outside your organization.
88
+
89
+ - **Privacy Notice URL**: `http://your-server:port/privacy`
90
+ - This URL should point to a page that explains your data collection and usage practices
91
+ - Configure this in your LMS admin panel alongside the Launch URL
92
+ - Users may be required to accept the privacy notice before accessing your tool
93
+ - The privacy notice page should be publicly accessible (no authentication required)
94
+
95
+ ## API
96
+
97
+ ### `handleLti10Launch(req, res, options?)`
98
+
99
+ Handles LTI 1.0/1.1 launch. Uses Express `Request` and `Response` types, and stores data in `req.session` (requires Express with session middleware).
100
+
101
+ **Stores in `req.session`:**
102
+ - `lti10Data` - Complete LTI launch data
103
+ - `userInfo` - User information
104
+ - `outcomeService` - Grade submission configuration (if available)
105
+
106
+ **Options:**
107
+ - `consumerKey?: string` - Override env var (default: `process.env.CONSUMER_KEY`)
108
+ - `consumerSecret?: string` - Override env var (default: `process.env.CONSUMER_SECRET`)
109
+ - `onSuccess?: (ltiData, userInfo, outcomeService?) => void` - Success callback
110
+ - `onError?: (error: Error) => void` - Error callback
111
+
112
+ ### `submitGrade(outcomeService, grade, maxScore?)`
113
+
114
+ Submits grade using LTI Basic Outcomes.
115
+
116
+ **Parameters:**
117
+ - `outcomeService` - From `req.session.outcomeService`
118
+ - `grade` - Grade value (e.g., 85)
119
+ - `maxScore` - Maximum score (default: 1.0)
120
+
121
+ **Returns:** `Promise<{ success: boolean, response?: any, status?: number, error?: string }>`
122
+
123
+
124
+ ## Types
125
+
126
+ ```typescript
127
+ import {
128
+ Lti10Data,
129
+ OutcomeService,
130
+ UserInfo,
131
+ GradeSubmissionResult
132
+ } from '1.0-lti-node-library';
133
+ ```
134
+
135
+ ## Framework Support
136
+
137
+ The library currently uses Express.js types (`Request`, `Response`) and accesses `req.session` for session storage. This makes it convenient for Express.js applications, but you can adapt it for other frameworks by:
138
+
139
+ 1. Using the utility functions directly (`extractUserInfo`, `extractOutcomeService`, `submitGrade`)
140
+ 2. Implementing your own session storage mechanism
141
+ 3. Adapting the request/response handling to your framework
142
+
143
+ ## Examples
144
+
145
+ ### Express.js App
146
+
147
+ ```typescript
148
+ import express from 'express';
149
+ import session from 'express-session';
150
+ import { handleLti10Launch, submitGrade } from '1.0-lti-node-library';
151
+
152
+ const app = express();
153
+ app.use(express.urlencoded({ extended: true }));
154
+ app.use(session({
155
+ secret: process.env.SESSION_SECRET || 'secret',
156
+ resave: true,
157
+ saveUninitialized: true
158
+ }));
159
+
160
+ // LTI launch
161
+ app.post('/lti-launch', (req, res) => {
162
+ handleLti10Launch(req, res, {
163
+ onSuccess: (ltiData, userInfo) => {
164
+ console.log('User:', userInfo.name);
165
+ res.redirect('/app');
166
+ }
167
+ });
168
+ });
169
+
170
+ // Submit grade
171
+ app.post('/submit-grade', async (req, res) => {
172
+ const result = await submitGrade(
173
+ req.session.outcomeService,
174
+ req.body.grade,
175
+ req.body.maxScore
176
+ );
177
+ res.json(result);
178
+ });
179
+
180
+ app.listen(3000);
181
+ ```
182
+
183
+ ## License
184
+
185
+ MIT
package/lib/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { handleLti10Launch, isLti10Launch } from './launch/lti10Handler';
2
+ export { submitGrade } from './outcomes/basicOutcomes';
3
+ export { extractUserInfo, extractOutcomeService, createSessionData } from './session/sessionHelpers';
4
+ export { buildBasicOutcomesXml } from './utils/xmlBuilder';
5
+ export { createOAuthInstance } from './oauth/oauth1';
6
+ export type { Lti10Data, OutcomeService, UserInfo, SessionData, GradeSubmissionResult, Lti10LaunchOptions, GradeSubmissionOptions, RequestWithSession } from './types';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGzE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGrG,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGrD,YAAY,EACV,SAAS,EACT,cAAc,EACd,QAAQ,EACR,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,SAAS,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createOAuthInstance = exports.buildBasicOutcomesXml = exports.createSessionData = exports.extractOutcomeService = exports.extractUserInfo = exports.submitGrade = exports.isLti10Launch = exports.handleLti10Launch = void 0;
4
+ // Launch handlers
5
+ var lti10Handler_1 = require("./launch/lti10Handler");
6
+ Object.defineProperty(exports, "handleLti10Launch", { enumerable: true, get: function () { return lti10Handler_1.handleLti10Launch; } });
7
+ Object.defineProperty(exports, "isLti10Launch", { enumerable: true, get: function () { return lti10Handler_1.isLti10Launch; } });
8
+ // Grade submission
9
+ var basicOutcomes_1 = require("./outcomes/basicOutcomes");
10
+ Object.defineProperty(exports, "submitGrade", { enumerable: true, get: function () { return basicOutcomes_1.submitGrade; } });
11
+ // Session helpers
12
+ var sessionHelpers_1 = require("./session/sessionHelpers");
13
+ Object.defineProperty(exports, "extractUserInfo", { enumerable: true, get: function () { return sessionHelpers_1.extractUserInfo; } });
14
+ Object.defineProperty(exports, "extractOutcomeService", { enumerable: true, get: function () { return sessionHelpers_1.extractOutcomeService; } });
15
+ Object.defineProperty(exports, "createSessionData", { enumerable: true, get: function () { return sessionHelpers_1.createSessionData; } });
16
+ // Utilities
17
+ var xmlBuilder_1 = require("./utils/xmlBuilder");
18
+ Object.defineProperty(exports, "buildBasicOutcomesXml", { enumerable: true, get: function () { return xmlBuilder_1.buildBasicOutcomesXml; } });
19
+ var oauth1_1 = require("./oauth/oauth1");
20
+ Object.defineProperty(exports, "createOAuthInstance", { enumerable: true, get: function () { return oauth1_1.createOAuthInstance; } });
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,kBAAkB;AAClB,sDAAyE;AAAhE,iHAAA,iBAAiB,OAAA;AAAE,6GAAA,aAAa,OAAA;AAEzC,mBAAmB;AACnB,0DAAuD;AAA9C,4GAAA,WAAW,OAAA;AAEpB,kBAAkB;AAClB,2DAAqG;AAA5F,iHAAA,eAAe,OAAA;AAAE,uHAAA,qBAAqB,OAAA;AAAE,mHAAA,iBAAiB,OAAA;AAElE,YAAY;AACZ,iDAA2D;AAAlD,mHAAA,qBAAqB,OAAA;AAC9B,yCAAqD;AAA5C,6GAAA,mBAAmB,OAAA"}
@@ -0,0 +1,21 @@
1
+ import { Request, Response } from 'express';
2
+ import { Lti10Data, Lti10LaunchOptions } from '../types';
3
+ /**
4
+ * Detect if request is an LTI 1.0 launch
5
+ *
6
+ * @param body - Request body
7
+ * @returns True if this appears to be an LTI 1.0 launch
8
+ */
9
+ export declare function isLti10Launch(body: any): body is Lti10Data;
10
+ /**
11
+ * Handle LTI 1.0 launch request
12
+ *
13
+ * Extracts LTI data, validates consumer key, stores session data,
14
+ * and extracts outcome service information for grade submission.
15
+ *
16
+ * @param req - Express request object
17
+ * @param res - Express response object
18
+ * @param options - Launch handler options
19
+ */
20
+ export declare function handleLti10Launch(req: Request, res: Response, options?: Lti10LaunchOptions): void;
21
+ //# sourceMappingURL=lti10Handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lti10Handler.d.ts","sourceRoot":"","sources":["../../src/launch/lti10Handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAsB,MAAM,UAAU,CAAC;AAG7E;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,IAAI,SAAS,CAE1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,OAAO,CAAC,EAAE,kBAAkB,GAC3B,IAAI,CA4DN"}
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isLti10Launch = isLti10Launch;
4
+ exports.handleLti10Launch = handleLti10Launch;
5
+ const sessionHelpers_1 = require("../session/sessionHelpers");
6
+ /**
7
+ * Detect if request is an LTI 1.0 launch
8
+ *
9
+ * @param body - Request body
10
+ * @returns True if this appears to be an LTI 1.0 launch
11
+ */
12
+ function isLti10Launch(body) {
13
+ return !!(body && (body.oauth_consumer_key || body.lti_version));
14
+ }
15
+ /**
16
+ * Handle LTI 1.0 launch request
17
+ *
18
+ * Extracts LTI data, validates consumer key, stores session data,
19
+ * and extracts outcome service information for grade submission.
20
+ *
21
+ * @param req - Express request object
22
+ * @param res - Express response object
23
+ * @param options - Launch handler options
24
+ */
25
+ function handleLti10Launch(req, res, options) {
26
+ const body = req.body;
27
+ // Check if this is an LTI 1.0 launch
28
+ if (!isLti10Launch(body)) {
29
+ if (options?.onError) {
30
+ options.onError(new Error('Not an LTI 1.0 launch'));
31
+ }
32
+ return;
33
+ }
34
+ const reqWithSession = req;
35
+ // Get consumer key and secret from options or environment
36
+ const expectedConsumerKey = options?.consumerKey || process.env.CONSUMER_KEY;
37
+ const consumerSecret = options?.consumerSecret || process.env.CONSUMER_SECRET || '';
38
+ const consumerKeyFromLaunch = body.oauth_consumer_key || '';
39
+ // Validate consumer key if expected key is provided
40
+ if (expectedConsumerKey && consumerKeyFromLaunch !== expectedConsumerKey) {
41
+ const error = new Error('Consumer key mismatch');
42
+ if (options?.onError) {
43
+ options.onError(error);
44
+ }
45
+ return;
46
+ }
47
+ // Create session data
48
+ const sessionData = (0, sessionHelpers_1.createSessionData)(body, consumerKeyFromLaunch || expectedConsumerKey || '', consumerSecret);
49
+ // Store in session
50
+ reqWithSession.session.lti10Data = body;
51
+ reqWithSession.session.ltiVersion = sessionData.ltiVersion;
52
+ reqWithSession.session.userLoggedIn = sessionData.userLoggedIn;
53
+ reqWithSession.session.userInfo = sessionData.userInfo;
54
+ reqWithSession.session.outcomeService = sessionData.outcomeService;
55
+ // Store return URL if available
56
+ if (body.launch_presentation_return_url) {
57
+ reqWithSession.session.returnUrl = body.launch_presentation_return_url;
58
+ }
59
+ // Store raw request for debugging
60
+ reqWithSession.session.rawRequest = {
61
+ method: req.method,
62
+ url: req.url,
63
+ headers: req.headers,
64
+ query: req.query,
65
+ body: req.body,
66
+ timestamp: new Date().toISOString()
67
+ };
68
+ // Call success callback if provided
69
+ if (options?.onSuccess) {
70
+ options.onSuccess(body, sessionData.userInfo, sessionData.outcomeService);
71
+ }
72
+ }
73
+ //# sourceMappingURL=lti10Handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lti10Handler.js","sourceRoot":"","sources":["../../src/launch/lti10Handler.ts"],"names":[],"mappings":";;AAUA,sCAEC;AAYD,8CAgEC;AAtFD,8DAA8D;AAE9D;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,IAAS;IACrC,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AACnE,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,iBAAiB,CAC/B,GAAY,EACZ,GAAa,EACb,OAA4B;IAE5B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAiB,CAAC;IAEnC,qCAAqC;IACrC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC;YACrB,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;QACtD,CAAC;QACD,OAAO;IACT,CAAC;IAED,MAAM,cAAc,GAAG,GAAyB,CAAC;IAEjD,0DAA0D;IAC1D,MAAM,mBAAmB,GAAG,OAAO,EAAE,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAC7E,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC;IACpF,MAAM,qBAAqB,GAAG,IAAI,CAAC,kBAAkB,IAAI,EAAE,CAAC;IAE5D,oDAAoD;IACpD,IAAI,mBAAmB,IAAI,qBAAqB,KAAK,mBAAmB,EAAE,CAAC;QACzE,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACjD,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC;YACrB,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;QACD,OAAO;IACT,CAAC;IAED,sBAAsB;IACtB,MAAM,WAAW,GAAG,IAAA,kCAAiB,EACnC,IAAI,EACJ,qBAAqB,IAAI,mBAAmB,IAAI,EAAE,EAClD,cAAc,CACf,CAAC;IAEF,mBAAmB;IACnB,cAAc,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IACxC,cAAc,CAAC,OAAO,CAAC,UAAU,GAAG,WAAW,CAAC,UAAU,CAAC;IAC3D,cAAc,CAAC,OAAO,CAAC,YAAY,GAAG,WAAW,CAAC,YAAY,CAAC;IAC/D,cAAc,CAAC,OAAO,CAAC,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC;IACvD,cAAc,CAAC,OAAO,CAAC,cAAc,GAAG,WAAW,CAAC,cAAc,CAAC;IAEnE,gCAAgC;IAChC,IAAI,IAAI,CAAC,8BAA8B,EAAE,CAAC;QACxC,cAAc,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,8BAA8B,CAAC;IACzE,CAAC;IAED,kCAAkC;IAClC,cAAc,CAAC,OAAO,CAAC,UAAU,GAAG;QAClC,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IAEF,oCAAoC;IACpC,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC;QACvB,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ import OAuth from 'oauth-1.0a';
2
+ /**
3
+ * Create OAuth 1.0 instance configured for LTI Basic Outcomes
4
+ *
5
+ * This includes the critical body_hash_function that calculates SHA1 hash
6
+ * of the request body (not HMAC) for oauth_body_hash parameter.
7
+ *
8
+ * @param consumerKey - OAuth consumer key
9
+ * @param consumerSecret - OAuth consumer secret
10
+ * @returns Configured OAuth instance
11
+ */
12
+ export declare function createOAuthInstance(consumerKey: string, consumerSecret: string): OAuth;
13
+ //# sourceMappingURL=oauth1.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth1.d.ts","sourceRoot":"","sources":["../../src/oauth/oauth1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAG/B;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,KAAK,CAkBtF"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createOAuthInstance = createOAuthInstance;
7
+ const oauth_1_0a_1 = __importDefault(require("oauth-1.0a"));
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ /**
10
+ * Create OAuth 1.0 instance configured for LTI Basic Outcomes
11
+ *
12
+ * This includes the critical body_hash_function that calculates SHA1 hash
13
+ * of the request body (not HMAC) for oauth_body_hash parameter.
14
+ *
15
+ * @param consumerKey - OAuth consumer key
16
+ * @param consumerSecret - OAuth consumer secret
17
+ * @returns Configured OAuth instance
18
+ */
19
+ function createOAuthInstance(consumerKey, consumerSecret) {
20
+ return new oauth_1_0a_1.default({
21
+ consumer: {
22
+ key: consumerKey,
23
+ secret: consumerSecret
24
+ },
25
+ signature_method: 'HMAC-SHA1',
26
+ hash_function(base_string, key) {
27
+ return crypto_1.default.createHmac('sha1', key).update(base_string).digest('base64');
28
+ },
29
+ // Body hash function: SHA1 hash of body (not HMAC, just SHA1)
30
+ // The library passes (body, signingKey) but for body hash we only use body
31
+ // This is critical for LTI Basic Outcomes - oauth_body_hash must be SHA1(body), not HMAC(body, key)
32
+ body_hash_function(body, key) {
33
+ // Ignore key parameter - OAuth body hash is just SHA1 of body
34
+ return crypto_1.default.createHash('sha1').update(body, 'utf8').digest('base64');
35
+ }
36
+ });
37
+ }
38
+ //# sourceMappingURL=oauth1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth1.js","sourceRoot":"","sources":["../../src/oauth/oauth1.ts"],"names":[],"mappings":";;;;;AAaA,kDAkBC;AA/BD,4DAA+B;AAC/B,oDAA4B;AAE5B;;;;;;;;;GASG;AACH,SAAgB,mBAAmB,CAAC,WAAmB,EAAE,cAAsB;IAC7E,OAAO,IAAI,oBAAK,CAAC;QACf,QAAQ,EAAE;YACR,GAAG,EAAE,WAAW;YAChB,MAAM,EAAE,cAAc;SACvB;QACD,gBAAgB,EAAE,WAAW;QAC7B,aAAa,CAAC,WAAmB,EAAE,GAAW;YAC5C,OAAO,gBAAM,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC7E,CAAC;QACD,8DAA8D;QAC9D,2EAA2E;QAC3E,oGAAoG;QACpG,kBAAkB,CAAC,IAAY,EAAE,GAAW;YAC1C,8DAA8D;YAC9D,OAAO,gBAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACzE,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { OutcomeService, GradeSubmissionResult, GradeSubmissionOptions } from '../types';
2
+ /**
3
+ * Submit a grade to A-Plus using LTI Basic Outcomes Service
4
+ *
5
+ * @param outcomeService - Outcome service configuration
6
+ * @param grade - The grade value to submit
7
+ * @param maxScore - Maximum possible score (default: 1.0)
8
+ * @param options - Additional options (reserved for future use)
9
+ * @returns Promise resolving to grade submission result
10
+ * @throws Error if outcome service info is missing or submission fails
11
+ */
12
+ export declare function submitGrade(outcomeService: OutcomeService, grade: number, maxScore?: number, options?: GradeSubmissionOptions): Promise<GradeSubmissionResult>;
13
+ //# sourceMappingURL=basicOutcomes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"basicOutcomes.d.ts","sourceRoot":"","sources":["../../src/outcomes/basicOutcomes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAIzF;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,cAAc,EAAE,cAAc,EAC9B,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,MAAY,EACtB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,qBAAqB,CAAC,CAkEhC"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.submitGrade = submitGrade;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const xml2js_1 = require("xml2js");
9
+ const oauth1_1 = require("../oauth/oauth1");
10
+ const xmlBuilder_1 = require("../utils/xmlBuilder");
11
+ /**
12
+ * Submit a grade to A-Plus using LTI Basic Outcomes Service
13
+ *
14
+ * @param outcomeService - Outcome service configuration
15
+ * @param grade - The grade value to submit
16
+ * @param maxScore - Maximum possible score (default: 1.0)
17
+ * @param options - Additional options (reserved for future use)
18
+ * @returns Promise resolving to grade submission result
19
+ * @throws Error if outcome service info is missing or submission fails
20
+ */
21
+ async function submitGrade(outcomeService, grade, maxScore = 1.0, options) {
22
+ // Validate outcome service
23
+ if (!outcomeService || !outcomeService.url || !outcomeService.sourcedid) {
24
+ throw new Error('Outcome service information missing');
25
+ }
26
+ if (!outcomeService.consumerKey || !outcomeService.consumerSecret) {
27
+ throw new Error('Consumer key or secret missing. Make sure the consumer secret matches what was configured in A-Plus.');
28
+ }
29
+ // Create OAuth instance
30
+ const oauth = (0, oauth1_1.createOAuthInstance)(outcomeService.consumerKey, outcomeService.consumerSecret);
31
+ // Build XML payload
32
+ const xmlBody = (0, xmlBuilder_1.buildBasicOutcomesXml)(outcomeService.sourcedid, grade, maxScore);
33
+ // Prepare request data with body hash
34
+ const requestData = {
35
+ url: outcomeService.url,
36
+ method: 'POST',
37
+ includeBodyHash: true, // This tells the library to calculate and include oauth_body_hash
38
+ data: xmlBody // Pass as string - library will use body_hash_function on this
39
+ };
40
+ // Generate OAuth authorization
41
+ // The library will automatically:
42
+ // 1. Call getBodyHash() which uses body_hash_function(xmlBody, signingKey)
43
+ // 2. Include oauth_body_hash in OAuth params
44
+ // 3. Include oauth_body_hash in signature base string
45
+ // No token for LTI Basic Outcomes - pass empty object
46
+ const authParams = oauth.authorize(requestData, {});
47
+ // Convert to header format
48
+ const authHeader = oauth.toHeader(authParams);
49
+ try {
50
+ const response = await axios_1.default.post(outcomeService.url, xmlBody, {
51
+ headers: {
52
+ 'Content-Type': 'application/xml',
53
+ ...authHeader
54
+ }
55
+ });
56
+ // Parse response XML
57
+ const parser = new xml2js_1.Parser();
58
+ const result = await parser.parseStringPromise(response.data);
59
+ return {
60
+ success: true,
61
+ response: result,
62
+ status: response.status
63
+ };
64
+ }
65
+ catch (error) {
66
+ const axiosError = error;
67
+ return {
68
+ success: false,
69
+ error: axiosError.message,
70
+ details: axiosError.response ? axiosError.response.data : null,
71
+ status: axiosError.response?.status
72
+ };
73
+ }
74
+ }
75
+ //# sourceMappingURL=basicOutcomes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"basicOutcomes.js","sourceRoot":"","sources":["../../src/outcomes/basicOutcomes.ts"],"names":[],"mappings":";;;;;AAgBA,kCAuEC;AAvFD,kDAA0C;AAC1C,mCAAgC;AAEhC,4CAAsD;AACtD,oDAA4D;AAE5D;;;;;;;;;GASG;AACI,KAAK,UAAU,WAAW,CAC/B,cAA8B,EAC9B,KAAa,EACb,WAAmB,GAAG,EACtB,OAAgC;IAEhC,2BAA2B;IAC3B,IAAI,CAAC,cAAc,IAAI,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,CAAC,cAAc,CAAC,WAAW,IAAI,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,sGAAsG,CAAC,CAAC;IAC1H,CAAC;IAED,wBAAwB;IACxB,MAAM,KAAK,GAAG,IAAA,4BAAmB,EAAC,cAAc,CAAC,WAAW,EAAE,cAAc,CAAC,cAAc,CAAC,CAAC;IAE7F,oBAAoB;IACpB,MAAM,OAAO,GAAG,IAAA,kCAAqB,EAAC,cAAc,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAEjF,sCAAsC;IACtC,MAAM,WAAW,GAAG;QAClB,GAAG,EAAE,cAAc,CAAC,GAAG;QACvB,MAAM,EAAE,MAAe;QACvB,eAAe,EAAE,IAAI,EAAE,kEAAkE;QACzF,IAAI,EAAE,OAAO,CAAC,+DAA+D;KAC9E,CAAC;IAEF,+BAA+B;IAC/B,kCAAkC;IAClC,2EAA2E;IAC3E,6CAA6C;IAC7C,sDAAsD;IACtD,sDAAsD;IACtD,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,EAAS,CAAC,CAAC;IAE3D,2BAA2B;IAC3B,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAE9C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,eAAK,CAAC,IAAI,CAC/B,cAAc,CAAC,GAAG,EAClB,OAAO,EACP;YACE,OAAO,EAAE;gBACP,cAAc,EAAE,iBAAiB;gBACjC,GAAG,UAAU;aACd;SACF,CACF,CAAC;QAEF,qBAAqB;QACrB,MAAM,MAAM,GAAG,IAAI,eAAM,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAE9D,OAAO;YACL,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,MAAM;YAChB,MAAM,EAAE,QAAQ,CAAC,MAAM;SACxB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,UAAU,GAAG,KAAmB,CAAC;QAEvC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,UAAU,CAAC,OAAO;YACzB,OAAO,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;YAC9D,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE,MAAM;SACpC,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { Lti10Data, UserInfo, OutcomeService } from '../types';
2
+ /**
3
+ * Extract user information from LTI 1.0 launch data
4
+ *
5
+ * @param ltiBody - LTI 1.0 launch body data
6
+ * @returns Extracted user information
7
+ */
8
+ export declare function extractUserInfo(ltiBody: Lti10Data): UserInfo;
9
+ /**
10
+ * Extract outcome service information from LTI 1.0 launch data
11
+ *
12
+ * @param ltiBody - LTI 1.0 launch body data
13
+ * @param consumerKey - Consumer key (from launch or environment)
14
+ * @param consumerSecret - Consumer secret (from environment)
15
+ * @returns Outcome service configuration or undefined if not available
16
+ */
17
+ export declare function extractOutcomeService(ltiBody: Lti10Data, consumerKey: string, consumerSecret: string): OutcomeService | undefined;
18
+ /**
19
+ * Create complete session data structure from LTI 1.0 launch
20
+ *
21
+ * @param ltiBody - LTI 1.0 launch body data
22
+ * @param consumerKey - Consumer key (from launch or environment)
23
+ * @param consumerSecret - Consumer secret (from environment)
24
+ * @returns Complete session data structure
25
+ */
26
+ export declare function createSessionData(ltiBody: Lti10Data, consumerKey: string, consumerSecret: string): {
27
+ lti10Data: Lti10Data;
28
+ ltiVersion: string;
29
+ userLoggedIn: boolean;
30
+ userInfo: UserInfo;
31
+ outcomeService?: OutcomeService;
32
+ };
33
+ //# sourceMappingURL=sessionHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionHelpers.d.ts","sourceRoot":"","sources":["../../src/session/sessionHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/D;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,GAAG,QAAQ,CAW5D;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,EAClB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,GACrB,cAAc,GAAG,SAAS,CAW5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,EAClB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,GACrB;IACD,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,QAAQ,CAAC;IACnB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC,CAWA"}
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractUserInfo = extractUserInfo;
4
+ exports.extractOutcomeService = extractOutcomeService;
5
+ exports.createSessionData = createSessionData;
6
+ /**
7
+ * Extract user information from LTI 1.0 launch data
8
+ *
9
+ * @param ltiBody - LTI 1.0 launch body data
10
+ * @returns Extracted user information
11
+ */
12
+ function extractUserInfo(ltiBody) {
13
+ return {
14
+ userId: ltiBody.user_id,
15
+ name: `${ltiBody.lis_person_name_given || ''} ${ltiBody.lis_person_name_family || ''}`.trim(),
16
+ email: ltiBody.lis_person_contact_email_primary,
17
+ roles: ltiBody.roles,
18
+ contextId: ltiBody.context_id,
19
+ contextLabel: ltiBody.context_label,
20
+ contextTitle: ltiBody.context_title,
21
+ resourceLinkId: ltiBody.resource_link_id
22
+ };
23
+ }
24
+ /**
25
+ * Extract outcome service information from LTI 1.0 launch data
26
+ *
27
+ * @param ltiBody - LTI 1.0 launch body data
28
+ * @param consumerKey - Consumer key (from launch or environment)
29
+ * @param consumerSecret - Consumer secret (from environment)
30
+ * @returns Outcome service configuration or undefined if not available
31
+ */
32
+ function extractOutcomeService(ltiBody, consumerKey, consumerSecret) {
33
+ if (!ltiBody.lis_outcome_service_url || !ltiBody.lis_result_sourcedid) {
34
+ return undefined;
35
+ }
36
+ return {
37
+ url: ltiBody.lis_outcome_service_url,
38
+ sourcedid: ltiBody.lis_result_sourcedid,
39
+ consumerKey: consumerKey,
40
+ consumerSecret: consumerSecret
41
+ };
42
+ }
43
+ /**
44
+ * Create complete session data structure from LTI 1.0 launch
45
+ *
46
+ * @param ltiBody - LTI 1.0 launch body data
47
+ * @param consumerKey - Consumer key (from launch or environment)
48
+ * @param consumerSecret - Consumer secret (from environment)
49
+ * @returns Complete session data structure
50
+ */
51
+ function createSessionData(ltiBody, consumerKey, consumerSecret) {
52
+ const userInfo = extractUserInfo(ltiBody);
53
+ const outcomeService = extractOutcomeService(ltiBody, consumerKey, consumerSecret);
54
+ return {
55
+ lti10Data: ltiBody,
56
+ ltiVersion: '1.0',
57
+ userLoggedIn: true,
58
+ userInfo,
59
+ outcomeService
60
+ };
61
+ }
62
+ //# sourceMappingURL=sessionHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionHelpers.js","sourceRoot":"","sources":["../../src/session/sessionHelpers.ts"],"names":[],"mappings":";;AAQA,0CAWC;AAUD,sDAeC;AAUD,8CAqBC;AAzED;;;;;GAKG;AACH,SAAgB,eAAe,CAAC,OAAkB;IAChD,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,OAAO;QACvB,IAAI,EAAE,GAAG,OAAO,CAAC,qBAAqB,IAAI,EAAE,IAAI,OAAO,CAAC,sBAAsB,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE;QAC7F,KAAK,EAAE,OAAO,CAAC,gCAAgC;QAC/C,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,SAAS,EAAE,OAAO,CAAC,UAAU;QAC7B,YAAY,EAAE,OAAO,CAAC,aAAa;QACnC,YAAY,EAAE,OAAO,CAAC,aAAa;QACnC,cAAc,EAAE,OAAO,CAAC,gBAAgB;KACzC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,qBAAqB,CACnC,OAAkB,EAClB,WAAmB,EACnB,cAAsB;IAEtB,IAAI,CAAC,OAAO,CAAC,uBAAuB,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC;QACtE,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,GAAG,EAAE,OAAO,CAAC,uBAAuB;QACpC,SAAS,EAAE,OAAO,CAAC,oBAAoB;QACvC,WAAW,EAAE,WAAW;QACxB,cAAc,EAAE,cAAc;KAC/B,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,iBAAiB,CAC/B,OAAkB,EAClB,WAAmB,EACnB,cAAsB;IAQtB,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,cAAc,GAAG,qBAAqB,CAAC,OAAO,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;IAEnF,OAAO;QACL,SAAS,EAAE,OAAO;QAClB,UAAU,EAAE,KAAK;QACjB,YAAY,EAAE,IAAI;QAClB,QAAQ;QACR,cAAc;KACf,CAAC;AACJ,CAAC"}
@@ -0,0 +1,103 @@
1
+ import { Request } from 'express';
2
+ /**
3
+ * LTI 1.0 launch data structure
4
+ */
5
+ export interface Lti10Data {
6
+ oauth_consumer_key?: string;
7
+ oauth_signature?: string;
8
+ oauth_signature_method?: string;
9
+ oauth_timestamp?: string;
10
+ oauth_nonce?: string;
11
+ oauth_version?: string;
12
+ lti_version?: string;
13
+ lti_message_type?: string;
14
+ resource_link_id?: string;
15
+ user_id?: string;
16
+ roles?: string;
17
+ lis_person_name_given?: string;
18
+ lis_person_name_family?: string;
19
+ lis_person_contact_email_primary?: string;
20
+ context_id?: string;
21
+ context_label?: string;
22
+ context_title?: string;
23
+ launch_presentation_document_target?: string;
24
+ launch_presentation_return_url?: string;
25
+ lis_outcome_service_url?: string;
26
+ lis_result_sourcedid?: string;
27
+ [key: string]: any;
28
+ }
29
+ /**
30
+ * Outcome service configuration for grade submission
31
+ */
32
+ export interface OutcomeService {
33
+ url: string;
34
+ sourcedid: string;
35
+ consumerKey: string;
36
+ consumerSecret: string;
37
+ }
38
+ /**
39
+ * Extracted user information from LTI launch
40
+ */
41
+ export interface UserInfo {
42
+ userId?: string;
43
+ name?: string;
44
+ email?: string;
45
+ roles?: string;
46
+ contextId?: string;
47
+ contextLabel?: string;
48
+ contextTitle?: string;
49
+ resourceLinkId?: string;
50
+ }
51
+ /**
52
+ * Complete session data structure
53
+ */
54
+ export interface SessionData {
55
+ lti10Data?: Lti10Data;
56
+ ltiVersion?: string;
57
+ userLoggedIn?: boolean;
58
+ userInfo?: UserInfo;
59
+ outcomeService?: OutcomeService;
60
+ returnUrl?: string;
61
+ rawRequest?: {
62
+ method: string;
63
+ url: string;
64
+ headers: any;
65
+ query: any;
66
+ body: any;
67
+ timestamp: string;
68
+ };
69
+ }
70
+ /**
71
+ * Grade submission result
72
+ */
73
+ export interface GradeSubmissionResult {
74
+ success: boolean;
75
+ response?: any;
76
+ status?: number;
77
+ error?: string;
78
+ details?: any;
79
+ }
80
+ /**
81
+ * Options for LTI 1.0 launch handler
82
+ */
83
+ export interface Lti10LaunchOptions {
84
+ consumerKey?: string;
85
+ consumerSecret?: string;
86
+ onSuccess?: (ltiData: Lti10Data, userInfo: UserInfo, outcomeService?: OutcomeService) => void;
87
+ onError?: (error: Error) => void;
88
+ }
89
+ /**
90
+ * Options for grade submission
91
+ */
92
+ export interface GradeSubmissionOptions {
93
+ [key: string]: any;
94
+ }
95
+ /**
96
+ * Express request with session
97
+ */
98
+ export interface RequestWithSession extends Request {
99
+ session: SessionData & {
100
+ [key: string]: any;
101
+ };
102
+ }
103
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Build XML payload for LTI Basic Outcomes replaceResultRequest
3
+ *
4
+ * @param sourcedId - The sourced ID from the LTI launch
5
+ * @param grade - The grade value
6
+ * @param maxScore - Maximum possible score (default: 1.0)
7
+ * @returns XML string for Basic Outcomes request
8
+ */
9
+ export declare function buildBasicOutcomesXml(sourcedId: string, grade: number, maxScore?: number): string;
10
+ //# sourceMappingURL=xmlBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xmlBuilder.d.ts","sourceRoot":"","sources":["../../src/utils/xmlBuilder.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,MAAY,GACrB,MAAM,CAiCR"}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildBasicOutcomesXml = buildBasicOutcomesXml;
4
+ const xml2js_1 = require("xml2js");
5
+ /**
6
+ * Build XML payload for LTI Basic Outcomes replaceResultRequest
7
+ *
8
+ * @param sourcedId - The sourced ID from the LTI launch
9
+ * @param grade - The grade value
10
+ * @param maxScore - Maximum possible score (default: 1.0)
11
+ * @returns XML string for Basic Outcomes request
12
+ */
13
+ function buildBasicOutcomesXml(sourcedId, grade, maxScore = 1.0) {
14
+ const xmlBuilder = new xml2js_1.Builder();
15
+ const normalizedGrade = (grade / maxScore).toFixed(2);
16
+ const xmlBody = xmlBuilder.buildObject({
17
+ 'imsx_POXEnvelopeRequest': {
18
+ '$': { xmlns: 'http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0' },
19
+ 'imsx_POXHeader': {
20
+ 'imsx_POXRequestHeaderInfo': {
21
+ 'imsx_version': 'V1.0',
22
+ 'imsx_messageIdentifier': Date.now().toString()
23
+ }
24
+ },
25
+ 'imsx_POXBody': {
26
+ 'replaceResultRequest': {
27
+ 'resultRecord': {
28
+ 'sourcedGUID': {
29
+ 'sourcedId': sourcedId
30
+ },
31
+ 'result': {
32
+ 'resultScore': {
33
+ 'language': 'en',
34
+ 'textString': normalizedGrade
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ });
42
+ return xmlBody;
43
+ }
44
+ //# sourceMappingURL=xmlBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xmlBuilder.js","sourceRoot":"","sources":["../../src/utils/xmlBuilder.ts"],"names":[],"mappings":";;AAUA,sDAqCC;AA/CD,mCAAiC;AAEjC;;;;;;;GAOG;AACH,SAAgB,qBAAqB,CACnC,SAAiB,EACjB,KAAa,EACb,WAAmB,GAAG;IAEtB,MAAM,UAAU,GAAG,IAAI,gBAAO,EAAE,CAAC;IAEjC,MAAM,eAAe,GAAG,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAEtD,MAAM,OAAO,GAAG,UAAU,CAAC,WAAW,CAAC;QACrC,yBAAyB,EAAE;YACzB,GAAG,EAAE,EAAE,KAAK,EAAE,2DAA2D,EAAE;YAC3E,gBAAgB,EAAE;gBAChB,2BAA2B,EAAE;oBAC3B,cAAc,EAAE,MAAM;oBACtB,wBAAwB,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;iBAChD;aACF;YACD,cAAc,EAAE;gBACd,sBAAsB,EAAE;oBACtB,cAAc,EAAE;wBACd,aAAa,EAAE;4BACb,WAAW,EAAE,SAAS;yBACvB;wBACD,QAAQ,EAAE;4BACR,aAAa,EAAE;gCACb,UAAU,EAAE,IAAI;gCAChB,YAAY,EAAE,eAAe;6BAC9B;yBACF;qBACF;iBACF;aACF;SACF;KACF,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "lti-v1.0-node-library",
3
+ "version": "1.0.0",
4
+ "description": "TypeScript library for LTI 1.0/1.1 integration with LMS platforms (including older versions of A-Plus), including Basic Outcomes grade submission. Focused on LTI 1.0/1.1 support for platforms like older versions of A-Plus that don't support LTI 1.3.",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "files": [
8
+ "lib/**/*"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepublishOnly": "npm run build",
13
+ "watch": "tsc --watch",
14
+ "clean": "rm -rf lib"
15
+ },
16
+ "keywords": [
17
+ "lti",
18
+ "lti-v1.0",
19
+ "lti-v1.1",
20
+ "a-plus",
21
+ "plussa",
22
+ "lms",
23
+ "basic-outcomes",
24
+ "grade-submission"
25
+ ],
26
+ "author": "Jaakko Rajala <jaakko.rajala@tuni.fi>",
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "oauth-1.0a": "^2.2.6",
30
+ "axios": "^1.7.7",
31
+ "xml2js": "^0.6.2"
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "^5.0.0",
35
+ "@types/node": "^20.0.0",
36
+ "@types/express": "^4.17.21",
37
+ "@types/xml2js": "^0.4.14"
38
+ }
39
+ }