Skip to content

Commit

Permalink
0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
CookieCookson committed Feb 10, 2020
0 parents commit e8def30
Show file tree
Hide file tree
Showing 27 changed files with 885 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
/lib
21 changes: 21 additions & 0 deletions LICENSE.md
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.
137 changes: 137 additions & 0 deletions README.md
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
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions package.json
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"
}
}
47 changes: 47 additions & 0 deletions src/LRSConnection.ts
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));
}
});
}
}
Loading

0 comments on commit e8def30

Please sign in to comment.