Skip to content

Commit d06f923

Browse files
fix(ios): implement push notification handling and add docs (#9)
* fix(ios): implement push notification handling and add docs - receivePush now calls Intercom.handlePushNotification() so tapping a notification opens the conversation (was a no-op before) - Dispatch Intercom SDK calls to main thread to avoid UI-on-background thread crashes - Add iOS push notification setup docs to README covering Info.plist, AppDelegate, and JS-side registration/tap handling * fix(ios): decode APNs hex token to raw bytes for Intercom The previous implementation used token.data(using: .utf8) which treated the hex string as UTF-8 text. APNs device tokens are hex-encoded and must be decoded to raw Data bytes. Added Data(hexString:) initializer to properly decode the token, with error handling for invalid hex strings and main thread dispatch for Intercom SDK calls. * style: fix prettier formatting in IntercomInitProvider.java
1 parent da8c5c6 commit d06f923

File tree

3 files changed

+130
-6
lines changed

3 files changed

+130
-6
lines changed

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,80 @@ Alternatively, you can initialize at runtime using `loadWithKeys()`.
6565

6666
The Intercom iOS SDK (`~> 19.0`) is included automatically via CocoaPods or Swift Package Manager.
6767

68+
#### Push Notifications
69+
70+
1. **Disable Intercom's auto push integration** — Add this to your `Info.plist` to prevent conflicts with Capacitor's push handling:
71+
72+
```xml
73+
<key>IntercomAutoIntegratePushNotifications</key>
74+
<false/>
75+
```
76+
77+
2. **Forward the device token to Capacitor** — In your `AppDelegate.swift`:
78+
79+
```swift
80+
import UIKit
81+
import Capacitor
82+
83+
@UIApplicationMain
84+
class AppDelegate: UIResponder, UIApplicationDelegate {
85+
86+
var window: UIWindow?
87+
88+
func application(_ application: UIApplication,
89+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
90+
return true
91+
}
92+
93+
func application(_ application: UIApplication,
94+
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
95+
// Forward to Capacitor — the plugin's observer picks this up
96+
// and sends the token to Intercom automatically
97+
NotificationCenter.default.post(
98+
name: .capacitorDidRegisterForRemoteNotifications,
99+
object: deviceToken
100+
)
101+
}
102+
103+
func application(_ application: UIApplication,
104+
didFailToRegisterForRemoteNotificationsWithError error: Error) {
105+
NotificationCenter.default.post(
106+
name: .capacitorDidFailToRegisterForRemoteNotifications,
107+
object: error
108+
)
109+
}
110+
}
111+
```
112+
113+
3. **Register for push and handle notification taps** — In your app's JavaScript:
114+
115+
```typescript
116+
import { PushNotifications } from '@capacitor/push-notifications';
117+
import { CapgoIntercom as Intercom } from '@capgo/capacitor-intercom';
118+
119+
// Request permission + register
120+
const perm = await PushNotifications.requestPermissions();
121+
if (perm.receive === 'granted') {
122+
await PushNotifications.register();
123+
}
124+
125+
// Auto-forward FCM token to Intercom
126+
PushNotifications.addListener('registration', async (token) => {
127+
await Intercom.sendPushTokenToIntercom({ value: token.value });
128+
});
129+
130+
// When user taps a notification, tell Intercom to open the conversation
131+
PushNotifications.addListener('pushNotificationActionPerformed', async (action) => {
132+
const data = action.notification?.data;
133+
// iOS uses 'intercom_push_type', Android uses 'receiver'
134+
if (data?.intercom_push_type || data?.receiver === 'intercom_sdk') {
135+
await Intercom.receivePush(data);
136+
}
137+
});
138+
```
139+
140+
> **Note:** The plugin automatically registers the APNs device token with Intercom when it detects `.capacitorDidRegisterForRemoteNotifications`. You must still upload your APNs Authentication Key (`.p8`) in the Firebase Console under **Project Settings → Cloud Messaging → APNs Authentication Key**.
141+
68142
### Android
69143

70144
The Intercom Android SDK (`17.4.2`) is included automatically via Gradle.

android/src/main/java/app/capgo/capacitor/intercom/IntercomInitProvider.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,27 @@ public boolean onCreate() {
6868
}
6969

7070
@Override
71-
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; }
71+
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
72+
return null;
73+
}
74+
7275
@Override
73-
public String getType(Uri uri) { return null; }
76+
public String getType(Uri uri) {
77+
return null;
78+
}
79+
7480
@Override
75-
public Uri insert(Uri uri, ContentValues values) { return null; }
81+
public Uri insert(Uri uri, ContentValues values) {
82+
return null;
83+
}
84+
7685
@Override
77-
public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; }
86+
public int delete(Uri uri, String selection, String[] selectionArgs) {
87+
return 0;
88+
}
89+
7890
@Override
79-
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; }
91+
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
92+
return 0;
93+
}
8094
}

ios/Sources/CapgoIntercomPlugin/CapgoIntercomPlugin.swift

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,32 @@ import Foundation
22
import Capacitor
33
import Intercom
44

5+
private extension Data {
6+
init?(hexString: String) {
7+
let hex = hexString.replacingOccurrences(of: ["<", ">", " "], with: "")
8+
guard hex.count.isMultiple(of: 2) else { return nil }
9+
var data = Data(capacity: hex.count / 2)
10+
var index = hex.startIndex
11+
while index < hex.endIndex {
12+
let nextIndex = hex.index(index, offsetBy: 2)
13+
guard let byte = UInt8(hex[index..<nextIndex], radix: 16) else { return nil }
14+
data.append(byte)
15+
index = nextIndex
16+
}
17+
self = data
18+
}
19+
}
20+
21+
private extension String {
22+
func replacingOccurrences(of targets: [String], with replacement: String) -> String {
23+
var result = self
24+
for target in targets {
25+
result = result.replacingOccurrences(of: target, with: replacement)
26+
}
27+
return result
28+
}
29+
}
30+
531
@objc(CapgoIntercomPlugin)
632
public class CapgoIntercomPlugin: CAPPlugin, CAPBridgedPlugin {
733
public let identifier = "CapgoIntercomPlugin"
@@ -355,13 +381,23 @@ public class CapgoIntercomPlugin: CAPPlugin, CAPBridgedPlugin {
355381
return
356382
}
357383

358-
if let tokenData = token.data(using: .utf8) {
384+
guard let tokenData = Data(hexString: token) else {
385+
call.reject("Invalid hex token string")
386+
return
387+
}
388+
DispatchQueue.main.async {
359389
Intercom.setDeviceToken(tokenData, completion: nil)
360390
}
361391
call.resolve()
362392
}
363393

364394
@objc func receivePush(_ call: CAPPluginCall) {
395+
let userInfo = call.options as? [AnyHashable: Any] ?? [:]
396+
DispatchQueue.main.async {
397+
if Intercom.isIntercomPushNotification(userInfo) {
398+
Intercom.handlePushNotification(userInfo)
399+
}
400+
}
365401
call.resolve()
366402
}
367403

0 commit comments

Comments
 (0)