smartystreets-javascript-sdk 1.13.7 → 2.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/make -f
2
2
 
3
- VERSION := $(shell tagit -p --dry-run)
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 -p
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": "1.13.7",
3
+ "version": "2.1.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.2.0",
36
- "mocha": "^9.2.1"
35
+ "chai": "^4.3.6",
36
+ "mocha": "^9.2.2"
37
37
  },
38
38
  "dependencies": {
39
39
  "axios": "^0.26.1",
@@ -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.maxRetries, this.proxy, this.debug);
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
- const agentSender = new AgentSender(signingSender);
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 axiosRetry = require("axios-retry");
3
+ const {buildSmartyResponse} = require("../src/util/buildSmartyResponse");
4
4
 
5
5
  class HttpSender {
6
- constructor(timeout = 10000, retries = 5, proxyConfig, debug = false) {
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 = this.buildSmartyResponse(response);
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(this.buildSmartyResponse(undefined, error)));
45
+ .catch(error => reject(buildSmartyResponse(undefined, error)));
54
46
  });
55
47
  }
56
48
 
package/src/Response.js CHANGED
@@ -1,8 +1,9 @@
1
1
  class Response {
2
- constructor (statusCode, payload, error = undefined) {
2
+ constructor (statusCode, payload, error, headers) {
3
3
  this.statusCode = statusCode;
4
4
  this.payload = payload;
5
5
  this.error = error;
6
+ this.headers = headers;
6
7
  }
7
8
  }
8
9
 
@@ -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;
@@ -25,6 +25,8 @@ class Candidate {
25
25
  this.components.countryIso3 = responseData.components.country_iso_3;
26
26
  this.components.superAdministrativeArea = responseData.components.super_administrative_area;
27
27
  this.components.administrativeArea = responseData.components.administrative_area;
28
+ this.components.administrativeAreaShort = responseData.components.administrative_area_short;
29
+ this.components.administrativeAreaLong = responseData.components.administrative_area_long;
28
30
  this.components.subAdministrativeArea = responseData.components.sub_administrative_area;
29
31
  this.components.dependentLocality = responseData.components.dependent_locality;
30
32
  this.components.dependentLocalityName = responseData.components.dependent_locality_name;
@@ -58,6 +60,8 @@ class Candidate {
58
60
  this.components.subBuildingNumber = responseData.components.sub_building_number;
59
61
  this.components.subBuildingName = responseData.components.sub_building_name;
60
62
  this.components.subBuilding = responseData.components.sub_building;
63
+ this.components.levelType = responseData.components.level_type;
64
+ this.components.levelNumber = responseData.components.level_number;
61
65
  this.components.postBox = responseData.components.post_box;
62
66
  this.components.postBoxType = responseData.components.post_box_type;
63
67
  this.components.postBoxNumber = responseData.components.post_box_number;
@@ -90,6 +94,8 @@ class Candidate {
90
94
  this.analysis.changes.components.countryIso3 = responseData.analysis.changes.components.country_iso_3;
91
95
  this.analysis.changes.components.superAdministrativeArea = responseData.analysis.changes.components.super_administrative_area;
92
96
  this.analysis.changes.components.administrativeArea = responseData.analysis.changes.components.administrative_area;
97
+ this.analysis.changes.components.administrativeAreaShort = responseData.analysis.changes.components.administrative_area_short;
98
+ this.analysis.changes.components.administrativeAreaLong = responseData.analysis.changes.components.administrative_area_long;
93
99
  this.analysis.changes.components.subAdministrativeArea = responseData.analysis.changes.components.sub_administrative_area;
94
100
  this.analysis.changes.components.dependentLocality = responseData.analysis.changes.components.dependent_locality;
95
101
  this.analysis.changes.components.dependentLocalityName = responseData.analysis.changes.components.dependent_locality_name;
@@ -123,6 +129,8 @@ class Candidate {
123
129
  this.analysis.changes.components.subBuildingNumber = responseData.analysis.changes.components.sub_building_number;
124
130
  this.analysis.changes.components.subBuildingName = responseData.analysis.changes.components.sub_building_name;
125
131
  this.analysis.changes.components.subBuilding = responseData.analysis.changes.components.sub_building;
132
+ this.analysis.changes.components.levelType = responseData.analysis.changes.components.level_type;
133
+ this.analysis.changes.components.levelNumber = responseData.analysis.changes.components.level_number;
126
134
  this.analysis.changes.components.postBox = responseData.analysis.changes.components.post_box;
127
135
  this.analysis.changes.components.postBoxType = responseData.analysis.changes.components.post_box_type;
128
136
  this.analysis.changes.components.postBoxNumber = responseData.analysis.changes.components.post_box_number;
@@ -0,0 +1,8 @@
1
+ class Sleeper {
2
+ constructor () {}
3
+ sleep(seconds) {
4
+ return new Promise(resolve => setTimeout(resolve, seconds*1000));
5
+ }
6
+ }
7
+
8
+ module.exports = Sleeper;
@@ -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
+ };
@@ -0,0 +1,10 @@
1
+ class MockSleeper {
2
+ constructor() {
3
+ this.sleepDurations = [];
4
+ }
5
+ sleep(ms) {
6
+ this.sleepDurations.push(ms);
7
+ }
8
+ }
9
+
10
+ module.exports = MockSleeper;
@@ -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
+ };
@@ -22,6 +22,8 @@ describe("An International match candidate", function () {
22
22
  country_iso_3: "14",
23
23
  super_administrative_area: "15",
24
24
  administrative_area: "16",
25
+ administrative_area_short: "16.1",
26
+ administrative_area_long: "16.2",
25
27
  sub_administrative_area: "17",
26
28
  dependent_locality: "18",
27
29
  dependent_locality_name: "19",
@@ -55,6 +57,8 @@ describe("An International match candidate", function () {
55
57
  sub_building_number: "46",
56
58
  sub_building_name: "47",
57
59
  sub_building: "48",
60
+ level_type: "48.1",
61
+ level_number: "48.2",
58
62
  post_box: "49",
59
63
  post_box_type: "50",
60
64
  post_box_number: "51",
@@ -88,6 +92,8 @@ describe("An International match candidate", function () {
88
92
  country_iso_3: "73",
89
93
  super_administrative_area: "74",
90
94
  administrative_area: "75",
95
+ administrative_area_short: "75.1",
96
+ administrative_area_long: "75.2",
91
97
  sub_administrative_area: "76",
92
98
  dependent_locality: "77",
93
99
  dependent_locality_name: "78",
@@ -121,6 +127,8 @@ describe("An International match candidate", function () {
121
127
  sub_building_number: "106",
122
128
  sub_building_name: "107",
123
129
  sub_building: "108",
130
+ level_type: "108.1",
131
+ level_number: "108.2",
124
132
  post_box: "109",
125
133
  post_box_type: "110",
126
134
  post_box_number: "111",
@@ -148,6 +156,8 @@ describe("An International match candidate", function () {
148
156
  expect(components.countryIso3).to.equal("14");
149
157
  expect(components.superAdministrativeArea).to.equal("15");
150
158
  expect(components.administrativeArea).to.equal("16");
159
+ expect(components.administrativeAreaShort).to.equal("16.1");
160
+ expect(components.administrativeAreaLong).to.equal("16.2");
151
161
  expect(components.subAdministrativeArea).to.equal("17");
152
162
  expect(components.dependentLocality).to.equal("18");
153
163
  expect(components.dependentLocalityName).to.equal("19");
@@ -181,6 +191,8 @@ describe("An International match candidate", function () {
181
191
  expect(components.subBuildingNumber).to.equal("46");
182
192
  expect(components.subBuildingName).to.equal("47");
183
193
  expect(components.subBuilding).to.equal("48");
194
+ expect(components.levelType).to.equal("48.1");
195
+ expect(components.levelNumber).to.equal("48.2");
184
196
  expect(components.postBox).to.equal("49");
185
197
  expect(components.postBoxType).to.equal("50");
186
198
  expect(components.postBoxNumber).to.equal("51");
@@ -212,6 +224,8 @@ describe("An International match candidate", function () {
212
224
  expect(ccomponents.countryIso3).to.equal("73");
213
225
  expect(ccomponents.superAdministrativeArea).to.equal("74");
214
226
  expect(ccomponents.administrativeArea).to.equal("75");
227
+ expect(ccomponents.administrativeAreaShort).to.equal("75.1");
228
+ expect(ccomponents.administrativeAreaLong).to.equal("75.2");
215
229
  expect(ccomponents.subAdministrativeArea).to.equal("76");
216
230
  expect(ccomponents.dependentLocality).to.equal("77");
217
231
  expect(ccomponents.dependentLocalityName).to.equal("78");
@@ -245,6 +259,8 @@ describe("An International match candidate", function () {
245
259
  expect(ccomponents.subBuildingNumber).to.equal("106");
246
260
  expect(ccomponents.subBuildingName).to.equal("107");
247
261
  expect(ccomponents.subBuilding).to.equal("108");
262
+ expect(ccomponents.levelType).to.equal("108.1");
263
+ expect(ccomponents.levelNumber).to.equal("108.2");
248
264
  expect(ccomponents.postBox).to.equal("109");
249
265
  expect(ccomponents.postBoxType).to.equal("110");
250
266
  expect(ccomponents.postBoxNumber).to.equal("111");
@@ -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 = sender.buildSmartyResponse(mockResponse);
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 = sender.buildSmartyResponse(mockResponse);
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
+ });