1
+ package com.segment.analytics.destinations.plugins
2
+
3
+ import android.app.Application
4
+ import com.segment.analytics.kotlin.android.plugins.AndroidLifecycle
5
+ import com.segment.analytics.kotlin.core.*
6
+ import com.segment.analytics.kotlin.core.platform.DestinationPlugin
7
+ import com.segment.analytics.kotlin.core.platform.Plugin
8
+ import com.segment.analytics.kotlin.core.platform.plugins.log
9
+ import com.segment.analytics.kotlin.core.utilities.*
10
+ import io.intercom.android.sdk.Company
11
+ import io.intercom.android.sdk.Intercom
12
+ import io.intercom.android.sdk.UserAttributes
13
+ import io.intercom.android.sdk.identity.Registration
14
+ import kotlinx.serialization.json.*
15
+
16
+ /*
17
+ This is an example of the Intercom device-mode destination plugin that can be integrated with
18
+ Segment analytics.
19
+ Note: This plugin is NOT SUPPORTED by Segment. It is here merely as an example,
20
+ and for your convenience should you find it useful.
21
+ # Instructions for adding Intercom:
22
+ - In your app-module build.gradle file add the following:
23
+ ```
24
+ ...
25
+ dependencies {
26
+ ...
27
+ // Intercom
28
+ implementation 'io.intercom.android:intercom-sdk-base:10.1.1'
29
+ implementation 'io.intercom.android:intercom-sdk-fcm:10.1.1'
30
+ }
31
+ ```
32
+ - Copy this entire IntercomDestination.kt file into your project's codebase.
33
+ - Go to your project's codebase and wherever u initialize the analytics client add these lines
34
+ ```
35
+ val intercom = IntercomDestination()
36
+ analytics.add(intercom)
37
+ ```
38
+
39
+ Note: due to the inclusion of Intercom partner integration your minSdk cannot be smaller than 21
40
+
41
+ MIT License
42
+ Copyright (c) 2021 Segment
43
+ Permission is hereby granted, free of charge, to any person obtaining a copy
44
+ of this software and associated documentation files (the "Software"), to deal
45
+ in the Software without restriction, including without limitation the rights
46
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
47
+ copies of the Software, and to permit persons to whom the Software is
48
+ furnished to do so, subject to the following conditions:
49
+ The above copyright notice and this permission notice shall be included in all
50
+ copies or substantial portions of the Software.
51
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
52
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
53
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
54
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
55
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
56
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
57
+ SOFTWARE.
58
+ */
59
+
60
+ class IntercomDestination (
61
+ private val application : Application
62
+ ): DestinationPlugin(), AndroidLifecycle {
63
+
64
+ override val key: String = " Intercom"
65
+ private var mobileApiKey: String = " "
66
+ private var appId: String = " "
67
+ lateinit var intercom: Intercom
68
+ private set
69
+
70
+ override fun update (settings : Settings , type : Plugin .UpdateType ) {
71
+ // if we've already set up this singleton SDK, can't do it again, so skip.
72
+ if (type != Plugin .UpdateType .Initial ) return
73
+ super .update(settings, type)
74
+
75
+ settings.integrations[key]?.jsonObject?.let {
76
+ mobileApiKey = it.getString(" mobileApiKey" ) ? : " "
77
+ appId = it.getString(" appId" ) ? : " "
78
+ }
79
+
80
+ Intercom .initialize(application, mobileApiKey, appId)
81
+ this .intercom = Intercom .client()
82
+ }
83
+
84
+ override fun track (payload : TrackEvent ): BaseEvent ? {
85
+ val result = super .track(payload)
86
+
87
+ val eventName = payload.event
88
+ val properties = payload.properties
89
+
90
+ if (! properties.isNullOrEmpty()) {
91
+ val price = buildJsonObject{
92
+ val amount = properties.getDouble(REVENUE ) ? : properties.getDouble(TOTAL )
93
+ amount?.let {
94
+ put(AMOUNT , it * 100 )
95
+ }
96
+
97
+ properties.getString(CURRENCY )?.let {
98
+ put(CURRENCY , it)
99
+ }
100
+ }
101
+
102
+ val eventProperties = buildJsonObject {
103
+ if (! price.isNullOrEmpty()) {
104
+ put(PRICE , price)
105
+ }
106
+
107
+ properties.forEach { (key, value) ->
108
+ // here we are only interested in primitive values and not maps or collections
109
+ if (key !in setOf (" products" , REVENUE , TOTAL , CURRENCY )
110
+ && value is JsonPrimitive ) {
111
+ put(key, value)
112
+ }
113
+ }
114
+ }
115
+
116
+ intercom.logEvent(eventName, eventProperties)
117
+ analytics.log(" Intercom.client().logEvent($eventName , $eventProperties )" )
118
+ }
119
+ else {
120
+ intercom.logEvent(eventName)
121
+ analytics.log(" Intercom.client().logEvent($eventName )" )
122
+ }
123
+
124
+ return result
125
+ }
126
+
127
+ override fun identify (payload : IdentifyEvent ): BaseEvent ? {
128
+ val result = super .identify(payload)
129
+
130
+ val userId = payload.userId
131
+ if (userId.isEmpty()) {
132
+ intercom.registerUnidentifiedUser()
133
+ analytics.log(" Intercom.client().registerUnidentifiedUser()" )
134
+ }
135
+ else {
136
+ val registration = Registration .create().withUserId(userId)
137
+ intercom.registerIdentifiedUser(registration)
138
+ analytics.log(" Intercom.client().registerIdentifiedUser(registration)" )
139
+ }
140
+
141
+ val intercomOptions = payload.integrations[" Intercom" ]?.safeJsonObject
142
+ intercomOptions?.getString(" userHash" )?.let {
143
+ intercom.setUserHash(it)
144
+ }
145
+
146
+ if (! payload.traits.isNullOrEmpty() && ! intercomOptions.isNullOrEmpty()) {
147
+ setUserAttributes(payload.traits, intercomOptions)
148
+ }
149
+ else {
150
+ setUserAttributes(payload.traits, null )
151
+ }
152
+
153
+ return result
154
+ }
155
+
156
+ override fun group (payload : GroupEvent ): BaseEvent ? {
157
+ val result = super .group(payload)
158
+
159
+ if (payload.groupId.isNotEmpty()) {
160
+ val traits = buildJsonObject {
161
+ putAll(payload.traits)
162
+ put(" id" , payload.groupId)
163
+ }
164
+ val company = setCompany(traits)
165
+ val userAttributes = UserAttributes .Builder ()
166
+ .withCompany(company)
167
+ .build()
168
+ intercom.updateUser(userAttributes)
169
+ }
170
+
171
+ return result
172
+ }
173
+
174
+ override fun reset () {
175
+ super .reset()
176
+ intercom.logout()
177
+ analytics.log(" Intercom.client().reset()" )
178
+ }
179
+
180
+ private fun setUserAttributes (traits : Traits , intercomOptions : JsonObject ? ) {
181
+ val builder = UserAttributes .Builder ()
182
+
183
+ traits.getString(NAME )?.let { builder.withName(it) }
184
+ traits.getString(EMAIL )?.let { builder.withEmail(it) }
185
+ traits.getString(PHONE )?.let { builder.withPhone(it) }
186
+
187
+ intercomOptions?.let {
188
+ builder.withLanguageOverride(it.getString(LANGUAGE_OVERRIDE ))
189
+ builder.withSignedUpAt(it.getLong(CREATED_AT ))
190
+ builder.withUnsubscribedFromEmails(it.getBoolean(UNSUBSCRIBED_FROM_EMAILS ))
191
+ }
192
+
193
+ traits[COMPANY ]?.safeJsonObject?.let {
194
+ val company = setCompany(it)
195
+ builder.withCompany(company)
196
+ }
197
+
198
+ traits.forEach { (key, value) ->
199
+ // here we are only interested in primitive values and not maps or collections
200
+ if (value is JsonPrimitive &&
201
+ key !in setOf (NAME , EMAIL , PHONE , " userId" , " anonymousId" )) {
202
+ builder.withCustomAttribute(key, value.toContent())
203
+ }
204
+ }
205
+
206
+ intercom.updateUser(builder.build())
207
+ analytics.log(" Intercom.client().updateUser(userAttributes)" )
208
+ }
209
+
210
+ private fun setCompany (company : JsonObject ): Company {
211
+ val builder = Company .Builder ()
212
+ company.getString(" id" )?.let {
213
+ builder.withCompanyId(it)
214
+ } ? : return builder.build()
215
+
216
+ company.getString(NAME )?.let { builder.withName(it) }
217
+ company.getLong(CREATED_AT )?.let { builder.withCreatedAt(it) }
218
+ company.getInt(MONTHLY_SPEND )?.let { builder.withMonthlySpend(it) }
219
+ company.getString(PLAN )?.let { builder.withPlan(it) }
220
+
221
+ company.forEach { (key, value) ->
222
+ // here we are only interested in primitive values and not maps or collections
223
+ if (value is JsonPrimitive &&
224
+ key !in setOf (" id" , NAME , CREATED_AT , MONTHLY_SPEND , PLAN )
225
+ ) {
226
+ builder.withCustomAttribute(key, value.toContent())
227
+ }
228
+ }
229
+
230
+ return builder.build()
231
+ }
232
+
233
+ companion object {
234
+
235
+ // Intercom common specced attributes
236
+ private const val NAME = " name"
237
+ private const val CREATED_AT = " createdAt"
238
+ private const val COMPANY = " company"
239
+ private const val PRICE = " price"
240
+ private const val AMOUNT = " amount"
241
+ private const val CURRENCY = " currency"
242
+
243
+ // Intercom specced user attributes
244
+ private const val EMAIL = " email"
245
+ private const val PHONE = " phone"
246
+ private const val LANGUAGE_OVERRIDE = " languageOverride"
247
+ private const val UNSUBSCRIBED_FROM_EMAILS = " unsubscribedFromEmails"
248
+
249
+ // Intercom specced group attributes
250
+ private const val MONTHLY_SPEND = " monthlySpend"
251
+ private const val PLAN = " plan"
252
+
253
+ // Segment specced properties
254
+ private const val REVENUE = " revenue"
255
+ private const val TOTAL = " total"
256
+ }
257
+ }
0 commit comments