Skip to content

Commit dcfd2c5

Browse files
cristeahubfathyb
authored andcommitted
fix(core): fix duplicate client error in dev mode
With this change the setup method will now resolve its promises and thereby be able to catch errors during setup. Fixes #16
1 parent a161af9 commit dcfd2c5

File tree

7 files changed

+121
-55
lines changed

7 files changed

+121
-55
lines changed

packages/core/android/src/main/java/com/segment/analytics/reactnative/core/RNAnalyticsModule.kt

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,36 @@
2424

2525
package com.segment.analytics.reactnative.core
2626

27-
import com.facebook.react.bridge.ReactApplicationContext
28-
import com.facebook.react.bridge.ReactContextBaseJavaModule
29-
import com.facebook.react.bridge.ReactMethod
30-
import com.facebook.react.bridge.ReadableMap
27+
import com.facebook.react.bridge.*
3128
import com.segment.analytics.Analytics
3229
import com.segment.analytics.Properties
3330
import com.segment.analytics.Traits
3431
import com.segment.analytics.ValueMap
3532
import java.util.concurrent.TimeUnit
3633

3734
class RNAnalyticsModule(context: ReactApplicationContext): ReactContextBaseJavaModule(context) {
35+
override fun getName() = "RNAnalytics"
36+
3837
private val analytics
3938
get() = Analytics.with(reactApplicationContext)
4039

41-
override fun getName() = "RNAnalytics"
40+
companion object {
41+
private var singletonJsonConfig: String? = null
42+
}
4243

4344
@ReactMethod
44-
fun setup(options: ReadableMap) {
45+
fun setup(options: ReadableMap, promise: Promise) {
46+
val json = options.getString("json")
47+
48+
if(singletonJsonConfig != null) {
49+
if(json == singletonJsonConfig) {
50+
return promise.resolve(null)
51+
}
52+
else {
53+
return promise.reject("E_SEGMENT_RECONFIGURED", "Duplicate Analytics client")
54+
}
55+
}
56+
4557
val builder = Analytics
4658
.Builder(reactApplicationContext, options.getString("writeKey"))
4759
.flushQueueSize(options.getInt("flushAt"))
@@ -69,9 +81,16 @@ class RNAnalyticsModule(context: ReactApplicationContext): ReactContextBaseJavaM
6981
builder.logLevel(Analytics.LogLevel.VERBOSE)
7082
}
7183

72-
Analytics.setSingletonInstance(
73-
RNAnalytics.buildWithIntegrations(builder)
74-
)
84+
try {
85+
Analytics.setSingletonInstance(
86+
RNAnalytics.buildWithIntegrations(builder)
87+
)
88+
} catch(e: Exception) {
89+
return promise.reject("E_SEGMENT_ERROR", e)
90+
}
91+
92+
singletonJsonConfig = json
93+
promise.resolve(null)
7594
}
7695

7796
@ReactMethod

packages/core/docs/classes/analytics.client.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ ___
5454

5555
**alias**(newId: *`string`*): `Promise`<`void`>
5656

57-
*Defined in [analytics.ts:260](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L260)*
57+
*Defined in [analytics.ts:261](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L261)*
5858

5959
Merge two user identities, effectively connecting two sets of user data as one. This may not be supported by all integrations.
6060

@@ -96,7 +96,7 @@ ___
9696

9797
**disable**(): `Promise`<`void`>
9898

99-
*Defined in [analytics.ts:299](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L299)*
99+
*Defined in [analytics.ts:300](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L300)*
100100

101101
Completely disable the sending of any analytics data.
102102

@@ -111,7 +111,7 @@ ___
111111

112112
**enable**(): `Promise`<`void`>
113113

114-
*Defined in [analytics.ts:289](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L289)*
114+
*Defined in [analytics.ts:290](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L290)*
115115

116116
Enable the sending of analytics data. Enabled by default.
117117

@@ -126,7 +126,7 @@ ___
126126

127127
**flush**(): `Promise`<`void`>
128128

129-
*Defined in [analytics.ts:280](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L280)*
129+
*Defined in [analytics.ts:281](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L281)*
130130

131131
Trigger an upload of all queued events.
132132

@@ -141,7 +141,7 @@ ___
141141

142142
**group**(groupId: *`string`*, traits?: *[JsonMap]()*): `Promise`<`void`>
143143

144-
*Defined in [analytics.ts:247](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L247)*
144+
*Defined in [analytics.ts:248](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L248)*
145145

146146
Associate a user with a group, organization, company, project, or w/e _you_ call them.
147147

@@ -163,7 +163,7 @@ ___
163163

164164
**identify**(user: *`string`*, traits?: *[JsonMap]()*): `Promise`<`void`>
165165

166-
*Defined in [analytics.ts:235](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L235)*
166+
*Defined in [analytics.ts:236](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L236)*
167167

168168
Associate a user with their unique ID and record traits about them.
169169

@@ -223,7 +223,7 @@ ___
223223

224224
**reset**(): `Promise`<`void`>
225225

226-
*Defined in [analytics.ts:270](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L270)*
226+
*Defined in [analytics.ts:271](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L271)*
227227

228228
Reset any user state that is cached on the device.
229229

@@ -238,7 +238,7 @@ ___
238238

239239
**screen**(name: *`string`*, properties?: *[JsonMap]()*): `Promise`<`void`>
240240

241-
*Defined in [analytics.ts:221](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L221)*
241+
*Defined in [analytics.ts:222](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L222)*
242242

243243
Record the screens or views your users see.
244244

@@ -290,7 +290,7 @@ ___
290290

291291
**track**(event: *`string`*, properties?: *[JsonMap]()*): `Promise`<`void`>
292292

293-
*Defined in [analytics.ts:203](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L203)*
293+
*Defined in [analytics.ts:204](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L204)*
294294

295295
Record the actions your users perform.
296296

packages/core/ios/RNAnalytics/RNAnalytics.m

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,24 @@ +(void)initialize {
3232

3333
@synthesize bridge = _bridge;
3434

35-
RCT_EXPORT_METHOD(setup:(NSDictionary*)options) {
35+
static NSString* singletonJsonConfig = nil;
36+
37+
RCT_EXPORT_METHOD(
38+
setup:(NSDictionary*)options
39+
:(RCTPromiseResolveBlock)resolver
40+
:(RCTPromiseRejectBlock)rejecter
41+
) {
42+
NSString* json = options[@"json"];
43+
44+
if(singletonJsonConfig != nil) {
45+
if(json == singletonJsonConfig) {
46+
return resolver(nil);
47+
}
48+
else {
49+
return rejecter(@"E_SEGMENT_RECONFIGURED", @"Duplicate Analytics client", nil);
50+
}
51+
}
52+
3653
SEGAnalyticsConfiguration* config = [SEGAnalyticsConfiguration configurationWithWriteKey:options[@"writeKey"]];
3754

3855
config.recordScreenViews = [options[@"recordScreenViews"] boolValue];
@@ -46,7 +63,13 @@ +(void)initialize {
4663
}
4764

4865
[SEGAnalytics debug:[options[@"debug"] boolValue]];
49-
[SEGAnalytics setupWithConfiguration:config];
66+
67+
@try {
68+
[SEGAnalytics setupWithConfiguration:config];
69+
}
70+
@catch(NSException* error) {
71+
return rejecter(@"E_SEGMENT_ERROR", @"Unexpected native Analtyics error", error);
72+
}
5073

5174
// On iOS we use method swizzling to intercept lifecycle events
5275
// However, React-Native calls our library after applicationDidFinishLaunchingWithOptions: is called
@@ -60,6 +83,9 @@ +(void)initialize {
6083
withObject:_bridge.launchOptions];
6184
}
6285
}
86+
87+
singletonJsonConfig = json;
88+
resolver(nil);
6389
}
6490

6591
#define withContext(context) @{@"context": context}

packages/core/src/__tests__/configuration.spec.ts

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,35 @@ import { configure } from '../configuration'
22

33
const writeKey = 'test-write-key'
44

5+
function withIntegrity<T extends {}>(config: T): T & { json: string } {
6+
const json = JSON.stringify(config)
7+
8+
return {
9+
...(config as any),
10+
json
11+
}
12+
}
13+
514
it('uses the default configuration', async () => {
6-
expect(await configure(writeKey, {})).toEqual({
7-
debug: false,
8-
flushAt: 20,
9-
recordScreenViews: false,
10-
trackAppLifecycleEvents: false,
11-
trackAttributionData: false,
12-
writeKey,
15+
expect(await configure(writeKey, {})).toEqual(
16+
withIntegrity({
17+
debug: false,
18+
flushAt: 20,
19+
recordScreenViews: false,
20+
trackAppLifecycleEvents: false,
21+
trackAttributionData: false,
22+
writeKey,
1323

14-
android: {
15-
collectDeviceId: true,
16-
flushInterval: undefined
17-
},
18-
ios: {
19-
trackAdvertising: false,
20-
trackDeepLinks: false
21-
}
22-
})
24+
android: {
25+
collectDeviceId: true,
26+
flushInterval: undefined
27+
},
28+
ios: {
29+
trackAdvertising: false,
30+
trackDeepLinks: false
31+
}
32+
})
33+
)
2334
})
2435

2536
it('produces a valid configuration', async () => {
@@ -40,23 +51,25 @@ it('produces a valid configuration', async () => {
4051
}
4152
})
4253

43-
expect(config).toEqual({
44-
debug: true,
45-
flushAt: 42,
46-
recordScreenViews: true,
47-
trackAppLifecycleEvents: true,
48-
trackAttributionData: true,
49-
writeKey,
54+
expect(config).toEqual(
55+
withIntegrity({
56+
debug: true,
57+
flushAt: 42,
58+
recordScreenViews: true,
59+
trackAppLifecycleEvents: true,
60+
trackAttributionData: true,
61+
writeKey,
5062

51-
android: {
52-
collectDeviceId: false,
53-
flushInterval: 72
54-
},
55-
ios: {
56-
trackAdvertising: true,
57-
trackDeepLinks: true
58-
}
59-
})
63+
android: {
64+
collectDeviceId: false,
65+
flushInterval: 72
66+
},
67+
ios: {
68+
trackAdvertising: true,
69+
trackDeepLinks: true
70+
}
71+
})
72+
)
6073
})
6174

6275
it('waits for integrations to register', async () => {

packages/core/src/analytics.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,9 @@ export module Analytics {
184184
* @param configuration An optional {@link Configuration} object.
185185
*/
186186
public async setup(writeKey: string, configuration: Configuration = {}) {
187-
await Bridge.setup(await configure(writeKey, configuration))
188-
187+
await Bridge.setup(
188+
await configure(writeKey, configuration)
189+
)
189190
this.wrapper.ready()
190191
}
191192

packages/core/src/bridge.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface Configuration {
77
trackAttributionData: boolean
88
debug: boolean
99
flushAt: number
10+
json: string
1011

1112
android: {
1213
flushInterval?: number

packages/core/src/configuration.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const configure = async (
3838
)
3939
)
4040

41-
return {
41+
const config = {
4242
debug,
4343
flushAt,
4444
recordScreenViews,
@@ -49,4 +49,10 @@ export const configure = async (
4949
android: defaults.android(android),
5050
ios: defaults.ios(ios)
5151
}
52+
const json = JSON.stringify(config)
53+
54+
return {
55+
...config,
56+
json
57+
}
5258
}

0 commit comments

Comments
 (0)