UNPKG

@mparticle/web-sdk

Version:
352 lines (299 loc) 13 kB
import Constants, { HTTP_ACCEPTED, HTTP_BAD_REQUEST, HTTP_OK } from './constants'; import { AsyncUploader, FetchUploader, XHRUploader, IFetchPayload, } from './uploaders'; import { CACHE_HEADER } from './identity-utils'; import { parseNumber, valueof } from './utils'; import { IAliasCallback, IAliasRequest, IdentityAPIMethod, IIdentity, IIdentityAPIRequestData, } from './identity.interfaces'; import { IdentityApiData, MPID, UserIdentities, } from '@mparticle/web-sdk'; import { IdentityCallback, IdentityResultBody, IIdentityResponse, } from './identity-user-interfaces'; import { IMParticleWebSDKInstance } from './mp-instance'; const { HTTPCodes, Messages, IdentityMethods } = Constants; const { Modify } = IdentityMethods; export interface IIdentityApiClient { sendAliasRequest: ( aliasRequest: IAliasRequest, aliasCallback: IAliasCallback ) => Promise<void>; sendIdentityRequest: ( identityApiRequest: IIdentityAPIRequestData, method: IdentityAPIMethod, callback: IdentityCallback, originalIdentityApiData: IdentityApiData, parseIdentityResponse: IIdentity['parseIdentityResponse'], mpid: MPID, knownIdentities: UserIdentities ) => Promise<void>; getUploadUrl: (method: IdentityAPIMethod, mpid: MPID) => string; getIdentityResponseFromFetch: ( response: Response, responseBody: IdentityResultBody ) => IIdentityResponse; getIdentityResponseFromXHR: (response: XMLHttpRequest) => IIdentityResponse; } // A successfull Alias request will return a 202 with no body export interface IAliasResponseBody {} interface IdentityApiRequestPayload extends IFetchPayload { headers: { Accept: string; 'Content-Type': string; 'x-mp-key': string; }; } type HTTP_STATUS_CODES = typeof HTTP_OK | typeof HTTP_ACCEPTED; interface IdentityApiError { code: string; message: string; } interface IdentityApiErrorResponse { Errors: IdentityApiError[], ErrorCode: string, StatusCode: valueof<HTTP_STATUS_CODES>; RequestId: string; } // All Identity Api Responses have the same structure, except for Alias interface IAliasErrorResponse extends IdentityApiError {} export default function IdentityAPIClient( this: IIdentityApiClient, mpInstance: IMParticleWebSDKInstance ) { this.sendAliasRequest = async function( aliasRequest: IAliasRequest, aliasCallback: IAliasCallback ) { const { verbose, error } = mpInstance.Logger; const { invokeAliasCallback } = mpInstance._Helpers; const { aliasUrl } = mpInstance._Store.SDKConfig; const { devToken: apiKey } = mpInstance._Store; verbose(Messages.InformationMessages.SendAliasHttp); // https://go.mparticle.com/work/SQDSDKS-6750 const uploadUrl = `https://${aliasUrl}${apiKey}/Alias`; const uploader: AsyncUploader = window.fetch ? new FetchUploader(uploadUrl) : new XHRUploader(uploadUrl); // https://go.mparticle.com/work/SQDSDKS-6568 const uploadPayload: IFetchPayload = { method: 'post', headers: { Accept: 'text/plain;charset=UTF-8', 'Content-Type': 'application/json', }, body: JSON.stringify(aliasRequest), }; try { const response: Response = await uploader.upload(uploadPayload); let aliasResponseBody: IAliasResponseBody; let message: string; let errorMessage: string; switch (response.status) { // A successfull Alias request will return without a body case HTTP_ACCEPTED: case HTTP_OK: // https://go.mparticle.com/work/SQDSDKS-6670 message = 'Received Alias Response from server: ' + JSON.stringify(response.status); break; // Our Alias Request API will 400 if there is an issue with the request body (ie timestamps are too far // in the past or MPIDs don't exist). // A 400 will return an error in the response body and will go through the happy path to report the error case HTTP_BAD_REQUEST: // 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. if (response.json) { try { aliasResponseBody = await response.json(); } catch (e) { verbose('The request has no response body'); } } else { // https://go.mparticle.com/work/SQDSDKS-6568 // XHRUploader returns the response as a string that we need to parse const xhrResponse = (response as unknown) as XMLHttpRequest; aliasResponseBody = xhrResponse.responseText ? JSON.parse(xhrResponse.responseText) : ''; } const errorResponse: IAliasErrorResponse = aliasResponseBody as unknown as IAliasErrorResponse; if (errorResponse?.message) { errorMessage = errorResponse.message; } message = 'Issue with sending Alias Request to mParticle Servers, received HTTP Code of ' + response.status; if (errorResponse?.code) { message += ' - ' + errorResponse.code; } break; // Any unhandled errors, such as 500 or 429, will be caught here as well default: { throw new Error('Received HTTP Code of ' + response.status); } } verbose(message); invokeAliasCallback(aliasCallback, response.status, errorMessage); } catch (e) { const errorMessage = (e as Error).message || e.toString(); error('Error sending alias request to mParticle servers. ' + errorMessage); invokeAliasCallback( aliasCallback, HTTPCodes.noHttpCoverage, errorMessage, ); } }; this.sendIdentityRequest = async function( identityApiRequest: IIdentityAPIRequestData, method: IdentityAPIMethod, callback: IdentityCallback, originalIdentityApiData: IdentityApiData, parseIdentityResponse: IIdentity['parseIdentityResponse'], mpid: MPID, knownIdentities: UserIdentities ) { const { verbose, error } = mpInstance.Logger; const { invokeCallback } = mpInstance._Helpers; verbose(Messages.InformationMessages.SendIdentityBegin); if (!identityApiRequest) { error(Messages.ErrorMessages.APIRequestEmpty); return; } verbose(Messages.InformationMessages.SendIdentityHttp); if (mpInstance._Store.identityCallInFlight) { invokeCallback( callback, HTTPCodes.activeIdentityRequest, 'There is currently an Identity request processing. Please wait for this to return before requesting again' ); return; } const previousMPID = mpid || null; const uploadUrl = this.getUploadUrl(method, mpid); const uploader: AsyncUploader = window.fetch ? new FetchUploader(uploadUrl) : new XHRUploader(uploadUrl); // https://go.mparticle.com/work/SQDSDKS-6568 const fetchPayload: IdentityApiRequestPayload = { method: 'post', headers: { Accept: 'text/plain;charset=UTF-8', 'Content-Type': 'application/json', 'x-mp-key': mpInstance._Store.devToken, }, body: JSON.stringify(identityApiRequest), }; mpInstance._Store.identityCallInFlight = true; try { const response: Response = await uploader.upload(fetchPayload); let identityResponse: IIdentityResponse; let message: string; switch (response.status) { case HTTP_ACCEPTED: case HTTP_OK: // Our Identity API will return a 400 error if there is an issue with the requeest body // such as if the body is empty or one of the attributes is missing or malformed // A 400 will return an error in the response body and will go through the happy path to report the error case HTTP_BAD_REQUEST: // FetchUploader returns the response as a JSON object that we have to await if (response.json) { // https://go.mparticle.com/work/SQDSDKS-6568 // FetchUploader returns the response as a JSON object that we have to await const responseBody: IdentityResultBody = await response.json(); identityResponse = this.getIdentityResponseFromFetch( response, responseBody ); } else { identityResponse = this.getIdentityResponseFromXHR( (response as unknown) as XMLHttpRequest ); } if (identityResponse.status === HTTP_BAD_REQUEST) { const errorResponse: IdentityApiErrorResponse = identityResponse.responseText as unknown as IdentityApiErrorResponse; message = 'Issue with sending Identity Request to mParticle Servers, received HTTP Code of ' + identityResponse.status; if (errorResponse?.Errors) { const errorMessage = errorResponse.Errors.map((error) => error.message).join(', '); message += ' - ' + errorMessage; } } else { message = 'Received Identity Response from server: '; message += JSON.stringify(identityResponse.responseText); } break; // Our Identity API will return: // - 401 if the `x-mp-key` is incorrect or missing // - 403 if the there is a permission or account issue related to the `x-mp-key` // 401 and 403 have no response bodies and should be rejected outright default: { throw new Error('Received HTTP Code of ' + response.status); } } mpInstance._Store.identityCallInFlight = false; verbose(message); parseIdentityResponse( identityResponse, previousMPID, callback, originalIdentityApiData, method, knownIdentities, false ); } catch (err) { mpInstance._Store.identityCallInFlight = false; const errorMessage = (err as Error).message || err.toString(); error('Error sending identity request to servers' + ' - ' + errorMessage); invokeCallback( callback, HTTPCodes.noHttpCoverage, errorMessage, ); } }; this.getUploadUrl = (method: IdentityAPIMethod, mpid: MPID) => { const uploadServiceUrl: string = mpInstance._Helpers.createServiceUrl( mpInstance._Store.SDKConfig.identityUrl ); const uploadUrl: string = method === Modify ? uploadServiceUrl + mpid + '/' + method : uploadServiceUrl + method; return uploadUrl; }; this.getIdentityResponseFromFetch = ( response: Response, responseBody: IdentityResultBody ): IIdentityResponse => ({ status: response.status, responseText: responseBody, cacheMaxAge: parseInt(response.headers.get(CACHE_HEADER)) || 0, expireTimestamp: 0, }); this.getIdentityResponseFromXHR = ( response: XMLHttpRequest ): IIdentityResponse => ({ status: response.status, responseText: response.responseText ? JSON.parse(response.responseText) : {}, cacheMaxAge: parseNumber( response.getResponseHeader(CACHE_HEADER) || '' ), expireTimestamp: 0, }); }