react-native-hubspot-wrapper 0.4.0 → 0.4.1
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/README.md +27 -2
- package/android/src/main/java/com/reactnativehubspotwrapper/HubspotBackButtonHider.kt +32 -3
- package/android/src/main/java/com/reactnativehubspotwrapper/HubspotWrapperModule.kt +2 -1
- package/ios/HubspotWrapperImpl.swift +6 -1
- package/ios/RNHubspotWrapper.mm +2 -2
- package/package.json +3 -2
- package/src/index.ts +6 -2
- package/src/specs/NativeHubspotWrapper.ts +1 -1
- 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 +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Early release (`0.x`). The public API may evolve before `1.0`.
|
|
|
12
12
|
Currently supported:
|
|
13
13
|
|
|
14
14
|
- `initialize()`
|
|
15
|
-
- `openChat(chatflow)`
|
|
15
|
+
- `openChat(chatflow, options?)`
|
|
16
16
|
- `setIdentity({ identityToken, email? })`
|
|
17
17
|
- `setProperties(properties)`
|
|
18
18
|
- `clearUserData()`
|
|
@@ -61,14 +61,39 @@ await HubspotWrapper.setProperties([{ name: 'plan', value: 'pro' }]);
|
|
|
61
61
|
await HubspotWrapper.openChat('support');
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
By default, `openChat` hides HubSpot's "back to inbox" / conversations-list
|
|
65
|
+
button inside the chat widget. This wrapper is opinionated toward single-chat
|
|
66
|
+
support flows, but you can preserve HubSpot's stock UI per chat:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
await HubspotWrapper.openChat('support', {
|
|
70
|
+
hideBackToInboxButton: false,
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
64
74
|
## API
|
|
65
75
|
|
|
66
76
|
- `initialize(): Promise<void>`
|
|
67
|
-
- `openChat(chatflow: string): Promise<void>`
|
|
77
|
+
- `openChat(chatflow: string, options?: { hideBackToInboxButton?: boolean }): Promise<void>`
|
|
68
78
|
- `setIdentity({ identityToken, email? }): Promise<void>`
|
|
69
79
|
- `setProperties(properties): Promise<void>`
|
|
70
80
|
- `clearUserData(): Promise<void>`
|
|
71
81
|
|
|
82
|
+
### `openChat(chatflow, options?)`
|
|
83
|
+
|
|
84
|
+
Opens the HubSpot chat UI for the provided chatflow.
|
|
85
|
+
|
|
86
|
+
- `hideBackToInboxButton` defaults to `true`.
|
|
87
|
+
- Set `hideBackToInboxButton: false` to keep HubSpot's default conversations
|
|
88
|
+
navigation visible.
|
|
89
|
+
|
|
90
|
+
### `clearUserData()`
|
|
91
|
+
|
|
92
|
+
Clears the native SDK's current user/session state and waits for HubSpot chat
|
|
93
|
+
visitor cookies/data cleanup to finish before resolving. This is intended for
|
|
94
|
+
logout or account-switch flows so the next chat session does not reuse the
|
|
95
|
+
previous visitor identity.
|
|
96
|
+
|
|
72
97
|
## iOS SDK source strategy
|
|
73
98
|
|
|
74
99
|
This package vendors HubSpot iOS SDK source files under `ios/HubspotMobileSDK`.
|
|
@@ -9,6 +9,7 @@ import android.util.Log
|
|
|
9
9
|
import android.view.View
|
|
10
10
|
import android.view.ViewGroup
|
|
11
11
|
import android.webkit.WebView
|
|
12
|
+
import java.util.WeakHashMap
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Hides the HubSpot chat widget's "back to inbox" / conversations-list navigation
|
|
@@ -38,6 +39,9 @@ import android.webkit.WebView
|
|
|
38
39
|
internal object HubspotBackButtonHider {
|
|
39
40
|
private const val TAG = "HubspotWrapper"
|
|
40
41
|
|
|
42
|
+
const val EXTRA_HIDE_BACK_TO_INBOX_BUTTON =
|
|
43
|
+
"com.marcinolek.reactnativehubspotwrapper.HIDE_BACK_TO_INBOX_BUTTON"
|
|
44
|
+
|
|
41
45
|
/**
|
|
42
46
|
* The fully-qualified name of HubSpot's chat activity in the AAR. Matched by
|
|
43
47
|
* string so we don't have to import / depend on it for compile-time resolution.
|
|
@@ -57,6 +61,9 @@ internal object HubspotBackButtonHider {
|
|
|
57
61
|
@Volatile
|
|
58
62
|
private var installed = false
|
|
59
63
|
|
|
64
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
65
|
+
private val scheduledInjections = WeakHashMap<WebView, Runnable>()
|
|
66
|
+
|
|
60
67
|
@Synchronized
|
|
61
68
|
fun installOnce(app: Application) {
|
|
62
69
|
if (installed) return
|
|
@@ -65,6 +72,10 @@ internal object HubspotBackButtonHider {
|
|
|
65
72
|
app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
|
|
66
73
|
override fun onActivityResumed(activity: Activity) {
|
|
67
74
|
if (activity.javaClass.name != WEB_ACTIVITY_NAME) return
|
|
75
|
+
if (!activity.intent.getBooleanExtra(EXTRA_HIDE_BACK_TO_INBOX_BUTTON, true)) {
|
|
76
|
+
cancelHiderInjection(findWebView(activity.window.decorView))
|
|
77
|
+
return
|
|
78
|
+
}
|
|
68
79
|
val webView = findWebView(activity.window.decorView)
|
|
69
80
|
if (webView == null) {
|
|
70
81
|
Log.w(TAG, "installBackButtonHider: WebView not found in $WEB_ACTIVITY_NAME view tree")
|
|
@@ -76,10 +87,18 @@ internal object HubspotBackButtonHider {
|
|
|
76
87
|
|
|
77
88
|
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
|
|
78
89
|
override fun onActivityStarted(activity: Activity) {}
|
|
79
|
-
override fun onActivityPaused(activity: Activity) {
|
|
90
|
+
override fun onActivityPaused(activity: Activity) {
|
|
91
|
+
if (activity.javaClass.name == WEB_ACTIVITY_NAME) {
|
|
92
|
+
cancelHiderInjection(findWebView(activity.window.decorView))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
80
95
|
override fun onActivityStopped(activity: Activity) {}
|
|
81
96
|
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
|
82
|
-
override fun onActivityDestroyed(activity: Activity) {
|
|
97
|
+
override fun onActivityDestroyed(activity: Activity) {
|
|
98
|
+
if (activity.javaClass.name == WEB_ACTIVITY_NAME) {
|
|
99
|
+
cancelHiderInjection(findWebView(activity.window.decorView))
|
|
100
|
+
}
|
|
101
|
+
}
|
|
83
102
|
})
|
|
84
103
|
}
|
|
85
104
|
|
|
@@ -95,7 +114,8 @@ internal object HubspotBackButtonHider {
|
|
|
95
114
|
}
|
|
96
115
|
|
|
97
116
|
private fun scheduleHiderInjection(webView: WebView) {
|
|
98
|
-
|
|
117
|
+
cancelHiderInjection(webView)
|
|
118
|
+
|
|
99
119
|
val start = System.currentTimeMillis()
|
|
100
120
|
val runnable = object : Runnable {
|
|
101
121
|
override fun run() {
|
|
@@ -106,12 +126,21 @@ internal object HubspotBackButtonHider {
|
|
|
106
126
|
}
|
|
107
127
|
if (System.currentTimeMillis() - start < MAX_SCAN_DURATION_MS) {
|
|
108
128
|
handler.postDelayed(this, RESCAN_INTERVAL_MS)
|
|
129
|
+
} else {
|
|
130
|
+
scheduledInjections.remove(webView)
|
|
109
131
|
}
|
|
110
132
|
}
|
|
111
133
|
}
|
|
134
|
+
scheduledInjections[webView] = runnable
|
|
112
135
|
handler.post(runnable)
|
|
113
136
|
}
|
|
114
137
|
|
|
138
|
+
private fun cancelHiderInjection(webView: WebView?) {
|
|
139
|
+
if (webView == null) return
|
|
140
|
+
val runnable = scheduledInjections.remove(webView) ?: return
|
|
141
|
+
handler.removeCallbacks(runnable)
|
|
142
|
+
}
|
|
143
|
+
|
|
115
144
|
/**
|
|
116
145
|
* Heuristic-based hider for the chat widget's back/inbox button. Identical in
|
|
117
146
|
* shape to the iOS version in `HubspotWrapperImpl.swift`. Designed to be safe
|
|
@@ -42,11 +42,12 @@ class HubspotWrapperModule(reactContext: ReactApplicationContext) :
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
override fun openChat(chatflow: String, promise: Promise) {
|
|
45
|
+
override fun openChat(chatflow: String, hideBackToInboxButton: Boolean, promise: Promise) {
|
|
46
46
|
try {
|
|
47
47
|
val intent = Intent(appContext, HubspotWebActivity::class.java)
|
|
48
48
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
49
49
|
intent.putExtra("chatflow", chatflow)
|
|
50
|
+
intent.putExtra(HubspotBackButtonHider.EXTRA_HIDE_BACK_TO_INBOX_BUTTON, hideBackToInboxButton)
|
|
50
51
|
appContext.startActivity(intent)
|
|
51
52
|
promise.resolve(null)
|
|
52
53
|
} catch (error: Exception) {
|
|
@@ -5,6 +5,8 @@ import WebKit
|
|
|
5
5
|
|
|
6
6
|
@objcMembers
|
|
7
7
|
public class HubspotWrapperImpl: NSObject {
|
|
8
|
+
private static var shouldHideBackToInboxButton = true
|
|
9
|
+
|
|
8
10
|
/// Substrings used to match `WKWebsiteDataRecord.displayName` (which is the host /
|
|
9
11
|
/// eTLD+1, e.g. `hubspot.com`, `hs-banner.com`, `hsadspixel.net`) for the various
|
|
10
12
|
/// domains the chat widget loads from. Anything matching is wiped on `clearUserData`.
|
|
@@ -46,7 +48,7 @@ public class HubspotWrapperImpl: NSObject {
|
|
|
46
48
|
return true
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
public func openChat(_ chatflow: String, error outError: NSErrorPointer) -> Bool {
|
|
51
|
+
public func openChat(_ chatflow: String, hideBackToInboxButton: Bool, error outError: NSErrorPointer) -> Bool {
|
|
50
52
|
var didSucceed = false
|
|
51
53
|
let presentBlock = {
|
|
52
54
|
guard let rootVC = Self.topViewController() else {
|
|
@@ -58,6 +60,7 @@ public class HubspotWrapperImpl: NSObject {
|
|
|
58
60
|
return
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
Self.shouldHideBackToInboxButton = hideBackToInboxButton
|
|
61
64
|
let chatView = HubspotChatView(chatFlow: chatflow)
|
|
62
65
|
let hostingController = UIHostingController(rootView: chatView)
|
|
63
66
|
rootVC.present(hostingController, animated: true)
|
|
@@ -164,6 +167,8 @@ public class HubspotWrapperImpl: NSObject {
|
|
|
164
167
|
/// and visible text. Re-scans periodically for the first few seconds to cover
|
|
165
168
|
/// late-mounted shadow roots. Injected at document start in every frame.
|
|
166
169
|
@objc public static func installBackButtonHider(on controller: WKUserContentController) {
|
|
170
|
+
guard shouldHideBackToInboxButton else { return }
|
|
171
|
+
|
|
167
172
|
let script = WKUserScript(
|
|
168
173
|
source: backButtonHiderJS,
|
|
169
174
|
injectionTime: .atDocumentStart,
|
package/ios/RNHubspotWrapper.mm
CHANGED
|
@@ -34,10 +34,10 @@ RCT_EXPORT_MODULE(NativeHubspotWrapper)
|
|
|
34
34
|
reject(@"INIT_ERROR", @"Failed to initialize HubSpot SDK", error);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
- (void)openChat:(NSString *)chatflow resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
37
|
+
- (void)openChat:(NSString *)chatflow hideBackToInboxButton:(BOOL)hideBackToInboxButton resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
38
38
|
{
|
|
39
39
|
NSError *error = nil;
|
|
40
|
-
BOOL didOpen = [_impl openChat:chatflow error:&error];
|
|
40
|
+
BOOL didOpen = [_impl openChat:chatflow hideBackToInboxButton:hideBackToInboxButton error:&error];
|
|
41
41
|
if (didOpen) {
|
|
42
42
|
resolve(nil);
|
|
43
43
|
return;
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-hubspot-wrapper",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "TurboModule wrapper for HubSpot mobile chat SDK",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"src",
|
|
9
|
-
"android",
|
|
9
|
+
"android/build.gradle",
|
|
10
|
+
"android/src",
|
|
10
11
|
"ios",
|
|
11
12
|
"ReactNativeHubspotWrapper.podspec",
|
|
12
13
|
"react-native.config.js",
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,10 @@ export type SetIdentityParams = {
|
|
|
7
7
|
email?: string | null;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
export type OpenChatOptions = {
|
|
11
|
+
hideBackToInboxButton?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
10
14
|
function ensureNonEmpty(value: string, fieldName: string): void {
|
|
11
15
|
if (!value || !value.trim()) {
|
|
12
16
|
throw new Error(`\`${fieldName}\` must be a non-empty string.`);
|
|
@@ -18,9 +22,9 @@ const HubspotWrapper = {
|
|
|
18
22
|
return NativeHubspotWrapper.initialize();
|
|
19
23
|
},
|
|
20
24
|
|
|
21
|
-
openChat(chatflow: string): Promise<void> {
|
|
25
|
+
openChat(chatflow: string, options: OpenChatOptions = {}): Promise<void> {
|
|
22
26
|
ensureNonEmpty(chatflow, 'chatflow');
|
|
23
|
-
return NativeHubspotWrapper.openChat(chatflow);
|
|
27
|
+
return NativeHubspotWrapper.openChat(chatflow, options.hideBackToInboxButton ?? true);
|
|
24
28
|
},
|
|
25
29
|
|
|
26
30
|
setIdentity(params: SetIdentityParams): Promise<void> {
|
|
@@ -8,7 +8,7 @@ export type HubspotProperty = {
|
|
|
8
8
|
|
|
9
9
|
export interface Spec extends TurboModule {
|
|
10
10
|
initialize(): Promise<void>;
|
|
11
|
-
openChat(chatflow: string): Promise<void>;
|
|
11
|
+
openChat(chatflow: string, hideBackToInboxButton: boolean): Promise<void>;
|
|
12
12
|
setIdentity(identityToken: string, email: string | null): Promise<void>;
|
|
13
13
|
setProperties(properties: ReadonlyArray<HubspotProperty>): Promise<void>;
|
|
14
14
|
clearUserData(): Promise<void>;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
File without changes
|