@freshworks/react-native-freshdesk-sdk
Version:
React Native wrapper for Freshdesk Android and iOS SDKs
118 lines (95 loc) • 8.39 kB
Markdown
# Platform API Differences — Freshdesk React Native SDK
Use this reference when wiring integrations. The JavaScript API is shared; native setup and a few JS methods differ by platform.
## JavaScript APIs — platform matrix
| API | Android | iOS | Notes |
|-----|---------|-----|-------|
| `initialize(config)` | Yes | Yes | Required once before any other call. **iOS resolves only after a ~2s settle delay** — expected, not a bug; see below. |
| `openSupport()` | Yes | Yes | On iOS, only call after `initialize()` has actually resolved — see settle-delay note below |
| `openKnowledgeBase()` | Yes | Yes | |
| `openTopic({ topicName, topicId? })` | Yes | Yes | |
| `getUnreadCount()` | Yes | Yes | Android: cached broadcast value, resolves `0` until the first broadcast after init. iOS: live value. |
| `dismiss()` | Yes | Yes | |
| `resetUser()` | Yes | Yes | Call on logout. **iOS has no failure callback** — always resolves `{success:true}` once initialized; only rejects if called before init. Do not treat a resolved promise as proof of a successful reset on iOS. |
| `setUserProperties(props)` | Yes | Yes | **Do not use when JWT is enforced.** On iOS, only call after `initialize()` has actually resolved. |
| `setTicketProperties(props)` | Yes | Yes | |
| `authenticateAndUpdate(jwt)` | Yes | Yes | Refresh JWT after init |
| `trackEvent(name, props?)` | Yes | Yes | **Android passes property values through typed** (`string \| number \| boolean`); **iOS coerces every value to a string** before handing it to the native SDK. A numeric property reaches Android's backend as a number, iOS's as a string. On iOS, only call after `initialize()` has actually resolved. |
| `getSDKVersion()` | Yes | Yes | |
| `getUser()` | Yes | Yes | |
| `setContentConfiguration(config)` | Yes | Yes | Persists immediately |
| `addUnreadCountListener(cb)` | Yes | Yes | Returns subscription with `.remove()` |
| `addUserStateListener(cb)` | Yes | Yes | JWT auth states |
| `addUserCreatedListener(cb)` | **No** | **Yes** | iOS only — new anonymous user created |
| `setLinkHandler(cb)` | Yes | Yes | Custom URL handling inside widget |
| `removeAllListeners()` | Yes | Yes | Cleanup on unmount |
| `enableDebugLogs(enabled)` | Yes | Yes | iOS toggles native logs immediately at any time. **Android only takes effect if `debugMode: true` was passed to `initialize()`** — calling `enableDebugLogs(true)` afterward is a no-op on Android, it does not retroactively enable logging or re-init. |
| `runDiagnostics()` | Partial | Full | See Diagnostics section |
| `debugMode` in `initialize()` | **Yes** | No-op | Android: the *only* reliable way to get Logcat output — set it here, not via `enableDebugLogs()` after the fact. iOS: ignored by `initialize()`; use `enableDebugLogs()` instead, works anytime. |
| `FreshdeskErrorCode` | Partial | Partial | Exported enum of all reject codes. Not every code is emitted on both platforms — Android has several method-specific codes iOS doesn't, iOS has `NO_VIEW_CONTROLLER`/`USER_PARSE_ERROR` Android doesn't. Check each member's JSDoc for which platform(s) can throw it. |
### iOS `initialize()` settle delay
The native iOS SDK's `Freshdesk.initialize(with:)` has **no completion
callback, `async` variant, or readiness signal of any kind** — confirmed by
inspecting the vendored xcframework's own `.swiftinterface`. It does its own
async internal loading after `initialize()` returns; calling `openSupport()`/
`trackEvent()`/`setUserProperties()`/etc. before that finishes gets silently
queued and can be dropped by the native SDK (logged as `"Tasks will be
executed once the SDK is loaded"`). The wrapper holds `initialize()`'s
promise for a fixed ~2 second settle delay past the native call returning
before resolving, so **normal `await initialize()` usage is safe** — the
only thing to know is that the `await` itself now always takes ≥2s on iOS.
Do not "optimize" this away, and do not diagnose a 2+ second `initialize()`
call on iOS as a bug on its own.
## Push notifications — no JavaScript API
Push is **native only**. FCM/APNs tokens can arrive before JavaScript loads.
| Concern | Android | iOS |
|---------|---------|-----|
| In-app init | **JS** `FreshdeskSDK.initialize()` | **JS** or native in AppDelegate for push |
| Headless/killed push init | **`FirebaseMessagingService` only** — not `MainApplication.onCreate` | `AppDelegate` `didFinishLaunchingWithOptions` (sync) |
| Token forwarding | `setPushRegistrationToken(token)` in FCM service | APNs token in `didRegisterForRemoteNotificationsWithDeviceToken` |
| Message handling | `isFreshdeskSDKNotification()` + `handleFCMNotification()` | Three paths: foreground, tap, background/killed |
| Credentials source | `BuildConfig.FRESHDESK_*` from `build.gradle` | `Info.plist` keys `FreshdeskToken`, `FreshdeskHost`, `FreshdeskSdkId`, `FreshdeskLocale`, `FreshdeskJwt` |
| Firebase / APNs | `google-services.json`, Google Services plugin, portal FCM upload | `.p8` Auth Key, Push + Background Modes capabilities, real device required |
| Diagnostics checks | `push.messagingService` (wrapper) | `push.permission`, `push.sdkToken`, `push.deviceRegistered`, etc. |
## Diagnostics — platform behavior
| Check area | iOS | Android |
|------------|-----|---------|
| Config (`config.token`, `config.host`, `config.sdkId`) | Native, secrets masked | Wrapper-level |
| Network (`network.configEndpoint`) | Native | Skipped until native parity |
| JWT (`jwt.*`, `remoteConfig.jwtEnforced`) | Native | Skipped |
| Push (`push.*`) | Native | `push.messagingService` wrapper check |
| Runtime (`runtime.sdkInitialized`, `runtime.nativeModule`) | Native + wrapper | Wrapper |
| `runtime.diagnostics` | pass | **skipped** — use `debugMode: true` + Logcat |
Always run after init:
```typescript
await FreshdeskSDK.enableDebugLogs(true);
const report = await FreshdeskSDK.runDiagnostics();
console.log(report.prettyPrinted);
```
Apply each non-pass check's `fixHint` verbatim.
## Credential file paths (discover in host app)
Agent must locate these paths in the target app — names vary by project:
| Value | JS / shared | Android (push) | iOS (push) |
|-------|-------------|----------------|------------|
| Token | `.env` → `FRESHDESK_TOKEN` | `android/app/build.gradle` → `buildConfigField "FRESHDESK_TOKEN"` | `ios/<App>/Info.plist` → `FreshdeskToken` |
| Host | `.env` → `FRESHDESK_HOST` | `buildConfigField "FRESHDESK_HOST"` | `FreshdeskHost` |
| SDK ID | `.env` → `FRESHDESK_SDK_ID` | `buildConfigField "FRESHDESK_SDK_ID"` | `FreshdeskSdkId` |
| Locale | `.env` → `FRESHDESK_LOCALE` | `buildConfigField "FRESHDESK_LOCALE"` | `FreshdeskLocale` |
| JWT | `.env` → `FRESHDESK_JWT` | `buildConfigField "FRESHDESK_JWT"` | `FreshdeskJwt` |
Host accepts bare domain or full URL; SDK normalizes to `https://` automatically (1.2.2+).
## Initialization verification
After wiring, confirm:
1. `FreshdeskSDK.initialize()` runs once, early (root provider / `App.tsx`).
2. `runtime.sdkInitialized` is `pass` in diagnostics.
3. If push enabled: native init runs on both platforms before JS (mirror sample app).
4. Opening support once before declaring success.
5. Re-run diagnostics; all targeted checks `pass` or acceptable `warn`.
Common init failures:
| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `FRESHDESK_INVALID_CONFIG` | Empty token/host/sdkId in `.env` or babel `@env` not wired | Fill `.env`, restart Metro |
| `FRESHDESK_NOT_INITIALIZED` | Method called before init completes | Await init in provider; guard UI |
| `runtime.nativeModule: fail` | Pods/Gradle not linked | `pod install`, clean rebuild |
| `network.configEndpoint: fail` | Wrong host format or revoked token | Host is auto-normalized (https:// prefixed) on wrapper 1.2.2+ |
| Push token not set | JS-only init | Add native init in FCM service only — not MainApplication |
| `runtime.doubleInit: warn` | Native init in MainApplication + JS init | Remove MainApplication.onCreate init; JS for in-app, FCM service for headless push |
| iOS: `openSupport()`/`trackEvent()`/etc. silently no-op right after `initialize()` resolves | Method called before the SDK's *own* async internal loading finished — see the settle-delay note above | Confirm the SDK version has the settle-delay fix (`CHANGELOG.md`); it makes `await initialize()` safe on its own |