From 31b09b2d9468edc2d16ddf77043f20e04843a765 Mon Sep 17 00:00:00 2001 From: Alan Kenyon Date: Sun, 2 Aug 2020 20:26:53 -0700 Subject: [PATCH] feat(core): http proxying via setup configuration extended the configuration API to allow directing analytics traffic through a hosted proxy (or other) --- .../reactnative/core/RNAnalyticsModule.kt | 40 ++++++++++++++--- packages/core/docs/README.md | 2 +- .../core/docs/classes/analytics.client.md | 30 ++++++------- .../interfaces/analytics.configuration.md | 32 ++++++++++---- packages/core/ios/RNAnalytics/RNAnalytics.m | 27 ++++++++++++ packages/core/src/analytics.ts | 43 +++++++++++++++++++ packages/core/src/bridge.ts | 6 +++ packages/core/src/configuration.ts | 2 + 8 files changed, 152 insertions(+), 30 deletions(-) diff --git a/packages/core/android/src/main/java/com/segment/analytics/reactnative/core/RNAnalyticsModule.kt b/packages/core/android/src/main/java/com/segment/analytics/reactnative/core/RNAnalyticsModule.kt index d43d3a7d7..bc6b90241 100644 --- a/packages/core/android/src/main/java/com/segment/analytics/reactnative/core/RNAnalyticsModule.kt +++ b/packages/core/android/src/main/java/com/segment/analytics/reactnative/core/RNAnalyticsModule.kt @@ -26,15 +26,14 @@ package com.segment.analytics.reactnative.core import android.content.pm.PackageInfo import android.content.pm.PackageManager +import android.net.Uri import com.facebook.react.bridge.* -import com.segment.analytics.Analytics -import com.segment.analytics.Properties -import com.segment.analytics.Options -import com.segment.analytics.Traits -import com.segment.analytics.ValueMap import com.segment.analytics.internal.Utils.getSegmentSharedPreferences import java.util.concurrent.TimeUnit import com.facebook.react.bridge.ReadableMap +import com.segment.analytics.* +import java.io.IOException +import java.net.HttpURLConnection @@ -155,6 +154,37 @@ class RNAnalyticsModule(context: ReactApplicationContext): ReactContextBaseJavaM builder.trackApplicationLifecycleEvents() } + if(options.hasKey("proxy") && options.getType("proxy") == ReadableType.Map) { + val proxyOptions = options.getMap("proxy")!! + + builder.connectionFactory(object:ConnectionFactory() { + override fun openConnection(url:String): HttpURLConnection { + val uri = Uri.parse(url) + val uriBuilder = uri.buildUpon(); + + if (proxyOptions.hasKey("scheme")) { + uriBuilder.scheme(proxyOptions.getString("scheme")) + } + + if (proxyOptions.hasKey("host")) { + var host = proxyOptions.getString("host"); + + if (proxyOptions.hasKey("port")) { + host = host + ":" + proxyOptions.getInt("port"); + } + + uriBuilder.encodedAuthority(host) + } + + if (proxyOptions.hasKey("path")) { + uriBuilder.path(proxyOptions.getString("path") + uri.path) + } + + return super.openConnection(uriBuilder.toString()) + } + }) + } + try { Analytics.setSingletonInstance( RNAnalytics.buildWithIntegrations(builder) diff --git a/packages/core/docs/README.md b/packages/core/docs/README.md index 658447474..0551e089c 100644 --- a/packages/core/docs/README.md +++ b/packages/core/docs/README.md @@ -24,7 +24,7 @@ **Ƭ Integration**: *`function` \| `object`* -*Defined in [analytics.ts:8](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L8)* +*Defined in [analytics.ts:8](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L8)* ___ diff --git a/packages/core/docs/classes/analytics.client.md b/packages/core/docs/classes/analytics.client.md index 0b67d6eac..ae51bb114 100644 --- a/packages/core/docs/classes/analytics.client.md +++ b/packages/core/docs/classes/analytics.client.md @@ -39,7 +39,7 @@ **● ready**: *`false`* = false -*Defined in [analytics.ts:104](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L104)* +*Defined in [analytics.ts:147](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L147)* Whether the client is ready to send events to Segment. @@ -55,7 +55,7 @@ ___ ▸ **alias**(newId: *`string`*, options?: *[Options]()*): `Promise`<`void`> -*Defined in [analytics.ts:274](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L274)* +*Defined in [analytics.ts:317](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L317)* Merge two user identities, effectively connecting two sets of user data as one. This may not be supported by all integrations. @@ -77,7 +77,7 @@ ___ ▸ **catch**(handler: *[ErrorHandler]()*): `this` -*Defined in [analytics.ts:119](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L119)* +*Defined in [analytics.ts:162](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L162)* Catch React-Native bridge errors @@ -98,7 +98,7 @@ ___ ▸ **disable**(): `Promise`<`void`> -*Defined in [analytics.ts:313](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L313)* +*Defined in [analytics.ts:356](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L356)* Completely disable the sending of any analytics data. @@ -113,7 +113,7 @@ ___ ▸ **enable**(): `Promise`<`void`> -*Defined in [analytics.ts:303](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L303)* +*Defined in [analytics.ts:346](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L346)* Enable the sending of analytics data. Enabled by default. @@ -128,7 +128,7 @@ ___ ▸ **flush**(): `Promise`<`void`> -*Defined in [analytics.ts:294](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L294)* +*Defined in [analytics.ts:337](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L337)* Trigger an upload of all queued events. @@ -143,7 +143,7 @@ ___ ▸ **getAnonymousId**(): `Promise`<`string`> -*Defined in [analytics.ts:318](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L318)* +*Defined in [analytics.ts:361](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L361)* Retrieve the anonymousId. @@ -156,7 +156,7 @@ ___ ▸ **group**(groupId: *`string`*, traits?: *[JsonMap]()*, options?: *[Options]()*): `Promise`<`void`> -*Defined in [analytics.ts:261](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L261)* +*Defined in [analytics.ts:304](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L304)* Associate a user with a group, organization, company, project, or w/e _you_ call them. @@ -179,7 +179,7 @@ ___ ▸ **identify**(user: *`string`*, traits?: *[JsonMap]()*, options?: *[Options]()*): `Promise`<`void`> -*Defined in [analytics.ts:248](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L248)* +*Defined in [analytics.ts:291](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L291)* Associate a user with their unique ID and record traits about them. @@ -202,7 +202,7 @@ ___ ▸ **middleware**(middleware: *[Middleware]()*): `this` -*Defined in [analytics.ts:157](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L157)* +*Defined in [analytics.ts:200](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L200)* Append a new middleware to the middleware chain. @@ -240,7 +240,7 @@ ___ ▸ **reset**(): `Promise`<`void`> -*Defined in [analytics.ts:284](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L284)* +*Defined in [analytics.ts:327](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L327)* Reset any user state that is cached on the device. @@ -255,7 +255,7 @@ ___ ▸ **screen**(name: *`string`*, properties?: *[JsonMap]()*, options?: *[Options]()*): `Promise`<`void`> -*Defined in [analytics.ts:233](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L233)* +*Defined in [analytics.ts:276](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L276)* Record the screens or views your users see. @@ -278,7 +278,7 @@ ___ ▸ **setup**(writeKey: *`string`*, configuration?: *[Configuration](../interfaces/analytics.configuration.md)*): `Promise`<`void`> -*Defined in [analytics.ts:196](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L196)* +*Defined in [analytics.ts:239](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L239)* Setup the Analytics module. All calls made before are queued and only executed if the configuration was successful. @@ -308,7 +308,7 @@ ___ ▸ **track**(event: *`string`*, properties?: *[JsonMap]()*, options?: *[Options]()*): `Promise`<`void`> -*Defined in [analytics.ts:215](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L215)* +*Defined in [analytics.ts:258](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L258)* Record the actions your users perform. @@ -331,7 +331,7 @@ ___ ▸ **useNativeConfiguration**(): `this` -*Defined in [analytics.ts:169](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L169)* +*Defined in [analytics.ts:212](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L212)* Use the native configuration. diff --git a/packages/core/docs/interfaces/analytics.configuration.md b/packages/core/docs/interfaces/analytics.configuration.md index bc3b2bb54..a6f3b3fdf 100644 --- a/packages/core/docs/interfaces/analytics.configuration.md +++ b/packages/core/docs/interfaces/analytics.configuration.md @@ -15,6 +15,7 @@ * [defaultProjectSettings](analytics.configuration.md#defaultprojectsettings) * [flushAt](analytics.configuration.md#flushat) * [ios](analytics.configuration.md#ios) +* [proxy](analytics.configuration.md#proxy) * [recordScreenViews](analytics.configuration.md#recordscreenviews) * [trackAppLifecycleEvents](analytics.configuration.md#trackapplifecycleevents) * [trackAttributionData](analytics.configuration.md#trackattributiondata) @@ -30,7 +31,7 @@ **● android**: *`undefined` \| `object`* -*Defined in [analytics.ts:77](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L77)* +*Defined in [analytics.ts:120](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L120)* Android specific settings. @@ -41,7 +42,7 @@ ___ **● debug**: *`undefined` \| `false` \| `true`* -*Defined in [analytics.ts:38](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L38)* +*Defined in [analytics.ts:38](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L38)* ___ @@ -50,7 +51,7 @@ ___ **● defaultProjectSettings**: *`undefined` \| `object`* -*Defined in [analytics.ts:46](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L46)* +*Defined in [analytics.ts:46](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L46)* Default project settings to use, if Segment.com cannot be reached. An example configuration can be found here, using your write key: [](https://cdn-settings.segment.com/v1/projects/YOUR_WRITE_KEY/settings)[https://cdn-settings.segment.com/v1/projects/YOUR\_WRITE\_KEY/settings](https://cdn-settings.segment.com/v1/projects/YOUR_WRITE_KEY/settings) @@ -61,7 +62,7 @@ ___ **● flushAt**: *`undefined` \| `number`* -*Defined in [analytics.ts:54](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L54)* +*Defined in [analytics.ts:54](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L54)* The number of queued events that the analytics client should flush at. Setting this to `1` will not queue any events and will use more battery. @@ -74,10 +75,23 @@ ___ **● ios**: *`undefined` \| `object`* -*Defined in [analytics.ts:59](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L59)* +*Defined in [analytics.ts:102](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L102)* iOS specific settings. +___ + + +### `` proxy + +**● proxy**: *`undefined` \| `object`* + +*Defined in [analytics.ts:72](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L72)* + +Whether the analytics client should send all requests through your own hosted proxy rather than directly to Segment. See: iOS: [https://segment.com/docs/connections/sources/catalog/libraries/mobile/ios/#proxy-http-calls](https://segment.com/docs/connections/sources/catalog/libraries/mobile/ios/#proxy-http-calls) android: [https://segment.com/docs/connections/sources/catalog/libraries/mobile/android/#proxy-http-calls](https://segment.com/docs/connections/sources/catalog/libraries/mobile/android/#proxy-http-calls) + +Ex. For a desired proxy through `http://localhost:64000/segment` the configuration would look like such { scheme: 'http', host: 'localhost', port: 64000, path: '/segment' } + ___ @@ -85,7 +99,7 @@ ___ **● recordScreenViews**: *`undefined` \| `false` \| `true`* -*Defined in [analytics.ts:19](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L19)* +*Defined in [analytics.ts:19](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L19)* Whether the analytics client should automatically make a screen call when a view controller is added to a view hierarchy. Because the iOS underlying implementation uses method swizzling, we recommend initializing the analytics client as early as possible. @@ -98,7 +112,7 @@ ___ **● trackAppLifecycleEvents**: *`undefined` \| `false` \| `true`* -*Defined in [analytics.ts:26](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L26)* +*Defined in [analytics.ts:26](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L26)* Whether the analytics client should automatically track application lifecycle events, such as "Application Installed", "Application Updated" and "Application Opened". @@ -111,7 +125,7 @@ ___ **● trackAttributionData**: *`undefined` \| `false` \| `true`* -*Defined in [analytics.ts:32](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L32)* +*Defined in [analytics.ts:32](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L32)* Whether the analytics client should automatically track attribution data from enabled providers using the mobile service. @@ -124,7 +138,7 @@ ___ **● using**: *[Integration](../#integration)[]* -*Defined in [analytics.ts:37](https://github.com/segmentio/analytics-react-native/blob/master/packages/core/src/analytics.ts#L37)* +*Defined in [analytics.ts:37](https://github.com/adkenyon/analytics-react-native/blob/master/packages/core/src/analytics.ts#L37)* Register a set of integrations to be used with this Analytics instance. diff --git a/packages/core/ios/RNAnalytics/RNAnalytics.m b/packages/core/ios/RNAnalytics/RNAnalytics.m index 9428b4d86..d4cd2079b 100644 --- a/packages/core/ios/RNAnalytics/RNAnalytics.m +++ b/packages/core/ios/RNAnalytics/RNAnalytics.m @@ -63,6 +63,33 @@ +(void)initialize { config.enableAdvertisingTracking = [options[@"ios"][@"trackAdvertising"] boolValue]; config.defaultSettings = options[@"defaultProjectSettings"]; + if ([options valueForKey:@"proxy"]) { + NSDictionary *proxyOptions = (NSDictionary *)[options valueForKey:@"proxy"]; + + config.requestFactory = ^(NSURL *url) { + NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + + if ([proxyOptions valueForKey:@"scheme"]) { + components.scheme = [proxyOptions[@"scheme"] stringValue]; + } + + if ([proxyOptions valueForKey:@"host"]) { + components.host = [proxyOptions[@"host"] stringValue]; + } + + if ([proxyOptions valueForKey:@"port"]) { + components.port = [NSNumber numberWithInt:[proxyOptions[@"port"] intValue]]; + } + + if ([proxyOptions valueForKey:@"path"]) { + components.path = [[proxyOptions[@"path"] stringValue] stringByAppendingString:components.path]; + } + + NSURL *transformedURL = components.URL; + return [NSMutableURLRequest requestWithURL:transformedURL]; + }; + } + for(id factory in RNAnalyticsIntegrations) { [config use:factory]; } diff --git a/packages/core/src/analytics.ts b/packages/core/src/analytics.ts index acb303e65..b8a1ec405 100644 --- a/packages/core/src/analytics.ts +++ b/packages/core/src/analytics.ts @@ -53,6 +53,49 @@ export module Analytics { */ flushAt?: number + /** + * Whether the analytics client should send all requests through your own hosted + * proxy rather than directly to Segment. + * See: + * iOS: https://segment.com/docs/connections/sources/catalog/libraries/mobile/ios/#proxy-http-calls + * android: https://segment.com/docs/connections/sources/catalog/libraries/mobile/android/#proxy-http-calls + * + * Ex. For a desired proxy through `http://localhost:64000/segment` the configuration would look like such + * { + * scheme: 'http', + * host: 'localhost', + * port: 64000, + * path: '/segment' + * } + * + */ + proxy?: { + + /** + * The proxy scheme, ex: http, https + * + * `https` by default. + */ + scheme?: string, + + /** + * The proxy host name, ex: api.segment.io, cdn.segment.io + * + * Note: When using localhost with an Android device or simulator use `adb reverse tcp: tcp:` + */ + host?: string, + + /** + * The proxy port number, ex: 80 + */ + port?: number, + + /** + * The proxy path, ex: /path/to/proxy + */ + path?: string, + }, + /** * iOS specific settings. */ diff --git a/packages/core/src/bridge.ts b/packages/core/src/bridge.ts index 7f8c4eff7..64244983b 100644 --- a/packages/core/src/bridge.ts +++ b/packages/core/src/bridge.ts @@ -9,6 +9,12 @@ export interface Configuration { flushAt: number json: string defaultProjectSettings: { [key: string]: any } + proxy?: { + scheme?: string + host?: string + port?: number + path?: string + } android: { flushInterval?: number diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index f40ba9e66..af3485666 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -28,6 +28,7 @@ export const configure = async ( trackAttributionData = false, using = [], defaultProjectSettings = {}, + proxy = undefined, ios = {}, android = {} @@ -47,6 +48,7 @@ export const configure = async ( trackAppLifecycleEvents, trackAttributionData, writeKey, + proxy, android: defaults.android(android), ios: defaults.ios(ios)