UNPKG

11.3 kBJavaScriptView Raw
1"use strict";
2//*******************************************************************************************************
3// significant portions of this file copied from: VSO\src\Vssf\WebPlatform\Platform\Scripts\VSS\WebApi\RestClient.ts
4//*******************************************************************************************************
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.VsoClient = exports.InvalidApiResourceVersionError = void 0;
7/// Imports of 3rd Party ///
8const url = require("url");
9const path = require("path");
10class InvalidApiResourceVersionError {
11 constructor(message) {
12 this.name = "Invalid resource version";
13 this.message = message;
14 }
15}
16exports.InvalidApiResourceVersionError = InvalidApiResourceVersionError;
17/**
18 * Base class that should be used (derived from) to make requests to VSS REST apis
19 */
20class VsoClient {
21 constructor(baseUrl, restClient) {
22 this.baseUrl = baseUrl;
23 this.basePath = url.parse(baseUrl).pathname;
24 this.restClient = restClient;
25 this._locationsByAreaPromises = {};
26 this._initializationPromise = Promise.resolve(true);
27 }
28 autoNegotiateApiVersion(location, requestedVersion) {
29 let negotiatedVersion;
30 let apiVersion;
31 let apiVersionString;
32 if (requestedVersion) {
33 let apiVersionRegEx = new RegExp('(\\d+(\\.\\d+)?)(-preview(\\.(\\d+))?)?');
34 // Need to handle 3 types of api versions + invalid apiversion
35 // '2.1-preview.1' = ["2.1-preview.1", "2.1", ".1", -preview.1", ".1", "1"]
36 // '2.1-preview' = ["2.1-preview", "2.1", ".1", "-preview", undefined, undefined]
37 // '2.1' = ["2.1", "2.1", ".1", undefined, undefined, undefined]
38 let isPreview = false;
39 let resourceVersion;
40 let regExExecArray = apiVersionRegEx.exec(requestedVersion);
41 if (regExExecArray) {
42 if (regExExecArray[1]) {
43 // we have an api version
44 apiVersion = +regExExecArray[1];
45 apiVersionString = regExExecArray[1];
46 if (regExExecArray[3]) {
47 // requesting preview
48 isPreview = true;
49 if (regExExecArray[5]) {
50 // we have a resource version
51 resourceVersion = +regExExecArray[5];
52 }
53 }
54 // compare the location version and requestedversion
55 if (apiVersion <= +location.releasedVersion
56 || (!resourceVersion && apiVersion <= +location.maxVersion && isPreview)
57 || (resourceVersion && apiVersion <= +location.maxVersion && resourceVersion <= +location.resourceVersion)) {
58 negotiatedVersion = requestedVersion;
59 }
60 // else fall back to latest version of the resource from location
61 }
62 }
63 }
64 if (!negotiatedVersion) {
65 // Use the latest version of the resource if the api version was not specified in the request or if the requested version is higher then the location's supported version
66 if (apiVersion < +location.maxVersion) {
67 negotiatedVersion = apiVersionString + "-preview";
68 }
69 else if (location.maxVersion === location.releasedVersion) {
70 negotiatedVersion = location.maxVersion;
71 }
72 else {
73 negotiatedVersion = location.maxVersion + "-preview." + location.resourceVersion;
74 }
75 }
76 return negotiatedVersion;
77 }
78 /**
79 * Gets the route template for a resource based on its location ID and negotiates the api version
80 */
81 getVersioningData(apiVersion, area, locationId, routeValues, queryParams) {
82 let requestUrl;
83 return this.beginGetLocation(area, locationId)
84 .then((location) => {
85 if (!location) {
86 throw new Error("Failed to find api location for area: " + area + " id: " + locationId);
87 }
88 apiVersion = this.autoNegotiateApiVersion(location, apiVersion);
89 requestUrl = this.getRequestUrl(location.routeTemplate, location.area, location.resourceName, routeValues, queryParams);
90 return {
91 apiVersion: apiVersion,
92 requestUrl: requestUrl
93 };
94 });
95 }
96 /**
97 * Sets a promise that is waited on before any requests are issued. Can be used to asynchronously
98 * set the request url and auth token manager.
99 */
100 _setInitializationPromise(promise) {
101 if (promise) {
102 this._initializationPromise = promise;
103 }
104 }
105 /**
106 * Gets information about an API resource location (route template, supported versions, etc.)
107 *
108 * @param area resource area name
109 * @param locationId Guid of the location to get
110 */
111 beginGetLocation(area, locationId) {
112 return this._initializationPromise.then(() => {
113 return this.beginGetAreaLocations(area);
114 }).then((areaLocations) => {
115 return areaLocations[(locationId || "").toLowerCase()];
116 });
117 }
118 beginGetAreaLocations(area) {
119 let areaLocationsPromise = this._locationsByAreaPromises[area];
120 if (!areaLocationsPromise) {
121 let requestUrl = this.resolveUrl(VsoClient.APIS_RELATIVE_PATH + "/" + area);
122 areaLocationsPromise = this.restClient.options(requestUrl)
123 .then((res) => {
124 if (!res.result) {
125 return {};
126 }
127 let locationsLookup = {};
128 let resourceLocations = res.result.value;
129 let i;
130 for (i = 0; i < resourceLocations.length; i++) {
131 let resourceLocation = resourceLocations[i];
132 locationsLookup[resourceLocation.id.toLowerCase()] = resourceLocation;
133 }
134 // If we have completed successfully, cache the response.
135 this._locationsByAreaPromises[area] = areaLocationsPromise;
136 return locationsLookup;
137 });
138 }
139 return areaLocationsPromise;
140 }
141 resolveUrl(relativeUrl) {
142 return url.resolve(this.baseUrl, path.join(this.basePath, relativeUrl));
143 }
144 queryParamsToStringHelper(queryParams, prefix) {
145 if (queryParams == null || queryParams.length === 0) {
146 return '';
147 }
148 let queryString = '';
149 if (typeof (queryParams) !== 'string') {
150 for (let property in queryParams) {
151 if (queryParams.hasOwnProperty(property)) {
152 const prop = queryParams[property];
153 const newPrefix = prefix + encodeURIComponent(property.toString()) + '.';
154 queryString += this.queryParamsToStringHelper(prop, newPrefix);
155 }
156 }
157 }
158 if (queryString === '' && prefix.length > 0) {
159 // Date.prototype.toString() returns a string that is not valid for the REST API.
160 // Need to specially call `toUTCString()` instead for such cases
161 const queryValue = typeof queryParams === 'object' && 'toUTCString' in queryParams ? queryParams.toUTCString() : queryParams.toString();
162 // Will always need to chop period off of end of prefix
163 queryString = prefix.slice(0, -1) + '=' + encodeURIComponent(queryValue) + '&';
164 }
165 return queryString;
166 }
167 queryParamsToString(queryParams) {
168 const queryString = '?' + this.queryParamsToStringHelper(queryParams, '');
169 // Will always need to slice either a ? or & off of the end
170 return queryString.slice(0, -1);
171 }
172 getRequestUrl(routeTemplate, area, resource, routeValues, queryParams) {
173 // Add area/resource route values (based on the location)
174 routeValues = routeValues || {};
175 if (!routeValues.area) {
176 routeValues.area = area;
177 }
178 if (!routeValues.resource) {
179 routeValues.resource = resource;
180 }
181 // Replace templated route values
182 let relativeUrl = this.replaceRouteValues(routeTemplate, routeValues);
183 // Append query parameters to the end
184 if (queryParams) {
185 relativeUrl += this.queryParamsToString(queryParams);
186 }
187 // Resolve the relative url with the base
188 return url.resolve(this.baseUrl, path.join(this.basePath, relativeUrl));
189 }
190 // helper method copied directly from VSS\WebAPI\restclient.ts
191 replaceRouteValues(routeTemplate, routeValues) {
192 let result = "", currentPathPart = "", paramName = "", insideParam = false, charIndex, routeTemplateLength = routeTemplate.length, c;
193 for (charIndex = 0; charIndex < routeTemplateLength; charIndex++) {
194 c = routeTemplate[charIndex];
195 if (insideParam) {
196 if (c == "}") {
197 insideParam = false;
198 if (routeValues[paramName]) {
199 currentPathPart += encodeURIComponent(routeValues[paramName]);
200 }
201 else {
202 // Normalize param name in order to capture wild-card routes
203 let strippedParamName = paramName.replace(/[^a-z0-9]/ig, '');
204 if (routeValues[strippedParamName]) {
205 currentPathPart += encodeURIComponent(routeValues[strippedParamName]);
206 }
207 }
208 paramName = "";
209 }
210 else {
211 paramName += c;
212 }
213 }
214 else {
215 if (c == "/") {
216 if (currentPathPart) {
217 if (result) {
218 result += "/";
219 }
220 result += currentPathPart;
221 currentPathPart = "";
222 }
223 }
224 else if (c == "{") {
225 if ((charIndex + 1) < routeTemplateLength && routeTemplate[charIndex + 1] == "{") {
226 // Escaped '{'
227 currentPathPart += c;
228 charIndex++;
229 }
230 else {
231 insideParam = true;
232 }
233 }
234 else if (c == '}') {
235 currentPathPart += c;
236 if ((charIndex + 1) < routeTemplateLength && routeTemplate[charIndex + 1] == "}") {
237 // Escaped '}'
238 charIndex++;
239 }
240 }
241 else {
242 currentPathPart += c;
243 }
244 }
245 }
246 if (currentPathPart) {
247 if (result) {
248 result += "/";
249 }
250 result += currentPathPart;
251 }
252 return result;
253 }
254}
255exports.VsoClient = VsoClient;
256VsoClient.APIS_RELATIVE_PATH = "_apis";
257VsoClient.PREVIEW_INDICATOR = "-preview.";