Skip to content

Commit

Permalink
Disable api calls (#851)
Browse files Browse the repository at this point in the history
* Stop sending api calls after unsuccessful request

* Update disable api calls logic

* Update access modifer

* Add test case for disabling api calls

* Reformat code

* Refactor and reformat code

* Improve tests

* Update unit tests

* Update test cases

* Remove static getter

* Reformat code

* Fix typo and refactor code

* Handle all non 2xx status codes
  • Loading branch information
DordeDimitrijev authored Dec 16, 2024
1 parent 682b9f2 commit c7bf0eb
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 11 deletions.
42 changes: 38 additions & 4 deletions src/script/acquisition-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,16 @@ export class AcquisitionStatus {
}

export class AcquisitionManager {
private readonly BASE_URL_PART = "appcenter.ms";
private _appVersion: string;
private _clientUniqueId: string;
private _deploymentKey: string;
private _httpRequester: Http.Requester;
private _ignoreAppVersion: boolean;
private _serverUrl: string;
private _publicPrefixUrl: string = "v0.1/public/codepush/";

private _statusCode: number;
private static _apiCallsDisabled: boolean = false;
constructor(httpRequester: Http.Requester, configuration: Configuration) {
this._httpRequester = httpRequester;

Expand All @@ -80,7 +82,21 @@ export class AcquisitionManager {
this._ignoreAppVersion = configuration.ignoreAppVersion;
}

private isRecoverable = (statusCode: number): boolean => statusCode >= 500 || statusCode === 408 || statusCode === 429;

private handleRequestFailure() {
if (this._serverUrl.includes(this.BASE_URL_PART) && !this.isRecoverable(this._statusCode)) {
AcquisitionManager._apiCallsDisabled = true;
}
}

public queryUpdateWithCurrentPackage(currentPackage: Package, callback?: Callback<RemotePackage | NativeUpdateNotification>): void {
if (AcquisitionManager._apiCallsDisabled) {
console.log(`[CodePush] Api calls are disabled, skipping API call`);
callback(/*error=*/ null, /*remotePackage=*/ null);
return;
}

if (!currentPackage || !currentPackage.appVersion) {
throw new CodePushPackageError("Calling common acquisition SDK with incorrect package"); // Unexpected; indicates error in our implementation
}
Expand All @@ -102,8 +118,10 @@ export class AcquisitionManager {
return;
}

if (response.statusCode !== 200) {
if (response.statusCode < 200 || response.statusCode >= 300) {
let errorMessage: any;
this._statusCode = response.statusCode;
this.handleRequestFailure();
if (response.statusCode === 0) {
errorMessage = `Couldn't send request to ${requestUrl}, xhr.statusCode = 0 was returned. One of the possible reasons for that might be connection problems. Please, check your internet connection.`;
} else {
Expand Down Expand Up @@ -147,6 +165,12 @@ export class AcquisitionManager {
}

public reportStatusDeploy(deployedPackage?: Package, status?: string, previousLabelOrAppVersion?: string, previousDeploymentKey?: string, callback?: Callback<void>): void {
if (AcquisitionManager._apiCallsDisabled) {
console.log(`[CodePush] Api calls are disabled, skipping API call`);
callback(/*error*/ null, /*not used*/ null);
return;
}

var url: string = this._serverUrl + this._publicPrefixUrl + "report_status/deploy";
var body: DeploymentStatusReport = {
app_version: this._appVersion,
Expand Down Expand Up @@ -196,7 +220,9 @@ export class AcquisitionManager {
return;
}

if (response.statusCode !== 200) {
if (response.statusCode < 200 || response.statusCode >= 300) {
this._statusCode = response.statusCode;
this.handleRequestFailure();
callback(new CodePushHttpError(response.statusCode + ": " + response.body), /*not used*/ null);
return;
}
Expand All @@ -207,6 +233,12 @@ export class AcquisitionManager {
}

public reportStatusDownload(downloadedPackage: Package, callback?: Callback<void>): void {
if (AcquisitionManager._apiCallsDisabled) {
console.log(`[CodePush] Api calls are disabled, skipping API call`);
callback(/*error*/ null, /*not used*/ null);
return;
}

var url: string = this._serverUrl + this._publicPrefixUrl + "report_status/download";
var body: DownloadReport = {
client_unique_id: this._clientUniqueId,
Expand All @@ -221,7 +253,9 @@ export class AcquisitionManager {
return;
}

if (response.statusCode !== 200) {
if (response.statusCode < 200 || response.statusCode >= 300) {
this._statusCode = response.statusCode;
this.handleRequestFailure();
callback(new CodePushHttpError(response.statusCode + ": " + response.body), /*not used*/ null);
return;
}
Expand Down
26 changes: 19 additions & 7 deletions src/test/acquisition-rest-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,31 @@ var reportStatusDeployUrl = serverUrl + publicPrefixUrl + "/report_status/deploy
var reportStatusDownloadUrl = serverUrl + publicPrefixUrl + "/report_status/download";
var updateCheckUrl = serverUrl + publicPrefixUrl + "/update_check?";

export function updateMockUrl() {
reportStatusDeployUrl = serverUrl + publicPrefixUrl + "/report_status/deploy";
reportStatusDownloadUrl = serverUrl + publicPrefixUrl + "/report_status/download";
updateCheckUrl = serverUrl + publicPrefixUrl + "/update_check?";
}

export class HttpRequester implements acquisitionSdk.Http.Requester {
private expectedStatusCode: number;

constructor(expectedStatusCode?: number) {
this.expectedStatusCode = expectedStatusCode;
}

public request(verb: acquisitionSdk.Http.Verb, url: string, requestBodyOrCallback: string | acquisitionSdk.Callback<acquisitionSdk.Http.Response>, callback?: acquisitionSdk.Callback<acquisitionSdk.Http.Response>): void {
if (!callback && typeof requestBodyOrCallback === "function") {
callback = <acquisitionSdk.Callback<acquisitionSdk.Http.Response>>requestBodyOrCallback;
}

if (verb === acquisitionSdk.Http.Verb.GET && url.indexOf(updateCheckUrl) === 0) {
var params = querystring.parse(url.substring(updateCheckUrl.length));
Server.onUpdateCheck(params, callback);
Server.onUpdateCheck(params, callback, this.expectedStatusCode);
} else if (verb === acquisitionSdk.Http.Verb.POST && url === reportStatusDeployUrl) {
Server.onReportStatus(callback);
Server.onReportStatus(callback, this.expectedStatusCode);
} else if (verb === acquisitionSdk.Http.Verb.POST && url === reportStatusDownloadUrl) {
Server.onReportStatus(callback);
Server.onReportStatus(callback, this.expectedStatusCode);
} else {
throw new Error("Unexpected call");
}
Expand Down Expand Up @@ -73,7 +85,7 @@ class Server {
}
}

public static onUpdateCheck(params: any, callback: acquisitionSdk.Callback<acquisitionSdk.Http.Response>): void {
public static onUpdateCheck(params: any, callback: acquisitionSdk.Callback<acquisitionSdk.Http.Response>, expectedStatusCode?: number): void {
var updateRequest: types.UpdateCheckRequest = {
deployment_key: params.deployment_key,
app_version: params.app_version,
Expand All @@ -97,13 +109,13 @@ class Server {
}

callback(/*error=*/ null, {
statusCode: 200,
statusCode: expectedStatusCode ? expectedStatusCode : 200,
body: JSON.stringify({ update_info: updateInfo })
});
}
}

public static onReportStatus(callback: acquisitionSdk.Callback<acquisitionSdk.Http.Response>): void {
callback(/*error*/ null, /*response*/ { statusCode: 200 });
public static onReportStatus(callback: acquisitionSdk.Callback<acquisitionSdk.Http.Response>, expectedStatusCode: number): void {
callback(/*error*/ null, /*response*/ { statusCode: expectedStatusCode ? expectedStatusCode : 200 });
}
}
65 changes: 65 additions & 0 deletions src/test/acquisition-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as acquisitionSdk from "../script/acquisition-sdk";
import * as acquisitionRestMock from "./acquisition-rest-mock";
import * as types from "../script/types";
import { CodePushPackageError } from "../script/code-push-error"
import { updateMockUrl } from "./acquisition-rest-mock";

const mockApi = acquisitionRestMock;
var latestPackage: types.UpdateCheckResponse = clone(mockApi.latestPackage);
Expand Down Expand Up @@ -44,6 +45,8 @@ var nativeUpdateResult: acquisitionSdk.NativeUpdateNotification = {
describe("Acquisition SDK", () => {
beforeEach(() => {
mockApi.latestPackage = clone(latestPackage);
mockApi.serverUrl = "http://myurl.com";
updateMockUrl();
});

it("Package with lower label and different package hash gives update", (done: Mocha.Done) => {
Expand Down Expand Up @@ -226,6 +229,68 @@ describe("Acquisition SDK", () => {
done();
}));
});

it("disables api calls on unsuccessful response", (done: Mocha.Done): void => {
var invalidJsonResponse: acquisitionSdk.Http.Response = {
statusCode: 404,
body: "Not found"
};

mockApi.serverUrl = "https://codepush.appcenter.ms";
updateMockUrl();
configuration = { ...configuration, serverUrl: "https://codepush.appcenter.ms" };

var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.CustomResponseHttpRequester(invalidJsonResponse), configuration);
acquisition.queryUpdateWithCurrentPackage(templateCurrentPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => {
assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, true);
(acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled = false;
});

acquisition.queryUpdateWithCurrentPackage(templateCurrentPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => {
assert.strictEqual(returnPackage, null);
acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(404), configuration);
(acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled = false;
});

acquisition.reportStatusDeploy(templateCurrentPackage, acquisitionSdk.AcquisitionStatus.DeploymentSucceeded, "1.5.0", mockApi.validDeploymentKey, ((error: Error, parameter: void): void => {
assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, true);
acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(404), configuration);
(acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled = false;
}));

acquisition.reportStatusDownload(templateCurrentPackage, ((error: Error, parameter: void): void => {
assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, true);
acquisition = acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.CustomResponseHttpRequester(invalidJsonResponse), configuration);
(acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled = false;
}));

done();
})

it("doesn't disable api calls on successful response", (done: Mocha.Done): void => {
var acquisition = new acquisitionSdk.AcquisitionManager(new mockApi.HttpRequester(), configuration);
mockApi.serverUrl = "https://codepush.appcenter.ms";
updateMockUrl();

acquisition.queryUpdateWithCurrentPackage(templateCurrentPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => {
assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, false);
});

acquisition.queryUpdateWithCurrentPackage(templateCurrentPackage, (error: Error, returnPackage: acquisitionSdk.RemotePackage | acquisitionSdk.NativeUpdateNotification) => {
assert.notStrictEqual(returnPackage, null);
});

acquisition.reportStatusDeploy(templateCurrentPackage, acquisitionSdk.AcquisitionStatus.DeploymentSucceeded, "1.5.0", mockApi.validDeploymentKey, ((error: Error, parameter: void): void => {
assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, false);
}));

acquisition.reportStatusDownload(templateCurrentPackage, ((error: Error, parameter: void): void => {
assert.strictEqual((acquisitionSdk.AcquisitionManager as any)._apiCallsDisabled, false);
}));

done();
})

});

function clone<T>(initialObject: T): T {
Expand Down

0 comments on commit c7bf0eb

Please sign in to comment.