\n
\n
\n
\n
\n
\n
\n
\n
\n
').concat(t,'
\n
').concat(n,'
\n
\n
{
+ const attr = Adjust.getAttribution()
+
+ if (attr) {
+ write('Attribution:')
+ write(JSON.stringify(attr, undefined, 2))
+ }
+ })()
+}
+
+export default init
diff --git a/src/demo/get-web-uuid/get-web-uuid.html b/src/demo/get-web-uuid/get-web-uuid.html
new file mode 100644
index 00000000..598c7a89
--- /dev/null
+++ b/src/demo/get-web-uuid/get-web-uuid.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/src/demo/get-web-uuid/get-web-uuid.js b/src/demo/get-web-uuid/get-web-uuid.js
new file mode 100644
index 00000000..6a913ffc
--- /dev/null
+++ b/src/demo/get-web-uuid/get-web-uuid.js
@@ -0,0 +1,16 @@
+import Adjust from '../../sdk/main'
+import SimpleAction from '../simple-action'
+import { write } from '../log'
+
+function init () {
+ SimpleAction('get-web-uuid', () => {
+ const web_uuid = Adjust.getWebUUID()
+
+ if (web_uuid) {
+ write('Web UUID:')
+ write(web_uuid)
+ }
+ })()
+}
+
+export default init
diff --git a/src/demo/main.js b/src/demo/main.js
index 7ba97e69..25e22afa 100644
--- a/src/demo/main.js
+++ b/src/demo/main.js
@@ -13,6 +13,8 @@ import stopInit from './stop/stop'
import restartInit from './restart/restart'
import gdprForgetMeInit from './gdpr-forget-me/gdpr-forget-me'
import disableThirdPartySharingInit from './disable-third-party-sharing/disable-third-party-sharing'
+import getWebUUID from './get-web-uuid/get-web-uuid'
+import getAttribution from './get-attribution/get-attribution'
function init (defaultAppConfig, defaultEventConfig) {
logInit()
@@ -30,6 +32,8 @@ function init (defaultAppConfig, defaultEventConfig) {
restartInit()
gdprForgetMeInit()
disableThirdPartySharingInit()
+ getWebUUID()
+ getAttribution()
}
export default init
diff --git a/src/index.html b/src/index.html
index dad9c0b3..90d5c103 100644
--- a/src/index.html
+++ b/src/index.html
@@ -27,6 +27,8 @@ Web SDK Demo
${require('./demo/restart/restart.html')}
${require('./demo/gdpr-forget-me/gdpr-forget-me.html')}
${require('./demo/disable-third-party-sharing/disable-third-party-sharing.html')}
+ ${require('./demo/get-web-uuid/get-web-uuid.html')}
+ ${require('./demo/get-attribution/get-attribution.html')}
diff --git a/src/sdk/__mocks__/url-strategy.ts b/src/sdk/__mocks__/url-strategy.ts
new file mode 100644
index 00000000..255fd626
--- /dev/null
+++ b/src/sdk/__mocks__/url-strategy.ts
@@ -0,0 +1,27 @@
+import type { BaseUrlsMap, UrlStrategy } from '../url-strategy'
+
+const module = jest.requireActual('../url-strategy')
+
+const testEndpoints = {
+ default: { app: 'app.default', gdpr: '' },
+ india: { app: 'app.india', gdpr: '' },
+ china: { app: 'app.china', gdpr: '' }
+}
+
+const singleEndpoint = { default: { app: 'app', gdpr: 'gdpr' } }
+
+export const mockEndpoints = {
+ endpoints: testEndpoints,
+ singleEndpoint
+}
+
+export function urlStrategyRetries
(
+ sendRequest: (urls: BaseUrlsMap) => Promise,
+ endpoints: Partial> = mockEndpoints.endpoints
+) {
+ return module.urlStrategyRetries(sendRequest, endpoints)
+}
+
+export function getBaseUrlsIterator(endpoints: Partial> = mockEndpoints.singleEndpoint) {
+ return module.getBaseUrlsIterator(endpoints)
+}
diff --git a/src/sdk/__tests__/activity-state.spec.js b/src/sdk/__tests__/activity-state.spec.js
index 4931fa1e..3aed37ed 100644
--- a/src/sdk/__tests__/activity-state.spec.js
+++ b/src/sdk/__tests__/activity-state.spec.js
@@ -1,6 +1,8 @@
import * as ActivityState from '../activity-state'
import {MINUTE, SECOND} from '../constants'
+jest.mock('../logger')
+
describe('activity state functionality', () => {
const now = Date.now()
@@ -315,4 +317,39 @@ describe('activity state functionality', () => {
})
+ describe('getting web-uuid', () => {
+
+ it('returns actual uuid', () => {
+ expect(ActivityState.default.getWebUUID()).toEqual('some-uuid')
+ })
+
+ it('returns null when ActivityState is not initialised', () => {
+ ActivityState.default.destroy()
+ localStorage.clear()
+
+ expect(ActivityState.default.getWebUUID()).toBeNull()
+ })
+
+ })
+
+ describe('getting attribution', () => {
+
+ it('returns null when ActivityState is not initialised', () => {
+ ActivityState.default.destroy()
+ localStorage.clear()
+
+ expect(ActivityState.default.getAttribution()).toBeNull()
+ })
+
+ it('returns null when not attributed', () => {
+ expect(ActivityState.default.getAttribution()).toBeNull()
+ })
+
+ it('returns actual attribution', () => {
+ ActivityState.default.current = { ...ActivityState.default.current, attribution: { adid: 'dummy-adid' } }
+
+ expect(ActivityState.default.getAttribution()).toEqual({ adid: 'dummy-adid' })
+ })
+ })
+
})
diff --git a/src/sdk/__tests__/event.spec.js b/src/sdk/__tests__/event.spec.js
index 123fb52a..04ac74ae 100644
--- a/src/sdk/__tests__/event.spec.js
+++ b/src/sdk/__tests__/event.spec.js
@@ -10,6 +10,7 @@ import * as Storage from '../storage/storage'
jest.mock('../http')
jest.mock('../logger')
+jest.mock('../url-strategy')
jest.useFakeTimers()
const appOptions = {
@@ -20,6 +21,7 @@ const appOptions = {
function expectRequest (requestConfig, timestamp) {
const fullConfig = {
+ endpoint: 'app',
...requestConfig,
params: {
attempts: 1,
diff --git a/src/sdk/__tests__/gdpr-forget-device.spec.js b/src/sdk/__tests__/gdpr-forget-device.spec.js
index f500040b..f46b064c 100644
--- a/src/sdk/__tests__/gdpr-forget-device.spec.js
+++ b/src/sdk/__tests__/gdpr-forget-device.spec.js
@@ -9,6 +9,7 @@ import * as PubSub from '../pub-sub'
jest.mock('../http')
jest.mock('../logger')
+jest.mock('../url-strategy')
jest.useFakeTimers()
const appOptions = {
@@ -23,6 +24,7 @@ function expectRequest () {
}
const fullConfig = {
+ endpoint: 'gdpr',
...requestConfig,
params: {
attempts: 1,
diff --git a/src/sdk/__tests__/http.spec.js b/src/sdk/__tests__/http.spec.js
index fa27bc1b..9382b080 100644
--- a/src/sdk/__tests__/http.spec.js
+++ b/src/sdk/__tests__/http.spec.js
@@ -4,9 +4,9 @@ import * as Time from '../time'
import * as ActivityState from '../activity-state'
import * as PubSub from '../pub-sub'
import * as Config from '../config'
-import * as UrlStrategy from '../url-strategy'
jest.mock('../logger')
+jest.mock('../url-strategy')
jest.useFakeTimers()
describe('perform api requests', () => {
@@ -30,9 +30,6 @@ describe('perform api requests', () => {
'queue_size=0'
].join('&')
- const urlStrategyRetriesMock = sendRequestCb => sendRequestCb({ app: 'app', gdpr: 'gdpr' })
- const urlStrategyRetriesActual = UrlStrategy.urlStrategyRetries
-
const oldXMLHttpRequest = global.XMLHttpRequest
const oldLocale = global.navigator.language
const oldPlatform = global.navigator.platform
@@ -47,9 +44,6 @@ describe('perform api requests', () => {
Utils.setGlobalProp(global.navigator, 'platform')
Utils.setGlobalProp(global.navigator, 'doNotTrack')
-
- jest.spyOn(UrlStrategy, 'urlStrategyRetries').mockImplementation(urlStrategyRetriesMock)
-
jest.spyOn(Time, 'getTimestamp').mockReturnValue('some-time')
ActivityState.default.init({uuid: 'some-uuid'})
@@ -78,6 +72,7 @@ describe('perform api requests', () => {
expect.assertions(1)
expect(http.default({
+ endpoint: 'app',
url: '/some-url',
params: {}
})).rejects.toEqual({
@@ -102,6 +97,7 @@ describe('perform api requests', () => {
expect.assertions(1)
expect(http.default({
+ endpoint: 'app',
url: '/some-url',
params: {}
})).rejects.toEqual({
@@ -126,6 +122,7 @@ describe('perform api requests', () => {
expect.assertions(1)
expect(http.default({
+ endpoint: 'app',
url: '/some-url',
params: {}
})).resolves.toEqual({
@@ -150,6 +147,7 @@ describe('perform api requests', () => {
expect.assertions(1)
expect(http.default({
+ endpoint: 'app',
url: '/some-url',
params: {}
})).rejects.toEqual({
@@ -174,6 +172,7 @@ describe('perform api requests', () => {
expect.assertions(1)
expect(http.default({
+ endpoint: 'app',
url: '/some-url',
params: {}
})).rejects.toEqual({
@@ -198,6 +197,7 @@ describe('perform api requests', () => {
expect.assertions(0)
expect(http.default({
+ endpoint: 'app',
url: '/not-resolved-request'
})).resolves.toEqual({})
@@ -228,6 +228,7 @@ describe('perform api requests', () => {
expect.assertions(4)
expect(http.default({
+ endpoint: 'app',
url: '/some-url',
params: {
eventToken: '567abc',
@@ -263,6 +264,7 @@ describe('perform api requests', () => {
expect.assertions(4)
expect(http.default({
+ endpoint: 'app',
url: '/some-url',
params: {
eventToken: '567abc'
@@ -305,6 +307,7 @@ describe('perform api requests', () => {
expect.assertions(4)
expect(http.default({
+ endpoint: 'app',
url: '/some-other-url',
params: {
bla: 'truc'
@@ -349,6 +352,7 @@ describe('perform api requests', () => {
expect.assertions(4)
expect(http.default({
+ endpoint: 'app',
url: '/some-other-url',
params: {
bla: 'truc'
@@ -375,6 +379,7 @@ describe('perform api requests', () => {
expect.assertions(4)
expect(http.default({
+ endpoint: 'gdpr',
url: '/gdpr_forget_device',
method: 'POST'
})).resolves.toEqual({
@@ -399,6 +404,7 @@ describe('perform api requests', () => {
expect.assertions(4)
expect(http.default({
+ endpoint: 'app',
url: '/sweet-url',
params: {
some: 'thing'
@@ -421,46 +427,12 @@ describe('perform api requests', () => {
})
})
- describe('custom url', () => {
- beforeAll(() => {
- jest.spyOn(UrlStrategy, 'urlStrategyRetries').mockImplementation(urlStrategyRetriesActual)
- })
-
- afterAll(() => {
- jest.spyOn(UrlStrategy, 'urlStrategyRetries').mockImplementation(urlStrategyRetriesMock)
- })
-
- it('overrides base and gdpr url with custom one', () => {
- Config.default.set({ customUrl: 'custom-base', ...appParams })
-
- expect.assertions(4)
-
- expect(http.default({
- url: '/some-url'
- })).resolves.toEqual({
- status: 'success',
- ...response
- })
-
- return Utils.flushPromises()
- .then(() => {
-
- expect(mockXHR.open).toHaveBeenCalledWith('GET', `custom-base/some-url?${defaultParamsString}`, true)
- expect(mockXHR.setRequestHeader).toHaveBeenCalledWith('Client-SDK', 'jsTEST')
- expect(mockXHR.send).toHaveBeenCalledWith(undefined)
-
- mockXHR.onreadystatechange()
-
- Config.default.set(appParams)
- })
- })
- })
-
it('excludes empty values from the request params', () => {
expect.assertions(4)
expect(http.default({
+ endpoint: 'app',
url: '/some-url',
params: {
some: 'thing',
@@ -493,6 +465,7 @@ describe('perform api requests', () => {
expect.assertions(5)
expect(http.default({
+ endpoint: 'app',
url: '/some-url',
method: 'POST',
params: {
@@ -535,6 +508,7 @@ describe('perform api requests', () => {
})
expect(http.default({
+ endpoint: 'app',
url: '/session',
params: {
some: 'thing',
@@ -564,6 +538,7 @@ describe('perform api requests', () => {
})
expect(http.default({
+ endpoint: 'app',
url: '/attribution',
params: {
some: 'thing',
@@ -617,6 +592,7 @@ describe('perform api requests', () => {
expect.assertions(4)
http.default({
+ endpoint: 'app',
url: '/session'
}).then(result => {
expect(result).toEqual({
@@ -678,6 +654,7 @@ describe('perform api requests', () => {
expect.assertions(3)
http.default({
+ endpoint: 'app',
url: '/event',
params: {
eventToken: 'token1'
@@ -708,6 +685,7 @@ describe('perform api requests', () => {
expect.assertions(3)
http.default({
+ endpoint: 'app',
url: '/anything',
params: {
bla: 'truc'
@@ -734,6 +712,7 @@ describe('perform api requests', () => {
expect.assertions(2)
http.default({
+ endpoint: 'app',
url: '/session'
}).then(result => {
expect(result).toEqual({status: 'success'})
@@ -757,6 +736,7 @@ describe('perform api requests', () => {
expect.assertions(4)
http.default({
+ endpoint: 'app',
url: '/anything',
params: {
bla: 'truc'
@@ -788,6 +768,7 @@ describe('perform api requests', () => {
expect.assertions(2)
http.default({
+ endpoint: 'gdpr',
url: '/gdpr_forget_device'
}).then(result => {
expect(result).toEqual({
@@ -811,6 +792,7 @@ describe('perform api requests', () => {
expect.assertions(2)
http.default({
+ endpoint: 'app',
url: '/disable_third_party_sharing'
}).then(result => {
expect(result).toEqual({
diff --git a/src/sdk/__tests__/main/main-disabled.gdpr.spec.js b/src/sdk/__tests__/main/main-disabled.gdpr.spec.js
index 9d823003..a22a18af 100644
--- a/src/sdk/__tests__/main/main-disabled.gdpr.spec.js
+++ b/src/sdk/__tests__/main/main-disabled.gdpr.spec.js
@@ -14,6 +14,7 @@ import * as GdprForgetDevice from '../../gdpr-forget-device'
import * as Listeners from '../../listeners'
import * as http from '../../http'
import * as Scheduler from '../../scheduler'
+import * as ActivityState from '../../activity-state'
import AdjustInstance from '../../main.js'
import Suite from './main.suite'
@@ -58,6 +59,8 @@ describe('main entry point - test GDPR-Forget-Me when in initially disabled stat
jest.spyOn(Listeners, 'register')
jest.spyOn(Scheduler, 'delay')
jest.spyOn(Scheduler, 'flush')
+ jest.spyOn(ActivityState.default, 'getWebUUID')
+ jest.spyOn(ActivityState.default, 'getAttribution')
Preferences.setDisabled({reason: 'general'})
})
@@ -80,7 +83,7 @@ describe('main entry point - test GDPR-Forget-Me when in initially disabled stat
it('initiates and prevents running all static methods and track event', () => {
AdjustInstance.initSdk(suite.config)
- expect.assertions(28)
+ expect.assertions(32)
return Utils.flushPromises()
.then(() => {
@@ -181,7 +184,7 @@ describe('main entry point - test GDPR-Forget-Me when in initially disabled stat
AdjustInstance.initSdk(suite.config)
- expect.assertions(30)
+ expect.assertions(34)
return Utils.flushPromises()
.then(() => {
@@ -230,7 +233,7 @@ describe('main entry point - test GDPR-Forget-Me when in initially disabled stat
AdjustInstance.initSdk(suite.config)
- expect.assertions(30)
+ expect.assertions(34)
return Utils.flushPromises()
.then(() => {
diff --git a/src/sdk/__tests__/main/main-disabled.general.spec.js b/src/sdk/__tests__/main/main-disabled.general.spec.js
index 691dd0b6..bd1b439d 100644
--- a/src/sdk/__tests__/main/main-disabled.general.spec.js
+++ b/src/sdk/__tests__/main/main-disabled.general.spec.js
@@ -13,6 +13,7 @@ import * as Preferences from '../../preferences'
import * as GdprForgetDevice from '../../gdpr-forget-device'
import * as Listeners from '../../listeners'
import * as Scheduler from '../../scheduler'
+import * as ActivityState from '../../activity-state'
import AdjustInstance from '../../main.js'
import Suite from './main.suite'
@@ -54,6 +55,8 @@ describe('main entry point - test disable/enable when in initially disabled stat
jest.spyOn(Listeners, 'destroy')
jest.spyOn(Scheduler, 'delay')
jest.spyOn(Scheduler, 'flush')
+ jest.spyOn(ActivityState.default, 'getWebUUID')
+ jest.spyOn(ActivityState.default, 'getAttribution')
Preferences.setDisabled({ reason: 'general' })
})
diff --git a/src/sdk/__tests__/main/main-enabled.gdpr.spec.js b/src/sdk/__tests__/main/main-enabled.gdpr.spec.js
index 13f02e90..0e137db2 100644
--- a/src/sdk/__tests__/main/main-enabled.gdpr.spec.js
+++ b/src/sdk/__tests__/main/main-enabled.gdpr.spec.js
@@ -13,6 +13,7 @@ import * as GdprForgetDevice from '../../gdpr-forget-device'
import * as Listeners from '../../listeners'
import * as http from '../../http'
import * as Scheduler from '../../scheduler'
+import * as ActivityState from '../../activity-state'
import AdjustInstance from '../../main.js'
import Suite from './main.suite'
@@ -59,6 +60,8 @@ describe('main entry point - test GDPR-Forget-Me when in initially enabled state
jest.spyOn(Listeners, 'destroy')
jest.spyOn(Scheduler, 'delay')
jest.spyOn(Scheduler, 'flush')
+ jest.spyOn(ActivityState.default, 'getWebUUID')
+ jest.spyOn(ActivityState.default, 'getAttribution')
Preferences.setDisabled(null)
})
diff --git a/src/sdk/__tests__/main/main-enabled.general.spec.js b/src/sdk/__tests__/main/main-enabled.general.spec.js
index 29f64688..4320337c 100644
--- a/src/sdk/__tests__/main/main-enabled.general.spec.js
+++ b/src/sdk/__tests__/main/main-enabled.general.spec.js
@@ -13,6 +13,7 @@ import * as Preferences from '../../preferences'
import * as GdprForgetDevice from '../../gdpr-forget-device'
import * as Listeners from '../../listeners'
import * as Scheduler from '../../scheduler'
+import * as ActivityState from '../../activity-state'
import AdjustInstance from '../../main.js'
import Suite from './main.suite'
@@ -54,6 +55,8 @@ describe('main entry point - test enable/disable when in initially enabled state
jest.spyOn(Listeners, 'destroy')
jest.spyOn(Scheduler, 'delay')
jest.spyOn(Scheduler, 'flush')
+ jest.spyOn(ActivityState.default, 'getWebUUID')
+ jest.spyOn(ActivityState.default, 'getAttribution')
})
afterEach(() => {
@@ -234,7 +237,7 @@ describe('main entry point - test enable/disable when in initially enabled state
AdjustInstance.initSdk(suite.config)
- expect.assertions(28)
+ expect.assertions(32)
return Utils.flushPromises()
.then(() => {
@@ -435,7 +438,7 @@ describe('main entry point - test enable/disable when in initially enabled state
AdjustInstance.initSdk(suite.config)
- expect.assertions(28)
+ expect.assertions(32)
return Utils.flushPromises()
.then(() => {
diff --git a/src/sdk/__tests__/main/main-gdpr-disabled.gdpr.spec.js b/src/sdk/__tests__/main/main-gdpr-disabled.gdpr.spec.js
index b6608b12..2ae0ca1c 100644
--- a/src/sdk/__tests__/main/main-gdpr-disabled.gdpr.spec.js
+++ b/src/sdk/__tests__/main/main-gdpr-disabled.gdpr.spec.js
@@ -13,6 +13,7 @@ import * as GdprForgetDevice from '../../gdpr-forget-device'
import * as Listeners from '../../listeners'
import * as http from '../../http'
import * as Scheduler from '../../scheduler'
+import * as ActivityState from '../../activity-state'
import AdjustInstance from '../../main.js'
import Suite from './main.suite'
@@ -56,6 +57,8 @@ describe('main entry point - test GDPR-Forget-Me when in initially GDPR disabled
jest.spyOn(Listeners, 'register')
jest.spyOn(Scheduler, 'delay')
jest.spyOn(Scheduler, 'flush')
+ jest.spyOn(ActivityState.default, 'getWebUUID')
+ jest.spyOn(ActivityState.default, 'getAttribution')
Preferences.setDisabled({reason: 'gdpr'})
})
@@ -79,7 +82,7 @@ describe('main entry point - test GDPR-Forget-Me when in initially GDPR disabled
AdjustInstance.initSdk(suite.config)
- expect.assertions(28)
+ expect.assertions(32)
return Utils.flushPromises()
.then(() => {
@@ -173,7 +176,7 @@ describe('main entry point - test GDPR-Forget-Me when in initially GDPR disabled
AdjustInstance.initSdk(suite.config)
- expect.assertions(30)
+ expect.assertions(34)
return Utils.flushPromises()
.then(() => {
@@ -222,7 +225,7 @@ describe('main entry point - test GDPR-Forget-Me when in initially GDPR disabled
AdjustInstance.initSdk(suite.config)
- expect.assertions(30)
+ expect.assertions(34)
return Utils.flushPromises()
.then(() => {
diff --git a/src/sdk/__tests__/main/main.storage-available.spec.js b/src/sdk/__tests__/main/main.storage-available.spec.js
index 490de711..197651e8 100644
--- a/src/sdk/__tests__/main/main.storage-available.spec.js
+++ b/src/sdk/__tests__/main/main.storage-available.spec.js
@@ -12,6 +12,7 @@ import * as Attribution from '../../attribution'
import * as Storage from '../../storage/storage'
import * as Listeners from '../../listeners'
import * as Scheduler from '../../scheduler'
+import * as ActivityState from '../../activity-state'
import AdjustInstance from '../../main'
import OtherInstance from '../../main'
import Suite from './main.suite'
@@ -49,6 +50,8 @@ describe('main entry point - test instance initiation when storage is available'
jest.spyOn(Listeners, 'register')
jest.spyOn(Listeners, 'destroy')
jest.spyOn(Scheduler, 'flush')
+ jest.spyOn(ActivityState.default, 'getWebUUID')
+ jest.spyOn(ActivityState.default, 'getAttribution')
sessionWatchSpy = jest.spyOn(Session, 'watch')
})
diff --git a/src/sdk/__tests__/main/main.suite.js b/src/sdk/__tests__/main/main.suite.js
index 825f6834..9e45b4be 100644
--- a/src/sdk/__tests__/main/main.suite.js
+++ b/src/sdk/__tests__/main/main.suite.js
@@ -106,7 +106,7 @@ function expectDelayedTrackEvent_Async () {
.then(() => {
PubSub.publish('sdk:installed')
jest.runOnlyPendingTimers()
-
+
expect(Logger.default.log).toHaveBeenLastCalledWith('Delayed track event task is running now')
expect(event.default).toHaveBeenCalledWith({eventToken: 'bla123'}, expect.any(Number))
@@ -164,6 +164,12 @@ function expectNotRunningTrackEventWhenNoStorage () {
function expectRunningStatic () {
+ _instance.getWebUUID()
+ expect(ActivityState.default.getWebUUID).toHaveBeenCalled()
+
+ _instance.getAttribution()
+ expect(ActivityState.default.getAttribution).toHaveBeenCalled()
+
const params = [
{key: 'key1', value: 'value1'},
{key: 'key2', value: 'value2'}
@@ -193,11 +199,19 @@ function expectRunningStatic () {
_instance.switchToOfflineMode()
expect(Queue.setOffline).toHaveBeenCalledWith(true)
- return {assertions: 8}
+ return {assertions: 10}
}
function expectNotRunningStatic () {
+ _instance.getWebUUID()
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Adjust SDK is disabled, can not get web_uuid')
+ expect(ActivityState.default.getWebUUID).not.toHaveBeenCalled()
+
+ _instance.getAttribution()
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Adjust SDK is disabled, can not get attribution')
+ expect(ActivityState.default.getAttribution).not.toHaveBeenCalled()
+
_instance.addGlobalCallbackParameters([{key: 'key', value: 'value'}])
expect(Logger.default.log).toHaveBeenLastCalledWith('Adjust SDK is disabled, can not add global callback parameters')
@@ -233,7 +247,7 @@ function expectNotRunningStatic () {
expect(Logger.default.log).toHaveBeenLastCalledWith('Adjust SDK is disabled, can not set offline mode')
expect(Queue.setOffline).not.toHaveBeenCalled()
- return {assertions: 14}
+ return {assertions: 18}
}
function expectNotRunningStaticWhenNoStorage () {
diff --git a/src/sdk/__tests__/request.spec.js b/src/sdk/__tests__/request.spec.js
index 09abc905..6ba4995a 100644
--- a/src/sdk/__tests__/request.spec.js
+++ b/src/sdk/__tests__/request.spec.js
@@ -3,9 +3,11 @@ import * as http from '../http'
import * as Time from '../time'
import * as Logger from '../logger'
import * as Listeners from '../listeners'
+import * as UrlStartegy from '../url-strategy'
jest.mock('../http')
jest.mock('../logger')
+jest.mock('../url-strategy')
jest.useFakeTimers()
describe('test request functionality', () => {
@@ -61,6 +63,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/global-request',
method: 'GET',
params: {
@@ -98,6 +101,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/global-request',
method: 'GET',
params: {
@@ -218,6 +222,7 @@ describe('test request functionality', () => {
expect(http.default).toHaveBeenCalledTimes(1)
expect(http.default).toHaveBeenLastCalledWith({
+ endpoint: 'app',
url: '/other-request',
method: 'POST',
params: {
@@ -238,6 +243,7 @@ describe('test request functionality', () => {
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1300)
expect(http.default).toHaveBeenCalledTimes(2)
expect(http.default).toHaveBeenLastCalledWith({
+ endpoint: 'app',
url: '/other-request',
method: 'POST',
params: {
@@ -261,6 +267,7 @@ describe('test request functionality', () => {
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1300)
expect(http.default).toHaveBeenCalledTimes(1)
expect(http.default).toHaveBeenLastCalledWith({
+ endpoint: 'app',
url: '/other-request',
method: 'POST',
params: {
@@ -292,6 +299,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/global-request',
method: 'GET',
params: {
@@ -336,6 +344,7 @@ describe('test request functionality', () => {
expect(http.default).toHaveBeenCalledTimes(1)
expect(http.default).toHaveBeenLastCalledWith({
+ endpoint: 'app',
url: '/some-request',
method: 'POST',
params: {
@@ -357,6 +366,7 @@ describe('test request functionality', () => {
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 666)
expect(http.default).toHaveBeenCalledTimes(2)
expect(http.default).toHaveBeenLastCalledWith({
+ endpoint: 'app',
url: '/some-request',
method: 'POST',
params: {
@@ -379,6 +389,7 @@ describe('test request functionality', () => {
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 777)
expect(http.default).toHaveBeenCalledTimes(1)
expect(http.default).toHaveBeenLastCalledWith({
+ endpoint: 'app',
url: '/some-request',
method: 'POST',
params: {
@@ -407,6 +418,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/global-request',
method: 'GET',
params: {
@@ -580,7 +592,6 @@ describe('test request functionality', () => {
})
it('retires unsuccessful request because of no connection without back-off', () => {
-
const newNow = Date.now()
const matchLocalCreatedAt = (attempts) => ({
params: {
@@ -855,6 +866,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/global-request',
method: 'GET',
params: {
@@ -1031,6 +1043,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/global-request',
method: 'GET',
params: {
@@ -1092,6 +1105,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/another-failed-request',
method: 'GET',
params: {
@@ -1202,6 +1216,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/another-global-request',
method: 'GET',
params: {
@@ -1230,6 +1245,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/new-url',
method: 'POST',
params: {
@@ -1283,6 +1299,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/another-global-request',
method: 'GET',
params: {
@@ -1318,6 +1335,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/another-global-request',
method: 'GET',
params: {
@@ -1342,6 +1360,7 @@ describe('test request functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/another-global-request',
method: 'GET',
params: {
@@ -1383,4 +1402,258 @@ describe('test request functionality', () => {
})
+ describe('url startegy retries functionality', () => {
+ const testEndpoints = jest.requireMock('../url-strategy').mockEndpoints.endpoints
+
+ // let getBaseUrlsIterator to return pre-created iterator so it's possible to spy iterator methods
+ const iterator = jest.requireActual(('../url-strategy')).getBaseUrlsIterator(testEndpoints)
+
+ const expectHttpCall = (times, endpoint, url) => {
+ expect(http.default).toHaveBeenCalledTimes(times)
+ expect(http.default).toHaveBeenCalledWith({
+ endpoint: endpoint,
+ url: url,
+ method: 'GET',
+ params: {
+ attempts: times,
+ createdAt: now
+ }
+ })
+ }
+
+ const clearIteratorMock = (iterator) => {
+ jest.spyOn(iterator, 'next').mockClear()
+ jest.spyOn(iterator, 'reset').mockClear()
+ }
+
+ beforeAll(() => {
+ jest.spyOn(UrlStartegy, 'getBaseUrlsIterator').mockImplementation(() => iterator)
+ jest.spyOn(iterator, 'next')
+ jest.spyOn(iterator, 'reset')
+
+ createdAtSpy.mockReturnValue(now)
+ })
+
+ afterEach(() => {
+ jest.clearAllMocks()
+ iterator.reset()
+ })
+
+ afterAll(() => {
+ jest.clearAllTimers()
+ jest.restoreAllMocks()
+ })
+
+ it('does not retries if request succesfully sent', () => {
+ Request
+ .default({
+ url: '/global-request',
+ params: {
+ some: 'param'
+ }
+ })
+ .send()
+
+ expect.assertions(9)
+
+ expect(UrlStartegy.getBaseUrlsIterator).toHaveBeenCalled()
+ expect(iterator.next).toHaveBeenCalledTimes(1)
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.default, done: false })
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Trying request /global-request in 150ms')
+
+ jest.runOnlyPendingTimers()
+
+ expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app.default',
+ url: '/global-request',
+ method: 'GET',
+ params: {
+ attempts: 1,
+ createdAt: now,
+ some: 'param'
+ }
+ })
+
+ return Utils.flushPromises()
+ .then(() => {
+ expect(Logger.default.log).toHaveBeenCalledWith('Request /global-request has been finished')
+
+ // iterator was reset and next called in request successful callback
+ expect(iterator.next).toHaveBeenCalledTimes(2)
+ expect(iterator.next).toHaveReturnedTimes(2)
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.default, done: false })
+ })
+ })
+
+ it('retries to send request to endpoints iteratively and stops to iterate when connected succesfully', () => {
+ http.default.mockRejectedValue(Utils.errorResponse('NO_CONNECTION'))
+
+ Request
+ .default({ url: '/global-request' })
+ .send()
+
+ expect.assertions(38)
+
+ expect(UrlStartegy.getBaseUrlsIterator).toHaveBeenCalled()
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.default, done: false })
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Trying request /global-request in 150ms')
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(1, 'app.default', '/global-request')
+
+ return Utils.flushPromises()
+ .then(() => {
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.india, done: false })
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Re-trying request /global-request in 150ms')
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(2, 'app.india', '/global-request')
+
+ return Utils.flushPromises()
+ })
+ .then(() => {
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.china, done: false })
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Re-trying request /global-request in 150ms')
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(3, 'app.china', '/global-request')
+
+ return Utils.flushPromises()
+ }).then(() => {
+ expect(iterator.next).toHaveReturnedWith({ value: undefined, done: true })
+ expect(iterator.reset).toHaveBeenCalled()
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.default, done: false })
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Re-trying request /global-request in 60000ms')
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(4, 'app.default', '/global-request')
+
+ return Utils.flushPromises()
+ }).then(() => {
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.india, done: false })
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Re-trying request /global-request in 150ms')
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(5, 'app.india', '/global-request')
+
+ return Utils.flushPromises()
+ })
+ .then(() => {
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.china, done: false })
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Re-trying request /global-request in 150ms')
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(6, 'app.china', '/global-request')
+
+ return Utils.flushPromises()
+ }).then(() => {
+ expect(iterator.next).toHaveReturnedWith({ value: undefined, done: true })
+ expect(iterator.reset).toHaveBeenCalled()
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.default, done: false })
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Re-trying request /global-request in 60000ms')
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(7, 'app.default', '/global-request')
+
+ http.default.mockResolvedValue({}) // let http successfully resolve next time
+
+ return Utils.flushPromises()
+ })
+ .then(() => {
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.india, done: false })
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Re-trying request /global-request in 150ms')
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(8, 'app.india', '/global-request')
+
+ return Utils.flushPromises()
+ })
+ .then(() => {
+
+ jest.runOnlyPendingTimers()
+
+ expect(Logger.default.log).toHaveBeenCalledWith('Request /global-request has been finished')
+ })
+ })
+
+ it('does not iterate endpoints if another error happened', () => {
+ http.default.mockRejectedValue(Utils.errorResponse('UNKNOWN'))
+
+ Request
+ .default({ url: '/global-request' })
+ .send()
+
+ expect.assertions(14)
+
+ expect(UrlStartegy.getBaseUrlsIterator).toHaveBeenCalled()
+ expect(iterator.next).toHaveReturnedWith({ value: testEndpoints.default, done: false })
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Trying request /global-request in 150ms')
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(1, 'app.default', '/global-request')
+
+ return Utils.flushPromises()
+ .then(() => {
+ expect(iterator.next).not.toHaveBeenCalled()
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Re-trying request /global-request in 100ms') // 100ms is because of back-off
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(2, 'app.default', '/global-request')
+
+ http.default.mockResolvedValue({}) // let http successfully resolve next time
+
+ return Utils.flushPromises()
+ })
+ .then(() => {
+ expect(iterator.next).not.toHaveBeenCalled()
+ clearIteratorMock(iterator)
+
+ expect(Logger.default.log).toHaveBeenLastCalledWith('Re-trying request /global-request in 200ms')
+
+ jest.runOnlyPendingTimers()
+
+ expectHttpCall(3, 'app.default', '/global-request')
+
+ return Utils.flushPromises()
+ })
+ .then(() => {
+ jest.runOnlyPendingTimers()
+
+ expect(Logger.default.log).toHaveBeenCalledWith('Request /global-request has been finished')
+ })
+ })
+
+ })
+
})
diff --git a/src/sdk/__tests__/sdk-click.spec.js b/src/sdk/__tests__/sdk-click.spec.js
index 1ec508fc..7a01626b 100644
--- a/src/sdk/__tests__/sdk-click.spec.js
+++ b/src/sdk/__tests__/sdk-click.spec.js
@@ -6,6 +6,7 @@ import * as ActivityState from '../activity-state'
jest.mock('../http')
jest.mock('../logger')
+jest.mock('../url-strategy')
jest.useFakeTimers()
describe('test sdk-click functionality', () => {
@@ -63,6 +64,7 @@ describe('test sdk-click functionality', () => {
}
const fullConfig = {
+ endpoint: 'app',
...requestConfig,
params: {
attempts: 1,
diff --git a/src/sdk/__tests__/session.spec.js b/src/sdk/__tests__/session.spec.js
index 34a7befd..a004961c 100644
--- a/src/sdk/__tests__/session.spec.js
+++ b/src/sdk/__tests__/session.spec.js
@@ -14,6 +14,7 @@ import {MINUTE, SECOND} from '../constants'
jest.mock('../logger')
jest.mock('../http')
+jest.mock('../url-strategy')
jest.useFakeTimers()
function goToForeground () {
@@ -372,6 +373,7 @@ describe('test session functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/session',
method: 'POST',
params: {
@@ -442,6 +444,7 @@ describe('test session functionality', () => {
jest.advanceTimersByTime(150)
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/session',
method: 'POST',
params: {
@@ -776,6 +779,7 @@ describe('test session functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/session',
method: 'POST',
params: {
@@ -847,6 +851,7 @@ describe('test session functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/session',
method: 'POST',
params: {
@@ -950,6 +955,7 @@ describe('test session functionality', () => {
jest.runOnlyPendingTimers()
expect(http.default).toHaveBeenCalledWith({
+ endpoint: 'app',
url: '/session',
method: 'POST',
params: {
diff --git a/src/sdk/__tests__/smart-banner/network/network.spec.ts b/src/sdk/__tests__/smart-banner/network/network.spec.ts
index 1a57c8ea..c8cd538a 100644
--- a/src/sdk/__tests__/smart-banner/network/network.spec.ts
+++ b/src/sdk/__tests__/smart-banner/network/network.spec.ts
@@ -1,25 +1,17 @@
import { NetworkError, NoConnectionError } from '../../../smart-banner/network/errors'
-import * as UrlStrategy from '../../../url-strategy'
jest.mock('../../../logger')
+jest.mock('../../../url-strategy')
+
+const UrlStartegyMock = jest.requireMock('../../../url-strategy')
+const testEndpoints = UrlStartegyMock.mockEndpoints.endpoints
describe('Network tests', () => {
const defaultEndpoint = 'https://app.adjust.com'
let Network
let xhrMock: jest.SpyInstance
- const testEndpoints = {
- default: { app: 'app.default', gdpr: '' },
- india: { app: 'app.india', gdpr: '' },
- china: { app: 'app.china', gdpr: '' }
- }
- const urlStrategyRetriesActual = UrlStrategy.urlStrategyRetries
- const urlStrategyRetriesMock = (sendRequestCb: (urls: UrlStrategy.BaseUrlsMap) => Promise) => urlStrategyRetriesActual(sendRequestCb, testEndpoints)
-
beforeEach(() => {
- const UrlStrategyModule = require('../../../url-strategy')
- jest.spyOn(UrlStrategyModule, 'urlStrategyRetries').mockImplementation(urlStrategyRetriesMock)
-
Network = require('../../../smart-banner/network/network').Network
xhrMock = jest.spyOn(Network, 'xhr')
})
diff --git a/src/sdk/__tests__/third-party-sharing.spec.js b/src/sdk/__tests__/third-party-sharing.spec.js
index 0ab906c6..2c6aace8 100644
--- a/src/sdk/__tests__/third-party-sharing.spec.js
+++ b/src/sdk/__tests__/third-party-sharing.spec.js
@@ -9,6 +9,7 @@ import * as Preferences from '../preferences'
jest.mock('../http')
jest.mock('../logger')
+jest.mock('../url-strategy')
jest.useFakeTimers()
const appOptions = {
@@ -23,6 +24,7 @@ function expectRequest () {
}
const fullConfig = {
+ endpoint: 'app',
...requestConfig,
params: {
attempts: 1,
diff --git a/src/sdk/__tests__/url-strategy.spec.ts b/src/sdk/__tests__/url-strategy.spec.ts
index 21a395b8..191fa544 100644
--- a/src/sdk/__tests__/url-strategy.spec.ts
+++ b/src/sdk/__tests__/url-strategy.spec.ts
@@ -1,65 +1,69 @@
-import { UrlStrategy, urlStrategyRetries } from '../url-strategy'
+import { UrlStrategy, urlStrategyRetries, getBaseUrlsIterator, BaseUrlsMap, BaseUrlsIterator } from '../url-strategy'
import * as Globals from '../globals'
jest.mock('../logger')
describe('test url strategy', () => {
+ const testEndpoints = {
+ default: {
+ app: 'app.default',
+ gdpr: 'gdpr.default'
+ },
+ india: {
+ app: 'app.india',
+ gdpr: 'gdpr.india'
+ },
+ china: {
+ app: 'app.china',
+ gdpr: 'gdpr.china'
+ }
+ }
- describe('development environment', () => {
- let Config
+ let Config
- const options = {
- appToken: '123abc',
- environment: 'sandbox'
- }
+ const options = {
+ appToken: '123abc',
+ environment: 'sandbox'
+ }
- const sendRequestMock = jest.fn(() => Promise.reject({ code: 'NO_CONNECTION' }))
-
- const testEndpoints = {
- default: {
- app: 'app.default',
- gdpr: 'gdpr.default'
- },
- india: {
- app: 'app.india',
- gdpr: 'gdpr.india'
- },
- china: {
- app: 'app.china',
- gdpr: 'gdpr.china'
- }
- }
+ const sendRequestMock = jest.fn(() => Promise.reject({ code: 'NO_CONNECTION' }))
- const env = Globals.default.env
+ const env = Globals.default.env
- beforeAll(() => {
- Globals.default.env = 'development'
- })
+ beforeAll(() => {
+ Globals.default.env = 'development'
+ })
- beforeEach(() => {
- Config = require('../config').default
- })
+ beforeEach(() => {
+ Config = require('../config').default
+ })
- afterEach(() => {
- Config.destroy()
- jest.clearAllMocks()
- })
+ afterEach(() => {
+ Config.destroy()
+ jest.clearAllMocks()
+ })
- afterAll(() => {
- Globals.default.env = env
- jest.restoreAllMocks()
- })
+ afterAll(() => {
+ Globals.default.env = env
+ jest.restoreAllMocks()
+ })
+
+ describe('Promise-based urlStrategyRetries tests', () => {
it('does not override custom url', () => {
const customUrl = 'custom-url'
Config.set({ ...options, customUrl })
+ expect.assertions(2)
+
return urlStrategyRetries(sendRequestMock, testEndpoints)
.catch(reason => expect(reason).toEqual({ code: 'NO_CONNECTION' }))
.then(() => expect(sendRequestMock).toHaveBeenCalledWith({ app: customUrl, gdpr: customUrl }))
})
- it('retries send requesrt to endpoints iteratively', () => {
+ it('retries to send request to endpoints iteratively', () => {
+ expect.assertions(5)
+
return urlStrategyRetries(sendRequestMock, testEndpoints)
.catch(reason => expect(reason).toEqual({ code: 'NO_CONNECTION' }))
.then(() => {
@@ -73,6 +77,8 @@ describe('test url strategy', () => {
it('prefers Indian enpoint and does not try reach Chinese one when india url strategy set', () => {
Config.set({ ...options, urlStrategy: UrlStrategy.India })
+ expect.assertions(4)
+
return urlStrategyRetries(sendRequestMock, testEndpoints)
.catch(reason => expect(reason).toEqual({ code: 'NO_CONNECTION' }))
.then(() => {
@@ -85,6 +91,8 @@ describe('test url strategy', () => {
it('prefers Chinese enpoint and does not try reach Indian one when china url strategy set', () => {
Config.set({ ...options, urlStrategy: UrlStrategy.China })
+ expect.assertions(4)
+
return urlStrategyRetries(sendRequestMock, testEndpoints)
.catch(reason => expect(reason).toEqual({ code: 'NO_CONNECTION' }))
.then(() => {
@@ -99,6 +107,8 @@ describe('test url strategy', () => {
.mockImplementationOnce(() => Promise.reject({ code: 'NO_CONNECTION' }))
.mockImplementationOnce(() => Promise.resolve())
+ expect.assertions(1)
+
return urlStrategyRetries(sendRequestMock, testEndpoints)
.then(() => expect(sendRequestMock).toHaveBeenCalledTimes(2))
})
@@ -106,8 +116,111 @@ describe('test url strategy', () => {
it('does not iterate endpoints if another error happened', () => {
const sendRequestMock = jest.fn(() => Promise.reject({ code: 'UNKNOWN' }))
+ expect.assertions(1)
+
return urlStrategyRetries(sendRequestMock, testEndpoints)
.catch(() => expect(sendRequestMock).toHaveBeenCalledTimes(1))
})
+
+ })
+
+ describe('BaseUrlsIterator tests', () => {
+
+ const numberOfIterations = Object.keys(testEndpoints).length
+
+ const iterateThrough = (iterator: BaseUrlsIterator, iterationsNumber?: number) => {
+ const results: BaseUrlsMap[] = []
+ let current
+ let steps = iterationsNumber === undefined ? -1 : iterationsNumber
+
+ do {
+ current = iterator.next()
+ if (current.value) {
+ results.push(current.value)
+ }
+ } while (!current.done && --steps !== 0)
+
+ return results
+ }
+
+ it('returns all values through iteration when default url startegy used', () => {
+ const iterator = getBaseUrlsIterator(testEndpoints)
+
+ expect(iterator.next()).toEqual({value: testEndpoints.default, done: false})
+ expect(iterator.next()).toEqual({value: testEndpoints.india, done: false})
+ expect(iterator.next()).toEqual({value: testEndpoints.china, done: false})
+ expect(iterator.next()).toEqual({value: undefined, done: true})
+ })
+
+ it('prefers Indian enpoint and does not try reach Chinese one when india url strategy set', () => {
+ Config.set({ ...options, urlStrategy: UrlStrategy.India })
+
+ const values = iterateThrough(getBaseUrlsIterator(testEndpoints))
+
+ expect(values.length).toEqual(2)
+ expect(values[0]).toEqual(testEndpoints.india)
+ expect(values[1]).toEqual(testEndpoints.default)
+ })
+
+ it('prefers Chinese enpoint and does not try reach Indian one when china url strategy set', () => {
+ Config.set({ ...options, urlStrategy: UrlStrategy.China })
+
+ const values = iterateThrough(getBaseUrlsIterator(testEndpoints))
+
+ expect(values.length).toEqual(2)
+ expect(values[0]).toEqual(testEndpoints.china)
+ expect(values[1]).toEqual(testEndpoints.default)
+ })
+
+ it('does not override custom url', () => {
+ const customUrl = 'custom-url'
+ Config.set({ ...options, customUrl })
+
+ const values = iterateThrough(getBaseUrlsIterator(testEndpoints))
+
+ expect(values.length).toEqual(1)
+ expect(values[0]).toEqual({ app: 'custom-url', gdpr: 'custom-url' })
+ })
+
+ describe('reset allows to restart iteration', () => {
+
+ it('iterate through all endpoints twice', () => {
+ const iterator = getBaseUrlsIterator(testEndpoints)
+
+ const first = iterateThrough(iterator)
+
+ iterator.reset()
+
+ const second = iterateThrough(iterator)
+
+ expect(first.length).toEqual(numberOfIterations)
+ expect(second.length).toEqual(numberOfIterations)
+ expect(second).toEqual(first)
+ })
+
+ it('iterate partially then reset', () => {
+ const iterator = getBaseUrlsIterator(testEndpoints)
+
+ const firstIteration = iterateThrough(iterator, 1)
+ iterator.reset()
+ const secondIteration = iterateThrough(iterator, 2)
+ iterator.reset()
+ const thirdIteration = iterateThrough(iterator, 3)
+ iterator.reset()
+
+ expect(firstIteration.length).toEqual(1)
+ expect(secondIteration.length).toEqual(2)
+ expect(thirdIteration.length).toEqual(3)
+
+ expect(firstIteration[0]).toEqual(testEndpoints.default)
+ expect(secondIteration[0]).toEqual(testEndpoints.default)
+ expect(thirdIteration[0]).toEqual(testEndpoints.default)
+
+ expect(secondIteration[1]).toEqual(testEndpoints.india)
+ expect(thirdIteration[1]).toEqual(testEndpoints.india)
+
+ expect(thirdIteration[2]).toEqual(testEndpoints.china)
+ })
+ })
})
})
diff --git a/src/sdk/activity-state.js b/src/sdk/activity-state.js
index feedfe44..7e35d2ee 100644
--- a/src/sdk/activity-state.js
+++ b/src/sdk/activity-state.js
@@ -2,12 +2,14 @@
import {
type UrlT,
type ActivityStateMapT,
+ type AttributionMapT,
type CommonRequestParams
} from './types'
import {SECOND} from './constants'
import {timePassed} from './time'
import {isRequest} from './utilities'
import Config from './config'
+import Logger from './logger'
/**
* Reference to the activity state
@@ -308,6 +310,27 @@ function destroy (): void {
_active = false
}
+function getAttribution (): AttributionMapT | null {
+ if (!_started) {
+ return null
+ }
+
+ if (!_activityState.attribution) {
+ Logger.log('No attribution data yet')
+ return null
+ }
+
+ return _activityState.attribution
+}
+
+function getWebUUID (): string {
+ if (!_started) {
+ return null
+ }
+
+ return _activityState.uuid
+}
+
const ActivityState = {
get current () { return currentGetter() },
set current (value) { currentSetter(value) },
@@ -323,7 +346,9 @@ const ActivityState = {
updateSessionLength,
resetSessionOffset,
updateLastActive,
- destroy
+ destroy,
+ getAttribution,
+ getWebUUID
}
export default ActivityState
diff --git a/src/sdk/http.js b/src/sdk/http.js
index 8dd51ac8..7f32e5d4 100644
--- a/src/sdk/http.js
+++ b/src/sdk/http.js
@@ -14,7 +14,6 @@ import Logger from './logger'
import {isObject, isValidJson, isRequest, entries, isEmptyEntry, reducer} from './utilities'
import {publish} from './pub-sub'
import defaultParams from './default-params'
-import {urlStrategyRetries, BaseUrlsMap} from './url-strategy'
type ParamsWithAttemptsT = $PropertyType
@@ -175,13 +174,11 @@ function _handleReadyStateChange (reject, resolve, {xhr, url}: {xhr: XMLHttpRequ
* @returns {{encodedParams: string, fullUrl: string}}
* @private
*/
-function _prepareUrlAndParams ({url, method, params}: HttpRequestParamsT, defaultParams: DefaultParamsT, baseUrlsMap: BaseUrlsMap): {fullUrl: string, encodedParams: string} {
+function _prepareUrlAndParams ({endpoint, url, method, params}: HttpRequestParamsT, defaultParams: DefaultParamsT): {fullUrl: string, encodedParams: string} {
const encodedParams = _encodeParams(params, defaultParams)
- const base = url === '/gdpr_forget_device' ? 'gdpr' : 'app'
- const baseUrl = baseUrlsMap[base]
return {
- fullUrl: baseUrl + url + (method === 'GET' ? `?${encodedParams}` : ''),
+ fullUrl: endpoint + url + (method === 'GET' ? `?${encodedParams}` : ''),
encodedParams
}
}
@@ -217,8 +214,8 @@ function _prepareHeaders (xhr: XMLHttpRequest, method: $PropertyType {
- const {fullUrl, encodedParams} = _prepareUrlAndParams({url, method, params}, defaultParams, baseUrlsMap)
+function _buildXhr ({endpoint, url, method = 'GET', params = {}}: HttpRequestParamsT, defaultParams: DefaultParamsT): Promise {
+ const {fullUrl, encodedParams} = _prepareUrlAndParams({endpoint, url, method, params}, defaultParams)
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
@@ -232,10 +229,6 @@ function _buildXhr ({url, method = 'GET', params = {}}: HttpRequestParamsT, defa
})
}
-function _sendRequestWithUrlStrategyRetries (options: HttpRequestParamsT, defaultParams: DefaultParamsT) {
- return urlStrategyRetries(baseUrlsMap => _buildXhr(options, defaultParams, baseUrlsMap))
-}
-
/**
* Intercept response from backend
*
@@ -302,6 +295,6 @@ function _interceptSuccess (result: HttpSuccessResponseT, url): HttpSuccessRespo
*/
export default function http (options: HttpRequestParamsT): Promise {
return defaultParams()
- .then(defaultParams => _sendRequestWithUrlStrategyRetries(options, defaultParams))
+ .then(defaultParams => _buildXhr(options, defaultParams))
.then(result => _interceptResponse(result, options.url))
}
diff --git a/src/sdk/main.js b/src/sdk/main.js
index d8f97185..4c2d44b6 100644
--- a/src/sdk/main.js
+++ b/src/sdk/main.js
@@ -6,7 +6,8 @@ import {
type GlobalParamsT,
type CustomErrorT,
type ActivityStateMapT,
- type SmartBannerOptionsT
+ type SmartBannerOptionsT,
+ type AttributionMapT
} from './types'
import Config from './config'
import Storage from './storage/storage'
@@ -99,6 +100,24 @@ function initSdk ({logLevel, logOutput, ...options}: InitConfigT = {}): void {
})
}
+/**
+ * Get user's current attribution information
+ *
+ * @returns {AttributionMapT|undefined} current attribution information if available or `undefined` otherwise
+ */
+function getAttribution (): ?AttributionMapT {
+ return _preCheck('get attribution', () => ActivityState.getAttribution())
+}
+
+/**
+ * Get `web_uuid` - a unique ID of user generated per subdomain and per browser
+ *
+ * @returns {string|undefined} `web_uuid` if available or `undefined` otherwise
+ */
+function getWebUUID (): ?string {
+ return _preCheck('get web_uuid', () => ActivityState.getWebUUID())
+}
+
/**
* Track event with already initiated instance
*
@@ -459,7 +478,7 @@ function _start (options: InitOptionsT): void {
* @param {boolean=false} schedule
* @private
*/
-function _preCheck (description: string, callback: () => mixed, {schedule, stopBeforeInit}: {schedule?: boolean, stopBeforeInit?: boolean} = {}) {
+function _preCheck (description: string, callback: () => mixed, {schedule, stopBeforeInit}: {schedule?: boolean, stopBeforeInit?: boolean} = {}): mixed {
if (Storage.getType() === STORAGE_TYPES.NO_STORAGE) {
Logger.log(`Adjust SDK can not ${description}, no storage available`)
return
@@ -480,7 +499,7 @@ function _preCheck (description: string, callback: () => mixed, {schedule, stopB
delay(callback, description)
Logger.log(`Running ${description} is delayed until Adjust SDK is up`)
} else {
- callback()
+ return callback()
}
}
}
@@ -491,6 +510,8 @@ function _clearDatabase () {
const Adjust = {
initSdk,
+ getAttribution,
+ getWebUUID,
trackEvent,
addGlobalCallbackParameters,
addGlobalPartnerParameters,
diff --git a/src/sdk/request.js b/src/sdk/request.js
index 70efa0f1..f07de4e9 100644
--- a/src/sdk/request.js
+++ b/src/sdk/request.js
@@ -17,6 +17,7 @@ import Logger from './logger'
import backOff from './backoff'
import {isConnected} from './listeners'
import {SECOND, HTTP_ERRORS} from './constants'
+import {getBaseUrlsIterator, BaseUrlsIterator, BaseUrlsMap} from './url-strategy'
type RequestConfigT = {|
url?: UrlT,
@@ -91,6 +92,33 @@ const Request = ({url, method = 'GET', params = {}, continueCb, strategy, wait}:
*/
const _strategy: ?BackOffStrategyT = strategy
+ /**
+ * Url Startegy iterator to go through endpoints to retry to send request
+ */
+ const _baseUrlsIterator: BaseUrlsIterator = getBaseUrlsIterator()
+
+ /**
+ * Current base urls map to send request
+ */
+ let _baseUrlsIteratorCurrent: { value: BaseUrlsMap, done: boolean } = _baseUrlsIterator.next()
+
+
+ /**
+ * Reset iterator state and get the first endpoint to use it in the next try
+ */
+ const _resetBaseUrlsIterator = () => {
+ _baseUrlsIterator.reset()
+ _baseUrlsIteratorCurrent = _baseUrlsIterator.next()
+ }
+
+ /**
+ * Returns base url depending on request path
+ */
+ const _getBaseUrl = (urlsMap: BaseUrlsMap, url: UrlT): string => {
+ const base = url === '/gdpr_forget_device' ? 'gdpr' : 'app'
+ return urlsMap[base]
+ }
+
/**
* Timeout id to be used for clearing
*
@@ -236,6 +264,7 @@ const Request = ({url, method = 'GET', params = {}, continueCb, strategy, wait}:
_startAt = Date.now()
return _preRequest({
+ endpoint: _getBaseUrl(_baseUrlsIteratorCurrent.value, _url),
url: _url,
method: _method,
params: {
@@ -287,6 +316,7 @@ const Request = ({url, method = 'GET', params = {}, continueCb, strategy, wait}:
.reduce(reducer, {})
return http({
+ endpoint: options.endpoint,
url: options.url,
method: options.method,
params: {
@@ -365,6 +395,8 @@ const Request = ({url, method = 'GET', params = {}, continueCb, strategy, wait}:
return
}
+ _resetBaseUrlsIterator()
+
if (typeof _continueCb === 'function') {
_continueCb(result, _finish, _retry)
} else {
@@ -384,7 +416,21 @@ const Request = ({url, method = 'GET', params = {}, continueCb, strategy, wait}:
*/
function _error (result: HttpErrorResponseT, resolve, reject): void {
if (result && result.action === 'RETRY') {
- resolve(_retry(result.code === 'NO_CONNECTION' ? NO_CONNECTION_WAIT : undefined))
+
+ if (result.code === 'NO_CONNECTION') {
+
+ const nextEndpoint = _baseUrlsIterator.next() // get next endpoint
+
+ if (!nextEndpoint.done) { // next endpoint exists
+ _baseUrlsIteratorCurrent = nextEndpoint // use the endpoint in the next try
+ resolve(_retry(DEFAULT_WAIT))
+ } else { // no more endpoints, seems there is no connection at all
+ _resetBaseUrlsIterator()
+ resolve(_retry(NO_CONNECTION_WAIT))
+ }
+ } else {
+ resolve(_retry())
+ }
return
}
diff --git a/src/sdk/types.js b/src/sdk/types.js
index 8d8ecc3b..2775f947 100644
--- a/src/sdk/types.js
+++ b/src/sdk/types.js
@@ -84,6 +84,7 @@ export type RequestParamsT = $Shape<{|
|}>
export type HttpRequestParamsT = $ReadOnly<{|
+ endpoint: string,
url: UrlT,
method?: MethodT,
params: $ReadOnly<{|
diff --git a/src/sdk/url-strategy.ts b/src/sdk/url-strategy.ts
index 87a263ed..2d59419d 100644
--- a/src/sdk/url-strategy.ts
+++ b/src/sdk/url-strategy.ts
@@ -97,4 +97,42 @@ function urlStrategyRetries(
}
}
-export { urlStrategyRetries, UrlStrategy, BaseUrlsMap }
+interface BaseUrlsIterator extends Iterator {
+ reset: () => void;
+}
+
+function getPreferredUrls(endpoints: Partial>): BaseUrlsMap[] {
+ const preferredUrls = getEndpointPreference()
+
+ if (!Array.isArray(preferredUrls)) {
+ return [preferredUrls]
+ } else {
+ const res = preferredUrls
+ .map(strategy => endpoints[strategy] || null)
+ .filter((i): i is BaseUrlsMap => !!i)
+
+ return res
+ }
+}
+
+function getBaseUrlsIterator(endpoints: Partial> = endpointMap): BaseUrlsIterator {
+ const _urls = getPreferredUrls(endpoints)
+
+ let _counter = 0
+
+ return {
+ next: () => {
+ if (_counter < _urls.length) {
+ return { value: _urls[_counter++], done: false }
+ } else {
+ return { value: undefined, done: true }
+ }
+ },
+ reset: () => {
+ _counter = 0
+ }
+ }
+}
+
+
+export { urlStrategyRetries, getBaseUrlsIterator, BaseUrlsIterator, UrlStrategy, BaseUrlsMap }