react-native-hubspot-wrapper 0.1.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.
Files changed (36) hide show
  1. package/HUBSPOT_IOS_SDK_VERSION.json +5 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION.md +19 -0
  4. package/README.md +119 -0
  5. package/ReactNativeHubspotWrapper.podspec +27 -0
  6. package/android/build.gradle +49 -0
  7. package/android/src/main/AndroidManifest.xml +1 -0
  8. package/android/src/main/java/com/reactnativehubspotwrapper/HubspotWrapperModule.kt +83 -0
  9. package/android/src/main/java/com/reactnativehubspotwrapper/HubspotWrapperPackage.kt +32 -0
  10. package/ios/HubspotMobileSDK/API/APIModels.swift +50 -0
  11. package/ios/HubspotMobileSDK/API/HubspotAPI.swift +168 -0
  12. package/ios/HubspotMobileSDK/DeviceTokenSyncState.swift +13 -0
  13. package/ios/HubspotMobileSDK/HubspotConfig.swift +145 -0
  14. package/ios/HubspotMobileSDK/HubspotManager+Notifications.swift +178 -0
  15. package/ios/HubspotMobileSDK/HubspotManager+Properties.swift +53 -0
  16. package/ios/HubspotMobileSDK/HubspotManager.swift +548 -0
  17. package/ios/HubspotMobileSDK/HubspotMobileSDK.swift +7 -0
  18. package/ios/HubspotMobileSDK/HubspotUserProperties.swift +115 -0
  19. package/ios/HubspotMobileSDK/LICENSE.txt +19 -0
  20. package/ios/HubspotMobileSDK/PushNotificationChatData.swift +63 -0
  21. package/ios/HubspotMobileSDK/Resources/Images.xcassets/Contents.json +6 -0
  22. package/ios/HubspotMobileSDK/Resources/Images.xcassets/GenericChatIcon.imageset/Contents.json +16 -0
  23. package/ios/HubspotMobileSDK/Resources/Images.xcassets/GenericChatIcon.imageset/chat-open-svg.svg +1 -0
  24. package/ios/HubspotMobileSDK/Resources/Localizable.xcstrings +28 -0
  25. package/ios/HubspotMobileSDK/Resources/PrivacyInfo.xcprivacy +62 -0
  26. package/ios/HubspotMobileSDK/Views/Buttons/FloatingActionButton.swift +126 -0
  27. package/ios/HubspotMobileSDK/Views/Buttons/TextChatButtonChatButton.swift +78 -0
  28. package/ios/HubspotMobileSDK/Views/ChatView/HubspotChatView.swift +612 -0
  29. package/ios/HubspotWrapperImpl.swift +108 -0
  30. package/ios/RNHubspotWrapper.h +9 -0
  31. package/ios/RNHubspotWrapper.mm +66 -0
  32. package/package.json +55 -0
  33. package/react-native.config.js +11 -0
  34. package/scripts/update-hubspot-ios-sdk.sh +142 -0
  35. package/src/index.ts +41 -0
  36. package/src/specs/NativeHubspotWrapper.ts +17 -0
