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 +21 -0
- package/README.md +185 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +21 -0
- package/lib/index.js.map +1 -0
- package/lib/launch/lti10Handler.d.ts +21 -0
- package/lib/launch/lti10Handler.d.ts.map +1 -0
- package/lib/launch/lti10Handler.js +73 -0
- package/lib/launch/lti10Handler.js.map +1 -0
- package/lib/oauth/oauth1.d.ts +13 -0
- package/lib/oauth/oauth1.d.ts.map +1 -0
- package/lib/oauth/oauth1.js +38 -0
- package/lib/oauth/oauth1.js.map +1 -0
- package/lib/outcomes/basicOutcomes.d.ts +13 -0
- package/lib/outcomes/basicOutcomes.d.ts.map +1 -0
- package/lib/outcomes/basicOutcomes.js +75 -0
- package/lib/outcomes/basicOutcomes.js.map +1 -0
- package/lib/session/sessionHelpers.d.ts +33 -0
- package/lib/session/sessionHelpers.d.ts.map +1 -0
- package/lib/session/sessionHelpers.js +62 -0
- package/lib/session/sessionHelpers.js.map +1 -0
- package/lib/types/index.d.ts +103 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +3 -0
- package/lib/types/index.js.map +1 -0
- package/lib/utils/xmlBuilder.d.ts +10 -0
- package/lib/utils/xmlBuilder.d.ts.map +1 -0
- package/lib/utils/xmlBuilder.js +44 -0
- package/lib/utils/xmlBuilder.js.map +1 -0
- package/package.json +39 -0
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
|
package/lib/index.js.map
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|