Skip to content

Commit 8f8ba4b

Browse files
committed
Fix issues with device identification in react native
1 parent df71208 commit 8f8ba4b

File tree

10 files changed

+164
-62
lines changed

10 files changed

+164
-62
lines changed

.jshintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"globals" : { // additional predefined global variables
8888
"BUILD_COMPAT_2_0": true,
8989
"BUILD_COMPAT_SNIPPET": true,
90-
"BUILD_COMPAT_LOCAL_STORAGE": true
90+
"BUILD_COMPAT_LOCAL_STORAGE": true,
91+
"BUILD_COMPAT_REACT_NATIVE": true
9192
}
9293
}

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,11 @@ README.md: $(SNIPPET_OUT) version
6464
# Target for `amplitude.js` file.
6565
#
6666

67-
$(OUT): node_modules $(SRC) package.json rollup.config.js rollup.min.js
67+
$(OUT): node_modules $(SRC) package.json rollup.config.js rollup.min.js rollup.native.js rollup.esm.js
6868
@$(JSHINT) --verbose $(SRC)
6969
@NODE_ENV=production $(ROLLUP) --config rollup.config.js
7070
@NODE_ENV=production $(ROLLUP) --config rollup.esm.js
71+
@NODE_ENV=production $(ROLLUP) --config rollup.native.js
7172
@NODE_ENV=production $(ROLLUP) --config rollup.nocompat.js
7273
@NODE_ENV=production $(ROLLUP) --config rollup.min.js
7374
@NODE_ENV=production $(ROLLUP) --config rollup.nocompat.min.js

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"repository": "git://github.com/amplitude/amplitude-javascript.git",
1212
"main": "amplitude.js",
1313
"module": "amplitude.esm.js",
14+
"react-native": "amplitude.native.js",
1415
"dependencies": {
1516
"@amplitude/ua-parser-js": "0.7.20",
1617
"blueimp-md5": "^2.10.0",

rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default {
2424
BUILD_COMPAT_SNIPPET: 'true',
2525
BUILD_COMPAT_2_0: 'true',
2626
BUILD_COMPAT_LOCAL_STORAGE: 'true',
27+
BUILD_COMPAT_REACT_NATIVE: 'false',
2728
}),
2829
commonjs(),
2930
],

