@webhare/moodle-webservice
Version:
Moodle Web Service API client with intellisense and typechecking
231 lines (230 loc) • 8.93 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MoodleApi = exports.MoodleClient = void 0;
const os_1 = __importDefault(require("os"));
const fs_1 = __importDefault(require("fs"));
const url_1 = require("url");
const debug_1 = __importDefault(require("debug"));
//Load package info
const package_json_1 = __importDefault(require("../package.json"));
const path_1 = __importDefault(require("path"));
const MoodleError_1 = __importDefault(require("./MoodleError"));
//Load function definitions
const json = fs_1.default.readFileSync(path_1.default.resolve(__dirname, "../api", "functions.json"), "utf8");
const definition = JSON.parse(json);
class MoodleClient {
constructor(options) {
this.options = options;
//Check the URL syntax
if (!/^https?:\/\//g.test(options.baseUrl)) {
throw new Error("Argument 'options.baseUrl' must be a URL string.");
}
//Check the URL syntax
if (options.baseUrl.includes("server.php")) {
throw new Error("Argument 'options.baseUrl' should NOT contain the complete URL. Hint: provide base URL such as https://mooodle.example.com");
}
this._loadApi();
//Sanitize base URL - trim trailing slash
options.baseUrl = options.baseUrl.trim().replace(/\/$/g, "");
}
_loadApi() {
//Store definition into the instance
this._definition = definition;
//List of generated functions
this._functions = [];
//Bind Moodle Web Service functions, e.g. core_user_create_users => core.user.createUsers()
this.api = { config: { token: this.options.token } };
const client = this;
const api = this.api;
for (let item of this._definition.items) {
//Create a new module
if (!api[item.module]) {
api[item.module] = {};
}
//Create a new facility
if (!api[item.module][item.facility]) {
api[item.module][item.facility] = {};
}
//Create a new function
if (!api[item.module][item.facility][item.preferName]) {
this._functions.push(item.module + "." + item.facility + "." + item.preferName);
api[item.module][item.facility][item.preferName] = function (params) {
return client._request(item, params);
};
}
}
}
static _buildUserAgent() {
return (package_json_1.default.name +
"/" +
package_json_1.default.version +
" (node.js " +
process.version +
"; " +
os_1.default.platform() +
" " +
os_1.default.release() +
")");
}
get userAgent() {
//Build User-Agent string
let userAgent = this.options.userAgent;
if (!userAgent)
userAgent = MoodleClient._buildUserAgent();
return userAgent;
}
static flatten(data) {
let result = {};
function dig(d, prefix) {
if (typeof d === "string" || typeof d === "number") {
result[prefix] = d;
return;
}
for (let key in d) {
let item = d[key];
if (item === null) {
continue;
}
else if (item instanceof Array) {
for (var i = 0; i < item.length; i++) {
dig(item[i], prefix.length === 0
? prefix + key + "[" + i + "]" //Root level has no square brackets
: prefix + "[" + key + "][" + i + "]" //Deeper levels must include brackets
);
}
}
else if (typeof item === "object") {
for (let i in item) {
dig(item, prefix + key + "[" + i + "]");
}
}
else {
result[prefix + "[" + key + "]"] = item;
}
}
}
dig(data, "");
return result;
}
static async authenticate({ baseUrl, credentials, userAgent, }) {
let options;
// if (!payload.body) {
//No data to be sent
options = {
method: "GET",
headers: {
"User-Agent": userAgent ?? MoodleClient._buildUserAgent(),
Accept: "application/json",
},
};
let form = new url_1.URLSearchParams({
...credentials,
service: credentials?.service ?? "moodle_mobile_app",
});
let url = baseUrl + "login/token.php?" + form;
const res = await fetch(url, options);
const result = await res.json();
if (typeof result.error === "string") {
throw new MoodleError_1.default(result);
}
return result;
}
static _format(data) {
const moodleData = [];
for (const key of Object.keys(data)) {
const currentItem = data[key];
if (currentItem instanceof Array) {
for (const value of currentItem) {
moodleData.push({ name: key, value: `${value}` });
}
}
else {
moodleData.push({ name: key, value: data[key] });
}
}
return { data: moodleData };
}
static _prepareParams(params) {
let finalParams;
finalParams = { ...params };
delete finalParams.token; //Getting rid of the token
for (const key of Object.keys(params)) {
const item = finalParams[key];
if (item instanceof Array) {
delete finalParams[key];
finalParams = {
...finalParams,
...MoodleClient.flatten({ [key]: item }),
};
}
}
if (finalParams.data)
finalParams = {
...finalParams,
...MoodleClient.flatten(MoodleClient._format(params.data)),
};
return new url_1.URLSearchParams(finalParams);
}
_request(item, params) {
return new Promise(async (resolve, reject) => {
let fnDebugger;
try {
//Get web service functio name
let wsfunction = null;
wsfunction = item.name;
fnDebugger = (0, debug_1.default)(`moodle:api:${item.module}:${item.facility}`);
fnDebugger(`Calling ${item.preferName}...`);
//Verify if function name is set
if (!wsfunction || wsfunction.length === 0) {
throw new Error("Web Service function not defined: " + item);
}
//Build request options
let options = null;
//No data to be sent
options = {
method: "GET",
headers: {
"User-Agent": this.userAgent,
Accept: "application/json",
},
};
let form = "";
if (params)
form = MoodleClient._prepareParams(params);
//Complete the URL
let url = this.options.baseUrl +
"/webservice/rest/server.php?wstoken=" +
(params?.token ?? this.api.config.token ?? "") +
"&moodlewsrestformat=json&wsfunction=" +
wsfunction +
"&" +
form;
//Make a HTTP request
let res = await fetch(url, options);
//Expected JSON as data object
let result = await res.json();
//Moodle always returns HTTP status code 200
//Error can be detected by object propertie
//NOTE: Moodle can also return 'null' so be careful when checking for exceptions
if (typeof result?.exception === "string") {
throw new MoodleError_1.default(result);
}
//Success
fnDebugger(`Successfully called ${item.preferName} with parameters: ${JSON.stringify(params) ?? "null"}.`);
resolve(result);
}
catch (err) {
fnDebugger(`Failed to call ${item.preferName} with parameters: ${JSON.stringify(params)}.`);
reject(err);
}
});
}
}
exports.MoodleClient = MoodleClient;
const MoodleApi = (options) => {
return new MoodleClient(options).api;
};
exports.MoodleApi = MoodleApi;