diff --git a/README.md b/README.md index 3b1defe1..112eb6fb 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ const Gitlab = require('node-gitlab-api/dist/es5').default // Instantiating const api = new Gitlab({ url: 'http://example.com', // Defaults to http://gitlab.com - token: 'abcdefghij123456' //Can be created in your profile. + token: 'abcdefghij123456' // Can be created in your profile. }) // Or, use a OAuth token instead! @@ -160,7 +160,7 @@ import { Projects } from 'node-gitlab-api'; const service = new Projects({ url: 'http://example.com', // Defaults to http://gitlab.com - token: 'abcdefghij123456' //Can be created in your profile. + token: 'abcdefghij123456' // Can be created in your profile. }) ``` @@ -175,7 +175,7 @@ import { ProjectsBundle } from 'node-gitlab-api'; const services = new ProjectsBundle({ url: 'http://example.com', // Defaults to http://gitlab.com - token: 'abcdefghij123456' //Can be created in your profile. + token: 'abcdefghij123456' // Can be created in your profile. }) services.Projects.all() @@ -253,6 +253,22 @@ EpicNotes EpicDiscussions ``` +### Using XMLHttpRequest +This package uses the [Request](https://github.com/request/request) library by default, which is built into Node. However, if your code is running in a browser, you can get better built-in resolution of proxies and self-signed certificates by using the browser's XMLHttpRequest implementation instead: + +```javascript +import Gitlab from 'node-gitlab-api'; + +const api = new Gitlab({ + url: 'http://example.com', // Defaults to http://gitlab.com + token: 'abcdefghij123456', // Can be created in your profile. + + useXMLHttpRequest: true // Use the browser's XMLHttpRequest instead of Node's Request library +}) +``` + +**WARNING:** Currently this option does not support the `multipart/form-data` content type, and therefore the endpoint for [uploading a file to a project](https://docs.gitlab.com/ee/api/projects.html#upload-a-file) will not work correctly. All other endpoints should work exactly as expected. + ### Examples Once you have your library instantiated, you can utilize many of the API's functionality: @@ -263,7 +279,7 @@ import Gitlab from 'node-gitlab-api'; const api = new Gitlab({ url: 'http://example.com', // Defaults to http://gitlab.com - token: 'abcdefghij123456' //Can be created in your profile. + token: 'abcdefghij123456' // Can be created in your profile. }); // Listing users @@ -280,14 +296,14 @@ General rule about all the function parameters: - If its a required parameter, it is a named argument in the functions - If its an optional parameter, it is defined in a options object following the named arguments -ie. +ie. ```javascript import Gitlab from 'node-gitlab-api'; const api = new Gitlab({ url: 'http://example.com', // Defaults to http://gitlab.com - token: 'abcdefghij123456' //Can be created in your profile. + token: 'abcdefghij123456' // Can be created in your profile. }); api.Projects.create(projectId, { @@ -305,7 +321,7 @@ import Gitlab from 'node-gitlab-api'; const api = new Gitlab({ url: 'http://example.com', // Defaults to http://gitlab.com - token: 'abcdefghij123456' //Can be created in your profile. + token: 'abcdefghij123456' // Can be created in your profile. }); let projects = await api.Projects.all({maxPages:2}); @@ -319,7 +335,7 @@ import Gitlab from 'node-gitlab-api'; const api = new Gitlab({ url: 'http://example.com', // Defaults to http://gitlab.com - token: 'abcdefghij123456' //Can be created in your profile. + token: 'abcdefghij123456' // Can be created in your profile. }); let projects = await api.Projects.all({maxPages:2, perPage:40}); @@ -349,7 +365,7 @@ This started off as a fork from [node-gitlab](https://github.com/node-gitlab/nod - [Christoph Lehmann](https://github.com/christophlehmann) - [Frank V](https://github.com/FrankV01) - [Salim Benabbou](https://github.com/Salimlou) -- [Tamás Török-Vistai](https://github.com/tvtamas) +- [Tamás Török-Vistai](https://github.com/tvtamas) - [Martin Benninger](https://github.com/MartinBenninger) - [Adam Dehnel](https://github.com/arsdehnel) - [fewieden](https://github.com/fewieden) @@ -359,6 +375,7 @@ This started off as a fork from [node-gitlab](https://github.com/node-gitlab/nod - [Jordan Wallet](https://github.com/Mr-Wallet) - [Ev Haus](https://github.com/EvHaus) - [zhao0](https://github.com/zhao0) +- [Joshua Grosso](https://github.com/jgrosso) ## License @@ -450,7 +467,7 @@ Runners = ProjectRunners + Runners. ProjectId is optional for all() [2.2.3](https://github.com/jdalrymple/node-gitlab-api/ce7f17693168b5dec3b36eb1d5ab796c9374613f) (2018-2-3) ------------------ -- Fixed #37 - Bug within the customAttributes logic +- Fixed #37 - Bug within the customAttributes logic [2.2.2](https://github.com/jdalrymple/node-gitlab-api/ca1906879d869bf5b9aca0b2f64e46c89f3b5f4f) (2018-1-24) ------------------ @@ -479,7 +496,7 @@ how to run locally via npm linking for Development testing thanks to [Adam Dehne - Updating project docs for consistency - Adding project unsharing to API. It was in the docs, but missing from the API - Updating deprecated protected branches endpoint. Previously this was `projects.branches.protect` now its `projects.protectedBranches.protect` -- Added Owned Runners and Runner Jobs API +- Added Owned Runners and Runner Jobs API ### Breaking Changes between 1.3.3 and 2.1.0 - The `list` functions are no longer supported and have all been renamed to `all` @@ -497,7 +514,7 @@ how to run locally via npm linking for Development testing thanks to [Adam Dehne - Updating pagination changes into v2.0.1 - Removed unused labels endpoint since it already exists under projects.labels - Added a mergeRequests class for the merge_requests endpoints -- Extended the ProjectMergeRequests class for additional functionality that was missing for project merge requests such as +- Extended the ProjectMergeRequests class for additional functionality that was missing for project merge requests such as accepting merge requests, cancelling merges when the pipeline succeeds, listing issues that will close on merge, subscribing/unsubscribing to merges, creating todos, time spent and time estimates as well as time stats. - Fixed the notes endpoints for ProjectMergeRequests. This can now be access via projects.mergeRequests.notes.[command here] - Added comments endpoints to the ProjectRepositoryCommits class @@ -507,7 +524,7 @@ accepting merge requests, cancelling merges when the pipeline succeeds, listing [1.3.3](https://github.com/jdalrymple/node-gitlab-api/b8a3db4a4aaf9482fb3905883d92d940babfb461) (2017-11-29) ------------------ - Adding pagination to project pipelines thanks to [Tamás Török-Vistai](https://github.com/tvtamas) - + [2.0.0-rc.2](https://github.com/jdalrymple/node-gitlab-api/62a4d360f0ca2cd584caf852d96ced3761992072) (2017-11-28) ------------------ - Updating all recent core changes into v2.0.0 @@ -566,7 +583,7 @@ accepting merge requests, cancelling merges when the pipeline succeeds, listing [1.1.1](https://github.com/jdalrymple/node-gitlab-api/67df1c8772614b3856f2995eaa7d260d0f697e49) (2017-09-24) ------------------ -- Patch, fixed a broken pagination property +- Patch, fixed a broken pagination property - Adding in missing options parameter in the groups API thanks to a pull request from [Cory Zibell](https://github.com/coryzibell) [1.1.0](https://github.com/jdalrymple/node-gitlab-api/385ef9f351981f26180e1381525ade458bcde1cd) (2017-09-24) @@ -673,4 +690,4 @@ to this "node-gitlab-api": "" ... } -``` \ No newline at end of file +``` diff --git a/package-lock.json b/package-lock.json index 30f4d558..3ec9b40b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1315,7 +1315,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "dev": true, "requires": { "foreach": "2.0.5", "object-keys": "1.0.11" @@ -1350,6 +1349,11 @@ "esutils": "2.0.2" } }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -1387,7 +1391,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.7.0.tgz", "integrity": "sha1-363ndOAb/Nl/lhgCmMRJyGI/uUw=", - "dev": true, "requires": { "es-to-primitive": "1.1.1", "function-bind": "1.1.0", @@ -1399,7 +1402,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "dev": true, "requires": { "is-callable": "1.1.3", "is-date-object": "1.0.1", @@ -1856,6 +1858,14 @@ "write": "0.2.1" } }, + "for-each": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", + "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=", + "requires": { + "is-function": "1.0.1" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -1874,8 +1884,7 @@ "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" }, "forever-agent": { "version": "0.6.1", @@ -2811,8 +2820,7 @@ "function-bind": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", - "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=", - "dev": true + "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=" }, "functional-red-black-tree": { "version": "1.0.1", @@ -2861,6 +2869,15 @@ "is-glob": "2.0.1" } }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "2.19.0", + "process": "0.5.2" + } + }, "globby": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", @@ -2899,7 +2916,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", - "dev": true, "requires": { "function-bind": "1.1.0" } @@ -3055,14 +3071,12 @@ "is-callable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", - "dev": true + "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=" }, "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-dotfile": { "version": "1.0.3", @@ -3097,6 +3111,11 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "is-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" + }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", @@ -3167,7 +3186,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, "requires": { "has": "1.0.1" } @@ -3187,8 +3205,7 @@ "is-symbol": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=" }, "is-typedarray": { "version": "1.0.0", @@ -3424,6 +3441,14 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "0.1.1" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -3518,8 +3543,16 @@ "object-keys": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", - "dev": true + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=" + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.7.0" + } }, "object.omit": { "version": "2.0.1", @@ -3616,6 +3649,15 @@ "is-glob": "2.0.1" } }, + "parse-headers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.1.tgz", + "integrity": "sha1-aug6eqJanZtwCswoaYzR8e1+lTY=", + "requires": { + "for-each": "0.3.2", + "trim": "0.0.1" + } + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -3728,6 +3770,11 @@ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -4345,6 +4392,11 @@ "punycode": "1.4.1" } }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", @@ -4425,6 +4477,15 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "requires": { + "define-properties": "1.1.2", + "object.getownpropertydescriptors": "2.0.3" + } + }, "uuid": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", @@ -4486,6 +4547,17 @@ "mkdirp": "0.5.1" } }, + "xhr": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.1.tgz", + "integrity": "sha512-pAIU5vBr9Hiy5cpFIbPnwf0C18ZF86DBsZKrlsf87N5De/JbA6RJ83UP/cv+aljl4S40iRVMqP4pr4sF9Dnj0A==", + "requires": { + "global": "4.3.2", + "is-function": "1.0.1", + "parse-headers": "2.0.1", + "xtend": "4.0.1" + } + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 4aca7dc7..a9f16329 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,12 @@ "humps": "^2.0.1", "lodash.pick": "^4.4.0", "parse-link-header": "^1.0.1", + "qs": "^6.5.1", "request": "^2.85.0", "request-promise": "^4.2.2", - "url-join": "^4.0.0" + "request-promise-core": "^1.1.1", + "url-join": "^4.0.0", + "util.promisify": "^1.0.0", + "xhr": "^2.4.1" } } diff --git a/src/infrastructure/BaseService.js b/src/infrastructure/BaseService.js index cdbf78cc..7b4d28a7 100644 --- a/src/infrastructure/BaseService.js +++ b/src/infrastructure/BaseService.js @@ -1,9 +1,13 @@ import URLJoin from 'url-join'; +import Request from 'request-promise'; +import XMLHttpRequester from './XMLHttpRequester'; class BaseModel { - constructor({ url = 'https://gitlab.com', token, oauthToken }) { + constructor({ url = 'https://gitlab.com', token, oauthToken, useXMLHttpRequest = false }) { this.url = URLJoin(url, 'api', 'v4'); this.headers = {}; + this.requester = useXMLHttpRequest ? XMLHttpRequester : Request; + this.useXMLHttpRequest = useXMLHttpRequest; if (oauthToken) { this.headers.Authorization = `Bearer ${oauthToken}`; diff --git a/src/infrastructure/RequestHelper.js b/src/infrastructure/RequestHelper.js index 5fee2a0e..c91e205f 100644 --- a/src/infrastructure/RequestHelper.js +++ b/src/infrastructure/RequestHelper.js @@ -1,10 +1,10 @@ -import Request from 'request-promise'; import Humps from 'humps'; import LinkParser from 'parse-link-header'; +import QS from 'qs'; import URLJoin from 'url-join'; function defaultRequest( - url, + { url, useXMLHttpRequest }, endpoint, { headers, body, qs, formData, resolveWithFullResponse = false }, ) { @@ -15,7 +15,12 @@ function defaultRequest( }; if (body) params.body = Humps.decamelizeKeys(body); - if (qs) params.qs = Humps.decamelizeKeys(qs); + if (qs) { + if (useXMLHttpRequest) { + // The xhr package doesn't have a way of passing in a qs object until v3 + params.url = URLJoin(params.url, `?${QS.stringify(qs)}`); + } else params.qs = Humps.decamelizeKeys(qs); + } if (formData) params.formData = formData; params.resolveWithFullResponse = resolveWithFullResponse; @@ -25,7 +30,7 @@ function defaultRequest( class RequestHelper { static async get(service, endpoint, options = {}) { - const response = await Request.get(defaultRequest(service.url, endpoint, { + const response = await service.requester.get(defaultRequest(service, endpoint, { headers: service.headers, qs: options, resolveWithFullResponse: true, @@ -48,21 +53,21 @@ class RequestHelper { static post(service, endpoint, options = {}, form = false) { const body = form ? 'fromData' : 'body'; - return Request.post(defaultRequest(service.url, endpoint, { + return service.requester.post(defaultRequest(service, endpoint, { headers: service.headers, [body]: options, })); } static put(service, endpoint, options = {}) { - return Request.put(defaultRequest(service.url, endpoint, { + return service.requester.put(defaultRequest(service, endpoint, { headers: service.headers, body: options, })); } static delete(service, endpoint, options = {}) { - return Request.delete(defaultRequest(service.url, endpoint, { + return service.requester.delete(defaultRequest(service, endpoint, { headers: service.headers, qs: options, })); diff --git a/src/infrastructure/XMLHttpRequester.js b/src/infrastructure/XMLHttpRequester.js new file mode 100644 index 00000000..45f37d6f --- /dev/null +++ b/src/infrastructure/XMLHttpRequester.js @@ -0,0 +1,32 @@ +import { + StatusCodeError, +} from 'request-promise-core/errors'; +import Promisify from 'util.promisify'; +import XHR from 'xhr'; + +function promisifyFn(fn) { + const promisifiedFn = Promisify(fn); + + return async function(opts) { + const response = await promisifiedFn(opts); + + if (response.statusCode >= 400 && response.statusCode <= 599) { + throw new StatusCodeError(response.statusCode, response.body, {}, null); + } + + return opts.resolveWithFullResponse + ? response + : response.body; + }; +} + +const XMLHttpRequester = promisifyFn(XHR); +XMLHttpRequester.del = promisifyFn(XHR.del); +XMLHttpRequester.delete = XMLHttpRequester.del; +XMLHttpRequester.get = promisifyFn(XHR.get); +XMLHttpRequester.head = promisifyFn(XHR.head); +XMLHttpRequester.patch = promisifyFn(XHR.patch); +XMLHttpRequester.post = promisifyFn(XHR.post); +XMLHttpRequester.put = promisifyFn(XHR.put); + +export default XMLHttpRequester;