diff --git a/src/components/operations/operation-details/ko/operationDetailsEditor.html b/src/components/operations/operation-details/ko/operationDetailsEditor.html index 4d0af986f..b10268ef5 100644 --- a/src/components/operations/operation-details/ko/operationDetailsEditor.html +++ b/src/components/operations/operation-details/ko/operationDetailsEditor.html @@ -1,24 +1,49 @@
-
- -
-
-
\ No newline at end of file diff --git a/src/components/operations/operation-details/ko/operationDetailsEditor.ts b/src/components/operations/operation-details/ko/operationDetailsEditor.ts index ffcf84d9d..b0c7ece6b 100644 --- a/src/components/operations/operation-details/ko/operationDetailsEditor.ts +++ b/src/components/operations/operation-details/ko/operationDetailsEditor.ts @@ -11,11 +11,13 @@ export class OperationDetailsEditor { public readonly enableConsole: ko.Observable; public readonly enableScrollTo: ko.Observable; public readonly defaultSchemaView: ko.Observable; + public readonly useCorsProxy: ko.Observable; constructor() { this.enableConsole = ko.observable(); this.enableScrollTo = ko.observable(); this.defaultSchemaView = ko.observable(); + this.useCorsProxy = ko.observable(); } @Param() @@ -27,15 +29,19 @@ export class OperationDetailsEditor { @OnMounted() public async initialize(): Promise { this.enableConsole(this.model.enableConsole); + this.useCorsProxy(this.model.useCorsProxy); this.enableScrollTo(this.model.enableScrollTo); this.defaultSchemaView(this.model.defaultSchemaView || "table"); + this.enableConsole.subscribe(this.applyChanges); + this.useCorsProxy.subscribe(this.applyChanges); this.enableScrollTo.subscribe(this.applyChanges); this.defaultSchemaView.subscribe(this.applyChanges); } private applyChanges(): void { this.model.enableConsole = this.enableConsole(); + this.model.useCorsProxy = this.useCorsProxy(); this.model.enableScrollTo = this.enableScrollTo(); this.model.defaultSchemaView = this.defaultSchemaView(); this.onChange(this.model); diff --git a/src/components/operations/operation-details/ko/operationDetailsViewModelBinder.ts b/src/components/operations/operation-details/ko/operationDetailsViewModelBinder.ts index d4830342e..68fc3e32e 100644 --- a/src/components/operations/operation-details/ko/operationDetailsViewModelBinder.ts +++ b/src/components/operations/operation-details/ko/operationDetailsViewModelBinder.ts @@ -29,7 +29,8 @@ export class OperationDetailsViewModelBinder implements ViewModelBinder; public isConsumptionMode: boolean; public templates: Object; + public backendUrl: string; constructor( private readonly apiService: ApiService, @@ -65,7 +68,8 @@ export class OperationConsole { private readonly httpClient: HttpClient, private readonly routeHelper: RouteHelper, private readonly oauthService: OAuthService, - private readonly sessionManager: SessionManager + private readonly sessionManager: SessionManager, + private readonly settingsProvider: ISettingsProvider ) { this.templates = templates; this.products = ko.observable(); @@ -94,7 +98,7 @@ export class OperationConsole { this.isHostnameWildcarded = ko.computed(() => this.selectedHostname().includes("*")); this.selectedGrantType = ko.observable(); this.authorizationServer = ko.observable(); - + this.useCorsProxy = ko.observable(false); this.wildcardSegment = ko.observable(); validation.rules["maxFileSize"] = { @@ -126,10 +130,14 @@ export class OperationConsole { @Param() public authorizationServer: ko.Observable; + @Param() + public useCorsProxy: ko.Observable; + @OnMounted() public async initialize(): Promise { const skuName = await this.tenantService.getServiceSkuName(); this.isConsumptionMode = skuName === ServiceSkuName.Consumption; + this.backendUrl = await this.settingsProvider.getSetting("backendUrl"); await this.resetConsole(); @@ -419,6 +427,48 @@ export class OperationConsole { this.sendRequest(); } + public async sendFromBrowser(request: HttpRequest): Promise> { + const response = await this.httpClient.send(request); + return response; + } + + public async sendFromProxy(request: HttpRequest): Promise> { + if (request.body) { + request.body = Buffer.from(request.body); + } + + const formData = new FormData(); + const requestPackage = new Blob([JSON.stringify(request)], { type: "application/json" }); + formData.append("requestPackage", requestPackage); + + const baseProxyUrl = this.backendUrl || ""; + const apiName = this.api().name; + + const proxiedRequest: HttpRequest = { + url: `${baseProxyUrl}/send`, + method: "POST", + headers: [{ name: "X-Ms-Api-Name", value: apiName }], + body: formData + }; + + const proxiedResponse = await this.httpClient.send(proxiedRequest); + const responsePackage = proxiedResponse.toObject(); + + const responseBodyBuffer = responsePackage.body + ? Buffer.from(responsePackage.body.data) + : null; + + const response: any = { + headers: responsePackage.headers, + statusCode: responsePackage.statusCode, + statusText: responsePackage.statusMessage, + body: responseBodyBuffer, + toText: () => responseBodyBuffer.toString("utf8") + }; + + return response; + } + private async sendRequest(): Promise { this.requestError(null); this.sendingRequest(true); @@ -455,7 +505,10 @@ export class OperationConsole { body: payload }; - const response = await this.httpClient.send(request); + const response = this.useCorsProxy() + ? await this.sendFromProxy(request) + : await this.sendFromBrowser(request); + this.responseHeadersString(response.headers.map(x => `${x.name}: ${x.value}`).join("\n")); const knownStatusCode = KnownStatusCodes.find(x => x.code === response.statusCode); diff --git a/src/components/operations/operation-details/ko/runtime/operation-details.html b/src/components/operations/operation-details/ko/runtime/operation-details.html index f5bf92cac..ce3782441 100644 --- a/src/components/operations/operation-details/ko/runtime/operation-details.html +++ b/src/components/operations/operation-details/ko/runtime/operation-details.html @@ -226,7 +226,7 @@

Definitions

+ params="{ api: api, operation: operation, hostnames: hostnames, authorizationServer: associatedAuthServer, useCorsProxy: useCorsProxy }">
\ No newline at end of file diff --git a/src/components/operations/operation-details/ko/runtime/operation-details.ts b/src/components/operations/operation-details/ko/runtime/operation-details.ts index 2859404b8..f6a487b0f 100644 --- a/src/components/operations/operation-details/ko/runtime/operation-details.ts +++ b/src/components/operations/operation-details/ko/runtime/operation-details.ts @@ -60,6 +60,7 @@ export class OperationDetails { this.consoleIsOpen = ko.observable(); this.definitions = ko.observableArray(); this.defaultSchemaView = ko.observable("table"); + this.useCorsProxy = ko.observable(); this.requestUrlSample = ko.computed(() => { if (!this.api() || !this.operation()) { return null; @@ -82,6 +83,9 @@ export class OperationDetails { @Param() public enableConsole: boolean; + @Param() + public useCorsProxy: ko.Observable; + @Param() public enableScrollTo: boolean; diff --git a/src/components/operations/operation-details/ko/runtime/responsePackage.ts b/src/components/operations/operation-details/ko/runtime/responsePackage.ts new file mode 100644 index 000000000..2e749b57e --- /dev/null +++ b/src/components/operations/operation-details/ko/runtime/responsePackage.ts @@ -0,0 +1,11 @@ +export interface NameValuePair { + name: string; + value: string; +} + +export interface ResponsePackage { + statusCode: number; + statusMessage: string; + headers: NameValuePair[]; + body: any; +} \ No newline at end of file diff --git a/src/components/operations/operation-details/operationDetailsContract.ts b/src/components/operations/operation-details/operationDetailsContract.ts index b6e918b88..97e3192f0 100644 --- a/src/components/operations/operation-details/operationDetailsContract.ts +++ b/src/components/operations/operation-details/operationDetailsContract.ts @@ -15,4 +15,9 @@ export interface OperationDetailsContract extends Contract { * Indicates whether operation details should appear in the visible area (for example if API details is too long). */ enableScrollTo?: boolean; + + /** + * Indicates whether the Test console should use CORS proxy vs direct calls from the browser. + */ + useCorsProxy?: boolean; } diff --git a/src/components/operations/operation-details/operationDetailsModel.ts b/src/components/operations/operation-details/operationDetailsModel.ts index 6b3f64f08..bbcaf978f 100644 --- a/src/components/operations/operation-details/operationDetailsModel.ts +++ b/src/components/operations/operation-details/operationDetailsModel.ts @@ -21,6 +21,11 @@ export class OperationDetailsModel { */ public enableScrollTo?: boolean; + /** + * Indicates whether the Test console should use CORS proxy vs direct calls from the browser. + */ + public useCorsProxy?: boolean; + constructor() { this.enableConsole = true; } diff --git a/src/components/operations/operation-details/operationDetailsModelBinder.ts b/src/components/operations/operation-details/operationDetailsModelBinder.ts index 407d1558b..462182f25 100644 --- a/src/components/operations/operation-details/operationDetailsModelBinder.ts +++ b/src/components/operations/operation-details/operationDetailsModelBinder.ts @@ -21,6 +21,7 @@ export class OperationDetailsModelBinder implements IModelBinder