Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
/**
17
 * The core/fetch module allows you to make web service requests to the Moodle API.
18
 *
19
 * @module     core/fetch
20
 * @copyright  Andrew Lyons <andrew@nicols.co.uk>
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 * @example <caption>Perform a single GET request</caption>
23
 * import Fetch from 'core/fetch';
24
 *
25
 * const result = Fetch.performGet('mod_example', 'animals', { params: { type: 'mammal' } });
26
 *
27
 * result.then((response) => {
28
 *    // Do something with the Response object.
29
 * })
30
 * .catch((error) => {
31
 *     // Handle the error
32
 * });
33
 */
34
 
35
import * as Cfg from 'core/config';
36
import PendingPromise from './pending';
37
 
38
/**
39
 * A wrapper around the Request, including a Promise that is resolved when the request is complete.
40
 *
41
 * @class RequestWrapper
42
 * @private
43
 */
44
class RequestWrapper {
45
    /** @var {Request} */
46
    #request = null;
47
 
48
    /** @var {Promise} */
49
    #promise = null;
50
 
51
    /** @var {Function} */
52
    #resolve = null;
53
 
54
    /** @var {Function} */
55
    #reject = null;
56
 
57
    /**
58
     * Create a new RequestWrapper.
59
     *
60
     * @param {Request} request The request object that is wrapped
61
     */
62
    constructor(request) {
63
        this.#request = request;
64
        this.#promise = new Promise((resolve, reject) => {
65
            this.#resolve = resolve;
66
            this.#reject = reject;
67
        });
68
    }
69
 
70
    /**
71
     * Get the wrapped Request.
72
     *
73
     * @returns {Request}
74
     * @private
75
     */
76
    get request() {
77
        return this.#request;
78
    }
79
 
80
    /**
81
     * Get the Promise link to this request.
82
     *
83
     * @return {Promise}
84
     * @private
85
     */
86
    get promise() {
87
        return this.#promise;
88
    }
89
 
90
    /**
91
     * Handle the response from the request.
92
     *
93
     * @param {Response} response
94
     * @private
95
     */
96
    handleResponse(response) {
97
        if (response.ok) {
98
            this.#resolve(response);
99
        } else {
100
            this.#reject(response.statusText);
101
        }
102
    }
103
}
104
 
105
/**
106
 * A class to handle requests to the Moodle REST API.
107
 *
108
 * @class Fetch
109
 */
110
export default class Fetch {
111
    /**
112
     * Make a single request to the Moodle API.
113
     *
114
     * @param {string} component The frankenstyle component name
115
     * @param {string} action The component action to perform
116
     * @param {object} params
117
     * @param {object} [params.params = {}] The parameters to pass to the API
118
     * @param {string|Object|FormData} [params.body = null] The HTTP method to use
119
     * @param {string} [params.method = "GET"] The HTTP method to use
120
     * @returns {Promise<Response>} A promise that resolves to the Response object for the request
121
     */
122
    static async request(
123
        component,
124
        action,
125
        {
126
            params = {},
127
            body = null,
128
            method = 'GET',
129
        } = {},
130
    ) {
131
        const pending = new PendingPromise(`Requesting ${component}/${action} with ${method}`);
132
        const requestWrapper = Fetch.#getRequest(
133
            Fetch.#normaliseComponent(component),
134
            action,
135
            { params, method, body },
136
        );
137
        const result = await fetch(requestWrapper.request);
138
 
139
        pending.resolve();
140
 
141
        requestWrapper.handleResponse(result);
142
 
143
        return requestWrapper.promise;
144
    }
145
 
146
    /**
147
     * Make a request to the Moodle API.
148
     *
149
     * @param {string} component The frankenstyle component name
150
     * @param {string} action The component action to perform
151
     * @param {object} params
152
     * @param {object} [params.params = {}] The parameters to pass to the API
153
     * @returns {Promise<Response>} A promise that resolves to the Response object for the request
154
     */
155
    static performGet(
156
        component,
157
        action,
158
        {
159
            params = {},
160
        } = {},
161
    ) {
162
        return this.request(
163
            component,
164
            action,
165
            { params, method: 'GET' },
166
        );
167
    }
168
 
169
    /**
170
     * Make a request to the Moodle API.
171
     *
172
     * @param {string} component The frankenstyle component name
173
     * @param {string} action The component action to perform
174
     * @param {object} params
175
     * @param {object} [params.params = {}] The parameters to pass to the API
176
     * @returns {Promise<Response>} A promise that resolves to the Response object for the request
177
     */