rollup.esm.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default {
2323
replace({
2424
BUILD_COMPAT_SNIPPET: 'false',
2525
BUILD_COMPAT_2_0: 'false',
26+
BUILD_COMPAT_REACT_NATIVE: 'false',
2627
BUILD_COMPAT_LOCAL_STORAGE: 'false',
2728
}),
2829
commonjs({

rollup.nocompat.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default {
2323
replace({
2424
BUILD_COMPAT_SNIPPET: 'false',
2525
BUILD_COMPAT_2_0: 'false',
26+
BUILD_COMPAT_REACT_NATIVE: 'false',
2627
BUILD_COMPAT_LOCAL_STORAGE: 'false',
2728
}),
2829
commonjs({

src/amplitude-client.js

Lines changed: 139 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ import UUID from './uuid';
1313
import { version } from '../package.json';
1414
import DEFAULT_OPTIONS from './options';
1515

16+
let asyncStorage;
17+
let Platform;
18+
let DeviceInfo;
19+
if (BUILD_COMPAT_REACT_NATIVE) {
20+
const reactNative = require('react-native');
21+
Platform = reactNative.Platform;
22+
asyncStorage = reactNative.AsyncStorage;
23+
try {
24+
DeviceInfo = require('react-native-device-info');
25+
} catch(e) {
26+
}
27+
}
28+
1629
/**
1730
* AmplitudeClient SDK API - instance constructor.
1831
* The Amplitude class handles creation of client instances, all you need to do is call amplitude.getInstance()
@@ -86,74 +99,105 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o
8699
this.options.domain = this.cookieStorage.options().domain;
87100

88101
_loadCookieData(this);
102+
this._pendingReadStorage = true;
103+
104+
const initFromStorage = () => {
105+
// load deviceId and userId from input, or try to fetch existing value from cookie
106+
this.options.deviceId = (type(opt_config) === 'object' && type(opt_config.deviceId) === 'string' &&
107+
!utils.isEmptyString(opt_config.deviceId) && opt_config.deviceId) ||
108+
(this.options.deviceIdFromUrlParam && this._getDeviceIdFromUrlParam(this._getUrlParams())) ||
109+
this.options.deviceId || UUID() + 'R';
110+
this.options.userId =
111+
(type(opt_userId) === 'string' && !utils.isEmptyString(opt_userId) && opt_userId) ||
112+
(type(opt_userId) === 'number' && opt_userId.toString()) ||
113+
this.options.userId || null;
114+
115+
var now = new Date().getTime();
116+
if (!this._sessionId || !this._lastEventTime || now - this._lastEventTime > this.options.sessionTimeout) {
117+
if (this.options.unsetParamsReferrerOnNewSession) {
118+
this._unsetUTMParams();
119+
}
120+
this._newSession = true;
121+
this._sessionId = now;
89122

90-
// load deviceId and userId from input, or try to fetch existing value from cookie
91-
this.options.deviceId = (type(opt_config) === 'object' && type(opt_config.deviceId) === 'string' &&
92-
!utils.isEmptyString(opt_config.deviceId) && opt_config.deviceId) ||
93-
(this.options.deviceIdFromUrlParam && this._getDeviceIdFromUrlParam(this._getUrlParams())) ||
94-
this.options.deviceId || UUID() + 'R';
95-
this.options.userId =
96-
(type(opt_userId) === 'string' && !utils.isEmptyString(opt_userId) && opt_userId) ||
97-
(type(opt_userId) === 'number' && opt_userId.toString()) ||
98-
this.options.userId || null;
99-
100-
// load unsent events and identifies before any attempt to log new ones
101-
if (this.options.saveEvents) {
102-
this._unsentEvents = this._loadSavedUnsentEvents(this.options.unsentKey);
103-
this._unsentIdentifys = this._loadSavedUnsentEvents(this.options.unsentIdentifyKey);
104-
105-
// validate event properties for unsent events
106-
for (var i = 0; i < this._unsentEvents.length; i++) {
107-
var eventProperties = this._unsentEvents[i].event_properties;
108-
var groups = this._unsentEvents[i].groups;
109-
this._unsentEvents[i].event_properties = utils.validateProperties(eventProperties);
110-
this._unsentEvents[i].groups = utils.validateGroups(groups);
123+
// only capture UTM params and referrer if new session
124+
if (this.options.saveParamsReferrerOncePerSession) {
125+
this._trackParamsAndReferrer();
126+
}
111127
}
112128

113-
// validate user properties for unsent identifys
114-
for (var j = 0; j < this._unsentIdentifys.length; j++) {
115-
var userProperties = this._unsentIdentifys[j].user_properties;
116-
var identifyGroups = this._unsentIdentifys[j].groups;
117-
this._unsentIdentifys[j].user_properties = utils.validateProperties(userProperties);
118-
this._unsentIdentifys[j].groups = utils.validateGroups(identifyGroups);
129+
if (!this.options.saveParamsReferrerOncePerSession) {
130+
this._trackParamsAndReferrer();
119131
}
120-
}
121132

122-
var now = new Date().getTime();
123-
if (!this._sessionId || !this._lastEventTime || now - this._lastEventTime > this.options.sessionTimeout) {
124-
if (this.options.unsetParamsReferrerOnNewSession) {
125-
this._unsetUTMParams();
133+
// load unsent events and identifies before any attempt to log new ones
134+
if (this.options.saveEvents) {
135+
this._unsentEvents = this._loadSavedUnsentEvents(this.options.unsentKey).concat(this._unsentEvents);
136+
this._unsentIdentifys = this._loadSavedUnsentEvents(this.options.unsentIdentifyKey).concat(this._unsentIdentifys);
137+
138+
// validate event properties for unsent events
139+
for (let i = 0; i < this._unsentEvents.length; i++) {
140+
var eventProperties = this._unsentEvents[i].event_properties;
141+
var groups = this._unsentEvents[i].groups;
142+
this._unsentEvents[i].event_properties = utils.validateProperties(eventProperties);
143+
this._unsentEvents[i].groups = utils.validateGroups(groups);
144+
}
145+
146+
// validate user properties for unsent identifys
147+
for (let j = 0; j < this._unsentIdentifys.length; j++) {
148+
var userProperties = this._unsentIdentifys[j].user_properties;
149+
var identifyGroups = this._unsentIdentifys[j].groups;
150+
this._unsentIdentifys[j].user_properties = utils.validateProperties(userProperties);
151+
this._unsentIdentifys[j].groups = utils.validateGroups(identifyGroups);
152+
}
126153
}
127-
this._newSession = true;
128-
this._sessionId = now;
129154

130-
// only capture UTM params and referrer if new session
131-
if (this.options.saveParamsReferrerOncePerSession) {
132-
this._trackParamsAndReferrer();
155+
this._lastEventTime = now;
156+
_saveCookieData(this);
157+
158+
this._pendingReadStorage = false;
159+
160+
this._sendEventsIfReady(); // try sending unsent events
161+
162+
for (let i = 0; i < this._onInit.length; i++) {
163+
this._onInit[i]();
133164
}
134-
}
165+
this._onInit = [];
166+
this._isInitialized = true;
167+
};
135168

136-
if (!this.options.saveParamsReferrerOncePerSession) {
137-
this._trackParamsAndReferrer();
169+
if (asyncStorage) {
170+
asyncStorage.getItem(this._storageSuffix).then((json) => {
171+
const cookieData = JSON.parse(json);
172+
if (cookieData) {
173+
_loadCookieDataProps(this, cookieData);
174+
}
175+
if (DeviceInfo) {
176+
Promise.all([DeviceInfo.getCarrier(),DeviceInfo.getModel(), DeviceInfo.getManufacturer()]).then(values => {
177+
this.deviceInfo = {
178+
carrier: values[0],
179+
model: values[1],
180+
manufacturer: values[2]
181+
};
182+
initFromStorage();
183+
this.runQueuedFunctions();
184+
});
185+
} else {
186+
initFromStorage();
187+
this.runQueuedFunctions();
188+
}
189+
});
190+
} else {
191+
initFromStorage();
138192
}
139193

140-
this._lastEventTime = now;
141-
_saveCookieData(this);
142-
143-
this._sendEventsIfReady(); // try sending unsent events
144194
} catch (e) {
145195
utils.log.error(e);
146196
} finally {
147197
if (type(opt_callback) === 'function') {
148198
opt_callback(this);
149199
}
150200
}
151-
152-
for (let i = 0; i < this._onInit.length; i++) {
153-
this._onInit[i]();
154-
}
155-
this._onInit = [];
156-
this._isInitialized = true;
157201
};
158202

159203
/**
@@ -436,7 +480,7 @@ var _loadCookieDataProps = function _loadCookieDataProps(scope, cookieData) {
436480
* @private
437481
*/
438482
var _saveCookieData = function _saveCookieData(scope) {
439-
scope.cookieStorage.set(scope.options.cookieName + scope._storageSuffix, {
483+
const cookieData = {
440484
deviceId: scope.options.deviceId,
441485
userId: scope.options.userId,
442486
optOut: scope.options.optOut,
@@ -445,7 +489,11 @@ var _saveCookieData = function _saveCookieData(scope) {
445489
eventId: scope._eventId,
446490
identifyId: scope._identifyId,
447491
sequenceNumber: scope._sequenceNumber
448-
});
492+
};
493+
if (asyncStorage) {
494+
asyncStorage.setItem(scope._storageSuffix, JSON.stringify(cookieData));
495+
}
496+
scope.cookieStorage.set(scope.options.cookieName + scope._storageSuffix, cookieData);
449497
};
450498

451499
/**
@@ -721,6 +769,9 @@ AmplitudeClient.prototype.setDeviceId = function setDeviceId(deviceId) {
721769
* @example amplitudeClient.setUserProperties({'gender': 'female', 'sign_up_complete': true})
722770
*/
723771
AmplitudeClient.prototype.setUserProperties = function setUserProperties(userProperties) {
772+
if (this._pendingReadStorage) {
773+
return this._q.push(['identify', userProperties]);
774+
}
724775
if (!this._apiKeySet('setUserProperties()') || !utils.validateInput(userProperties, 'userProperties', 'object')) {
725776
return;
726777
}
@@ -782,6 +833,9 @@ var _convertProxyObjectToRealObject = function _convertProxyObjectToRealObject(i
782833
* amplitude.identify(identify);
783834
*/
784835
AmplitudeClient.prototype.identify = function(identify_obj, opt_callback) {
836+
if (this._pendingReadStorage) {
837+
return this._q.push(['identify', identify_obj, opt_callback]);
838+
}
785839
if (!this._apiKeySet('identify()')) {
786840
if (type(opt_callback) === 'function') {
787841
opt_callback(0, 'No request sent', {reason: 'API key is not set'});
@@ -814,6 +868,9 @@ AmplitudeClient.prototype.identify = function(identify_obj, opt_callback) {
814868
};
815869

816870
AmplitudeClient.prototype.groupIdentify = function(group_type, group_name, identify_obj, opt_callback) {
871+
if (this._pendingReadStorage) {
872+
return this._q.push(['groupIdentify', group_type, group_name, identify_obj, opt_callback]);
873+
}
817874
if (!this._apiKeySet('groupIdentify()')) {
818875
if (type(opt_callback) === 'function') {
819876
opt_callback(0, 'No request sent', {reason: 'API key is not set'});
@@ -907,6 +964,21 @@ AmplitudeClient.prototype._logEvent = function _logEvent(eventType, eventPropert
907964
this._lastEventTime = eventTime;
908965
_saveCookieData(this);
909966

967+
let osName = this._ua.browser.name;
968+
let osVersion = this._ua.browser.major;
969+
let deviceModel = this._ua.os.name;
970+
let deviceManufacturer;
971+
let carrier;
972+
if (BUILD_COMPAT_REACT_NATIVE) {
973+
osName = Platform.OS;
974+
osVersion = Platform.Version;
975+
if (this.deviceInfo) {
976+
carrier = this.deviceInfo.carrier;
977+
deviceManufacturer = this.deviceInfo.manufacturer;
978+
deviceModel = this.deviceInfo.model;
979+
}
980+
}
981+
910982
userProperties = userProperties || {};
911983
var trackingOptions = {...this._apiPropertiesTrackingOptions};
912984
apiProperties = {...(apiProperties || {}), ...trackingOptions};
@@ -922,10 +994,12 @@ AmplitudeClient.prototype._logEvent = function _logEvent(eventType, eventPropert
922994
event_type: eventType,
923995
version_name: _shouldTrackField(this, 'version_name') ? (this.options.versionName || null) : null,
924996
platform: _shouldTrackField(this, 'platform') ? this.options.platform : null,
925-
os_name: _shouldTrackField(this, 'os_name') ? (this._ua.browser.name || null) : null,
926-
os_version: _shouldTrackField(this, 'os_version') ? (this._ua.browser.major || null) : null,
927-
device_model: _shouldTrackField(this, 'device_model') ? (this._ua.os.name || null) : null,
997+
os_name: _shouldTrackField(this, 'os_name') ? (osName || null) : null,
998+
os_version: _shouldTrackField(this, 'os_version') ? (osVersion || null) : null,
999+
device_model: _shouldTrackField(this, 'device_model') ? (deviceModel || null) : null,
1000+
device_manufacturer: _shouldTrackField(this, 'device_manufacturer') ? (deviceManufacturer || null) : null,
9281001
language: _shouldTrackField(this, 'language') ? this.options.language : null,
1002+
carrier: _shouldTrackField(this, 'carrier') ? (carrier || null): null,
9291003
api_properties: apiProperties,
9301004
event_properties: utils.truncate(utils.validateProperties(eventProperties)),
9311005
user_properties: utils.truncate(utils.validateProperties(userProperties)),
@@ -1007,6 +1081,9 @@ AmplitudeClient.prototype._limitEventsQueued = function _limitEventsQueued(queue
10071081
* @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15});
10081082
*/
10091083
AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventProperties, opt_callback) {
1084+
if (this._pendingReadStorage) {
1085+
return this._q.push(['logEvent', eventType, eventProperties, opt_callback]);
1086+
}
10101087
return this.logEventWithTimestamp(eventType, eventProperties, null, opt_callback);
10111088
};
10121089

@@ -1021,6 +1098,9 @@ AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventPropertie
10211098
* @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15});
10221099
*/
10231100
AmplitudeClient.prototype.logEventWithTimestamp = function logEvent(eventType, eventProperties, timestamp, opt_callback) {
1101+
if (this._pendingReadStorage) {
1102+
return this._q.push(['logEventWithTimestamp', eventType, eventProperties, timestamp, opt_callback]);
1103+
}
10241104
if (!this._apiKeySet('logEvent()')) {
10251105
if (type(opt_callback) === 'function') {
10261106
opt_callback(0, 'No request sent', {reason: 'API key not set'});
@@ -1058,6 +1138,9 @@ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent(eventType, e
10581138
* @example amplitudeClient.logEventWithGroups('Clicked Button', null, {'orgId': 24});
10591139
*/
10601140
AmplitudeClient.prototype.logEventWithGroups = function(eventType, eventProperties, groups, opt_callback) {
1141+
if (this._pendingReadStorage) {
1142+
return this._q.push(['logEventWithGroups', eventType, eventProperties, groups, opt_callback]);
1143+
}
10611144
if (!this._apiKeySet('logEventWithGroups()')) {
10621145
if (type(opt_callback) === 'function') {
10631146
opt_callback(0, 'No request sent', {reason: 'API key not set'});
@@ -1193,7 +1276,7 @@ AmplitudeClient.prototype.sendEvents = function sendEvents(callback) {
11931276
}
11941277
if (this._sending) {
11951278
if (type(callback) === 'function') {
1196-
callback(0, 'No request sent', {reason: 'Request already in progress'});
1279+
callback(0, 'No request sent', {reason: 'Request already in progress. Events will be sent once this request is complete'});
11971280
}
11981281
return;
11991282
}

src/cookiestorage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ cookieStorage.prototype.getStorage = function() {
5959
opts = opts || {};
6060
this._options.expirationDays = opts.expirationDays || this._options.expirationDays;
6161
// localStorage is specific to subdomains
62-
this._options.domain = opts.domain || this._options.domain || window.location.hostname;
62+
this._options.domain = opts.domain || this._options.domain || window && window.location && window.location.hostname;
6363
return this._options.secure = opts.secure || false;
6464
},
6565
get: function(name) {

src/localstorage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ if (BUILD_COMPAT_LOCAL_STORAGE) {
3636
} catch (e) {
3737
// Something bad happened...
3838
}
39-
} else {
39+
} else if (typeof document !== 'undefined') {
4040
// IE 5-7 use userData
4141
// See http://msdn.microsoft.com/en-us/library/ms531424(v=vs.85).aspx
4242
var div = document.createElement('div'),

0 commit comments

Comments
 (0)