@@ -0,0 +1,108 @@
1
+ import Foundation
2
+ import SwiftUI
3
+ import UIKit
4
+
5
+ @objcMembers
6
+ public class HubspotWrapperImpl: NSObject {
7
+ public func initialize(_ outError: NSErrorPointer) -> Bool {
8
+ let semaphore = DispatchSemaphore(value: 0)
9
+ var configureError: NSError?
10
+
11
+ Task { @MainActor in
12
+ do {
13
+ try HubspotManager.configure()
14
+ } catch let error {
15
+ configureError = error as NSError
16
+ }
17
+ semaphore.signal()
18
+ }
19
+
20
+ semaphore.wait()
21
+
22
+ if let configureError {
23
+ outError?.pointee = configureError
24
+ return false
25
+ }
26
+ return true
27
+ }
28
+
29
+ public func openChat(_ chatflow: String, error outError: NSErrorPointer) -> Bool {
30
+ var didSucceed = false
31
+ let presentBlock = {
32
+ guard let rootVC = Self.topViewController() else {
33
+ outError?.pointee = NSError(
34
+ domain: "HubspotWrapper",
35
+ code: 1,
36
+ userInfo: [NSLocalizedDescriptionKey: "No root view controller available"]
37
+ )
38
+ return
39
+ }
40
+
41
+ let chatView = HubspotChatView(chatFlow: chatflow)
42
+ let hostingController = UIHostingController(rootView: chatView)
43
+ rootVC.present(hostingController, animated: true)
44
+ didSucceed = true
45
+ }
46
+
47
+ if Thread.isMainThread {
48
+ presentBlock()
49
+ } else {
50
+ DispatchQueue.main.sync(execute: presentBlock)
51
+ }
52
+
53
+ return didSucceed
54
+ }
55
+
56
+ public func setIdentity(_ identityToken: String, email: String?) {
57
+ let semaphore = DispatchSemaphore(value: 0)
58
+ Task { @MainActor in
59
+ HubspotManager.shared.setUserIdentity(identityToken: identityToken, email: email ?? "")
60
+ semaphore.signal()
61
+ }
62
+ semaphore.wait()
63
+ }
64
+
65
+ public func setProperties(_ properties: [NSDictionary]) {
66
+ var mapped: [String: String] = [:]
67
+
68
+ for item in properties {
69
+ guard
70
+ let name = item["name"] as? String,
71
+ let value = item["value"] as? String
72
+ else {
73
+ continue
74
+ }
75
+ mapped[name] = value
76
+ }
77
+
78
+ Task {
79
+ await HubspotManager.shared.setChatProperties(data: mapped)
80
+ }
81
+ }
82
+
83
+ public func clearUserData() {
84
+ Task {
85
+ await HubspotManager.shared.clearUserData()
86
+ }
87
+ }
88
+
89
+ private static func topViewController(
90
+ base: UIViewController? = UIApplication.shared.connectedScenes
91
+ .compactMap { $0 as? UIWindowScene }
92
+ .flatMap { $0.windows }
93
+ .first(where: \.isKeyWindow)?
94
+ .rootViewController
95
+ ) -> UIViewController? {
96
+ if let navigationController = base as? UINavigationController {
97
+ return topViewController(base: navigationController.visibleViewController)
98
+ }
99
+ if let tabBarController = base as? UITabBarController,
100
+ let selected = tabBarController.selectedViewController {
101
+ return topViewController(base: selected)
102
+ }
103
+ if let presented = base?.presentedViewController {
104
+ return topViewController(base: presented)
105
+ }
106
+ return base
107
+ }
108
+ }
@@ -0,0 +1,9 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <HubspotWrapperSpec/HubspotWrapperSpec.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ @interface RNHubspotWrapper : NSObject <NativeHubspotWrapperSpec>
7
+ @end
8
+
9
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,66 @@
1
+ #import "RNHubspotWrapper.h"
2
+ #import <UserNotifications/UserNotifications.h>
3
+ #import "ReactNativeHubspotWrapper-Swift.h"
4
+
5
+ @implementation RNHubspotWrapper {
6
+ HubspotWrapperImpl *_impl;
7
+ }
8
+
9
+ RCT_EXPORT_MODULE(NativeHubspotWrapper)
10
+
11
+ - (instancetype)init
12
+ {
13
+ self = [super init];
14
+ if (self) {
15
+ _impl = [HubspotWrapperImpl new];
16
+ }
17
+ return self;
18
+ }
19
+
20
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
21
+ (const facebook::react::ObjCTurboModule::InitParams &)params
22
+ {
23
+ return std::make_shared<facebook::react::NativeHubspotWrapperSpecJSI>(params);
24
+ }
25
+
26
+ - (void)initialize:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
27
+ {
28
+ NSError *error = nil;
29
+ BOOL didInitialize = [_impl initialize:&error];
30
+ if (didInitialize) {
31
+ resolve(nil);
32
+ return;
33
+ }
34
+ reject(@"INIT_ERROR", @"Failed to initialize HubSpot SDK", error);
35
+ }
36
+
37
+ - (void)openChat:(NSString *)chatflow resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
38
+ {
39
+ NSError *error = nil;
40
+ BOOL didOpen = [_impl openChat:chatflow error:&error];
41
+ if (didOpen) {
42
+ resolve(nil);
43
+ return;
44
+ }
45
+ reject(@"OPEN_CHAT_ERROR", @"Failed to open HubSpot chat", error);
46
+ }
47
+
48
+ - (void)setIdentity:(NSString *)identityToken email:(NSString * _Nullable)email resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
49
+ {
50
+ [_impl setIdentity:identityToken email:email];
51
+ resolve(nil);
52
+ }
53
+
54
+ - (void)setProperties:(NSArray<NSDictionary *> *)properties resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
55
+ {
56
+ [_impl setProperties:properties];
57
+ resolve(nil);
58
+ }
59
+
60
+ - (void)clearUserData:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
61
+ {
62
+ [_impl clearUserData];
63
+ resolve(nil);
64
+ }
65
+
66
+ @end
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "react-native-hubspot-wrapper",
3
+ "version": "0.1.0",
4
+ "description": "TurboModule wrapper for HubSpot mobile chat SDK",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "files": [
8
+ "src",
9
+ "android",
10
+ "ios",
11
+ "ReactNativeHubspotWrapper.podspec",
12
+ "react-native.config.js",
13
+ "README.md",
14
+ "MIGRATION.md",
15
+ "HUBSPOT_IOS_SDK_VERSION.json",
16
+ "scripts",
17
+ "LICENSE"
18
+ ],
19
+ "keywords": [
20
+ "react-native",
21
+ "hubspot",
22
+ "chat",
23
+ "turbomodule"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/marcinolek/react-native-hubspot-wrapper.git"
28
+ },
29
+ "author": "Marcin Olek",
30
+ "license": "MIT",
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "scripts": {
35
+ "update:hubspot:ios": "bash ./scripts/update-hubspot-ios-sdk.sh"
36
+ },
37
+ "peerDependencies": {
38
+ "react": "*",
39
+ "react-native": ">=0.81.0"
40
+ },
41
+ "codegenConfig": {
42
+ "name": "HubspotWrapperSpec",
43
+ "type": "modules",
44
+ "jsSrcsDir": "src/specs",
45
+ "android": {
46
+ "javaPackageName": "com.marcinolek.reactnativehubspotwrapper"
47
+ },
48
+ "ios": {
49
+ "modulesProvider": {
50
+ "NativeHubspotWrapper": "RNHubspotWrapper"
51
+ }
52
+ }
53
+ },
54
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
55
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ android: {
5
+ packageImportPath:
6
+ 'import com.marcinolek.reactnativehubspotwrapper.HubspotWrapperPackage;',
7
+ packageInstance: 'new HubspotWrapperPackage()'
8
+ }
9
+ }
10
+ }
11
+ };
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ REPO_URL="https://github.com/HubSpot/mobile-chat-sdk-ios.git"
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
8
+ TARGET_DIR="${ROOT_DIR}/ios/HubspotMobileSDK"
9
+ VERSION_FILE="${ROOT_DIR}/HUBSPOT_IOS_SDK_VERSION.json"
10
+
11
+ resolve_latest_tag() {
12
+ git ls-remote --tags --refs "${REPO_URL}" \
13
+ | awk -F'/' '{print $3}' \
14
+ | sort -V \
15
+ | tail -n 1
16
+ }
17
+
18
+ apply_cocoapods_compat_patches() {
19
+ python3 - "${TARGET_DIR}" <<'PY'
20
+ from pathlib import Path
21
+ import sys
22
+
23
+ target_dir = Path(sys.argv[1])
24
+
25
+ def replace_once(path: Path, old: str, new: str) -> None:
26
+ content = path.read_text()
27
+ if old not in content:
28
+ raise RuntimeError(f"Expected snippet not found in {path}")
29
+ path.write_text(content.replace(old, new, 1))
30
+
31
+ # 1) Replace SPM-only asset symbol usage in FloatingActionButton.
32
+ replace_once(
33
+ target_dir / "Views/Buttons/FloatingActionButton.swift",
34
+ "Image(.genericChatIcon)",
35
+ "Image.hubspotChat",
36
+ )
37
+
38
+ # 2) Replace SPM-only asset/localization usage in TextChatButton.
39
+ replace_once(
40
+ target_dir / "Views/Buttons/TextChatButtonChatButton.swift",
41
+ "Image(.genericChatIcon)",
42
+ "Image.hubspotChat",
43
+ )
44
+ replace_once(
45
+ target_dir / "Views/Buttons/TextChatButtonChatButton.swift",
46
+ 'Text("chat.label", bundle: .module)',
47
+ 'Text("chat.label", bundle: .hubspotResources)',
48
+ )
49
+
50
+ # 3) Add Bundle helper + shared image helper in HubspotManager.
51
+ hubspot_manager = target_dir / "HubspotManager.swift"
52
+ content = hubspot_manager.read_text()
53
+ old_block = """extension Image {
54
+ /// Exporting chat icon - initially for demo use - but maybe sharing some resources that aren't buttons or views might be needed eventually, if so refactor this
55
+ public static var hubspotChat: Image {
56
+ Image(.genericChatIcon)
57
+ }
58
+ }
59
+ """
60
+ new_block = """extension Image {
61
+ /// Exporting chat icon - initially for demo use - but maybe sharing some resources that aren't buttons or views might be needed eventually, if so refactor this
62
+ public static var hubspotChat: Image {
63
+ Image("GenericChatIcon", bundle: .hubspotResources)
64
+ }
65
+ }
66
+
67
+ extension Bundle {
68
+ static var hubspotResources: Bundle? {
69
+ // CocoaPods bundles resources into a separate bundle, unlike Swift Package's `Bundle.module`.
70
+ Bundle.main.url(forResource: "HubspotMobileSDKResources", withExtension: "bundle")
71
+ .flatMap { Bundle(url: $0) }
72
+ }
73
+ }
74
+ """
75
+ if old_block not in content:
76
+ raise RuntimeError("Expected Image extension block was not found in HubspotManager.swift")
77
+ hubspot_manager.write_text(content.replace(old_block, new_block, 1))
78
+ PY
79
+ }
80
+
81
+ TAG="${1:-}"
82
+ if [[ -z "${TAG}" ]]; then
83
+ TAG="$(resolve_latest_tag)"
84
+ if [[ -z "${TAG}" ]]; then
85
+ echo "Could not determine latest tag from ${REPO_URL}" >&2
86
+ exit 1
87
+ fi
88
+ fi
89
+
90
+ TMP_DIR="$(mktemp -d)"
91
+ cleanup() {
92
+ rm -rf "${TMP_DIR}"
93
+ }
94
+ trap cleanup EXIT
95
+
96
+ echo "Cloning ${REPO_URL} at tag ${TAG}..."
97
+ git clone --depth 1 --branch "${TAG}" "${REPO_URL}" "${TMP_DIR}/upstream"
98
+
99
+ if [[ ! -d "${TMP_DIR}/upstream/Sources/HubspotMobileSDK" ]]; then
100
+ echo "Expected Sources/HubspotMobileSDK was not found in tag ${TAG}" >&2
101
+ exit 1
102
+ fi
103
+
104
+ echo "Updating vendored iOS SDK sources..."
105
+ rm -rf "${TARGET_DIR}"
106
+ mkdir -p "$(dirname "${TARGET_DIR}")"
107
+ cp -R "${TMP_DIR}/upstream/Sources/HubspotMobileSDK" "${TARGET_DIR}"
108
+
109
+ # Drop upstream Xcode documentation bundle: ~800KB of PNGs and markdown files
110
+ # that the podspec does not pick up and that aren't needed by consumers.
111
+ rm -rf "${TARGET_DIR}/Documentation.docc"
112
+
113
+ # MIT requires distributing the upstream license alongside the vendored sources.
114
+ UPSTREAM_LICENSE=""
115
+ for candidate in LICENSE.txt LICENSE LICENSE.md; do
116
+ if [[ -f "${TMP_DIR}/upstream/${candidate}" ]]; then
117
+ UPSTREAM_LICENSE="${TMP_DIR}/upstream/${candidate}"
118
+ break
119
+ fi
120
+ done
121
+ if [[ -z "${UPSTREAM_LICENSE}" ]]; then
122
+ echo "Could not find an upstream LICENSE file at tag ${TAG}" >&2
123
+ exit 1
124
+ fi
125
+ cp "${UPSTREAM_LICENSE}" "${TARGET_DIR}/LICENSE.txt"
126
+
127
+ echo "Applying CocoaPods compatibility patches..."
128
+ apply_cocoapods_compat_patches
129
+
130
+ UPSTREAM_COMMIT="$(git -C "${TMP_DIR}/upstream" rev-parse HEAD)"
131
+
132
+ cat > "${VERSION_FILE}" <<EOF
133
+ {
134
+ "sourceRepository": "${REPO_URL}",
135
+ "tag": "${TAG}",
136
+ "commit": "${UPSTREAM_COMMIT}"
137
+ }
138
+ EOF
139
+
140
+ echo "Updated:"
141
+ echo " - ${TARGET_DIR}"
142
+ echo " - ${VERSION_FILE}"
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ import NativeHubspotWrapper, { HubspotProperty } from './specs/NativeHubspotWrapper';
2
+
3
+ export type { HubspotProperty };
4
+
5
+ export type SetIdentityParams = {
6
+ identityToken: string;
7
+ email?: string | null;
8
+ };
9
+
10
+ function ensureNonEmpty(value: string, fieldName: string): void {
11
+ if (!value || !value.trim()) {
12
+ throw new Error(`\`${fieldName}\` must be a non-empty string.`);
13
+ }
14
+ }
15
+
16
+ const HubspotWrapper = {
17
+ initialize(): Promise<void> {
18
+ return NativeHubspotWrapper.initialize();
19
+ },
20
+
21
+ openChat(chatflow: string): Promise<void> {
22
+ ensureNonEmpty(chatflow, 'chatflow');
23
+ return NativeHubspotWrapper.openChat(chatflow);
24
+ },
25
+
26
+ setIdentity(params: SetIdentityParams): Promise<void> {
27
+ ensureNonEmpty(params.identityToken, 'identityToken');
28
+ const email = params.email?.trim() || null;
29
+ return NativeHubspotWrapper.setIdentity(params.identityToken, email);
30
+ },
31
+
32
+ setProperties(properties: HubspotProperty[]): Promise<void> {
33
+ return NativeHubspotWrapper.setProperties(properties);
34
+ },
35
+
36
+ clearUserData(): Promise<void> {
37
+ return NativeHubspotWrapper.clearUserData();
38
+ }
39
+ };
40
+
41
+ export default HubspotWrapper;
@@ -0,0 +1,17 @@
1
+ import type { TurboModule } from 'react-native';
2
+ import { TurboModuleRegistry } from 'react-native';
3
+
4
+ export type HubspotProperty = {
5
+ name: string;
6
+ value: string;
7
+ };
8
+
9
+ export interface Spec extends TurboModule {
10
+ initialize(): Promise<void>;
11
+ openChat(chatflow: string): Promise<void>;
12
+ setIdentity(identityToken: string, email: string | null): Promise<void>;
13
+ setProperties(properties: ReadonlyArray<HubspotProperty>): Promise<void>;
14
+ clearUserData(): Promise<void>;
15
+ }
16
+
17
+ export default TurboModuleRegistry.getEnforcing<Spec>('NativeHubspotWrapper');