From d85201adcb22b4fbd9210c3850e01e63805b13ab Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 7 Aug 2024 22:53:01 +0800 Subject: [PATCH] MDL-82778 core: Update core/fetch module to use Class --- .upgradenotes/MDL-82778-2024081502451922.yml | 9 + lib/amd/build/fetch.min.js | 9 +- lib/amd/build/fetch.min.js.map | 2 +- lib/amd/src/fetch.js | 482 +++++++++++------- .../page_requirements_manager.php | 4 +- user/amd/build/repository.min.js | 4 +- user/amd/build/repository.min.js.map | 2 +- user/amd/src/repository.js | 10 +- 8 files changed, 317 insertions(+), 205 deletions(-) create mode 100644 .upgradenotes/MDL-82778-2024081502451922.yml diff --git a/.upgradenotes/MDL-82778-2024081502451922.yml b/.upgradenotes/MDL-82778-2024081502451922.yml new file mode 100644 index 00000000000..09631a471cd --- /dev/null +++ b/.upgradenotes/MDL-82778-2024081502451922.yml @@ -0,0 +1,9 @@ +issueNumber: MDL-82778 +notes: + core: + - message: > + A new JS module for interacting with the Routed REST API has been + introduced. + + For more information see the documentation in the `core/fetch` module. + type: improved diff --git a/lib/amd/build/fetch.min.js b/lib/amd/build/fetch.min.js index e90b107327f..70a3e715d3b 100644 --- a/lib/amd/build/fetch.min.js +++ b/lib/amd/build/fetch.min.js @@ -1,10 +1,3 @@ -define("core/fetch",["exports","core/config","./pending"],(function(_exports,_config,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} -/** - * The core/fetch module allows you to make web service requests to the Moodle API. - * - * @module core/fetch - * @copyright 2023 Andrew Lyons - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.request=_exports.performPut=_exports.performPost=_exports.performHead=_exports.performGet=_exports.performDelete=void 0,_config=_interopRequireDefault(_config),_pending=_interopRequireDefault(_pending);const normaliseComponent=component=>component.replace(/^core_/,""),getRequest=(component,endpoint,_ref)=>{let{params:params={},body:body=null,method:method="GET"}=_ref;const url=new URL("".concat(_config.default.apibase,"rest/v2/").concat(component,"/").concat(endpoint)),options={method:method,headers:{Accept:"application/json","Content-Type":"application/json"}};return Object.entries(params).forEach((_ref2=>{let[key,value]=_ref2;url.searchParams.append(key,value)})),body&&(body instanceof FormData?options.body=body:options.body=body instanceof Object?JSON.stringify(body):body),new Request(url,options)},request=async function(component,action){let{params:params={},body:body=null,method:method="GET"}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const pending=new _pending.default("Requesting ".concat(component,"/").concat(action," with ").concat(method)),result=await fetch(getRequest(normaliseComponent(component),action,{params:params,method:method,body:body}));if(pending.resolve(),result.ok)return result.json();throw new Error(result.statusText)};_exports.request=request;_exports.performGet=function(component,action){let{params:params={}}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{params:params,method:"GET"})};_exports.performHead=function(component,action){let{params:params={}}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{params:params,method:"HEAD"})};_exports.performPost=function(component,action){let{body:body}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{body:body,method:"POST"})};_exports.performPut=function(component,action){let{body:body}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{body:body,method:"POST"})};_exports.performDelete=function(component,action){let{params:params={},body:body=null}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{body:body,params:params,method:"DELETE"})}})); +define("core/fetch",["exports","core/config","./pending"],(function(_exports,_config,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _classStaticPrivateMethodGet(receiver,classConstructor,method){return function(receiver,classConstructor){if(receiver!==classConstructor)throw new TypeError("Private static access of wrong provenance")}(receiver,classConstructor),method}function _classPrivateFieldInitSpec(obj,privateMap,value){!function(obj,privateCollection){if(privateCollection.has(obj))throw new TypeError("Cannot initialize the same private elements twice on an object")}(obj,privateMap),privateMap.set(obj,value)}function _classPrivateFieldGet(receiver,privateMap){return function(receiver,descriptor){if(descriptor.get)return descriptor.get.call(receiver);return descriptor.value}(receiver,_classExtractFieldDescriptor(receiver,privateMap,"get"))}function _classPrivateFieldSet(receiver,privateMap,value){return function(receiver,descriptor,value){if(descriptor.set)descriptor.set.call(receiver,value);else{if(!descriptor.writable)throw new TypeError("attempted to set read only private field");descriptor.value=value}}(receiver,_classExtractFieldDescriptor(receiver,privateMap,"set"),value),value}function _classExtractFieldDescriptor(receiver,privateMap,action){if(!privateMap.has(receiver))throw new TypeError("attempted to "+action+" private field on non-instance");return privateMap.get(receiver)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_config=_interopRequireDefault(_config),_pending=_interopRequireDefault(_pending);var _request=new WeakMap,_promise=new WeakMap,_resolve=new WeakMap,_reject=new WeakMap;class RequestWrapper{constructor(request){_classPrivateFieldInitSpec(this,_request,{writable:!0,value:null}),_classPrivateFieldInitSpec(this,_promise,{writable:!0,value:null}),_classPrivateFieldInitSpec(this,_resolve,{writable:!0,value:null}),_classPrivateFieldInitSpec(this,_reject,{writable:!0,value:null}),_classPrivateFieldSet(this,_request,request),_classPrivateFieldSet(this,_promise,new Promise(((resolve,reject)=>{_classPrivateFieldSet(this,_resolve,resolve),_classPrivateFieldSet(this,_reject,reject)})))}get request(){return _classPrivateFieldGet(this,_request)}get promise(){return _classPrivateFieldGet(this,_promise)}handleResponse(response){response.ok?_classPrivateFieldGet(this,_resolve).call(this,response):_classPrivateFieldGet(this,_reject).call(this,response.statusText)}}class Fetch{static async request(component,action){let{params:params={},body:body=null,method:method="GET"}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const pending=new _pending.default("Requesting ".concat(component,"/").concat(action," with ").concat(method)),requestWrapper=_classStaticPrivateMethodGet(Fetch,Fetch,_getRequest).call(Fetch,_classStaticPrivateMethodGet(Fetch,Fetch,_normaliseComponent).call(Fetch,component),action,{params:params,method:method,body:body}),result=await fetch(requestWrapper.request);return pending.resolve(),requestWrapper.handleResponse(result),requestWrapper.promise}static performGet(component,action){let{params:params={}}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this.request(component,action,{params:params,method:"GET"})}static performHead(component,action){let{params:params={}}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this.request(component,action,{params:params,method:"HEAD"})}static performPost(component,action){let{body:body}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this.request(component,action,{body:body,method:"POST"})}static performPut(component,action){let{body:body}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this.request(component,action,{body:body,method:"PUT"})}static performPatch(component,action){let{body:body}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this.request(component,action,{body:body,method:"PATCH"})}static performDelete(component,action){let{params:params={},body:body=null}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this.request(component,action,{body:body,params:params,method:"DELETE"})}}function _normaliseComponent(component){return component.replace(/^core_/,"")}function _getRequest(component,endpoint,_ref){let{params:params={},body:body=null,method:method="GET"}=_ref;const url=new URL("".concat(_config.default.apibase,"/rest/v2/").concat(component,"/").concat(endpoint)),options={method:method,headers:{Accept:"application/json","Content-Type":"application/json"}};return Object.entries(params).forEach((_ref2=>{let[key,value]=_ref2;url.searchParams.append(key,value)})),body&&(body instanceof FormData?options.body=body:options.body=body instanceof Object?JSON.stringify(body):body),new RequestWrapper(new Request(url,options))}return _exports.default=Fetch,_exports.default})); //# sourceMappingURL=fetch.min.js.map \ No newline at end of file diff --git a/lib/amd/build/fetch.min.js.map b/lib/amd/build/fetch.min.js.map index 9c770a19164..5b3c5b1c36b 100644 --- a/lib/amd/build/fetch.min.js.map +++ b/lib/amd/build/fetch.min.js.map @@ -1 +1 @@ -{"version":3,"file":"fetch.min.js","sources":["../src/fetch.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * The core/fetch module allows you to make web service requests to the Moodle API.\n *\n * @module core/fetch\n * @copyright 2023 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Cfg from 'core/config';\nimport PendingPromise from './pending';\n\n/**\n * Normalise the component name to remove the core_ prefix.\n *\n * @param {string} component\n * @returns {string}\n */\nconst normaliseComponent = (component) => component.replace(/^core_/, '');\n\n/**\n * Get the Request object for a given API request.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} endpoint The endpoint within the componet to call\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @param {string} [params.method = \"GET\"] The HTTP method to use\n * @returns {Request}\n */\nconst getRequest = (\n component,\n endpoint,\n {\n params = {},\n body = null,\n method = 'GET',\n }\n) => {\n const url = new URL(`${Cfg.apibase}rest/v2/${component}/${endpoint}`);\n const options = {\n method,\n headers: {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json',\n },\n };\n\n Object.entries(params).forEach(([key, value]) => {\n url.searchParams.append(key, value);\n });\n\n if (body) {\n if (body instanceof FormData) {\n options.body = body;\n } else if (body instanceof Object) {\n options.body = JSON.stringify(body);\n } else {\n options.body = body;\n }\n }\n\n return new Request(url, options);\n};\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @param {string} [params.method = \"GET\"] The HTTP method to use\n * @returns {Promise}\n */\nconst request = async(\n component,\n action,\n {\n params = {},\n body = null,\n method = 'GET',\n } = {},\n) => {\n const pending = new PendingPromise(`Requesting ${component}/${action} with ${method}`);\n const result = await fetch(\n getRequest(\n normaliseComponent(component),\n action,\n {params, method, body},\n ),\n );\n\n pending.resolve();\n\n if (result.ok) {\n return result.json();\n }\n\n throw new Error(result.statusText);\n};\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @returns {Promise}\n */\nconst performGet = (\n component,\n action,\n {\n params = {},\n } = {},\n) => request(\n component,\n action,\n {params, method: 'GET'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @returns {Promise}\n */\nconst performHead = (\n component,\n action,\n {\n params = {},\n } = {},\n) => request(\n component,\n action,\n {params, method: 'HEAD'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {string|Object|FormData} params.body The HTTP method to use\n * @returns {Promise}\n */\nconst performPost = (\n component,\n action,\n {\n body,\n } = {},\n) => request(\n component,\n action,\n {body, method: 'POST'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {string|Object|FormData} params.body The HTTP method to use\n * @returns {Promise}\n */\nconst performPut = (\n component,\n action,\n {\n body,\n } = {},\n) => request(\n component,\n action,\n {body, method: 'POST'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @returns {Promise}\n */\nconst performDelete = (\n component,\n action,\n {\n params = {},\n body = null,\n } = {},\n) => request(\n component,\n action,\n {\n body,\n params,\n method: 'DELETE',\n },\n);\n\nexport {\n request,\n performGet,\n performHead,\n performPost,\n performPut,\n performDelete,\n};\n"],"names":["normaliseComponent","component","replace","getRequest","endpoint","params","body","method","url","URL","Cfg","apibase","options","headers","Object","entries","forEach","_ref2","key","value","searchParams","append","FormData","JSON","stringify","Request","request","async","action","pending","PendingPromise","result","fetch","resolve","ok","json","Error","statusText"],"mappings":";;;;;;;sRAgCMA,mBAAsBC,WAAcA,UAAUC,QAAQ,SAAU,IAahEC,WAAa,CACfF,UACAG,qBACAC,OACIA,OAAS,GADbC,KAEIA,KAAO,KAFXC,OAGIA,OAAS,kBAGPC,IAAM,IAAIC,cAAOC,gBAAIC,2BAAkBV,sBAAaG,WACpDQ,QAAU,CACZL,OAAAA,OACAM,QAAS,QACK,kCACM,4BAIxBC,OAAOC,QAAQV,QAAQW,SAAQC,YAAEC,IAAKC,aAClCX,IAAIY,aAAaC,OAAOH,IAAKC,UAG7Bb,OACIA,gBAAgBgB,SAChBV,QAAQN,KAAOA,KAEfM,QAAQN,KADDA,gBAAgBQ,OACRS,KAAKC,UAAUlB,MAEfA,MAIhB,IAAImB,QAAQjB,IAAKI,UActBc,QAAUC,eACZ1B,UACA2B,YACAvB,OACIA,OAAS,GADbC,KAEIA,KAAO,KAFXC,OAGIA,OAAS,8DACT,SAEEsB,QAAU,IAAIC,sCAA6B7B,sBAAa2B,wBAAerB,SACvEwB,aAAeC,MACjB7B,WACIH,mBAAmBC,WACnB2B,OACA,CAACvB,OAAAA,OAAQE,OAAAA,OAAQD,KAAAA,WAIzBuB,QAAQI,UAEJF,OAAOG,UACAH,OAAOI,aAGZ,IAAIC,MAAML,OAAOM,0DAYR,SACfpC,UACA2B,YACAvB,OACIA,OAAS,2DACT,UACHqB,QACDzB,UACA2B,OACA,CAACvB,OAAAA,OAAQE,OAAQ,8BAYD,SAChBN,UACA2B,YACAvB,OACIA,OAAS,2DACT,UACHqB,QACDzB,UACA2B,OACA,CAACvB,OAAAA,OAAQE,OAAQ,+BAYD,SAChBN,UACA2B,YACAtB,KACIA,6DACA,UACHoB,QACDzB,UACA2B,OACA,CAACtB,KAAAA,KAAMC,OAAQ,8BAYA,SACfN,UACA2B,YACAtB,KACIA,6DACA,UACHoB,QACDzB,UACA2B,OACA,CAACtB,KAAAA,KAAMC,OAAQ,iCAaG,SAClBN,UACA2B,YACAvB,OACIA,OAAS,GADbC,KAEIA,KAAO,6DACP,UACHoB,QACDzB,UACA2B,OACA,CACItB,KAAAA,KACAD,OAAAA,OACAE,OAAQ"} \ No newline at end of file +{"version":3,"file":"fetch.min.js","sources":["../src/fetch.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * The core/fetch module allows you to make web service requests to the Moodle API.\n *\n * @module core/fetch\n * @copyright Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @example Perform a single GET request\n * import Fetch from 'core/fetch';\n *\n * const result = Fetch.performGet('mod_example', 'animals', { params: { type: 'mammal' } });\n *\n * result.then((response) => {\n * // Do something with the Response object.\n * })\n * .catch((error) => {\n * // Handle the error\n * });\n */\n\nimport Cfg from 'core/config';\nimport PendingPromise from './pending';\n\n/**\n * A wrapper around the Request, including a Promise that is resolved when the request is complete.\n *\n * @class RequestWrapper\n * @private\n */\nclass RequestWrapper {\n /** @var {Request} */\n #request = null;\n\n /** @var {Promise} */\n #promise = null;\n\n /** @var {Function} */\n #resolve = null;\n\n /** @var {Function} */\n #reject = null;\n\n /**\n * Create a new RequestWrapper.\n *\n * @param {Request} request The request object that is wrapped\n */\n constructor(request) {\n this.#request = request;\n this.#promise = new Promise((resolve, reject) => {\n this.#resolve = resolve;\n this.#reject = reject;\n });\n }\n\n /**\n * Get the wrapped Request.\n *\n * @returns {Request}\n * @private\n */\n get request() {\n return this.#request;\n }\n\n /**\n * Get the Promise link to this request.\n *\n * @return {Promise}\n * @private\n */\n get promise() {\n return this.#promise;\n }\n\n /**\n * Handle the response from the request.\n *\n * @param {Response} response\n * @private\n */\n handleResponse(response) {\n if (response.ok) {\n this.#resolve(response);\n } else {\n this.#reject(response.statusText);\n }\n }\n}\n\n/**\n * A class to handle requests to the Moodle REST API.\n *\n * @class Fetch\n */\nexport default class Fetch {\n /**\n * Make a single request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @param {string} [params.method = \"GET\"] The HTTP method to use\n * @returns {Promise} A promise that resolves to the Response object for the request\n */\n static async request(\n component,\n action,\n {\n params = {},\n body = null,\n method = 'GET',\n } = {},\n ) {\n const pending = new PendingPromise(`Requesting ${component}/${action} with ${method}`);\n const requestWrapper = Fetch.#getRequest(\n Fetch.#normaliseComponent(component),\n action,\n { params, method, body },\n );\n const result = await fetch(requestWrapper.request);\n\n pending.resolve();\n\n requestWrapper.handleResponse(result);\n\n return requestWrapper.promise;\n }\n\n /**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @returns {Promise} A promise that resolves to the Response object for the request\n */\n static performGet(\n component,\n action,\n {\n params = {},\n } = {},\n ) {\n return this.request(\n component,\n action,\n { params, method: 'GET' },\n );\n }\n\n /**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @returns {Promise} A promise that resolves to the Response object for the request\n */\n static performHead(\n component,\n action,\n {\n params = {},\n } = {},\n ) {\n return this.request(\n component,\n action,\n { params, method: 'HEAD' },\n );\n }\n\n /**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {string|Object|FormData} params.body The HTTP method to use\n * @returns {Promise} A promise that resolves to the Response object for the request\n */\n static performPost(\n component,\n action,\n {\n body,\n } = {},\n ) {\n return this.request(\n component,\n action,\n { body, method: 'POST' },\n );\n }\n\n /**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {string|Object|FormData} params.body The HTTP method to use\n * @returns {Promise} A promise that resolves to the Response object for the request\n */\n static performPut(\n component,\n action,\n {\n body,\n } = {},\n ) {\n return this.request(\n component,\n action,\n { body, method: 'PUT' },\n );\n }\n\n /**\n * Make a PATCH request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {string|Object|FormData} params.body The HTTP method to use\n * @returns {Promise} A promise that resolves to the Response object for the request\n */\n static performPatch(\n component,\n action,\n {\n body,\n } = {},\n ) {\n return this.request(\n component,\n action,\n { body, method: 'PATCH' },\n );\n }\n\n /**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @returns {Promise} A promise that resolves to the Response object for the request\n */\n static performDelete(\n component,\n action,\n {\n params = {},\n body = null,\n } = {},\n ) {\n return this.request(\n component,\n action,\n {\n body,\n params,\n method: 'DELETE',\n },\n );\n }\n\n /**\n * Normalise the component name to remove the core_ prefix.\n *\n * @param {string} component\n * @returns {string}\n */\n static #normaliseComponent(component) {\n return component.replace(/^core_/, '');\n }\n\n /**\n * Get the Request for a given API request.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} endpoint The endpoint within the componet to call\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @param {string} [params.method = \"GET\"] The HTTP method to use\n * @returns {RequestWrapper}\n */\n static #getRequest(\n component,\n endpoint,\n {\n params = {},\n body = null,\n method = 'GET',\n }\n ) {\n const url = new URL(`${Cfg.apibase}/rest/v2/${component}/${endpoint}`);\n const options = {\n method,\n headers: {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json',\n },\n };\n\n Object.entries(params).forEach(([key, value]) => {\n url.searchParams.append(key, value);\n });\n\n if (body) {\n if (body instanceof FormData) {\n options.body = body;\n } else if (body instanceof Object) {\n options.body = JSON.stringify(body);\n } else {\n options.body = body;\n }\n }\n\n return new RequestWrapper(new Request(url, options));\n }\n}\n"],"names":["RequestWrapper","constructor","request","Promise","resolve","reject","this","promise","handleResponse","response","ok","statusText","Fetch","component","action","params","body","method","pending","PendingPromise","requestWrapper","result","fetch","replace","endpoint","url","URL","Cfg","apibase","options","headers","Object","entries","forEach","_ref2","key","value","searchParams","append","FormData","JSON","stringify","Request"],"mappings":"srDA2CMA,eAkBFC,YAAYC,qEAhBD,mEAGA,mEAGA,kEAGD,2CAQUA,6CACA,IAAIC,SAAQ,CAACC,QAASC,8CAClBD,4CACDC,YAUnBH,2CACOI,eASPC,2CACOD,eASXE,eAAeC,UACPA,SAASC,kDACKD,wDAEDA,SAASE,mBAUbC,2BAabC,UACAC,YACAC,OACIA,OAAS,GADbC,KAEIA,KAAO,KAFXC,OAGIA,OAAS,8DACT,SAEEC,QAAU,IAAIC,sCAA6BN,sBAAaC,wBAAeG,SACvEG,4CAAiBR,MAtBVA,wBAsBUA,mCACnBA,MAvBSA,gCAuBTA,MAA0BC,WAC1BC,OACA,CAAEC,OAAAA,OAAQE,OAAAA,OAAQD,KAAAA,OAEhBK,aAAeC,MAAMF,eAAelB,gBAE1CgB,QAAQd,UAERgB,eAAeZ,eAAea,QAEvBD,eAAeb,0BAatBM,UACAC,YACAC,OACIA,OAAS,2DACT,UAEGT,KAAKJ,QACRW,UACAC,OACA,CAAEC,OAAAA,OAAQE,OAAQ,2BActBJ,UACAC,YACAC,OACIA,OAAS,2DACT,UAEGT,KAAKJ,QACRW,UACAC,OACA,CAAEC,OAAAA,OAAQE,OAAQ,4BActBJ,UACAC,YACAE,KACIA,6DACA,UAEGV,KAAKJ,QACRW,UACAC,OACA,CAAEE,KAAAA,KAAMC,OAAQ,2BAcpBJ,UACAC,YACAE,KACIA,6DACA,UAEGV,KAAKJ,QACRW,UACAC,OACA,CAAEE,KAAAA,KAAMC,OAAQ,4BAcpBJ,UACAC,YACAE,KACIA,6DACA,UAEGV,KAAKJ,QACRW,UACAC,OACA,CAAEE,KAAAA,KAAMC,OAAQ,+BAepBJ,UACAC,YACAC,OACIA,OAAS,GADbC,KAEIA,KAAO,6DACP,UAEGV,KAAKJ,QACRW,UACAC,OACA,CACIE,KAAAA,KACAD,OAAAA,OACAE,OAAQ,yCAWOJ,kBAChBA,UAAUU,QAAQ,SAAU,yBAenCV,UACAW,mBACAT,OACIA,OAAS,GADbC,KAEIA,KAAO,KAFXC,OAGIA,OAAS,kBAGPQ,IAAM,IAAIC,cAAOC,gBAAIC,4BAAmBf,sBAAaW,WACrDK,QAAU,CACZZ,OAAAA,OACAa,QAAS,QACK,kCACM,4BAIxBC,OAAOC,QAAQjB,QAAQkB,SAAQC,YAAEC,IAAKC,aAClCX,IAAIY,aAAaC,OAAOH,IAAKC,UAG7BpB,OACIA,gBAAgBuB,SAChBV,QAAQb,KAAOA,KAEfa,QAAQb,KADDA,gBAAgBe,OACRS,KAAKC,UAAUzB,MAEfA,MAIhB,IAAIhB,eAAe,IAAI0C,QAAQjB,IAAKI"} \ No newline at end of file diff --git a/lib/amd/src/fetch.js b/lib/amd/src/fetch.js index f73d0b8d372..b7c5323d6ff 100644 --- a/lib/amd/src/fetch.js +++ b/lib/amd/src/fetch.js @@ -17,221 +17,329 @@ * The core/fetch module allows you to make web service requests to the Moodle API. * * @module core/fetch - * @copyright 2023 Andrew Lyons + * @copyright Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @example Perform a single GET request + * import Fetch from 'core/fetch'; + * + * const result = Fetch.performGet('mod_example', 'animals', { params: { type: 'mammal' } }); + * + * result.then((response) => { + * // Do something with the Response object. + * }) + * .catch((error) => { + * // Handle the error + * }); */ import Cfg from 'core/config'; import PendingPromise from './pending'; /** - * Normalise the component name to remove the core_ prefix. + * A wrapper around the Request, including a Promise that is resolved when the request is complete. * - * @param {string} component - * @returns {string} + * @class RequestWrapper + * @private */ -const normaliseComponent = (component) => component.replace(/^core_/, ''); +class RequestWrapper { + /** @var {Request} */ + #request = null; -/** - * Get the Request object for a given API request. - * - * @param {string} component The frankenstyle component name - * @param {string} endpoint The endpoint within the componet to call - * @param {object} params - * @param {object} [params.params = {}] The parameters to pass to the API - * @param {string|Object|FormData} [params.body = null] The HTTP method to use - * @param {string} [params.method = "GET"] The HTTP method to use - * @returns {Request} - */ -const getRequest = ( - component, - endpoint, - { - params = {}, - body = null, - method = 'GET', + /** @var {Promise} */ + #promise = null; + + /** @var {Function} */ + #resolve = null; + + /** @var {Function} */ + #reject = null; + + /** + * Create a new RequestWrapper. + * + * @param {Request} request The request object that is wrapped + */ + constructor(request) { + this.#request = request; + this.#promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + }); } -) => { - const url = new URL(`${Cfg.apibase}rest/v2/${component}/${endpoint}`); - const options = { - method, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - }; - Object.entries(params).forEach(([key, value]) => { - url.searchParams.append(key, value); - }); + /** + * Get the wrapped Request. + * + * @returns {Request} + * @private + */ + get request() { + return this.#request; + } - if (body) { - if (body instanceof FormData) { - options.body = body; - } else if (body instanceof Object) { - options.body = JSON.stringify(body); + /** + * Get the Promise link to this request. + * + * @return {Promise} + * @private + */ + get promise() { + return this.#promise; + } + + /** + * Handle the response from the request. + * + * @param {Response} response + * @private + */ + handleResponse(response) { + if (response.ok) { + this.#resolve(response); } else { - options.body = body; + this.#reject(response.statusText); } } - - return new Request(url, options); -}; +} /** - * Make a request to the Moodle API. + * A class to handle requests to the Moodle REST API. * - * @param {string} component The frankenstyle component name - * @param {string} action The component action to perform - * @param {object} params - * @param {object} [params.params = {}] The parameters to pass to the API - * @param {string|Object|FormData} [params.body = null] The HTTP method to use - * @param {string} [params.method = "GET"] The HTTP method to use - * @returns {Promise} + * @class Fetch */ -const request = async( - component, - action, - { - params = {}, - body = null, - method = 'GET', - } = {}, -) => { - const pending = new PendingPromise(`Requesting ${component}/${action} with ${method}`); - const result = await fetch( - getRequest( - normaliseComponent(component), +export default class Fetch { + /** + * Make a single request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @param {string|Object|FormData} [params.body = null] The HTTP method to use + * @param {string} [params.method = "GET"] The HTTP method to use + * @returns {Promise} A promise that resolves to the Response object for the request + */ + static async request( + component, + action, + { + params = {}, + body = null, + method = 'GET', + } = {}, + ) { + const pending = new PendingPromise(`Requesting ${component}/${action} with ${method}`); + const requestWrapper = Fetch.#getRequest( + Fetch.#normaliseComponent(component), action, - {params, method, body}, - ), - ); + { params, method, body }, + ); + const result = await fetch(requestWrapper.request); - pending.resolve(); + pending.resolve(); - if (result.ok) { - return result.json(); + requestWrapper.handleResponse(result); + + return requestWrapper.promise; } - throw new Error(result.statusText); -}; + /** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @returns {Promise} A promise that resolves to the Response object for the request + */ + static performGet( + component, + action, + { + params = {}, + } = {}, + ) { + return this.request( + component, + action, + { params, method: 'GET' }, + ); + } -/** - * Make a request to the Moodle API. - * - * @param {string} component The frankenstyle component name - * @param {string} action The component action to perform - * @param {object} params - * @param {object} [params.params = {}] The parameters to pass to the API - * @returns {Promise} - */ -const performGet = ( - component, - action, - { - params = {}, - } = {}, -) => request( - component, - action, - {params, method: 'GET'}, -); + /** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @returns {Promise} A promise that resolves to the Response object for the request + */ + static performHead( + component, + action, + { + params = {}, + } = {}, + ) { + return this.request( + component, + action, + { params, method: 'HEAD' }, + ); + } -/** - * Make a request to the Moodle API. - * - * @param {string} component The frankenstyle component name - * @param {string} action The component action to perform - * @param {object} params - * @param {object} [params.params = {}] The parameters to pass to the API - * @returns {Promise} - */ -const performHead = ( - component, - action, - { - params = {}, - } = {}, -) => request( - component, - action, - {params, method: 'HEAD'}, -); + /** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {string|Object|FormData} params.body The HTTP method to use + * @returns {Promise} A promise that resolves to the Response object for the request + */ + static performPost( + component, + action, + { + body, + } = {}, + ) { + return this.request( + component, + action, + { body, method: 'POST' }, + ); + } -/** - * Make a request to the Moodle API. - * - * @param {string} component The frankenstyle component name - * @param {string} action The component action to perform - * @param {object} params - * @param {string|Object|FormData} params.body The HTTP method to use - * @returns {Promise} - */ -const performPost = ( - component, - action, - { - body, - } = {}, -) => request( - component, - action, - {body, method: 'POST'}, -); + /** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {string|Object|FormData} params.body The HTTP method to use + * @returns {Promise} A promise that resolves to the Response object for the request + */ + static performPut( + component, + action, + { + body, + } = {}, + ) { + return this.request( + component, + action, + { body, method: 'PUT' }, + ); + } -/** - * Make a request to the Moodle API. - * - * @param {string} component The frankenstyle component name - * @param {string} action The component action to perform - * @param {object} params - * @param {string|Object|FormData} params.body The HTTP method to use - * @returns {Promise} - */ -const performPut = ( - component, - action, - { - body, - } = {}, -) => request( - component, - action, - {body, method: 'POST'}, -); + /** + * Make a PATCH request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {string|Object|FormData} params.body The HTTP method to use + * @returns {Promise} A promise that resolves to the Response object for the request + */ + static performPatch( + component, + action, + { + body, + } = {}, + ) { + return this.request( + component, + action, + { body, method: 'PATCH' }, + ); + } -/** - * Make a request to the Moodle API. - * - * @param {string} component The frankenstyle component name - * @param {string} action The component action to perform - * @param {object} params - * @param {object} [params.params = {}] The parameters to pass to the API - * @param {string|Object|FormData} [params.body = null] The HTTP method to use - * @returns {Promise} - */ -const performDelete = ( - component, - action, - { - params = {}, - body = null, - } = {}, -) => request( - component, - action, - { - body, - params, - method: 'DELETE', - }, -); + /** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @param {string|Object|FormData} [params.body = null] The HTTP method to use + * @returns {Promise} A promise that resolves to the Response object for the request + */ + static performDelete( + component, + action, + { + params = {}, + body = null, + } = {}, + ) { + return this.request( + component, + action, + { + body, + params, + method: 'DELETE', + }, + ); + } -export { - request, - performGet, - performHead, - performPost, - performPut, - performDelete, -}; + /** + * Normalise the component name to remove the core_ prefix. + * + * @param {string} component + * @returns {string} + */ + static #normaliseComponent(component) { + return component.replace(/^core_/, ''); + } + + /** + * Get the Request for a given API request. + * + * @param {string} component The frankenstyle component name + * @param {string} endpoint The endpoint within the componet to call + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @param {string|Object|FormData} [params.body = null] The HTTP method to use + * @param {string} [params.method = "GET"] The HTTP method to use + * @returns {RequestWrapper} + */ + static #getRequest( + component, + endpoint, + { + params = {}, + body = null, + method = 'GET', + } + ) { + const url = new URL(`${Cfg.apibase}/rest/v2/${component}/${endpoint}`); + const options = { + method, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + }; + + Object.entries(params).forEach(([key, value]) => { + url.searchParams.append(key, value); + }); + + if (body) { + if (body instanceof FormData) { + options.body = body; + } else if (body instanceof Object) { + options.body = JSON.stringify(body); + } else { + options.body = body; + } + } + + return new RequestWrapper(new Request(url, options)); + } +} diff --git a/lib/classes/output/requirements/page_requirements_manager.php b/lib/classes/output/requirements/page_requirements_manager.php index 81879f970b1..e6b86afd28b 100644 --- a/lib/classes/output/requirements/page_requirements_manager.php +++ b/lib/classes/output/requirements/page_requirements_manager.php @@ -360,13 +360,13 @@ class page_requirements_manager { if (!empty($CFG->router_configured)) { return sprintf( - "%s/api/", + "%s/api", $CFG->wwwroot, ); } return sprintf( - "%s/r.php/api/", + "%s/r.php/api", $CFG->wwwroot, ); } diff --git a/user/amd/build/repository.min.js b/user/amd/build/repository.min.js index edbf4e9a78e..9cacca63f79 100644 --- a/user/amd/build/repository.min.js +++ b/user/amd/build/repository.min.js @@ -1,10 +1,10 @@ -define("core_user/repository",["exports","core/config","core/ajax","core/fetch"],(function(_exports,_config,_ajax,_fetch){var obj; +define("core_user/repository",["exports","core/config","core/ajax","core/fetch"],(function(_exports,_config,_ajax,_fetch){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Module to handle AJAX interactions. * * @module core_user/repository * @copyright 2020 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.unenrolUser=_exports.submitUserEnrolmentForm=_exports.setUserPreferences=_exports.setUserPreference=_exports.sendMessagesToUsers=_exports.getUserPreferences=_exports.getUserPreference=_exports.createNotesForUsers=void 0,_config=(obj=_config)&&obj.__esModule?obj:{default:obj};const checkUserId=userid=>{if(0!==Number(userid)&&Number(userid)!==_config.default.userId)throw new Error("Invalid user ID: ".concat(userid,". It is only possible to manage preferences for the current user."))},addLegacySavedProperty=(response,preferences)=>{const debugLogger={get:(target,prop,receiver)=>"then"===prop?null:"saved"===prop?(window.console.warn("The saved property is deprecated. Please use the response object directly."),preferences.filter((preference=>target.hasOwnProperty(preference.name))).map((preference=>({name:preference.name,userid:_config.default.userid})))):Reflect.get(target,prop,receiver)};return Promise.resolve(new Proxy(response,debugLogger))};_exports.getUserPreference=function(name){let userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return getUserPreferences(name,userid).then((response=>response[name]))};const getUserPreferences=function(){let name=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;checkUserId(userid);const endpoint=["current","preferences"];return name&&endpoint.push(name),(0,_fetch.performGet)("core_user",endpoint.join("/"))};_exports.getUserPreferences=getUserPreferences;_exports.setUserPreference=function(name){let value=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,userid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return checkUserId(userid),(0,_fetch.performPost)("core_user","current/preferences/".concat(name),{body:{value:value}}).then((response=>addLegacySavedProperty(response,[{name:name}])))};_exports.setUserPreferences=preferences=>(preferences.forEach((preference=>checkUserId(preference.userid))),(0,_fetch.performPost)("core_user","current/preferences",{body:{preferences:Object.fromEntries(preferences.map((preference=>[preference.name,preference.value])))}}).then((response=>addLegacySavedProperty(response,preferences))));_exports.unenrolUser=userEnrolmentId=>(0,_ajax.call)([{methodname:"core_enrol_unenrol_user_enrolment",args:{ueid:userEnrolmentId}}])[0];_exports.submitUserEnrolmentForm=formdata=>(0,_ajax.call)([{methodname:"core_enrol_submit_user_enrolment_form",args:{formdata:formdata}}])[0];_exports.createNotesForUsers=notes=>(0,_ajax.call)([{methodname:"core_notes_create_notes",args:{notes:notes}}])[0];_exports.sendMessagesToUsers=messages=>(0,_ajax.call)([{methodname:"core_message_send_instant_messages",args:{messages:messages}}])[0]})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.unenrolUser=_exports.submitUserEnrolmentForm=_exports.setUserPreferences=_exports.setUserPreference=_exports.sendMessagesToUsers=_exports.getUserPreferences=_exports.getUserPreference=_exports.createNotesForUsers=void 0,_config=_interopRequireDefault(_config),_fetch=_interopRequireDefault(_fetch);const checkUserId=userid=>{if(0!==Number(userid)&&Number(userid)!==_config.default.userId)throw new Error("Invalid user ID: ".concat(userid,". It is only possible to manage preferences for the current user."))},addLegacySavedProperty=(response,preferences)=>{const debugLogger={get:(target,prop,receiver)=>"then"===prop?null:"saved"===prop?(window.console.warn("The saved property is deprecated. Please use the response object directly."),preferences.filter((preference=>target.hasOwnProperty(preference.name))).map((preference=>({name:preference.name,userid:_config.default.userid})))):Reflect.get(target,prop,receiver)};return Promise.resolve(new Proxy(response,debugLogger))};_exports.getUserPreference=function(name){let userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return getUserPreferences(name,userid).then((response=>response[name]))};const getUserPreferences=function(){let name=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;checkUserId(userid);const endpoint=["current","preferences"];return name&&endpoint.push(name),_fetch.default.performGet("core_user",endpoint.join("/")).then((response=>response.json()))};_exports.getUserPreferences=getUserPreferences;_exports.setUserPreference=function(name){let value=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,userid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return checkUserId(userid),_fetch.default.performPost("core_user","current/preferences/".concat(name),{body:{value:value}}).then((response=>response.json())).then((response=>addLegacySavedProperty(response,[{name:name}])))};_exports.setUserPreferences=preferences=>(preferences.forEach((preference=>checkUserId(preference.userid))),_fetch.default.performPost("core_user","current/preferences",{body:{preferences:Object.fromEntries(preferences.map((preference=>[preference.name,preference.value])))}}).then((response=>response.json())).then((response=>addLegacySavedProperty(response,preferences))));_exports.unenrolUser=userEnrolmentId=>(0,_ajax.call)([{methodname:"core_enrol_unenrol_user_enrolment",args:{ueid:userEnrolmentId}}])[0];_exports.submitUserEnrolmentForm=formdata=>(0,_ajax.call)([{methodname:"core_enrol_submit_user_enrolment_form",args:{formdata:formdata}}])[0];_exports.createNotesForUsers=notes=>(0,_ajax.call)([{methodname:"core_notes_create_notes",args:{notes:notes}}])[0];_exports.sendMessagesToUsers=messages=>(0,_ajax.call)([{methodname:"core_message_send_instant_messages",args:{messages:messages}}])[0]})); //# sourceMappingURL=repository.min.js.map \ No newline at end of file diff --git a/user/amd/build/repository.min.js.map b/user/amd/build/repository.min.js.map index 731768594ea..4ec0e3d9f1a 100644 --- a/user/amd/build/repository.min.js.map +++ b/user/amd/build/repository.min.js.map @@ -1 +1 @@ -{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to handle AJAX interactions.\n *\n * @module core_user/repository\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Config from 'core/config';\nimport {call as fetchMany} from 'core/ajax';\nimport {performGet, performPost} from 'core/fetch';\n\nconst checkUserId = (userid) => {\n if (Number(userid) === 0) {\n return;\n }\n if (Number(userid) === Config.userId) {\n return;\n }\n throw new Error(\n `Invalid user ID: ${userid}. It is only possible to manage preferences for the current user.`,\n );\n};\n\n/**\n * Turn the response object into a Proxy object that will log a warning if the saved property is accessed.\n *\n * @param {Object} response\n * @param {Object} preferences The preferences that might be in the response\n * @return {Promise}\n */\nconst addLegacySavedProperty = (response, preferences) => {\n const debugLogger = {\n get(target, prop, receiver) {\n if (prop === 'then') {\n // To proxy a Promise we have to return null when the then key is requested.\n return null;\n }\n if (prop === 'saved') {\n window.console.warn(\n 'The saved property is deprecated. Please use the response object directly.',\n );\n\n return preferences\n .filter((preference) => target.hasOwnProperty(preference.name))\n .map((preference) => ({\n name: preference.name,\n userid: Config.userid,\n }));\n }\n return Reflect.get(target, prop, receiver);\n },\n };\n\n return Promise.resolve(new Proxy(response, debugLogger));\n};\n\n/**\n * Get single user preference\n *\n * @param {String} name Name of the preference\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const getUserPreference = (name, userid = 0) => getUserPreferences(name, userid)\n .then((response) => response[name]);\n\n/**\n * Get multiple user preferences\n *\n * @param {String|null} name Name of the preference (omit if you want to retrieve all)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise>}\n */\nexport const getUserPreferences = (name = null, userid = 0) => {\n checkUserId(userid);\n const endpoint = ['current', 'preferences'];\n\n if (name) {\n endpoint.push(name);\n }\n\n return performGet('core_user', endpoint.join('/'));\n};\n\n/**\n * Set single user preference\n *\n * @param {String} name Name of the preference\n * @param {String|null} value Value of the preference (omit if you want to remove the current value)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const setUserPreference = (name, value = null, userid = 0) => {\n checkUserId(userid);\n return performPost(\n 'core_user',\n `current/preferences/${name}`,\n {\n body: {value},\n },\n )\n // Return the result of the fetch call, and also add in the legacy saved property.\n .then((response) => addLegacySavedProperty(response, [{name}]));\n};\n\n/**\n * Set multiple user preferences\n *\n * @param {Object[]} preferences Array of preferences containing name/value/userid attributes\n * @return {Promise}\n */\nexport const setUserPreferences = (preferences) => {\n preferences.forEach((preference) => checkUserId(preference.userid));\n return performPost(\n 'core_user',\n 'current/preferences',\n {\n body: {\n preferences: Object.fromEntries (preferences.map((preference) => ([preference.name, preference.value]))),\n },\n },\n )\n // Return the result of the fetch call, and also add in the legacy saved property.\n .then((response) => addLegacySavedProperty(response, preferences));\n};\n\n/**\n * Unenrol the user with the specified user enrolmentid ID.\n *\n * @param {Number} userEnrolmentId\n * @return {Promise}\n */\nexport const unenrolUser = userEnrolmentId => {\n return fetchMany([{\n methodname: 'core_enrol_unenrol_user_enrolment',\n args: {\n ueid: userEnrolmentId,\n },\n }])[0];\n};\n\n/**\n * Submit the user enrolment form with the specified form data.\n *\n * @param {String} formdata\n * @return {Promise}\n */\nexport const submitUserEnrolmentForm = formdata => {\n return fetchMany([{\n methodname: 'core_enrol_submit_user_enrolment_form',\n args: {\n formdata,\n },\n }])[0];\n};\n\nexport const createNotesForUsers = notes => {\n return fetchMany([{\n methodname: 'core_notes_create_notes',\n args: {\n notes\n }\n }])[0];\n};\n\nexport const sendMessagesToUsers = messages => {\n return fetchMany([{\n methodname: 'core_message_send_instant_messages',\n args: {messages}\n }])[0];\n};\n"],"names":["checkUserId","userid","Number","Config","userId","Error","addLegacySavedProperty","response","preferences","debugLogger","get","target","prop","receiver","window","console","warn","filter","preference","hasOwnProperty","name","map","Reflect","Promise","resolve","Proxy","getUserPreferences","then","endpoint","push","join","value","body","forEach","Object","fromEntries","userEnrolmentId","methodname","args","ueid","formdata","notes","messages"],"mappings":";;;;;;;gWA2BMA,YAAeC,YACM,IAAnBC,OAAOD,SAGPC,OAAOD,UAAYE,gBAAOC,aAGxB,IAAIC,iCACcJ,8EAWtBK,uBAAyB,CAACC,SAAUC,qBAChCC,YAAc,CAChBC,IAAG,CAACC,OAAQC,KAAMC,WACD,SAATD,KAEO,KAEE,UAATA,MACAE,OAAOC,QAAQC,KACX,8EAGGR,YACFS,QAAQC,YAAeP,OAAOQ,eAAeD,WAAWE,QACxDC,KAAKH,cACFE,KAAMF,WAAWE,KACjBnB,OAAQE,gBAAOF,YAGpBqB,QAAQZ,IAAIC,OAAQC,KAAMC,kBAIlCU,QAAQC,QAAQ,IAAIC,MAAMlB,SAAUE,0CAUd,SAACW,UAAMnB,8DAAS,SAAMyB,mBAAmBN,KAAMnB,QAC3E0B,MAAMpB,UAAaA,SAASa,eASpBM,mBAAqB,eAACN,4DAAO,KAAMnB,8DAAS,EACrDD,YAAYC,cACN2B,SAAW,CAAC,UAAW,sBAEzBR,MACAQ,SAASC,KAAKT,OAGX,qBAAW,YAAaQ,SAASE,KAAK,iFAWhB,SAACV,UAAMW,6DAAQ,KAAM9B,8DAAS,SAC3DD,YAAYC,SACL,sBACH,0CACuBmB,MACvB,CACIY,KAAM,CAACD,MAAAA,SAIdJ,MAAMpB,UAAaD,uBAAuBC,SAAU,CAAC,CAACa,KAAAA,uCASxBZ,cAC/BA,YAAYyB,SAASf,YAAelB,YAAYkB,WAAWjB,WACpD,sBACH,YACA,sBACA,CACI+B,KAAM,CACFxB,YAAa0B,OAAOC,YAAa3B,YAAYa,KAAKH,YAAgB,CAACA,WAAWE,KAAMF,WAAWa,aAK1GJ,MAAMpB,UAAaD,uBAAuBC,SAAUC,qCAS9B4B,kBAChB,cAAU,CAAC,CACdC,WAAY,oCACZC,KAAM,CACFC,KAAMH,oBAEV,oCAS+BI,WAC5B,cAAU,CAAC,CACdH,WAAY,wCACZC,KAAM,CACFE,SAAAA,aAEJ,gCAG2BC,QACxB,cAAU,CAAC,CACdJ,WAAY,0BACZC,KAAM,CACFG,MAAAA,UAEJ,gCAG2BC,WACxB,cAAU,CAAC,CACdL,WAAY,qCACZC,KAAM,CAACI,SAAAA,aACP"} \ No newline at end of file +{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to handle AJAX interactions.\n *\n * @module core_user/repository\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Config from 'core/config';\nimport {call as fetchMany} from 'core/ajax';\nimport Fetch from 'core/fetch';\n\nconst checkUserId = (userid) => {\n if (Number(userid) === 0) {\n return;\n }\n if (Number(userid) === Config.userId) {\n return;\n }\n throw new Error(\n `Invalid user ID: ${userid}. It is only possible to manage preferences for the current user.`,\n );\n};\n\n/**\n * Turn the response object into a Proxy object that will log a warning if the saved property is accessed.\n *\n * @param {Object} response\n * @param {Object} preferences The preferences that might be in the response\n * @return {Promise}\n */\nconst addLegacySavedProperty = (response, preferences) => {\n const debugLogger = {\n get(target, prop, receiver) {\n if (prop === 'then') {\n // To proxy a Promise we have to return null when the then key is requested.\n return null;\n }\n if (prop === 'saved') {\n window.console.warn(\n 'The saved property is deprecated. Please use the response object directly.',\n );\n\n return preferences\n .filter((preference) => target.hasOwnProperty(preference.name))\n .map((preference) => ({\n name: preference.name,\n userid: Config.userid,\n }));\n }\n return Reflect.get(target, prop, receiver);\n },\n };\n\n return Promise.resolve(new Proxy(response, debugLogger));\n};\n\n/**\n * Get single user preference\n *\n * @param {String} name Name of the preference\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const getUserPreference = (name, userid = 0) => getUserPreferences(name, userid)\n .then((response) => response[name]);\n\n/**\n * Get multiple user preferences\n *\n * @param {String|null} name Name of the preference (omit if you want to retrieve all)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise>}\n */\nexport const getUserPreferences = (name = null, userid = 0) => {\n checkUserId(userid);\n const endpoint = ['current', 'preferences'];\n\n if (name) {\n endpoint.push(name);\n }\n\n return Fetch.performGet('core_user', endpoint.join('/')).then((response) => response.json());\n};\n\n/**\n * Set single user preference\n *\n * @param {String} name Name of the preference\n * @param {String|null} value Value of the preference (omit if you want to remove the current value)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const setUserPreference = (name, value = null, userid = 0) => {\n checkUserId(userid);\n return Fetch.performPost(\n 'core_user',\n `current/preferences/${name}`,\n {\n body: {value},\n },\n )\n // Return the result of the fetch call, and also add in the legacy saved property.\n .then((response) => response.json())\n .then((response) => addLegacySavedProperty(response, [{name}]));\n};\n\n/**\n * Set multiple user preferences\n *\n * @param {Object[]} preferences Array of preferences containing name/value/userid attributes\n * @return {Promise}\n */\nexport const setUserPreferences = (preferences) => {\n preferences.forEach((preference) => checkUserId(preference.userid));\n return Fetch.performPost(\n 'core_user',\n 'current/preferences',\n {\n body: {\n preferences: Object.fromEntries (preferences.map((preference) => ([preference.name, preference.value]))),\n },\n },\n )\n // Return the result of the fetch call, and also add in the legacy saved property.\n .then((response) => response.json())\n .then((response) => addLegacySavedProperty(response, preferences));\n};\n\n/**\n * Unenrol the user with the specified user enrolmentid ID.\n *\n * @param {Number} userEnrolmentId\n * @return {Promise}\n */\nexport const unenrolUser = userEnrolmentId => {\n return fetchMany([{\n methodname: 'core_enrol_unenrol_user_enrolment',\n args: {\n ueid: userEnrolmentId,\n },\n }])[0];\n};\n\n/**\n * Submit the user enrolment form with the specified form data.\n *\n * @param {String} formdata\n * @return {Promise}\n */\nexport const submitUserEnrolmentForm = formdata => {\n return fetchMany([{\n methodname: 'core_enrol_submit_user_enrolment_form',\n args: {\n formdata,\n },\n }])[0];\n};\n\nexport const createNotesForUsers = notes => {\n return fetchMany([{\n methodname: 'core_notes_create_notes',\n args: {\n notes\n }\n }])[0];\n};\n\nexport const sendMessagesToUsers = messages => {\n return fetchMany([{\n methodname: 'core_message_send_instant_messages',\n args: {messages}\n }])[0];\n};\n"],"names":["checkUserId","userid","Number","Config","userId","Error","addLegacySavedProperty","response","preferences","debugLogger","get","target","prop","receiver","window","console","warn","filter","preference","hasOwnProperty","name","map","Reflect","Promise","resolve","Proxy","getUserPreferences","then","endpoint","push","Fetch","performGet","join","json","value","performPost","body","forEach","Object","fromEntries","userEnrolmentId","methodname","args","ueid","formdata","notes","messages"],"mappings":";;;;;;;sXA2BMA,YAAeC,YACM,IAAnBC,OAAOD,SAGPC,OAAOD,UAAYE,gBAAOC,aAGxB,IAAIC,iCACcJ,8EAWtBK,uBAAyB,CAACC,SAAUC,qBAChCC,YAAc,CAChBC,IAAG,CAACC,OAAQC,KAAMC,WACD,SAATD,KAEO,KAEE,UAATA,MACAE,OAAOC,QAAQC,KACX,8EAGGR,YACFS,QAAQC,YAAeP,OAAOQ,eAAeD,WAAWE,QACxDC,KAAKH,cACFE,KAAMF,WAAWE,KACjBnB,OAAQE,gBAAOF,YAGpBqB,QAAQZ,IAAIC,OAAQC,KAAMC,kBAIlCU,QAAQC,QAAQ,IAAIC,MAAMlB,SAAUE,0CAUd,SAACW,UAAMnB,8DAAS,SAAMyB,mBAAmBN,KAAMnB,QAC3E0B,MAAMpB,UAAaA,SAASa,eASpBM,mBAAqB,eAACN,4DAAO,KAAMnB,8DAAS,EACrDD,YAAYC,cACN2B,SAAW,CAAC,UAAW,sBAEzBR,MACAQ,SAASC,KAAKT,MAGXU,eAAMC,WAAW,YAAaH,SAASI,KAAK,MAAML,MAAMpB,UAAaA,SAAS0B,oFAWxD,SAACb,UAAMc,6DAAQ,KAAMjC,8DAAS,SAC3DD,YAAYC,QACL6B,eAAMK,YACT,0CACuBf,MACvB,CACIgB,KAAM,CAACF,MAAAA,SAIdP,MAAMpB,UAAaA,SAAS0B,SAC5BN,MAAMpB,UAAaD,uBAAuBC,SAAU,CAAC,CAACa,KAAAA,uCASxBZ,cAC/BA,YAAY6B,SAASnB,YAAelB,YAAYkB,WAAWjB,UACpD6B,eAAMK,YACT,YACA,sBACA,CACIC,KAAM,CACF5B,YAAa8B,OAAOC,YAAa/B,YAAYa,KAAKH,YAAgB,CAACA,WAAWE,KAAMF,WAAWgB,aAK1GP,MAAMpB,UAAaA,SAAS0B,SAC5BN,MAAMpB,UAAaD,uBAAuBC,SAAUC,qCAS9BgC,kBAChB,cAAU,CAAC,CACdC,WAAY,oCACZC,KAAM,CACFC,KAAMH,oBAEV,oCAS+BI,WAC5B,cAAU,CAAC,CACdH,WAAY,wCACZC,KAAM,CACFE,SAAAA,aAEJ,gCAG2BC,QACxB,cAAU,CAAC,CACdJ,WAAY,0BACZC,KAAM,CACFG,MAAAA,UAEJ,gCAG2BC,WACxB,cAAU,CAAC,CACdL,WAAY,qCACZC,KAAM,CAACI,SAAAA,aACP"} \ No newline at end of file diff --git a/user/amd/src/repository.js b/user/amd/src/repository.js index 1b8b592e72d..0d51b92c579 100644 --- a/user/amd/src/repository.js +++ b/user/amd/src/repository.js @@ -23,7 +23,7 @@ import Config from 'core/config'; import {call as fetchMany} from 'core/ajax'; -import {performGet, performPost} from 'core/fetch'; +import Fetch from 'core/fetch'; const checkUserId = (userid) => { if (Number(userid) === 0) { @@ -95,7 +95,7 @@ export const getUserPreferences = (name = null, userid = 0) => { endpoint.push(name); } - return performGet('core_user', endpoint.join('/')); + return Fetch.performGet('core_user', endpoint.join('/')).then((response) => response.json()); }; /** @@ -108,7 +108,7 @@ export const getUserPreferences = (name = null, userid = 0) => { */ export const setUserPreference = (name, value = null, userid = 0) => { checkUserId(userid); - return performPost( + return Fetch.performPost( 'core_user', `current/preferences/${name}`, { @@ -116,6 +116,7 @@ export const setUserPreference = (name, value = null, userid = 0) => { }, ) // Return the result of the fetch call, and also add in the legacy saved property. + .then((response) => response.json()) .then((response) => addLegacySavedProperty(response, [{name}])); }; @@ -127,7 +128,7 @@ export const setUserPreference = (name, value = null, userid = 0) => { */ export const setUserPreferences = (preferences) => { preferences.forEach((preference) => checkUserId(preference.userid)); - return performPost( + return Fetch.performPost( 'core_user', 'current/preferences', { @@ -137,6 +138,7 @@ export const setUserPreferences = (preferences) => { }, ) // Return the result of the fetch call, and also add in the legacy saved property. + .then((response) => response.json()) .then((response) => addLegacySavedProperty(response, preferences)); };