-
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e8def30
Showing
27 changed files
with
885 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
/lib |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2020 Christian Cook | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
# xAPI JS | ||
## Overview | ||
An XAPI JS library for communicating with an LRS. | ||
|
||
## Supported API Versions | ||
1.0.0 | ||
|
||
## Installation | ||
```bash | ||
npm install xapi-js | ||
``` | ||
|
||
## Usage | ||
This library has been developed with both opinionated and unopinionated approaches to xAPI. To construct statements manually with full control, use the [xAPI Wrapper](#xAPI-Wrapper). To send off premade statements based off a profile, use the corresponding profile. Profiles currently supported: | ||
- [xAPI SCORM Profile (WIP)](#xAPI-SCORM-Profile-(WIP)) | ||
|
||
### xAPI Wrapper | ||
|
||
#### Basic Example | ||
```ts | ||
import { LRSConnection, Statement } from "xapi-js"; | ||
|
||
// Create LRS connection | ||
const endpoint = "https://my-lms.com/endpoint"; | ||
const auth = `Basic ${btoa('username:password')}`; | ||
const lrsConnection = new LRSConnection(endpoint, auth); | ||
|
||
// Create your statement | ||
const myStatement: Statement = { ... }; | ||
|
||
// Send your statement | ||
lrsConnection.sendStatement(myStatement); | ||
``` | ||
|
||
#### Docs | ||
|
||
##### Creating an LRS connection | ||
To read or write data to the LRS, you need an instance of `LRSConnection`: | ||
```ts | ||
const lrsConnection = new LRSConnection(endpoint, auth); | ||
``` | ||
|
||
`LRSConnection` accepts two parameters, `endpoint` and `auth`. The `endpoint` string is a URL of your LRS endpoint. The `auth` string is the `Authorization` header appended to all requests. | ||
|
||
##### Creating a statement | ||
xAPI JS is strongly-typed, which will ensure that the statements you create are valid and can assist in the construction of your statements: | ||
|
||
```ts | ||
import { Statement } from "xapi-js"; | ||
|
||
const myStatement: Statement = { ... }; | ||
``` | ||
|
||
Alternatively, if do not wish to write entire statements, it has been broken down into smaller interfaces for each part of a statement: | ||
|
||
```ts | ||
import { Agent, Statement } from "xapi-js"; | ||
|
||
const myAgent: Agent = { ... } | ||
const myStatement: Statement = { agent: myAgent }; | ||
``` | ||
|
||
##### Sending statements | ||
To send a statement, you must use the `sendStatement` method on the `LRSConnection` instance: | ||
|
||
```ts | ||
lrsConnection.sendStatement(myStatement); | ||
``` | ||
|
||
This method returns a `Promise` with the success containing an array of statement ID strings if successful, or if unsuccessful the rejection contains an error message. | ||
|
||
##### Getting a statement | ||
To receive a single statement, you must use the `getStatement` method and pass the statement ID on the `LRSConnection` instance: | ||
|
||
```ts | ||
lrsConnection.getStatement("abcdefgh-1234").then((statement: Statement) => { | ||
// do stuff with `statement` | ||
}); | ||
``` | ||
|
||
##### WIP: Getting multiple statements | ||
To receive an array of statements, you must use the `getStatements` method on the `LRSConnection` instance: | ||
|
||
```ts | ||
lrsConnection.getStatements().then((statements: Statement[]) => { | ||
// do stuff with `statements` | ||
}); | ||
``` | ||
|
||
At the moment this library is limited to querying without a filter for multiple statements. | ||
|
||
### xAPI SCORM Profile (WIP) | ||
|
||
#### Basic Example | ||
This basic example is equivilant of performing `Initialise()` / `LMSInitialize()` in a SCORM 1.2 / 2004 environment: | ||
```ts | ||
import { Config, SCORMProfile } from "xapi-js"; | ||
let config: Config = { ... }; | ||
let scormProfile = new SCORMProfile(config); | ||
this.scormProfile.initialise() | ||
``` | ||
|
||
#### Docs | ||
|
||
##### Creating the SCORM Profile | ||
To begin using the SCORM Profile, it must be constructed. By default, `SCORMProfile` will attempt to obtain the configuration parameters from the query string as provided by the launcher. If an expected parameter is missing, or there is no query string provided it will fall back to parameters supplied in the `Config` object. | ||
|
||
```ts | ||
import { Config, SCORMProfile } from "xapi-js"; | ||
let config: Config = { | ||
endpoint: "https://my-lms.com/endpoint", | ||
}; | ||
|
||
let defaultSCORMProfile = new SCORMProfile(); | ||
let preconfiguredSCORMProfile = new SCORMProfile(config); | ||
``` | ||
|
||
In this example, `defaultSCORMProfile` will rely on parameters passed in the query string. If no parameters are passed it will fail to communicate with the LRS. | ||
|
||
On the other hand, `preconfiguredSCORMProfile` has a `Config` property supplied, which acts as a base/fallback configuration. Any parameters passed in the querystring will override these values, but any values not overridden will default back to the values set in the supplied config. This is very useful if you plan on hosting a standalone module, or if you wish to provide values which the LRS may fail to supply when launching your content. | ||
|
||
##### Sending the SCORM Equvilant statements to the LRS | ||
The benefits of using a profile is that the statements are preconstructed based off of a profile designed to match the SCORM model. | ||
|
||
|Method|xAPI Verb|SCORM Equivalent| | ||
|-|-|-| | ||
|initialise|initialize|Initialise(), LMSInitialize()| | ||
|
||
At the moment only the `initialise` method is supported, but there are plans to support: | ||
- terminate | ||
- cmi.exit=suspend | ||
- cmi.entry=resume | ||
- cmi.success_status=passed | ||
- cmi.success_status=failed | ||
- cmi.scored.scaled | ||
- cmi.completion_status=completed | ||
- cmi.interactions.n.learner_response |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name": "xapi-js", | ||
"version": "0.0.1", | ||
"description": "An XAPI JS library for communicating with an LRS.", | ||
"main": "lib/index.js", | ||
"typings": "lib/index.d.ts", | ||
"files": [ | ||
"lib/**/*" | ||
], | ||
"scripts": { | ||
"prepare": "npm run build", | ||
"build": "tsc" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/CookieCookson/XAPI-JS.git" | ||
}, | ||
"keywords": [ | ||
"xapi", | ||
"tincan", | ||
"javascript", | ||
"typescript" | ||
], | ||
"author": "Christian Cook", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/CookieCookson/XAPI-JS/issues" | ||
}, | ||
"homepage": "https://github.com/CookieCookson/XAPI-JS", | ||
"devDependencies": { | ||
"typescript": "^3.7.5" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Statement } from "./interfaces/Statement"; | ||
|
||
export class LRSConnection { | ||
private endpoint: string; | ||
private headers: Headers; | ||
|
||
public constructor(endpoint: string, auth: string) { | ||
this.endpoint = endpoint; | ||
let headers: Headers = new Headers(); | ||
headers.append("X-Experience-API-Version", "1.0.0"); | ||
headers.append("Content-Type", "application/json"); | ||
if (auth) { | ||
headers.append("Authorization", auth); | ||
} | ||
this.headers = headers; | ||
} | ||
|
||
public getStatements(): Promise<{ statements: Statement[]; more: string }> { | ||
return this.request(`${this.endpoint}statements`); | ||
} | ||
|
||
public getStatement(statementId: string): Promise<Statement> { | ||
return this.request( | ||
`${this.endpoint}statements?statementId=${statementId}` | ||
); | ||
} | ||
|
||
public sendStatement(statement: Statement): Promise<string[]> { | ||
return this.request(`${this.endpoint}statements`, { | ||
method: "POST", | ||
body: JSON.stringify(statement) | ||
}); | ||
} | ||
|
||
private request(input: RequestInfo, init?: RequestInit | undefined): any { | ||
return fetch(input, { | ||
headers: this.headers, | ||
...init | ||
}).then(response => { | ||
if (response.ok) { | ||
return response.json(); | ||
} else { | ||
return response.text().then(error => Promise.reject(error)); | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.