react-native-hubspot-wrapper 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/.gradle/8.13/checksums/checksums.lock +0 -0
- package/android/.gradle/8.13/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.13/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.13/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/src/main/java/com/reactnativehubspotwrapper/HubspotWrapperModule.kt +90 -2
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
File without changes
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.marcinolek.reactnativehubspotwrapper
|
|
2
2
|
|
|
3
3
|
import android.content.Intent
|
|
4
|
+
import android.util.Log
|
|
4
5
|
import android.webkit.CookieManager
|
|
5
6
|
import com.facebook.react.bridge.Promise
|
|
6
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
@@ -72,16 +73,94 @@ class HubspotWrapperModule(reactContext: ReactApplicationContext) :
|
|
|
72
73
|
override fun clearUserData(promise: Promise) {
|
|
73
74
|
CoroutineScope(Dispatchers.Main).launch {
|
|
74
75
|
try {
|
|
76
|
+
Log.i(TAG, "clearUserData: invoked")
|
|
75
77
|
hubspotManager.logout()
|
|
76
|
-
|
|
78
|
+
clearHubspotSessionCookies()
|
|
79
|
+
Log.i(TAG, "clearUserData: done")
|
|
77
80
|
promise.resolve(null)
|
|
78
81
|
} catch (error: Exception) {
|
|
82
|
+
Log.e(TAG, "clearUserData: failed", error)
|
|
79
83
|
promise.reject("CLEAR_USER_DATA_ERROR", "Failed to clear HubSpot user data", error)
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Mirror the iOS SDK's `cookiesToDeleteWhenClearingData` behavior: only drop the cookies that
|
|
90
|
+
* tie the embedded chat WebView to a previous HubSpot visitor identity (`hubspotutk`,
|
|
91
|
+
* `messagesUtk`). Anything else - Cloudflare bot-management cookies, future preference cookies,
|
|
92
|
+
* etc. - is intentionally preserved so the next chat session starts with a fresh identity but
|
|
93
|
+
* doesn't pay any unrelated cold-start tax.
|
|
94
|
+
*
|
|
95
|
+
* Implementation notes for Android:
|
|
96
|
+
*
|
|
97
|
+
* - `CookieManager.getCookie(url)` only returns `name=value` pairs - no `Domain`/`Path`
|
|
98
|
+
* metadata - so to delete a cookie reliably we have to overwrite it with an expired date
|
|
99
|
+
* under every plausible scope (host-only, exact host `Domain`, parent `Domain`). Writes
|
|
100
|
+
* whose `Domain` doesn't match an existing cookie are silently rejected by the cookie store,
|
|
101
|
+
* so issuing all three is safe.
|
|
102
|
+
*
|
|
103
|
+
* - The chat URL's host is computed from `hubspot-info.json` using the same rule as the SDK's
|
|
104
|
+
* internal `Hublet`/`Environment` value classes (which are package-private):
|
|
105
|
+
*
|
|
106
|
+
* `https://app[-<hublet>].hubspot[qa].com/`
|
|
107
|
+
*
|
|
108
|
+
* For example: hublet `na2` + env `prod` -> `https://app-na2.hubspot.com/`.
|
|
109
|
+
* Hublet `na1` is special-cased to `app.hubspot.com`. Env `qa` adds the `qa` suffix
|
|
110
|
+
* to the apex (`hubspotqa.com`).
|
|
111
|
+
*
|
|
112
|
+
* - If `HubspotManager` hasn't been configured yet, we fall back to `removeAllCookies()` so
|
|
113
|
+
* we never leak chat identity even in misconfigured states.
|
|
114
|
+
*
|
|
115
|
+
* Caveat (matches iOS): clearing `messagesUtk` resets the visitor identity, which causes
|
|
116
|
+
* HubSpot's chat embed to re-show its cookie consent banner on the next session. This is by
|
|
117
|
+
* design on HubSpot's side - their chat treats every new visitor id as a new visitor that
|
|
118
|
+
* must consent. There is no way to keep both "fresh chat" and "no consent reprompt" without
|
|
119
|
+
* also leaving the previous conversation thread visible to the user.
|
|
120
|
+
*/
|
|
121
|
+
private suspend fun clearHubspotSessionCookies() {
|
|
122
|
+
val hubletId = runCatching { hubspotManager.getHublet() }.getOrNull()
|
|
123
|
+
val environment = runCatching { hubspotManager.getEnvironment() }.getOrNull()
|
|
124
|
+
|
|
125
|
+
if (hubletId.isNullOrEmpty() || environment.isNullOrEmpty()) {
|
|
126
|
+
Log.w(TAG, "clearUserData: missing hublet/environment, falling back to removeAllCookies()")
|
|
127
|
+
clearAllWebViewCookies()
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
val cookieManager = CookieManager.getInstance()
|
|
132
|
+
val urlSuffix = if (environment.equals("qa", ignoreCase = true)) "qa" else ""
|
|
133
|
+
val rootDomain = "hubspot$urlSuffix.com"
|
|
134
|
+
val appsSubDomain = if (hubletId.equals("na1", ignoreCase = true)) "app" else "app-$hubletId"
|
|
135
|
+
val chatHost = "$appsSubDomain.$rootDomain"
|
|
136
|
+
val chatUrl = "https://$chatHost/"
|
|
137
|
+
|
|
138
|
+
val existingCookieNames = cookieManager.getCookie(chatUrl).orEmpty()
|
|
139
|
+
.split(";")
|
|
140
|
+
.mapNotNull { it.substringBefore("=").trim().takeIf(String::isNotEmpty) }
|
|
141
|
+
.toSet()
|
|
142
|
+
|
|
143
|
+
val toDelete = COOKIES_TO_CLEAR.filter { it in existingCookieNames }
|
|
144
|
+
if (toDelete.isEmpty()) {
|
|
145
|
+
Log.i(TAG, "clearUserData: no chat-identity cookies present at $chatHost, nothing to delete")
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
Log.i(TAG, "clearUserData: deleting cookies=$toDelete on $chatHost")
|
|
150
|
+
val expiry = "Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT"
|
|
151
|
+
toDelete.forEach { name ->
|
|
152
|
+
cookieManager.setCookie(chatUrl, "$name=; $expiry")
|
|
153
|
+
cookieManager.setCookie(chatUrl, "$name=; Domain=$chatHost; $expiry")
|
|
154
|
+
cookieManager.setCookie(chatUrl, "$name=; Domain=.$rootDomain; $expiry")
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
suspendCancellableCoroutine<Unit> { continuation ->
|
|
158
|
+
cookieManager.flush()
|
|
159
|
+
continuation.resume(Unit)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private suspend fun clearAllWebViewCookies() {
|
|
85
164
|
suspendCancellableCoroutine<Unit> { continuation ->
|
|
86
165
|
val cookieManager = CookieManager.getInstance()
|
|
87
166
|
cookieManager.removeAllCookies {
|
|
@@ -93,5 +172,14 @@ class HubspotWrapperModule(reactContext: ReactApplicationContext) :
|
|
|
93
172
|
|
|
94
173
|
companion object {
|
|
95
174
|
const val NAME = "NativeHubspotWrapper"
|
|
175
|
+
|
|
176
|
+
private const val TAG = "HubspotWrapper"
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Cookies that bind the embedded chat WebView to a specific HubSpot visitor identity.
|
|
180
|
+
* Matches the iOS SDK's `cookiesToDeleteWhenClearingData`.
|
|
181
|
+
* See https://knowledge.hubspot.com/privacy-and-consent/what-cookies-does-hubspot-set-in-a-visitor-s-browser
|
|
182
|
+
*/
|
|
183
|
+
private val COOKIES_TO_CLEAR = listOf("hubspotutk", "messagesUtk")
|
|
96
184
|
}
|
|
97
185
|
}
|