UNPKG

@webhare/moodle-webservice

Version:

Moodle Web Service API client with intellisense and typechecking

231 lines (230 loc) 8.93 kB
"use strict"; 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;