Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): fix duplicate client error in dev mode #51

Merged
merged 2 commits into from
Mar 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,36 @@

package com.segment.analytics.reactnative.core

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.*
import com.segment.analytics.Analytics
import com.segment.analytics.Properties
import com.segment.analytics.Traits
import com.segment.analytics.ValueMap
import java.util.concurrent.TimeUnit

class RNAnalyticsModule(context: ReactApplicationContext): ReactContextBaseJavaModule(context) {
override fun getName() = "RNAnalytics"

private val analytics
get() = Analytics.with(reactApplicationContext)

override fun getName() = "RNAnalytics"
companion object {
private var singletonJsonConfig: String? = null
}

@ReactMethod
fun setup(options: ReadableMap) {
fun setup(options: ReadableMap, promise: Promise) {
val json = options.getString("json")

if(singletonJsonConfig != null) {
if(json == singletonJsonConfig) {
return promise.resolve(null)
}
else {
return promise.reject("E_SEGMENT_RECONFIGURED", "Duplicate Analytics client")
}
}

val builder = Analytics
.Builder(reactApplicationContext, options.getString("writeKey"))
.flushQueueSize(options.getInt("flushAt"))
Expand Down Expand Up @@ -69,9 +81,16 @@ class RNAnalyticsModule(context: ReactApplicationContext): ReactContextBaseJavaM
builder.logLevel(Analytics.LogLevel.VERBOSE)
}

Analytics.setSingletonInstance(
RNAnalytics.buildWithIntegrations(builder)
)
try {
Analytics.setSingletonInstance(
RNAnalytics.buildWithIntegrations(builder)
)
} catch(e: Exception) {
return promise.reject("E_SEGMENT_ERROR", e)
}

singletonJsonConfig = json
promise.resolve(null)
}

@ReactMethod
Expand Down
18 changes: 9 additions & 9 deletions packages/core/docs/classes/analytics.client.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ ___

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

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

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

Expand Down Expand Up @@ -96,7 +96,7 @@ ___

▸ **disable**(): `Promise`<`void`>

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

Completely disable the sending of any analytics data.

Expand All @@ -111,7 +111,7 @@ ___

▸ **enable**(): `Promise`<`void`>

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

Enable the sending of analytics data. Enabled by default.

Expand All @@ -126,7 +126,7 @@ ___

▸ **flush**(): `Promise`<`void`>

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

Trigger an upload of all queued events.

Expand All @@ -141,7 +141,7 @@ ___

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

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

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

Expand All @@ -163,7 +163,7 @@ ___

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

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

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

Expand Down Expand Up @@ -223,7 +223,7 @@ ___

▸ **reset**(): `Promise`<`void`>

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

Reset any user state that is cached on the device.

Expand All @@ -238,7 +238,7 @@ ___

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

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

Record the screens or views your users see.

Expand Down Expand Up @@ -290,7 +290,7 @@ ___

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

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

Record the actions your users perform.

Expand Down
30 changes: 28 additions & 2 deletions packages/core/ios/RNAnalytics/RNAnalytics.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,24 @@ +(void)initialize {

@synthesize bridge = _bridge;

RCT_EXPORT_METHOD(setup:(NSDictionary*)options) {
static NSString* singletonJsonConfig = nil;

RCT_EXPORT_METHOD(
setup:(NSDictionary*)options
:(RCTPromiseResolveBlock)resolver
:(RCTPromiseRejectBlock)rejecter
) {
NSString* json = options[@"json"];

if(singletonJsonConfig != nil) {
if([json isEqualToString:singletonJsonConfig]) {
return resolver(nil);
}
else {
return rejecter(@"E_SEGMENT_RECONFIGURED", @"Duplicate Analytics client", nil);
}
}

SEGAnalyticsConfiguration* config = [SEGAnalyticsConfiguration configurationWithWriteKey:options[@"writeKey"]];

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

[SEGAnalytics debug:[options[@"debug"] boolValue]];
[SEGAnalytics setupWithConfiguration:config];

@try {
[SEGAnalytics setupWithConfiguration:config];
}
@catch(NSException* error) {
return rejecter(@"E_SEGMENT_ERROR", @"Unexpected native Analtyics error", error);
}

// On iOS we use method swizzling to intercept lifecycle events
// However, React-Native calls our library after applicationDidFinishLaunchingWithOptions: is called
Expand All @@ -60,6 +83,9 @@ +(void)initialize {
withObject:_bridge.launchOptions];
}
}

singletonJsonConfig = json;
resolver(nil);
}

#define withContext(context) @{@"context": context}
Expand Down
77 changes: 45 additions & 32 deletions packages/core/src/__tests__/configuration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,35 @@ import { configure } from '../configuration'

const writeKey = 'test-write-key'

function withIntegrity<T extends {}>(config: T): T & { json: string } {
const json = JSON.stringify(config)

return {
...(config as any),
json
}
}

it('uses the default configuration', async () => {
expect(await configure(writeKey, {})).toEqual({
debug: false,
flushAt: 20,
recordScreenViews: false,
trackAppLifecycleEvents: false,
trackAttributionData: false,
writeKey,
expect(await configure(writeKey, {})).toEqual(
withIntegrity({
debug: false,
flushAt: 20,
recordScreenViews: false,
trackAppLifecycleEvents: false,
trackAttributionData: false,
writeKey,

android: {
collectDeviceId: true,
flushInterval: undefined
},
ios: {
trackAdvertising: false,
trackDeepLinks: false
}
})
android: {
collectDeviceId: true,
flushInterval: undefined
},
ios: {
trackAdvertising: false,
trackDeepLinks: false
}
})
)
})

it('produces a valid configuration', async () => {
Expand All @@ -40,23 +51,25 @@ it('produces a valid configuration', async () => {
}
})

expect(config).toEqual({
debug: true,
flushAt: 42,
recordScreenViews: true,
trackAppLifecycleEvents: true,
trackAttributionData: true,
writeKey,
expect(config).toEqual(
withIntegrity({
debug: true,
flushAt: 42,
recordScreenViews: true,
trackAppLifecycleEvents: true,
trackAttributionData: true,
writeKey,

android: {
collectDeviceId: false,
flushInterval: 72
},
ios: {
trackAdvertising: true,
trackDeepLinks: true
}
})
android: {
collectDeviceId: false,
flushInterval: 72
},
ios: {
trackAdvertising: true,
trackDeepLinks: true
}
})
)
})

it('waits for integrations to register', async () => {
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,9 @@ export module Analytics {
* @param configuration An optional {@link Configuration} object.
*/
public async setup(writeKey: string, configuration: Configuration = {}) {
await Bridge.setup(await configure(writeKey, configuration))

await Bridge.setup(
await configure(writeKey, configuration)
)
this.wrapper.ready()
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface Configuration {
trackAttributionData: boolean
debug: boolean
flushAt: number
json: string

android: {
flushInterval?: number
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const configure = async (
)
)

return {
const config = {
debug,
flushAt,
recordScreenViews,
Expand All @@ -49,4 +49,10 @@ export const configure = async (
android: defaults.android(android),
ios: defaults.ios(ios)
}
const json = JSON.stringify(config)

return {
...config,
json
}
}