From 7038654f9edfbbc19b174e9e7a4b0593a3d6bc44 Mon Sep 17 00:00:00 2001 From: Alan Charles Date: Fri, 6 May 2022 10:00:56 -0600 Subject: [PATCH 1/3] feat: add attribution callbacks to appsflyer --- example/ios/Podfile.lock | 4 +- .../plugin-appsflyer/src/AppsflyerPlugin.tsx | 107 ++++++++++++++++-- .../plugins/plugin-appsflyer/src/types.ts | 9 ++ 3 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 packages/plugins/plugin-appsflyer/src/types.ts diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 4a29b1ee8..3dee3fe97 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -286,7 +286,7 @@ PODS: - React - RNGestureHandler (2.3.0): - React-Core - - segment-analytics-react-native (2.1.11): + - segment-analytics-react-native (2.1.12): - React-Core - sovran-react-native - segment-analytics-react-native-plugin-idfa (0.2.1): @@ -459,7 +459,7 @@ SPEC CHECKSUMS: RNCAsyncStorage: b49b4e38a1548d03b74b30e558a1d18465b94be7 RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489 RNGestureHandler: 77d59828d40838c9fabb76a12d2d0a80c006906f - segment-analytics-react-native: b251b9e7277a8cbf0a8379d9bca155f0aa1d5d4a + segment-analytics-react-native: 5287504fa5aa60e64dbb497bee5c7eb6f94e5e49 segment-analytics-react-native-plugin-idfa: 80e5d610f537156833eabea12a1804523355de95 sovran-react-native: ef02f663b489ac5e63ea7b80cd8426bf82992263 Yoga: 90dcd029e45d8a7c1ff059e8b3c6612ff409061a diff --git a/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx b/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx index ed722d917..28697ed6c 100644 --- a/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx +++ b/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx @@ -3,27 +3,78 @@ import { IdentifyEventType, PluginType, TrackEventType, + UpdateType, + SegmentAPISettings, } from '@segment/analytics-react-native'; -import appsFlyer, { InitSDKOptions } from 'react-native-appsflyer'; +import type { SegmentAppsflyerSettings } from './types'; +import appsFlyer from 'react-native-appsflyer'; import identify from './methods/identify'; import track from './methods/track'; export class AppsflyerPlugin extends DestinationPlugin { type = PluginType.destination; - key = 'Appsflyer'; + key = 'AppsFlyer'; - constructor(opts: InitSDKOptions) { - super(); + private settings: SegmentAppsflyerSettings | null = null; + private hasRegisteredInstallCallback: Boolean = false; + private hasRegisteredDeepLinkCallback: Boolean = false; - const defaultOpts = { + private hasInitialized: Boolean = false; + + update(settings: SegmentAPISettings, _: UpdateType) { + let defaultOpts = { isDebug: true, timeToWaitForATTUserAuthorization: 60, + onInstallConversionDataListener: true, }; - appsFlyer.initSdk({ - ...defaultOpts, - ...opts, - }); + const appsflyerSettings = settings.integrations[ + this.key + ] as SegmentAppsflyerSettings; + + if (!appsflyerSettings) { + return; + } + const clientConfig = this.analytics?.getConfig(); + + this.settings = appsflyerSettings; + + if ( + this.settings.trackAttributionData === true && + this.hasRegisteredInstallCallback === false + ) { + this.conversionCallback(); + this.hasRegisteredInstallCallback = true; + } + + if ( + clientConfig?.trackDeepLinks === true && + this.hasRegisteredDeepLinkCallback === false + ) { + this.deepLinkCallback(); + this.hasRegisteredDeepLinkCallback = true; + } + + if ( + this.hasInitialized === false && + clientConfig?.trackDeepLinks === true + ) { + appsFlyer.initSdk({ + devKey: this.settings.appsFlyerDevKey, + appId: this.settings.appleAppID, + onDeepLinkListener: true, + ...defaultOpts, + }); + this.hasInitialized = true; + } else { + appsFlyer.initSdk({ + devKey: this.settings.appsFlyerDevKey, + appId: this.settings.appleAppID, + ...defaultOpts, + }); + this.hasInitialized = true; + } + console.log('SETTINGS', appsflyerSettings); } identify(event: IdentifyEventType) { @@ -35,4 +86,42 @@ export class AppsflyerPlugin extends DestinationPlugin { track(event); return event; } + + conversionCallback = () => { + appsFlyer.onInstallConversionData((res) => { + console.log('RESPONSE', res); + const isFirstLaunch = JSON.parse(res?.data?.is_first_launch); + const status = res?.data?.af_status; + const properties = { + provider: 'Appsflyer', + campaign: { + source: res?.data?.media_source, + name: res?.data?.campaign, + }, + }; + + if (isFirstLaunch === true) { + if (status === 'Non-organic') { + this.analytics?.track('Install Attributed', properties); + } else { + this.analytics?.track('Organic Install', { provider: 'AppsFlyer' }); + } + } + }); + }; + + deepLinkCallback = () => { + appsFlyer.onAppOpenAttribution((res) => { + if (res?.status === 'success') { + const properties = { + provider: 'Appsflyer', + campaign: { + name: res.data.campaign, + source: res.data.media_source, + }, + }; + this.analytics?.track('Deep Link Opened', properties); + } + }); + }; } diff --git a/packages/plugins/plugin-appsflyer/src/types.ts b/packages/plugins/plugin-appsflyer/src/types.ts new file mode 100644 index 000000000..6a6544e58 --- /dev/null +++ b/packages/plugins/plugin-appsflyer/src/types.ts @@ -0,0 +1,9 @@ +export type SegmentAppsflyerSettings = { + appleAppID?: string; + appsFlyerDevKey: string; + httpFallback: boolean; + rokuAppID?: string; + trackAttributionData: boolean; + type: string; + versionSettings: { [key: string]: string[] }; +}; From 539954c1186597bf0cc9da23aaaadf47f7292ea9 Mon Sep 17 00:00:00 2001 From: Alan Charles Date: Fri, 6 May 2022 10:10:02 -0600 Subject: [PATCH 2/3] refactor: remove console logs --- packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx b/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx index 28697ed6c..50dbf0bc3 100644 --- a/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx +++ b/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx @@ -74,7 +74,6 @@ export class AppsflyerPlugin extends DestinationPlugin { }); this.hasInitialized = true; } - console.log('SETTINGS', appsflyerSettings); } identify(event: IdentifyEventType) { @@ -89,7 +88,6 @@ export class AppsflyerPlugin extends DestinationPlugin { conversionCallback = () => { appsFlyer.onInstallConversionData((res) => { - console.log('RESPONSE', res); const isFirstLaunch = JSON.parse(res?.data?.is_first_launch); const status = res?.data?.af_status; const properties = { From 57632aa87bcc97533c02bd89001aa66038713f67 Mon Sep 17 00:00:00 2001 From: Alan Charles Date: Fri, 6 May 2022 11:54:47 -0600 Subject: [PATCH 3/3] refactor: incorporate Oscar's review --- .../plugin-adjust/src/AdjustPlugin.tsx | 2 +- .../plugin-appsflyer/src/AppsflyerPlugin.tsx | 60 ++++++++----------- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/packages/plugins/plugin-adjust/src/AdjustPlugin.tsx b/packages/plugins/plugin-adjust/src/AdjustPlugin.tsx index f0b3dba13..f029e8b67 100644 --- a/packages/plugins/plugin-adjust/src/AdjustPlugin.tsx +++ b/packages/plugins/plugin-adjust/src/AdjustPlugin.tsx @@ -17,7 +17,7 @@ export class AdjustPlugin extends DestinationPlugin { key = 'Adjust'; private settings: SegmentAdjustSettings | null = null; - private hasRegisteredCallback: Boolean = false; + private hasRegisteredCallback: boolean = false; update(settings: SegmentAPISettings, _: UpdateType) { const adjustSettings = settings.integrations[ diff --git a/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx b/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx index 50dbf0bc3..ac905f77b 100644 --- a/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx +++ b/packages/plugins/plugin-appsflyer/src/AppsflyerPlugin.tsx @@ -16,14 +16,13 @@ export class AppsflyerPlugin extends DestinationPlugin { key = 'AppsFlyer'; private settings: SegmentAppsflyerSettings | null = null; - private hasRegisteredInstallCallback: Boolean = false; - private hasRegisteredDeepLinkCallback: Boolean = false; - - private hasInitialized: Boolean = false; + private hasRegisteredInstallCallback: boolean = false; + private hasRegisteredDeepLinkCallback: boolean = false; + private hasInitialized: boolean = false; update(settings: SegmentAPISettings, _: UpdateType) { let defaultOpts = { - isDebug: true, + isDebug: false, timeToWaitForATTUserAuthorization: 60, onInstallConversionDataListener: true, }; @@ -32,7 +31,7 @@ export class AppsflyerPlugin extends DestinationPlugin { this.key ] as SegmentAppsflyerSettings; - if (!appsflyerSettings) { + if (appsflyerSettings === undefined) { return; } const clientConfig = this.analytics?.getConfig(); @@ -40,36 +39,25 @@ export class AppsflyerPlugin extends DestinationPlugin { this.settings = appsflyerSettings; if ( - this.settings.trackAttributionData === true && - this.hasRegisteredInstallCallback === false + this.settings.trackAttributionData && + !this.hasRegisteredInstallCallback ) { - this.conversionCallback(); + this.registerConversionCallback(); this.hasRegisteredInstallCallback = true; } if ( clientConfig?.trackDeepLinks === true && - this.hasRegisteredDeepLinkCallback === false + !this.hasRegisteredDeepLinkCallback ) { - this.deepLinkCallback(); + this.registerDeepLinkCallback(); this.hasRegisteredDeepLinkCallback = true; } - - if ( - this.hasInitialized === false && - clientConfig?.trackDeepLinks === true - ) { - appsFlyer.initSdk({ - devKey: this.settings.appsFlyerDevKey, - appId: this.settings.appleAppID, - onDeepLinkListener: true, - ...defaultOpts, - }); - this.hasInitialized = true; - } else { + if (!this.hasInitialized) { appsFlyer.initSdk({ devKey: this.settings.appsFlyerDevKey, appId: this.settings.appleAppID, + onDeepLinkListener: clientConfig?.trackDeepLinks === true, ...defaultOpts, }); this.hasInitialized = true; @@ -86,20 +74,19 @@ export class AppsflyerPlugin extends DestinationPlugin { return event; } - conversionCallback = () => { + registerConversionCallback = () => { appsFlyer.onInstallConversionData((res) => { - const isFirstLaunch = JSON.parse(res?.data?.is_first_launch); - const status = res?.data?.af_status; + const { af_status, media_source, campaign, is_first_launch } = res?.data; const properties = { - provider: 'Appsflyer', + provider: this.key, campaign: { - source: res?.data?.media_source, - name: res?.data?.campaign, + source: media_source, + name: campaign, }, }; - if (isFirstLaunch === true) { - if (status === 'Non-organic') { + if (is_first_launch === 'true') { + if (af_status === 'Non-organic') { this.analytics?.track('Install Attributed', properties); } else { this.analytics?.track('Organic Install', { provider: 'AppsFlyer' }); @@ -108,14 +95,15 @@ export class AppsflyerPlugin extends DestinationPlugin { }); }; - deepLinkCallback = () => { + registerDeepLinkCallback = () => { appsFlyer.onAppOpenAttribution((res) => { if (res?.status === 'success') { + const { campaign, media_source } = res.data; const properties = { - provider: 'Appsflyer', + provider: this.key, campaign: { - name: res.data.campaign, - source: res.data.media_source, + name: campaign, + source: media_source, }, }; this.analytics?.track('Deep Link Opened', properties);