smartystreets-javascript-sdk 1.13.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Makefile +2 -2
- package/package.json +3 -3
- package/src/ClientBuilder.js +8 -2
- package/src/HttpSender.js +4 -12
- package/src/Response.js +2 -1
- package/src/RetrySender.js +50 -0
- package/src/util/Sleeper.js +8 -0
- package/src/util/buildSmartyResponse.js +10 -0
- package/tests/fixtures/MockSleeper.js +10 -0
- package/tests/fixtures/mock_senders.js +19 -1
- package/tests/test_HttpSender.js +3 -2
- package/tests/test_RetrySender.js +98 -0
package/Makefile
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/make -f
|
|
2
2
|
|
|
3
|
-
VERSION := $(shell tagit -
|
|
3
|
+
VERSION := $(shell tagit -M --dry-run)
|
|
4
4
|
VERSION_FILE1 := package.json
|
|
5
5
|
VERSION_FILE2 := package-lock.json
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ node_modules:
|
|
|
11
11
|
npm install
|
|
12
12
|
|
|
13
13
|
publish: test version upload unversion
|
|
14
|
-
tagit -
|
|
14
|
+
tagit -M
|
|
15
15
|
git push origin --tags
|
|
16
16
|
|
|
17
17
|
upload:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smartystreets-javascript-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Quick and easy Smarty address validation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"smarty",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"url": "github:smartystreets/smartystreets-javascript-sdk"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"chai": "^4.
|
|
36
|
-
"mocha": "^9.2.
|
|
35
|
+
"chai": "^4.3.6",
|
|
36
|
+
"mocha": "^9.2.2"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"axios": "^0.26.1",
|
package/src/ClientBuilder.js
CHANGED
|
@@ -8,6 +8,8 @@ const CustomHeaderSender = require("./CustomHeaderSender");
|
|
|
8
8
|
const StatusCodeSender = require("./StatusCodeSender");
|
|
9
9
|
const LicenseSender = require("./LicenseSender");
|
|
10
10
|
const BadCredentialsError = require("./Errors").BadCredentialsError;
|
|
11
|
+
const RetrySender = require("./RetrySender.js");
|
|
12
|
+
const Sleeper = require("./util/Sleeper.js");
|
|
11
13
|
|
|
12
14
|
//TODO: refactor this to work more cleanly with a bundler.
|
|
13
15
|
const UsStreetClient = require("./us_street/Client");
|
|
@@ -151,10 +153,14 @@ class ClientBuilder {
|
|
|
151
153
|
buildSender() {
|
|
152
154
|
if (this.httpSender) return this.httpSender;
|
|
153
155
|
|
|
154
|
-
const httpSender = new HttpSender(this.maxTimeout, this.
|
|
156
|
+
const httpSender = new HttpSender(this.maxTimeout, this.proxy, this.debug);
|
|
155
157
|
const statusCodeSender = new StatusCodeSender(httpSender);
|
|
156
158
|
const signingSender = new SigningSender(statusCodeSender, this.signer);
|
|
157
|
-
|
|
159
|
+
let agentSender = new AgentSender(signingSender);
|
|
160
|
+
if (this.maxRetries > 0) {
|
|
161
|
+
const retrySender = new RetrySender(this.maxRetries, signingSender, new Sleeper());
|
|
162
|
+
agentSender = new AgentSender(retrySender);
|
|
163
|
+
}
|
|
158
164
|
const customHeaderSender = new CustomHeaderSender(agentSender, this.customHeaders);
|
|
159
165
|
const baseUrlSender = new BaseUrlSender(customHeaderSender, this.baseUrl);
|
|
160
166
|
const licenseSender = new LicenseSender(baseUrlSender, this.licenses);
|
package/src/HttpSender.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
const Response = require("./Response");
|
|
2
2
|
const Axios = require("axios");
|
|
3
|
-
const
|
|
3
|
+
const {buildSmartyResponse} = require("../src/util/buildSmartyResponse");
|
|
4
4
|
|
|
5
5
|
class HttpSender {
|
|
6
|
-
constructor(timeout = 10000,
|
|
7
|
-
axiosRetry(Axios, {
|
|
8
|
-
retries: retries,
|
|
9
|
-
});
|
|
6
|
+
constructor(timeout = 10000, proxyConfig, debug = false) {
|
|
10
7
|
this.timeout = timeout;
|
|
11
8
|
this.proxyConfig = proxyConfig;
|
|
12
9
|
if (debug) this.enableDebug();
|
|
@@ -33,24 +30,19 @@ class HttpSender {
|
|
|
33
30
|
return config;
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
buildSmartyResponse(response, error) {
|
|
37
|
-
if (response) return new Response(response.status, response.data);
|
|
38
|
-
return new Response(undefined, undefined, error)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
33
|
send(request) {
|
|
42
34
|
return new Promise((resolve, reject) => {
|
|
43
35
|
let requestConfig = this.buildRequestConfig(request);
|
|
44
36
|
|
|
45
37
|
Axios(requestConfig)
|
|
46
38
|
.then(response => {
|
|
47
|
-
let smartyResponse =
|
|
39
|
+
let smartyResponse = buildSmartyResponse(response);
|
|
48
40
|
|
|
49
41
|
if (smartyResponse.statusCode >= 400) reject(smartyResponse);
|
|
50
42
|
|
|
51
43
|
resolve(smartyResponse);
|
|
52
44
|
})
|
|
53
|
-
.catch(error => reject(
|
|
45
|
+
.catch(error => reject(buildSmartyResponse(undefined, error)));
|
|
54
46
|
});
|
|
55
47
|
}
|
|
56
48
|
|
package/src/Response.js
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class RetrySender {
|
|
2
|
+
constructor(maxRetires = 5, inner, sleeper) {
|
|
3
|
+
this.maxRetries = maxRetires;
|
|
4
|
+
this.statusToRetry = [408, 429, 500, 502, 503, 504];
|
|
5
|
+
this.statusTooManyRequests = 429;
|
|
6
|
+
this.maxBackoffDuration = 10;
|
|
7
|
+
this.inner = inner;
|
|
8
|
+
this.sleeper = sleeper;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async send(request) {
|
|
12
|
+
let response = await this.inner.send(request);
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < this.maxRetries; i++) {
|
|
15
|
+
|
|
16
|
+
if (!this.statusToRetry.includes(parseInt(response.statusCode))) {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (parseInt(response.statusCode) === this.statusTooManyRequests) {
|
|
21
|
+
let secondsToBackoff = 10;
|
|
22
|
+
if (response.headers) {
|
|
23
|
+
const retryAfterHeader = response.headers["Retry-After"];
|
|
24
|
+
if (Number.isInteger(retryAfterHeader)) {
|
|
25
|
+
secondsToBackoff = retryAfterHeader;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
await this.rateLimitBackOff(secondsToBackoff);
|
|
29
|
+
} else {
|
|
30
|
+
await this.backoff(i);
|
|
31
|
+
}
|
|
32
|
+
response = await this.inner.send(request);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return response;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async backoff(attempt) {
|
|
39
|
+
const backoffDuration = Math.min(attempt, this.maxBackoffDuration);
|
|
40
|
+
console.log(`There was an error processing the request. Retrying in ${backoffDuration} seconds...`);
|
|
41
|
+
await this.sleeper.sleep(backoffDuration);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
async rateLimitBackOff(backoffDuration) {
|
|
45
|
+
console.log(`Rate limit reached. Retrying in ${backoffDuration} seconds...`);
|
|
46
|
+
await this.sleeper.sleep(backoffDuration);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = RetrySender;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const Response = require("../Response.js");
|
|
2
|
+
|
|
3
|
+
function buildSmartyResponse(response, error) {
|
|
4
|
+
if (response) return new Response(response.status, response.data, response.error, response.headers);
|
|
5
|
+
return new Response(undefined, undefined, error)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
buildSmartyResponse
|
|
10
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const {buildSmartyResponse} = require("../../src/util/buildSmartyResponse.js");
|
|
1
2
|
const Response = require("../../src/Response");
|
|
2
3
|
|
|
3
4
|
module.exports = {
|
|
@@ -20,4 +21,21 @@ module.exports = {
|
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
23
|
},
|
|
23
|
-
|
|
24
|
+
MockSenderWithStatusCodesAndHeaders: function (statusCodes, headers = undefined, error = undefined) {
|
|
25
|
+
this.statusCodes = statusCodes;
|
|
26
|
+
this.headers = headers;
|
|
27
|
+
this.error = error;
|
|
28
|
+
this.currentStatusCodeIndex = 0;
|
|
29
|
+
|
|
30
|
+
this.send = function (request) {
|
|
31
|
+
let mockResponse = {
|
|
32
|
+
status: this.statusCodes[this.currentStatusCodeIndex],
|
|
33
|
+
headers: this.headers,
|
|
34
|
+
error: this.error,
|
|
35
|
+
};
|
|
36
|
+
const response = buildSmartyResponse(mockResponse);
|
|
37
|
+
this.currentStatusCodeIndex += 1;
|
|
38
|
+
return response;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
package/tests/test_HttpSender.js
CHANGED
|
@@ -2,6 +2,7 @@ const chai = require("chai");
|
|
|
2
2
|
const expect = chai.expect;
|
|
3
3
|
const Request = require("../src/Request");
|
|
4
4
|
const HttpSender = require("../src/HttpSender");
|
|
5
|
+
const {buildSmartyResponse} = require("../src/util/buildSmartyResponse");
|
|
5
6
|
|
|
6
7
|
describe ("An Axios implementation of a HTTP sender", function () {
|
|
7
8
|
it("adds a data payload to the HTTP request config.", function () {
|
|
@@ -71,7 +72,7 @@ describe ("An Axios implementation of a HTTP sender", function () {
|
|
|
71
72
|
let mockResponse = {
|
|
72
73
|
status: 200
|
|
73
74
|
};
|
|
74
|
-
let smartyResponse =
|
|
75
|
+
let smartyResponse = buildSmartyResponse(mockResponse);
|
|
75
76
|
|
|
76
77
|
expect(smartyResponse.hasOwnProperty("statusCode")).to.equal(true);
|
|
77
78
|
expect(smartyResponse.statusCode).to.equal(200);
|
|
@@ -84,7 +85,7 @@ describe ("An Axios implementation of a HTTP sender", function () {
|
|
|
84
85
|
status: 200,
|
|
85
86
|
data: mockData
|
|
86
87
|
};
|
|
87
|
-
let smartyResponse =
|
|
88
|
+
let smartyResponse = buildSmartyResponse(mockResponse);
|
|
88
89
|
|
|
89
90
|
expect(smartyResponse.hasOwnProperty("payload")).to.equal(true);
|
|
90
91
|
expect(smartyResponse.payload).to.equal(mockData);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const chai = require("chai");
|
|
2
|
+
const expect = chai.expect;
|
|
3
|
+
const RetrySender = require("../src/RetrySender");
|
|
4
|
+
const {MockSenderWithStatusCodesAndHeaders} = require("./fixtures/mock_senders");
|
|
5
|
+
const Request = require("../src/Request.js");
|
|
6
|
+
const MockSleeper = require("./fixtures/MockSleeper.js");
|
|
7
|
+
|
|
8
|
+
async function sendWithRetry(retries, inner, sleeper) {
|
|
9
|
+
const request = new Request();
|
|
10
|
+
const sender = new RetrySender(retries, inner, sleeper);
|
|
11
|
+
return await sender.send(request);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe ("Retry Sender tests", function () {
|
|
15
|
+
it("test success does not retry", async function () {
|
|
16
|
+
let inner = new MockSenderWithStatusCodesAndHeaders(["200"]);
|
|
17
|
+
await sendWithRetry(5, inner, new MockSleeper());
|
|
18
|
+
|
|
19
|
+
expect(inner.currentStatusCodeIndex).to.equal(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("test client error does not retry", async function () {
|
|
23
|
+
let inner = new MockSenderWithStatusCodesAndHeaders(["422"]);
|
|
24
|
+
await sendWithRetry(5, inner, new MockSleeper());
|
|
25
|
+
|
|
26
|
+
expect(inner.currentStatusCodeIndex).to.equal(1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("test will retry until success", async function () {
|
|
30
|
+
let inner = new MockSenderWithStatusCodesAndHeaders(["500", "500", "500", "200", "500"]);
|
|
31
|
+
await sendWithRetry(10, inner, new MockSleeper());
|
|
32
|
+
|
|
33
|
+
expect(inner.currentStatusCodeIndex).to.equal(4);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("test return response if retry limit exceeded", async function () {
|
|
37
|
+
let inner = new MockSenderWithStatusCodesAndHeaders(["500", "500", "500", "500", "500"]);
|
|
38
|
+
const sleeper = new MockSleeper();
|
|
39
|
+
const response = await sendWithRetry(4, inner, sleeper);
|
|
40
|
+
|
|
41
|
+
expect(response);
|
|
42
|
+
expect(inner.currentStatusCodeIndex).to.equal(5);
|
|
43
|
+
expect(response.statusCode).to.equal("500");
|
|
44
|
+
expect(sleeper.sleepDurations).to.deep.equal([0, 1, 2, 3]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("test backoff does not exceed max", async function () {
|
|
48
|
+
let inner = new MockSenderWithStatusCodesAndHeaders(["500", "500", "500", "500", "500", "500", "500", "500", "500", "500", "500", "500", "500", "200"]);
|
|
49
|
+
const sleeper = new MockSleeper();
|
|
50
|
+
|
|
51
|
+
await sendWithRetry(20, inner, sleeper);
|
|
52
|
+
|
|
53
|
+
expect(sleeper.sleepDurations).to.deep.equal([0,1,2,3,4,5,6,7,8,9,10,10,10]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("test empty status does not retry", async function () {
|
|
57
|
+
let inner = new MockSenderWithStatusCodesAndHeaders([]);
|
|
58
|
+
await sendWithRetry(5, inner, new MockSleeper());
|
|
59
|
+
|
|
60
|
+
expect(inner.currentStatusCodeIndex).to.equal(1);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("test sleep on rate limit", async function () {
|
|
64
|
+
let inner = new MockSenderWithStatusCodesAndHeaders(["429", "200"]);
|
|
65
|
+
const sleeper = new MockSleeper();
|
|
66
|
+
|
|
67
|
+
await sendWithRetry(5, inner, sleeper);
|
|
68
|
+
|
|
69
|
+
expect(sleeper.sleepDurations).to.deep.equal([10]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("test rate limit error return", async function () {
|
|
73
|
+
let inner = new MockSenderWithStatusCodesAndHeaders(["429"], {"Retry-After": 7});
|
|
74
|
+
const sleeper = new MockSleeper();
|
|
75
|
+
|
|
76
|
+
await sendWithRetry(10, inner, sleeper);
|
|
77
|
+
|
|
78
|
+
expect(sleeper.sleepDurations).to.deep.equal([7]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("test retry after invalid value", async function () {
|
|
82
|
+
let inner = new MockSenderWithStatusCodesAndHeaders(["429"], {"Retry-After": "a"});
|
|
83
|
+
const sleeper = new MockSleeper();
|
|
84
|
+
|
|
85
|
+
await sendWithRetry(10, inner, sleeper);
|
|
86
|
+
|
|
87
|
+
expect(sleeper.sleepDurations).to.deep.equal([10]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("test retry error", async function () {
|
|
91
|
+
let inner = new MockSenderWithStatusCodesAndHeaders(["429"], undefined, "Big Bad");
|
|
92
|
+
const sleeper = new MockSleeper();
|
|
93
|
+
|
|
94
|
+
const response = await sendWithRetry(10, inner, sleeper);
|
|
95
|
+
|
|
96
|
+
expect(response.error).to.equal("Big Bad");
|
|
97
|
+
});
|
|
98
|
+
});
|