diff --git a/src/authorization/authorization.test.ts b/src/authorization/authorization.test.ts index 88311c45..2e51bef4 100644 --- a/src/authorization/authorization.test.ts +++ b/src/authorization/authorization.test.ts @@ -6,7 +6,7 @@ import { getInAppMessages } from '../inapp'; import { track, trackInAppClose } from '../events'; import { updateSubscriptions, updateUser, updateUserEmail } from '../users'; import { trackPurchase, updateCart } from '../commerce'; -import { GETMESSAGES_PATH } from '../constants'; +import { GETMESSAGES_PATH, INITIALIZE_ERROR } from '../constants'; const localStorageMock = { getItem: jest.fn(), @@ -380,9 +380,7 @@ describe('User Identification', () => { packageName: 'my-lil-website' }); } catch (e) { - expect(e).toStrictEqual( - new Error('Cannot make API request until a user is signed in') - ); + expect(e).toStrictEqual(INITIALIZE_ERROR); } }); @@ -398,9 +396,7 @@ describe('User Identification', () => { packageName: 'my-lil-website' }); } catch (e) { - expect(e).toStrictEqual( - new Error('Cannot make API request until a user is signed in') - ); + expect(e).toStrictEqual(INITIALIZE_ERROR); } }); }); @@ -714,9 +710,7 @@ describe('User Identification', () => { packageName: 'my-lil-website' }); } catch (e) { - expect(e).toStrictEqual( - new Error('Cannot make API request until a user is signed in') - ); + expect(e).toStrictEqual(INITIALIZE_ERROR); } }); @@ -733,9 +727,7 @@ describe('User Identification', () => { packageName: 'my-lil-website' }); } catch (e) { - expect(e).toStrictEqual( - new Error('Cannot make API request until a user is signed in') - ); + expect(e).toStrictEqual(INITIALIZE_ERROR); } }); }); @@ -1117,8 +1109,7 @@ describe('User Identification', () => { .fn() .mockReturnValue(Promise.resolve(MOCK_JWT_KEY)); const { refreshJwtToken } = initialize('123', mockGenerateJWT); - const res = await refreshJwtToken('hello@gmail.com'); - console.log({ res }); + await refreshJwtToken('hello@gmail.com'); expect(mockGenerateJWT).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(60000 * 4.1); expect(mockGenerateJWT).toHaveBeenCalledTimes(2); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index d2de01dc..f2d31000 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -27,24 +27,9 @@ import { registerAnonUserIdSetter } from 'src/anonymousUserTracking/anonymousUserEventManager'; import { IdentityResolution, Options, config } from 'src/utils/config'; +import { getTypeOfAuth, setTypeOfAuth, TypeOfAuth } from 'src/utils/typeOfAuth'; const MAX_TIMEOUT = ONE_DAY; -/* - AKA did the user auth with their email (setEmail) or user ID (setUserID) - - we're going to use this variable for one circumstance - when calling _updateUserEmail_. - Essentially, when we call the Iterable API to update a user's email address and we get a - successful 200 request, we're going to request a new JWT token, since it might need to - be re-signed with the new email address; however, if the customer code never authorized the - user with an email and instead a user ID, we'll just continue to sign the JWT with the user ID. - - This is mainly just a quality-of-life feature, so that the customer's JWT generation code - doesn't _need_ to support email-signed JWTs if they don't want and purely want to issue the - tokens by user ID. - */ -export type TypeOfAuth = null | 'email' | 'userID' -export let typeOfAuth: TypeOfAuth = null; -/* this will be the literal user ID or email they choose to auth with */ let authIdentifier: null | string = null; let userInterceptor: number | null = null; let apiKey: null | string = null; @@ -124,7 +109,7 @@ const initializeUserId = (userId: string) => { } const addUserIdToRequest = (userId: string) => { - typeOfAuth = 'userID'; + setTypeOfAuth('userID'); authIdentifier = userId; if (typeof userInterceptor === 'number') { @@ -230,7 +215,7 @@ const syncEvents = () => { }; const addEmailToRequest = (email: string) => { - typeOfAuth = 'email'; + setTypeOfAuth('email'); authIdentifier = email; if (typeof userInterceptor === 'number') { @@ -426,6 +411,7 @@ export function initialize( isEmail: boolean, merge?: boolean ): Promise => { + const typeOfAuth = getTypeOfAuth(); const enableAnonTracking = config.getConfig('enableAnonTracking'); const sourceUserIdOrEmail = authIdentifier === null ? getAnonUserId() : authIdentifier; @@ -519,7 +505,7 @@ export function initialize( }, logout: () => { anonUserManager.removeAnonSessionCriteriaData(); - typeOfAuth = null; + setTypeOfAuth(null); authIdentifier = null; /* clear fetched in-app messages */ clearMessages(); @@ -552,7 +538,7 @@ export function initialize( localStorage.removeItem(SHARED_PREF_ANON_USER_ID); localStorage.removeItem(SHARED_PREF_ANON_USAGE_TRACKED); - typeOfAuth = null; + setTypeOfAuth(null); authIdentifier = null; /* clear fetched in-app messages */ clearMessages(); @@ -633,7 +619,7 @@ export function initialize( const newEmail = JSON.parse(config.config.data)?.newEmail; const payloadToPass = - typeOfAuth === 'email' + getTypeOfAuth() === 'email' ? { email: newEmail } : { userID: authIdentifier! }; @@ -894,7 +880,7 @@ export function initialize( }, logout: () => { anonUserManager.removeAnonSessionCriteriaData(); - typeOfAuth = null; + setTypeOfAuth(null); authIdentifier = null; /* clear fetched in-app messages */ clearMessages(); @@ -941,7 +927,7 @@ export function initialize( localStorage.removeItem(SHARED_PREF_ANON_USER_ID); localStorage.removeItem(SHARED_PREF_ANON_USAGE_TRACKED); - typeOfAuth = null; + setTypeOfAuth(null); authIdentifier = null; /* clear fetched in-app messages */ clearMessages(); @@ -984,5 +970,9 @@ export function initializeWithConfig(initializeParams: InitializeParams) { } export function setTypeOfAuthForTestingOnly(authType: TypeOfAuth) { - typeOfAuth = authType + if (!authType) { + setTypeOfAuth(null); + } else { + setTypeOfAuth(authType); + } } diff --git a/src/commerce/commerce.ts b/src/commerce/commerce.ts index 10f26d70..523568d8 100644 --- a/src/commerce/commerce.ts +++ b/src/commerce/commerce.ts @@ -6,7 +6,6 @@ import { IterableResponse } from '../types'; import { updateCartSchema, trackPurchaseSchema } from './commerce.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from '../utils/commonFunctions'; -import { typeOfAuth } from '../authorization'; export const updateCart = (payload: UpdateCartRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -20,10 +19,6 @@ export const updateCart = (payload: UpdateCartRequestParams) => { return Promise.reject(INITIALIZE_ERROR); } - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.commerce_update_cart.route, @@ -52,10 +47,6 @@ export const trackPurchase = (payload: TrackPurchaseRequestParams) => { return Promise.reject(INITIALIZE_ERROR); } - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.commerce_track_purchase.route, diff --git a/src/embedded/embeddedManager.ts b/src/embedded/embeddedManager.ts index e97faa25..e5078480 100644 --- a/src/embedded/embeddedManager.ts +++ b/src/embedded/embeddedManager.ts @@ -7,15 +7,9 @@ import { import { IterableResponse } from '../types'; import { EmbeddedMessagingProcessor } from './embeddedMessageProcessor'; import { ErrorMessage } from './consts'; -import { - SDK_VERSION, - WEB_PLATFORM, - ENDPOINTS, - INITIALIZE_ERROR -} from '../constants'; +import { SDK_VERSION, WEB_PLATFORM, ENDPOINTS } from '../constants'; import { trackEmbeddedReceived } from '../events/embedded/events'; import { handleEmbeddedClick } from './utils'; -import { typeOfAuth } from '../authorization'; export class IterableEmbeddedManager { public appPackageName: string; @@ -33,12 +27,8 @@ export class IterableEmbeddedManager { callback: () => void, placementIds?: number[] ) { - if (typeOfAuth !== null) { - await this.retrieveEmbeddedMessages(packageName, placementIds || []); - callback(); - } else { - Promise.reject(INITIALIZE_ERROR); - } + await this.retrieveEmbeddedMessages(packageName, placementIds || []); + callback(); } private async retrieveEmbeddedMessages( diff --git a/src/events/embedded/events.ts b/src/events/embedded/events.ts index 77a6d5d9..fafcb542 100644 --- a/src/events/embedded/events.ts +++ b/src/events/embedded/events.ts @@ -1,4 +1,4 @@ -import { WEB_PLATFORM, ENDPOINTS, INITIALIZE_ERROR } from '../../constants'; +import { WEB_PLATFORM, ENDPOINTS } from '../../constants'; import { baseIterableRequest } from '../../request'; import { IterableEmbeddedDismissRequestPayload, @@ -12,17 +12,12 @@ import { embeddedDismissSchema, embeddedSessionSchema } from './events.schema'; -import { typeOfAuth } from '../../authorization'; export const trackEmbeddedReceived = ( messageId: string, appPackageName: string -) => { - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - - return baseIterableRequest({ +) => + baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_received_event_track.route, data: { @@ -37,17 +32,12 @@ export const trackEmbeddedReceived = ( data: trackEmbeddedSchema } }); -}; export const trackEmbeddedClick = ( payload: IterableEmbeddedClickRequestPayload ) => { const { appPackageName, ...rest } = payload; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_click_event_track.route, @@ -71,10 +61,6 @@ export const trackEmbeddedDismiss = ( ) => { const { appPackageName, ...rest } = payload; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_dismiss.route, @@ -98,10 +84,6 @@ export const trackEmbeddedSession = ( ) => { const { appPackageName, ...rest } = payload; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_session_event_track.route, diff --git a/src/events/events.ts b/src/events/events.ts index 155b0ffe..486cb61f 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -6,7 +6,6 @@ import { IterableResponse } from '../types'; import { trackSchema } from './events.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from '../utils/commonFunctions'; -import { typeOfAuth } from '../authorization'; export const track = (payload: InAppTrackRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -17,9 +16,6 @@ export const track = (payload: InAppTrackRequestParams) => { anonymousUserEventManager.trackAnonEvent(payload); return Promise.reject(INITIALIZE_ERROR); } - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } return baseIterableRequest({ method: 'POST', url: ENDPOINTS.event_track.route, diff --git a/src/events/inapp/events.ts b/src/events/inapp/events.ts index cf66a5cb..16c21d34 100644 --- a/src/events/inapp/events.ts +++ b/src/events/inapp/events.ts @@ -2,19 +2,14 @@ import { baseIterableRequest } from '../../request'; import { InAppEventRequestParams } from './types'; import { IterableResponse } from '../../types'; -import { ENDPOINTS, INITIALIZE_ERROR, WEB_PLATFORM } from '../../constants'; +import { ENDPOINTS, WEB_PLATFORM } from '../../constants'; import { eventRequestSchema } from './events.schema'; -import { typeOfAuth } from '../../authorization'; export const trackInAppClose = (payload: InAppEventRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_close.route, @@ -42,10 +37,6 @@ export const trackInAppOpen = ( delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_open.route, @@ -75,10 +66,6 @@ export const trackInAppClick = ( delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_click.route, @@ -107,10 +94,6 @@ export const trackInAppDelivery = ( delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_delivery.route, @@ -142,10 +125,6 @@ export const trackInAppConsume = ( delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_consume.route, diff --git a/src/inapp/request.ts b/src/inapp/request.ts index 4fd8c7f4..bffe2f1a 100644 --- a/src/inapp/request.ts +++ b/src/inapp/request.ts @@ -1,6 +1,5 @@ /* eslint-disable no-unreachable */ import { delMany, entries } from 'idb-keyval'; -import { typeOfAuth } from '../authorization'; import { GETMESSAGES_PATH, SDK_VERSION, WEB_PLATFORM } from '../constants'; import { baseIterableRequest } from '../request'; import { addNewMessagesToCache, getCachedMessagesToDelete } from './cache'; @@ -20,13 +19,8 @@ type RequestInAppMessagesProps = { export const requestInAppMessages = ({ latestCachedMessageId, payload -}: RequestInAppMessagesProps) => { - if (typeOfAuth === null) { - return Promise.reject( - new Error('Cannot make API request until a user is signed in') - ); - } - return baseIterableRequest({ +}: RequestInAppMessagesProps) => + baseIterableRequest({ method: 'GET', /** @note TBD: Parameter will be enabled once new endpoint is ready */ // url: options?.useLocalCache ? CACHE_ENABLED_GETMESSAGES_PATH : GETMESSAGES_PATH, @@ -39,7 +33,6 @@ export const requestInAppMessages = ({ latestCachedMessageId } }); -}; type RequestMessagesProps = { payload: InAppMessagesRequestParams; diff --git a/src/request.ts b/src/request.ts index 8badc433..53ff86ac 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,9 +1,18 @@ import Axios, { AxiosRequestConfig } from 'axios'; import qs from 'qs'; import { AnySchema, ValidationError } from 'yup'; -import { BASE_URL, STATIC_HEADERS, EU_ITERABLE_API } from './constants'; +import { + BASE_URL, + STATIC_HEADERS, + EU_ITERABLE_API, + GET_CRITERIA_PATH, + INITIALIZE_ERROR, + ENDPOINT_MERGE_USER, + ENDPOINT_TRACK_ANON_SESSION +} from './constants'; import { IterablePromise, IterableResponse } from './types'; import { config } from './utils/config'; +import { getTypeOfAuth } from './utils/typeOfAuth'; interface ExtendedRequestConfig extends AxiosRequestConfig { validation?: { @@ -20,6 +29,12 @@ interface ClientError extends IterableResponse { }[]; } +const ENDPOINTS_REQUIRING_SET_USER = [ + GET_CRITERIA_PATH, + ENDPOINT_MERGE_USER, + ENDPOINT_TRACK_ANON_SESSION +]; + export const baseAxiosRequest = Axios.create({ baseURL: BASE_URL }); @@ -28,6 +43,15 @@ export const baseIterableRequest = ( payload: ExtendedRequestConfig ): IterablePromise => { try { + const endpoint = payload?.url ?? ''; + + // for most Iterable API endpoints, we require a user to be initialized in the SDK. + if ( + !ENDPOINTS_REQUIRING_SET_USER.includes(endpoint) && + getTypeOfAuth() === null + ) { + return Promise.reject(INITIALIZE_ERROR); + } if (payload.validation?.data && payload.data) { payload.validation.data.validateSync(payload.data, { abortEarly: false }); } diff --git a/src/users/users.ts b/src/users/users.ts index 42e9c8c0..538637d7 100644 --- a/src/users/users.ts +++ b/src/users/users.ts @@ -7,15 +7,9 @@ import { updateSubscriptionsSchema, updateUserSchema } from './users.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from '../utils/commonFunctions'; import { INITIALIZE_ERROR, ENDPOINTS } from '../constants'; -import { typeOfAuth } from '../authorization'; -export const updateUserEmail = (newEmail: string) => { - if (typeOfAuth === null) { - return Promise.reject( - new Error('Cannot make API request until a user is signed in') - ); - } - return baseIterableRequest({ +export const updateUserEmail = (newEmail: string) => + baseIterableRequest({ method: 'POST', url: ENDPOINTS.update_email.route, data: { @@ -27,7 +21,6 @@ export const updateUserEmail = (newEmail: string) => { }) } }); -}; export const updateUser = (payloadParam: UpdateUserParams = {}) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -40,9 +33,6 @@ export const updateUser = (payloadParam: UpdateUserParams = {}) => { anonymousUserEventManager.trackAnonUpdateUser(payload); return Promise.reject(INITIALIZE_ERROR); } - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } return baseIterableRequest({ method: 'POST', url: ENDPOINTS.users_update.route, @@ -64,10 +54,6 @@ export const updateSubscriptions = ( delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.users_update_subscriptions.route, diff --git a/src/utils/commonFunctions.ts b/src/utils/commonFunctions.ts index 24b3715b..ecf94eab 100644 --- a/src/utils/commonFunctions.ts +++ b/src/utils/commonFunctions.ts @@ -1,5 +1,5 @@ -import { typeOfAuth } from '../authorization/authorization'; import config from './config'; +import { getTypeOfAuth } from './typeOfAuth'; export const canTrackAnonUser = (): boolean => - config.getConfig('enableAnonTracking') && typeOfAuth === null; + config.getConfig('enableAnonTracking') && getTypeOfAuth() === null; diff --git a/src/utils/typeOfAuth.ts b/src/utils/typeOfAuth.ts new file mode 100644 index 00000000..38092020 --- /dev/null +++ b/src/utils/typeOfAuth.ts @@ -0,0 +1,24 @@ +/* eslint-disable import/no-mutable-exports */ + +/* + AKA did the user auth with their email (setEmail) or user ID (setUserID) + + we're going to use this variable for one circumstance - when calling _updateUserEmail_. + Essentially, when we call the Iterable API to update a user's email address and we get a + successful 200 request, we're going to request a new JWT token, since it might need to + be re-signed with the new email address; however, if the customer code never authorized the + user with an email and instead a user ID, we'll just continue to sign the JWT with the user ID. + + This is mainly just a quality-of-life feature, so that the customer's JWT generation code + doesn't _need_ to support email-signed JWTs if they don't want and purely want to issue the + tokens by user ID. + */ +/* this will be the literal user ID or email they choose to auth with */ + +export type TypeOfAuth = null | 'email' | 'userID'; +let typeOfAuth: TypeOfAuth = null; +export const setTypeOfAuth = (value: TypeOfAuth) => { + typeOfAuth = value; +}; + +export const getTypeOfAuth = () => typeOfAuth;