analytics-client
Advanced tools
Comparing version 0.1.0-roman-url-params-c6b3a1ce20618c2b4dbb9082e6aeac10598a5796 to 0.1.0
@@ -9,2 +9,3 @@ # Change Log | ||
* Avoid calling mixpanel in constructor [Roman Mazur] | ||
* Add URL query string tools [Roman Mazur] |
import { Mixpanel } from 'mixpanel-browser'; | ||
export declare class AnalyticsUrlParams { | ||
private mixpanel?; | ||
private readonly cookiesSupported; | ||
private deviceIds; | ||
constructor(mixpanel?: Mixpanel | undefined); | ||
private storeInboundDeviceIds; | ||
private retrieveAndMerge; | ||
private takeFirstId; | ||
static clearCookies(): void; | ||
private setDeviceIds; | ||
clearCookies(): void; | ||
consumeUrlParameters(queryString: string): string | null; | ||
allDeviceIds(): string; | ||
deviceIdQuery(): string; | ||
allDeviceIds(): string[]; | ||
getDeviceIdsQueryString(): string; | ||
} |
{ | ||
"name": "analytics-client", | ||
"version": "0.1.0-roman-url-params-c6b3a1ce20618c2b4dbb9082e6aeac10598a5796", | ||
"version": "0.1.0", | ||
"description": "Convenient builders to compose analytics tools", | ||
@@ -16,3 +16,3 @@ "repository": { | ||
"lint": "resin-lint --typescript src/ test/ && tsc --noEmit", | ||
"build": "npm run test && webpack", | ||
"build": "npm run test && tsc && webpack", | ||
"prepublish": "npm run build" | ||
@@ -22,3 +22,2 @@ }, | ||
"@analytics/cookie-utils": "^0.2.2", | ||
"lodash": "^4.17.15", | ||
"typescript": "^3.6.3" | ||
@@ -30,3 +29,5 @@ }, | ||
"@types/mixpanel-browser": "^2.23.1", | ||
"husky": "^3.0.9", | ||
"jest": "^24.9.0", | ||
"lint-staged": "^9.4.2", | ||
"mixpanel-browser": "^2.29.1", | ||
@@ -38,3 +39,19 @@ "resin-lint": "^3.1.0", | ||
"webpack-cli": "^3.3.9" | ||
}, | ||
"lint-staged": { | ||
"*.ts": [ | ||
"prettier --config ./node_modules/resin-lint/config/.prettierrc --write", | ||
"resin-lint --typescript --no-prettier", | ||
"git add" | ||
], | ||
"test/**/*.ts": [ | ||
"resin-lint --typescript --no-prettier --tests" | ||
] | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged", | ||
"pre-push": "npm run lint" | ||
} | ||
} | ||
} |
Analytics client | ||
================ | ||
Client side analytics tools. | ||
Client part of analytics services used at balena. | ||
@@ -23,3 +23,3 @@ ## Installation | ||
const signupUrl = '/signup?' + analytics.deviceIdQuery(); | ||
const signupUrl = '/signup?' + analytics.getDeviceIdsQueryString(); | ||
``` |
@@ -1,6 +0,5 @@ | ||
import * as _ from 'lodash'; | ||
import { | ||
getCookie, | ||
hasCookieSupport, | ||
setCookie, | ||
hasCookieSupport, | ||
} from '@analytics/cookie-utils'; | ||
@@ -14,4 +13,2 @@ import { Mixpanel } from 'mixpanel-browser'; | ||
const cookiesSupported = hasCookieSupport(); | ||
/** | ||
@@ -22,31 +19,29 @@ * AnalyticsUrlParams helps with handling analytics-related URL parameters | ||
export class AnalyticsUrlParams { | ||
private deviceIds: string | null; | ||
private readonly cookiesSupported = hasCookieSupport(); | ||
private deviceIds: Set<string> = new Set(); | ||
constructor(private mixpanel?: Mixpanel) {} | ||
constructor(private mixpanel?: Mixpanel) { | ||
const storedValue = this.cookiesSupported | ||
? getCookie(COOKIES_DEVICE_IDS) | ||
: null; | ||
this.setDeviceIds(storedValue, null); | ||
} | ||
private storeInboundDeviceIds(ids: string, currentDeviceId: string | null) { | ||
const list = ids ? ids.split(deviceIdSeparator) : []; | ||
private setDeviceIds( | ||
inputIdString: string | null, | ||
currentDeviceId: string | null, | ||
) { | ||
const list = inputIdString ? inputIdString.split(deviceIdSeparator) : []; | ||
if (currentDeviceId) { | ||
list.push(currentDeviceId); | ||
} | ||
const res = this.retrieveAndMerge(list); | ||
this.deviceIds = res; | ||
if (cookiesSupported) { | ||
setCookie(COOKIES_DEVICE_IDS, res); | ||
this.deviceIds = new Set(list.concat(Array.from(this.deviceIds))); | ||
if (this.cookiesSupported) { | ||
setCookie(COOKIES_DEVICE_IDS, Array.from(this.deviceIds).join(',')); | ||
} | ||
return res; | ||
return list.length > 0 ? list[0] : null; | ||
} | ||
private retrieveAndMerge(input: string[]) { | ||
const storedValue = cookiesSupported ? getCookie(COOKIES_DEVICE_IDS) : null; | ||
const storedList = storedValue ? storedValue.split(deviceIdSeparator) : []; | ||
return _.union(storedList, input).join(','); | ||
} | ||
private takeFirstId(passedId: string) { | ||
return passedId.split(deviceIdSeparator, 1)[0]; | ||
} | ||
static clearCookies() { | ||
if (cookiesSupported) { | ||
clearCookies() { | ||
if (this.cookiesSupported) { | ||
setCookie(COOKIES_DEVICE_IDS, ''); | ||
@@ -68,19 +63,20 @@ } | ||
if (passedDeviceId) { | ||
let originalMpId: string | null = null; | ||
const originalMixpanelId = | ||
this.mixpanel != null ? this.mixpanel.get_distinct_id() : null; | ||
if (this.mixpanel != null) { | ||
originalMpId = this.mixpanel.get_distinct_id(); | ||
const newCurrentDeviceId = this.setDeviceIds( | ||
passedDeviceId, | ||
originalMixpanelId, | ||
); | ||
if (this.mixpanel != null && newCurrentDeviceId) { | ||
// Switch mixpanel ID to using the passed device ID, so we can track events in one timeline branch. | ||
// Previous user activity recorded with another device ID will be merged upon signup, | ||
// since we store the previous ID to pass it to other sites. | ||
const mpId = this.takeFirstId(passedDeviceId); | ||
this.mixpanel.register({ | ||
distinct_id: mpId, | ||
$device_id: mpId, | ||
distinct_id: newCurrentDeviceId, | ||
$device_id: newCurrentDeviceId, | ||
}); | ||
} | ||
this.storeInboundDeviceIds(passedDeviceId, originalMpId); | ||
params.delete(URL_PARAM_DEVICE_ID); | ||
@@ -94,10 +90,12 @@ return params.toString(); | ||
/** | ||
* @return all anonymous device IDs that can be passed to other sites, separated with comma | ||
* @return all anonymous device IDs that can be passed to other sites | ||
*/ | ||
allDeviceIds(): string { | ||
if (this.deviceIds == null) { | ||
const knownIds = this.mixpanel == null ? [] : [ this.mixpanel.get_distinct_id() ]; | ||
this.deviceIds = this.retrieveAndMerge(knownIds); | ||
allDeviceIds() { | ||
const mixpanelId = this.mixpanel ? this.mixpanel.get_distinct_id() : null; | ||
if (mixpanelId == null) { | ||
return Array.from(this.deviceIds); | ||
} | ||
return this.deviceIds!; | ||
const res = new Set(this.deviceIds); | ||
res.add(mixpanelId); | ||
return Array.from(res); | ||
} | ||
@@ -108,9 +106,9 @@ | ||
*/ | ||
deviceIdQuery(): string { | ||
getDeviceIdsQueryString(): string { | ||
const ids = this.allDeviceIds(); | ||
if (ids === '') { | ||
if (ids.length === 0) { | ||
return ''; | ||
} | ||
return `${URL_PARAM_DEVICE_ID}=${encodeURIComponent(ids)}`; | ||
return `${URL_PARAM_DEVICE_ID}=${encodeURIComponent(ids.join(','))}`; | ||
} | ||
} |
import { Mixpanel } from 'mixpanel-browser'; | ||
import { AnalyticsUrlParams } from '../src/url-params'; | ||
beforeEach(AnalyticsUrlParams.clearCookies); | ||
beforeEach(() => new AnalyticsUrlParams().clearCookies()); | ||
@@ -29,4 +29,6 @@ test('remove device ID from query string', () => { | ||
const urlParams = new AnalyticsUrlParams(); | ||
urlParams.consumeUrlParameters('d_id=' + encodeURIComponent('d1,d2,d3') + '&other=value'); | ||
expect(urlParams.allDeviceIds()).toBe('d1,d2,d3'); | ||
urlParams.consumeUrlParameters( | ||
'd_id=' + encodeURIComponent('d1,d2,d3') + '&other=value', | ||
); | ||
expect(urlParams.allDeviceIds()).toStrictEqual(['d1', 'd2', 'd3']); | ||
}); | ||
@@ -36,6 +38,8 @@ | ||
const urlParams = new AnalyticsUrlParams(); | ||
expect(urlParams.deviceIdQuery()).toBe(''); | ||
expect(urlParams.getDeviceIdsQueryString()).toBe(''); | ||
urlParams.consumeUrlParameters('d_id=d1,d2,d3&other=value'); | ||
expect(urlParams.deviceIdQuery()).toBe('d_id=' + encodeURIComponent('d1,d2,d3')); | ||
expect(urlParams.getDeviceIdsQueryString()).toBe( | ||
'd_id=' + encodeURIComponent('d1,d2,d3'), | ||
); | ||
}); | ||
@@ -51,2 +55,3 @@ | ||
registerParams: any; | ||
distinctIdRetrieved: boolean; | ||
} | ||
@@ -57,4 +62,8 @@ | ||
registerParams: null, | ||
distinctIdRetrieved: false, | ||
get_distinct_id: () => 'test_mp_distinct_id', | ||
get_distinct_id() { | ||
this.distinctIdRetrieved = true; | ||
return 'test_mp_distinct_id'; | ||
}, | ||
register(params: any) { | ||
@@ -81,2 +90,7 @@ this.registerParams = params; | ||
test("don't call mixpanel in constructor", () => { | ||
const [, mock] = mpUrlParameters(); | ||
expect(mock.distinctIdRetrieved).toBeFalsy(); | ||
}); | ||
test('update mixpanel state', () => { | ||
@@ -110,5 +124,10 @@ const [urlParams, mp] = mpUrlParameters(); | ||
const [urlParams] = mpUrlParameters(); | ||
expect(urlParams.allDeviceIds()).toBe('test_mp_distinct_id'); | ||
expect(urlParams.allDeviceIds()).toStrictEqual(['test_mp_distinct_id']); | ||
urlParams.consumeUrlParameters('d_id=d1,d2,d3&other=value'); | ||
expect(urlParams.allDeviceIds()).toBe('d1,d2,d3,test_mp_distinct_id'); | ||
expect(urlParams.allDeviceIds()).toStrictEqual([ | ||
'd1', | ||
'd2', | ||
'd3', | ||
'test_mp_distinct_id', | ||
]); | ||
}); | ||
@@ -119,9 +138,14 @@ | ||
new AnalyticsUrlParams().consumeUrlParameters('d_id=2'); | ||
expect(new AnalyticsUrlParams().allDeviceIds()).toBe('1,2'); | ||
expect(new AnalyticsUrlParams().allDeviceIds()).toContain('1'); | ||
expect(new AnalyticsUrlParams().allDeviceIds()).toContain('2'); | ||
}); | ||
test('encodes URI component', () => { | ||
new AnalyticsUrlParams().consumeUrlParameters('d_id=' + encodeURIComponent('%%$!!')); | ||
expect(new AnalyticsUrlParams().allDeviceIds()).toBe('%%$!!'); | ||
expect(new AnalyticsUrlParams().deviceIdQuery()).toBe('d_id=%25%25%24!!'); | ||
new AnalyticsUrlParams().consumeUrlParameters( | ||
'd_id=' + encodeURIComponent('%%$!!'), | ||
); | ||
expect(new AnalyticsUrlParams().allDeviceIds()).toStrictEqual(['%%$!!']); | ||
expect(new AnalyticsUrlParams().getDeviceIdsQueryString()).toBe( | ||
'd_id=%25%25%24!!', | ||
); | ||
}); |
@@ -21,3 +21,5 @@ const path = require('path'); | ||
path: path.resolve(__dirname, 'dist'), | ||
libraryTarget: 'var', | ||
library: 'analyticsClient' | ||
} | ||
}; |
Sorry, the diff of this file is too big to display
2
18
501
22910
12
- Removedlodash@^4.17.15
- Removedlodash@4.17.21(transitive)