178
    static performHead(
179
        component,
180
        action,
181
        {
182
            params = {},
183
        } = {},
184
    ) {
185
        return this.request(
186
            component,
187
            action,
188
            { params, method: 'HEAD' },
189
        );
190
    }
191
 
192
    /**
193
     * Make a request to the Moodle API.
194
     *
195
     * @param {string} component The frankenstyle component name
196
     * @param {string} action The component action to perform
197
     * @param {object} params
198
     * @param {string|Object|FormData} params.body The HTTP method to use
199
     * @returns {Promise<Response>} A promise that resolves to the Response object for the request
200
     */
201
    static performPost(
202
        component,
203
        action,
204
        {
205
            body,
206
        } = {},
207
    ) {
208
        return this.request(
209
            component,
210
            action,
211
            { body, method: 'POST' },
212
        );
213
    }
214
 
215
    /**
216
     * Make a request to the Moodle API.
217
     *
218
     * @param {string} component The frankenstyle component name
219
     * @param {string} action The component action to perform
220
     * @param {object} params
221
     * @param {string|Object|FormData} params.body The HTTP method to use
222
     * @returns {Promise<Response>} A promise that resolves to the Response object for the request
223
     */
224
    static performPut(
225
        component,
226
        action,
227
        {
228
            body,
229
        } = {},
230
    ) {
231
        return this.request(
232
            component,
233
            action,
234
            { body, method: 'PUT' },
235
        );
236
    }
237
 
238
    /**
239
     * Make a PATCH request to the Moodle API.
240
     *
241
     * @param {string} component The frankenstyle component name
242
     * @param {string} action The component action to perform
243
     * @param {object} params
244
     * @param {string|Object|FormData} params.body The HTTP method to use
245
     * @returns {Promise<Response>} A promise that resolves to the Response object for the request
246
     */
247
    static performPatch(
248
        component,
249
        action,
250
        {
251
            body,
252
        } = {},
253
    ) {
254
        return this.request(
255
            component,
256
            action,
257
            { body, method: 'PATCH' },
258
        );
259
    }
260
 
261
    /**
262
     * Make a request to the Moodle API.
263
     *
264
     * @param {string} component The frankenstyle component name
265
     * @param {string} action The component action to perform
266
     * @param {object} params
267
     * @param {object} [params.params = {}] The parameters to pass to the API
268
     * @param {string|Object|FormData} [params.body = null] The HTTP method to use
269
     * @returns {Promise<Response>} A promise that resolves to the Response object for the request
270
     */
271
    static performDelete(
272
        component,
273
        action,
274
        {
275
            params = {},
276
            body = null,
277
        } = {},
278
    ) {
279
        return this.request(
280
            component,
281
            action,
282
            {
283
                body,
284
                params,
285
                method: 'DELETE',
286
            },
287
        );
288
    }
289
 
290
    /**
291
     * Normalise the component name to remove the core_ prefix.
292
     *
293
     * @param {string} component
294
     * @returns {string}
295
     */
296
    static #normaliseComponent(component) {
297
        return component.replace(/^core_/, '');
298
    }
299
 
300
    /**
301
     * Get the Request for a given API request.
302
     *
303
     * @param {string} component The frankenstyle component name
304
     * @param {string} endpoint The endpoint within the componet to call
305
     * @param {object} params
306
     * @param {object} [params.params = {}] The parameters to pass to the API
307
     * @param {string|Object|FormData} [params.body = null] The HTTP method to use
308
     * @param {string} [params.method = "GET"] The HTTP method to use
309
     * @returns {RequestWrapper}
310
     */
311
    static #getRequest(
312
        component,
313
        endpoint,
314
        {
315
            params = {},
316
            body = null,
317
            method = 'GET',
318
        }
319
    ) {
320
        const url = new URL(`${Cfg.apibase}/rest/v2/${component}/${endpoint}`);
321
        const options = {
322
            method,
323
            headers: {
324
                'Accept': 'application/json',
325
                'Content-Type': 'application/json',
326
            },
327
        };
328
 
329
        Object.entries(params).forEach(([key, value]) => {
330
            url.searchParams.append(key, value);
331
        });
332
 
333
        if (body) {
334
            if (body instanceof FormData) {
335
                options.body = body;
336
            } else if (body instanceof Object) {
337
                options.body = JSON.stringify(body);
338
            } else {
339
                options.body = body;
340
            }
341
        }
342
 
343
        return new RequestWrapper(new Request(url, options));
344
    }
345
}