-
Notifications
You must be signed in to change notification settings - Fork 852
Repackage azure-devops-extensions-api to fix Azure DevOps report plugin #6299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
a32cfb0
Repackage azure-devops-extensions-api
peterwald 1fa4c80
Unify headers
peterwald 7182185
Regenerate the package-lock.json file
peterwald 50dcd33
Remove unneeded packages
peterwald a83cef5
Make tsc build verbose
peterwald dc4dc09
Add debug flag for vite build
peterwald 92bb435
Add NPM build command for testing
peterwald d130ab9
Fix the tsc build
peterwald 3e577ae
Make sure we log stdout when an error occurs.
peterwald File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
40 changes: 40 additions & 0 deletions
40
....Reporting/TypeScript/azure-devops-report/src/azure-devops-extension-api/Common/Client.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| import { getAccessToken, getService } from "azure-devops-extension-sdk"; | ||
| import { IVssRestClientOptions } from "./Context"; | ||
| import { CommonServiceIds, ILocationService } from "./CommonServices"; | ||
|
|
||
| /** | ||
| * A REST client factory is the method used to create and initialize an IVssRestClient. | ||
| */ | ||
| export type RestClientFactory<T> = { | ||
| new (options: IVssRestClientOptions): T; | ||
| RESOURCE_AREA_ID?: string; | ||
| } | ||
|
|
||
| export function getClient<T>(clientClass: RestClientFactory<T>, clientOptions?: IVssRestClientOptions) { | ||
|
|
||
| const options = clientOptions || {}; | ||
|
|
||
| if (!options.rootPath) { | ||
| options.rootPath = getService<ILocationService>(CommonServiceIds.LocationService).then(locationService => { | ||
| if (clientClass.RESOURCE_AREA_ID) { | ||
| return locationService.getResourceAreaLocation(clientClass.RESOURCE_AREA_ID); | ||
| } | ||
| else { | ||
| return locationService.getServiceLocation(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| if (!options.authTokenProvider) { | ||
| options.authTokenProvider = { | ||
| getAuthorizationHeader: (): Promise<string> => { | ||
| return getAccessToken().then(token => token ? ("Bearer " + token) : ""); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| return new clientClass(options); | ||
| } | ||
68 changes: 68 additions & 0 deletions
68
...ng/TypeScript/azure-devops-report/src/azure-devops-extension-api/Common/CommonServices.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| /** | ||
| * Contribution ids of core DevOps services which can be obtained from DevOps.getService | ||
| */ | ||
| export const enum CommonServiceIds { | ||
|
|
||
| /** | ||
| * Service for getting URLs/locations from the host context | ||
| */ | ||
| LocationService = "ms.vss-features.location-service", | ||
|
|
||
| } | ||
|
|
||
| /** | ||
| * Host level for a VSS service | ||
| */ | ||
| export const enum TeamFoundationHostType { | ||
| /** | ||
| * The Deployment host | ||
| */ | ||
| Deployment = 1, | ||
|
|
||
| /** | ||
| * The Enterprise host | ||
| */ | ||
| Enterprise = 2, | ||
|
|
||
| /** | ||
| * The organization/project collection host | ||
| */ | ||
| Organization = 4 | ||
| } | ||
|
|
||
| /** | ||
| * Service for external content to get locations | ||
| */ | ||
| export interface ILocationService { | ||
|
|
||
| /** | ||
| * Gets the URL for the given REST resource area | ||
| * | ||
| * @param resourceAreaId - Id of the resource area | ||
| */ | ||
| getResourceAreaLocation(resourceAreaId: string): Promise<string>; | ||
|
|
||
| /** | ||
| * Gets the location of a remote service at a given host type. | ||
| * | ||
| * @param serviceInstanceType - The GUID of the service instance type to lookup | ||
| * @param hostType - The host type to lookup (defaults to the host type of the current page data) | ||
| */ | ||
| getServiceLocation(serviceInstanceType?: string, hostType?: TeamFoundationHostType): Promise<string>; | ||
|
|
||
| /** | ||
| * Attemps to create a url for the specified route template and paramaters. The url will include host path. | ||
| * For example, if the page url is https://dev.azure.com/foo and you try to create admin settings url for project "bar", | ||
| * the output will be /foo/bar/_admin. | ||
| * | ||
| * This will asynchronously fetch a route contribution if it has not been included in page data. | ||
| * | ||
| * @param routeId - Id of the route contribution | ||
| * @param routeValues - Route value replacements | ||
| * @param hostPath - Optional host path to use rather than the default host path for the page. | ||
| */ | ||
| routeUrl(routeId: string, routeValues?: { [key: string]: string }, hostPath?: string): Promise<string>; | ||
| } |
43 changes: 43 additions & 0 deletions
43
...porting/TypeScript/azure-devops-report/src/azure-devops-extension-api/Common/Context.d.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| /** | ||
| * Interface for a class that can retrieve authorization tokens to be used in fetch requests. | ||
| */ | ||
| export interface IAuthorizationTokenProvider { | ||
| /** | ||
| * Gets the authorization header to use in a fetch request | ||
| * | ||
| * @param forceRefresh - If true, indicates that we should get a new token, if applicable for current provider. | ||
| * @returns the value to use for the Authorization header in a request. | ||
| */ | ||
| getAuthorizationHeader(forceRefresh?: boolean): Promise<string>; | ||
| } | ||
|
|
||
| /** | ||
| * Options for a specific instance of a REST client. | ||
| */ | ||
| export interface IVssRestClientOptions { | ||
|
|
||
| /** | ||
| * Auth token manager that can be used to get and attach auth tokens to requests. | ||
| * If not supplied, the default token provider is used if the serviceInstanceType option is supplied | ||
| * and is different from the hosting page's service instance type. | ||
| */ | ||
| authTokenProvider?: IAuthorizationTokenProvider; | ||
|
|
||
| /** | ||
| * The root URL path to use for this client. Can be relative or absolute. | ||
| */ | ||
| rootPath?: string | Promise<string>; | ||
|
|
||
| /** | ||
| * Current session id. | ||
| */ | ||
| sessionId?: string; | ||
|
|
||
| /** | ||
| * Current command for activity logging. | ||
| */ | ||
| command?: string; | ||
| } |
238 changes: 238 additions & 0 deletions
238
...n.Reporting/TypeScript/azure-devops-report/src/azure-devops-extension-api/Common/Fetch.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,238 @@ | ||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
|
|
||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| // Fetch polyfill for IE11 | ||
| // import "whatwg-fetch"; | ||
|
|
||
| import { IAuthorizationTokenProvider } from "./Context"; | ||
|
|
||
| /** | ||
| * VSS-specific options for VSS requests | ||
| */ | ||
| export interface IVssRequestOptions { | ||
|
|
||
| /* | ||
| * Auth token manager that can be used to get and attach auth tokens to requests | ||
| */ | ||
| authTokenProvider?: IAuthorizationTokenProvider; | ||
|
|
||
| /** | ||
| * Current session id. | ||
| */ | ||
| sessionId?: string; | ||
|
|
||
| /** | ||
| * Current command for activity logging. | ||
| */ | ||
| command?: string; | ||
| } | ||
|
|
||
| /** | ||
| * An IPreRequestEvent is sent before a fetch request is made. | ||
| */ | ||
| export interface IPreRequestEvent { | ||
|
|
||
| /** | ||
| * A unique id that can be used to track this particular request (id is unique among all clients) | ||
| */ | ||
| requestId: number; | ||
|
|
||
| /** | ||
| * Url of the request that is about to be issued | ||
| */ | ||
| requestUrl: string; | ||
|
|
||
| /** | ||
| * Request settings being used | ||
| */ | ||
| options?: RequestInit; | ||
|
|
||
| /** | ||
| * Additional VSS-specific options supplied in the request | ||
| */ | ||
| vssRequestOptions?: IVssRequestOptions; | ||
| } | ||
|
|
||
| /** | ||
| * An IPostRequestEvent is sent after a fetch request has completed. | ||
| */ | ||
| export interface IPostRequestEvent { | ||
|
|
||
| /** | ||
| * A unique id that can be used to track this particular request (id is unique among all clients) | ||
| */ | ||
| requestId: number; | ||
|
|
||
| /** | ||
| * Url of the request that is about to be issued | ||
| */ | ||
| requestUrl: string; | ||
|
|
||
| /** | ||
| * The Response returned for this request, if the request fails it will be undefined | ||
| */ | ||
| response?: Response; | ||
|
|
||
| /** | ||
| * Additional VSS-specific options supplied in the request | ||
| */ | ||
| vssRequestOptions?: IVssRequestOptions; | ||
| } | ||
|
|
||
| /** | ||
| * When a fetch request fails, it will throw a VssServerError. Failure is defined | ||
| * as a request that made it to the server, and the server successfully responded | ||
| * with a failure. This will be any status return that is not a status code in | ||
| * the success range (200-299). | ||
| */ | ||
| export interface VssServerError extends Error { | ||
|
|
||
| /** | ||
| * The status code returned from the server. | ||
| */ | ||
| status: number; | ||
|
|
||
| /** | ||
| * The raw text that was returned from the server. If any is available. | ||
| */ | ||
| responseText: string; | ||
|
|
||
| /** | ||
| * If the response text was sent and it was in the form of a JSON response | ||
| * the object will be parsed and deserialized object is available here. | ||
| * | ||
| * This commonly has the exception details about the failure from the server. | ||
| * Things like the message, exception type, and stack trace will be available. | ||
| */ | ||
| serverError?: any; | ||
| } | ||
|
|
||
| /** | ||
| * Issue a fetch request. This is a wrapper around the browser fetch method that handles VSS authentication | ||
| * and triggers events that can be listened to by other modules. | ||
| * | ||
| * @param requestUrl Url to send the request to | ||
| * @param options fetch settings/options for the request | ||
| * @param vssRequestOptions VSS specific request options | ||
| * | ||
| * Triggered Events: | ||
| * afterRequest -> IPostRequestEvent is sent after the request has completed. | ||
| * beforeRequest -> IPreRequestEvent is sent before the request is made. | ||
| */ | ||
| export async function issueRequest(requestUrl: string, options?: RequestInit, vssRequestOptions?: IVssRequestOptions): Promise<Response> { | ||
| let response: Response | undefined = undefined; | ||
|
|
||
| // Add a X-VSS-ReauthenticationAction header to all fetch requests | ||
| if (!options) { | ||
| options = {}; | ||
| } | ||
|
|
||
| let headers: Headers; | ||
|
|
||
| // If options.headers is set, check if it is a Headers object, and if not, convert it to one. | ||
| if (options.headers) { | ||
| if (options.headers instanceof Headers) { | ||
| headers = options.headers; | ||
| } | ||
| else { | ||
| headers = new Headers(); | ||
| if (typeof options.headers.hasOwnProperty === "function") { | ||
| for (const key in options.headers) { | ||
| if (Object.prototype.hasOwnProperty.call(options.headers, key)) { | ||
| headers.append(key, (options.headers as { [key: string]: string })[key]); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| else { | ||
| headers = new Headers(); | ||
| } | ||
| options.headers = headers; | ||
|
|
||
| headers.append("X-VSS-ReauthenticationAction", "Suppress"); | ||
|
|
||
| // Add a X-TFS-Session header with the current sessionId and command for correlation | ||
| if (vssRequestOptions) { | ||
| const sessionId = vssRequestOptions.sessionId; | ||
| const command = vssRequestOptions.command; | ||
|
|
||
| if (sessionId) { | ||
| if (command) { | ||
| headers.append("X-TFS-Session", sessionId + "," + command); | ||
| } | ||
| else { | ||
| headers.append("X-TFS-Session", sessionId); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Send credentials to the same origin, we will use tokens for differing origins. | ||
| options.credentials = "same-origin"; | ||
|
|
||
| let refreshToken = false; | ||
|
|
||
| do { | ||
|
|
||
| // Ensure we have an authorization token available from the auth manager. | ||
| if (vssRequestOptions && vssRequestOptions.authTokenProvider) { | ||
| const authHeader = await vssRequestOptions.authTokenProvider.getAuthorizationHeader(refreshToken); | ||
| if (authHeader) { | ||
| headers.append("Authorization", authHeader); | ||
| refreshToken = true; | ||
| } | ||
| headers.append("X-TFS-FedAuthRedirect", "Suppress"); | ||
| } | ||
|
|
||
| // Execute the http request defined by the caller. | ||
| try { | ||
| response = await fetch(requestUrl, options); | ||
| } | ||
| catch (ex) { | ||
| console.warn(`Unhandled exception in fetch for ${requestUrl}: ${ex && ex.toString()}`); | ||
| const error = new Error("TF400893: Unable to contact the server. Please check your network connection and try again."); | ||
| error.name = "NetworkException"; | ||
| throw error; | ||
| } | ||
|
|
||
| // If we recieved a 401 and have a token manager, we will refresh our token and try again. | ||
| if (response.status === 401 && !refreshToken && vssRequestOptions && vssRequestOptions.authTokenProvider) { | ||
| refreshToken = true; | ||
| continue; | ||
| } | ||
|
|
||
| // eslint-disable-next-line no-constant-condition | ||
| } while (false); | ||
|
|
||
| // Parse error details from requests that returned non 200-299 status codes. | ||
| if (!response.ok) { | ||
|
|
||
| // Create a VssServerError and throw it as the result of the fetch request. | ||
| const error = new Error() as VssServerError; | ||
| error.name = "TFS.WebApi.Exception"; | ||
| error.status = response.status; | ||
| error.responseText = await response.text(); | ||
|
|
||
| // Attempt to parse the response as a json object, for many of the failures | ||
| // the server will serialize details into the body. | ||
| if (error.responseText && error.responseText !== "") { | ||
| try { | ||
| error.serverError = JSON.parse(error.responseText); | ||
| if (error.serverError && error.serverError.message) { | ||
| error.message = error.serverError.message; | ||
| } | ||
| } | ||
| catch { /* empty */ } | ||
| } | ||
|
|
||
| if (!error.message) { | ||
| error.message = response.status + ": " + response.statusText; | ||
| } | ||
|
|
||
| throw error; | ||
| } | ||
|
|
||
| return response; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.