Skip to content

Commit 096ea0b

Browse files
refactor: Update Error handling for Identity API Client
1 parent 802b639 commit 096ea0b

File tree

3 files changed

+80
-43
lines changed

3 files changed

+80
-43
lines changed

src/identityApiClient.ts

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
IFetchPayload,
77
} from './uploaders';
88
import { CACHE_HEADER } from './identity-utils';
9-
import { parseNumber } from './utils';
9+
import { parseNumber, valueof } from './utils';
1010
import {
1111
IAliasCallback,
1212
IAliasRequest,
@@ -15,7 +15,6 @@ import {
1515
IIdentityAPIRequestData,
1616
} from './identity.interfaces';
1717
import {
18-
Callback,
1918
IdentityApiData,
2019
MPID,
2120
UserIdentities,
@@ -53,9 +52,8 @@ export interface IIdentityApiClient {
5352
getIdentityResponseFromXHR: (response: XMLHttpRequest) => IIdentityResponse;
5453
}
5554

56-
export interface IAliasResponseBody {
57-
message?: string;
58-
}
55+
// A successfull Alias request will return a 202 with no body
56+
export interface IAliasResponseBody {}
5957

6058
interface IdentityApiRequestPayload extends IFetchPayload {
6159
headers: {
@@ -65,6 +63,23 @@ interface IdentityApiRequestPayload extends IFetchPayload {
6563
};
6664
}
6765

66+
type HTTP_STATUS_CODES = typeof HTTP_OK | typeof HTTP_ACCEPTED;
67+
68+
interface IdentityApiError {
69+
code: string;
70+
message: string;
71+
}
72+
73+
interface IdentityApiErrorResponse {
74+
Errors: IdentityApiError[],
75+
ErrorCode: string,
76+
StatusCode: valueof<HTTP_STATUS_CODES>;
77+
RequestId: string;
78+
}
79+
80+
// All Identity Api Responses have the same structure, except for Alias
81+
interface IAliasErrorResponse extends IdentityApiError {}
82+
6883
export default function IdentityAPIClient(
6984
this: IIdentityApiClient,
7085
mpInstance: MParticleWebSDK
@@ -104,7 +119,10 @@ export default function IdentityAPIClient(
104119

105120
// FetchUploader returns the response as a JSON object that we have to await
106121
if (response.json) {
107-
// HTTP responses of 202, 200, and 403 do not have a response. response.json will always exist on a fetch, but can only be await-ed when the response is not empty, otherwise it will throw an error.
122+
// HTTP responses of 202, 200, and 403 do not have a response.
123+
// response.json will always exist on a fetch, but can only be
124+
// await-ed when the response is not empty, otherwise it will
125+
// throw an error.
108126
try {
109127
aliasResponseBody = await response.json();
110128
} catch (e) {
@@ -123,6 +141,7 @@ export default function IdentityAPIClient(
123141
let errorMessage: string;
124142

125143
switch (response.status) {
144+
// Alias response is a 202 with no body
126145
case HTTP_OK:
127146
case HTTP_ACCEPTED:
128147
// https://go.mparticle.com/work/SQDSDKS-6670
@@ -131,23 +150,28 @@ export default function IdentityAPIClient(
131150
break;
132151
default:
133152
// 400 has an error message, but 403 doesn't
134-
if (aliasResponseBody?.message) {
135-
errorMessage = aliasResponseBody.message;
153+
const errorResponse: IAliasErrorResponse = aliasResponseBody as unknown as IAliasErrorResponse;
154+
if (errorResponse?.message) {
155+
errorMessage = errorResponse.message;
136156
}
137157
message =
138158
'Issue with sending Alias Request to mParticle Servers, received HTTP Code of ' +
139159
response.status;
160+
161+
if (errorResponse?.code) {
162+
message += ' - ' + errorResponse.code;
163+
}
140164
}
141165

142166
verbose(message);
143167
invokeAliasCallback(aliasCallback, response.status, errorMessage);
144168
} catch (e) {
145-
const err = e as Error;
146-
error('Error sending alias request to mParticle servers. ' + err);
169+
const errorMessage = (e as Error).message || e.toString();
170+
error('Error sending alias request to mParticle servers. ' + errorMessage);
147171
invokeAliasCallback(
148172
aliasCallback,
149173
HTTPCodes.noHttpCoverage,
150-
err.message
174+
errorMessage,
151175
);
152176
}
153177
};
@@ -197,13 +221,15 @@ export default function IdentityAPIClient(
197221
},
198222
body: JSON.stringify(identityApiRequest),
199223
};
224+
mpInstance._Store.identityCallInFlight = true;
200225

201226
try {
202-
mpInstance._Store.identityCallInFlight = true;
203227
const response: Response = await uploader.upload(fetchPayload);
204228

205229
let identityResponse: IIdentityResponse;
230+
let message: string;
206231

232+
// FetchUploader returns the response as a JSON object that we have to await
207233
if (response.json) {
208234
// https://go.mparticle.com/work/SQDSDKS-6568
209235
// FetchUploader returns the response as a JSON object that we have to await
@@ -219,11 +245,26 @@ export default function IdentityAPIClient(
219245
);
220246
}
221247

222-
verbose(
223-
'Received Identity Response from server: ' +
224-
JSON.stringify(identityResponse.responseText)
225-
);
226248

249+
switch (identityResponse.status) {
250+
case HTTP_OK:
251+
case HTTP_ACCEPTED:
252+
message = 'Received Identity Response from server: ';
253+
message += JSON.stringify(identityResponse.responseText);
254+
break;
255+
256+
default:
257+
// 400 has an error message, but 403 doesn't
258+
const errorResponse: IdentityApiErrorResponse = identityResponse.responseText as unknown as IdentityApiErrorResponse;
259+
if (errorResponse?.Errors) {
260+
message = 'Issue with sending Identity Request to mParticle Servers, received HTTP Code of ' + identityResponse.status;
261+
262+
const errorMessage = errorResponse.Errors.map((error) => error.message).join(', ');
263+
message += ' - ' + errorMessage;
264+
}
265+
}
266+
267+
verbose(message);
227268
parseIdentityResponse(
228269
identityResponse,
229270
previousMPID,
@@ -234,15 +275,15 @@ export default function IdentityAPIClient(
234275
false
235276
);
236277
} catch (err) {
237-
const errorMessage = (err as Error).message || err.toString();
238-
239278
mpInstance._Store.identityCallInFlight = false;
279+
280+
const errorMessage = (err as Error).message || err.toString();
281+
error('Error sending identity request to servers' + ' - ' + errorMessage);
240282
invokeCallback(
241283
callback,
242284
HTTPCodes.noHttpCoverage,
243285
errorMessage,
244286
);
245-
error('Error sending identity request to servers' + ' - ' + err);
246287
}
247288
};
248289

test/src/config/utils.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,8 @@ var pluses = /\+/g,
634634
hasIdentifyReturned = () => {
635635
return window.mParticle.Identity.getCurrentUser()?.getMPID() === testMPID;
636636
},
637-
hasIdentityCallInflightReturned = () => !mParticle.getInstance()?._Store?.identityCallInFlight;
637+
hasIdentityCallInflightReturned = () => !mParticle.getInstance()?._Store?.identityCallInFlight,
638+
hasConfigLoaded = () => !!mParticle.getInstance()?._Store?.configurationLoaded
638639

639640
var TestsCore = {
640641
getLocalStorageProducts: getLocalStorageProducts,
@@ -663,6 +664,7 @@ var TestsCore = {
663664
fetchMockSuccess: fetchMockSuccess,
664665
hasIdentifyReturned: hasIdentifyReturned,
665666
hasIdentityCallInflightReturned,
667+
hasConfigLoaded,
666668
};
667669

668670
export default TestsCore;

test/src/tests-core-sdk.js

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ const DefaultConfig = Constants.DefaultConfig,
1212
findEventFromRequest = Utils.findEventFromRequest,
1313
findBatch = Utils.findBatch;
1414

15-
const { waitForCondition, fetchMockSuccess, hasIdentifyReturned, hasIdentityCallInflightReturned } = Utils;
15+
const {
16+
waitForCondition,
17+
fetchMockSuccess,
18+
hasIdentifyReturned,
19+
hasIdentityCallInflightReturned,
20+
hasConfigLoaded,
21+
} = Utils;
1622

1723
describe('core SDK', function() {
1824
beforeEach(function() {
@@ -1126,7 +1132,7 @@ describe('core SDK', function() {
11261132
})
11271133
});
11281134

1129-
it('should initialize and log events even with a failed /config fetch and empty config', function async(done) {
1135+
it('should initialize and log events even with a failed /config fetch and empty config', async () => {
11301136
// this instance occurs when self hosting and the user only passes an object into init
11311137
mParticle._resetForTests(MPConfig);
11321138

@@ -1152,12 +1158,7 @@ describe('core SDK', function() {
11521158

11531159
mParticle.init(apiKey, window.mParticle.config);
11541160

1155-
waitForCondition(() => {
1156-
return (
1157-
mParticle.getInstance()._Store.configurationLoaded === true
1158-
);
1159-
})
1160-
.then(() => {
1161+
await waitForCondition(hasConfigLoaded);
11611162
// fetching the config is async and we need to wait for it to finish
11621163
mParticle.getInstance()._Store.isInitialized.should.equal(true);
11631164

@@ -1170,23 +1171,16 @@ describe('core SDK', function() {
11701171
mParticle.Identity.identify({
11711172
userIdentities: { customerid: 'test' },
11721173
});
1173-
waitForCondition(() => {
1174-
return (
1175-
mParticle.Identity.getCurrentUser()?.getMPID() === 'MPID1'
1176-
);
1177-
})
1178-
.then(() => {
1179-
mParticle.logEvent('Test Event');
1180-
const testEvent = findEventFromRequest(
1181-
fetchMock.calls(),
1182-
'Test Event'
1183-
);
11841174

1185-
testEvent.should.be.ok();
1175+
await waitForCondition(() => mParticle.Identity.getCurrentUser()?.getMPID() === 'MPID1');
11861176

1187-
done();
1188-
});
1189-
});
1177+
mParticle.logEvent('Test Event');
1178+
const testEvent = findEventFromRequest(
1179+
fetchMock.calls(),
1180+
'Test Event'
1181+
);
1182+
1183+
testEvent.should.be.ok();
11901184
});
11911185

11921186
it('should initialize without a config object passed to init', async function() {

0 commit comments

Comments
 (0)