@booleanmaths/booleanmaths-rn-sdk 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.
@@ -10,11 +10,37 @@ Pod::Spec.new do |s|
10
10
  s.license = package["license"]
11
11
  s.authors = package["author"]
12
12
 
13
+ # No platform override needed. BooleanMathsSDK 1.0.1 lowered its own floor to
14
+ # 15.1, which is exactly React Native's min_ios_version_supported — pinning a
15
+ # higher floor here would push it onto every consuming app.
13
16
  s.platforms = { :ios => min_ios_version_supported }
14
17
  s.source = { :git => "https://github.com/medront/react-native-booleanmaths-sdk.git", :tag => "v#{s.version}" }
15
18
 
19
+ # Globs `swift` so ios/BMBooleanMathsBridge.swift is compiled. That shim is
20
+ # mandatory, not stylistic: BooleanMaths is a pure Swift `@MainActor final
21
+ # class` with no Objective-C surface, so the .mm cannot reach it directly.
16
22
  s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
23
+ # Does not match the generated BooleanmathsRnSdk-Swift.h, which the .mm needs.
17
24
  s.private_header_files = "ios/**/*.h"
18
25
 
26
+ s.swift_version = "5.9"
27
+
28
+ # `~> 1.0.1`, deliberately not `~> 1.0`: 1.0.0 is still published on Trunk
29
+ # carrying an iOS 17.0 deployment target, and resolving to it would break the
30
+ # install for any app below iOS 17.
31
+ s.dependency "BooleanMathsSDK", "~> 1.0.1"
32
+
33
+ # Mirrors Android's BuildConfig.WRAPPER_VERSION, which is read from
34
+ # package.json so the reported wrapper_version cannot drift from the published
35
+ # npm version — s.version is that same value. Consumed by the .mm as an
36
+ # Objective-C preprocessor define; it does not reach Swift.
37
+ #
38
+ # $(inherited) is load-bearing: without it this replaces the pod target's
39
+ # inherited defines instead of appending, dropping DEBUG, RCT_NEW_ARCH_ENABLED
40
+ # and the FOLLY_* flags that React Native's own xcconfig sets.
41
+ s.pod_target_xcconfig = {
42
+ "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) BMRN_WRAPPER_VERSION=\\\"#{s.version}\\\""
43
+ }
44
+
19
45
  install_modules_dependencies(s)
20
46
  end
package/README.md CHANGED
@@ -5,42 +5,90 @@ and attribution.
5
5
 
6
6
  This package is a thin TurboModule bridge over the native
7
7
  [`com.booleanmaths:bm-sdk`](https://central.sonatype.com/artifact/com.booleanmaths/bm-sdk)
8
- Android SDK.
8
+ Android SDK and the
9
+ [`BooleanMathsSDK`](https://cocoapods.org/pods/BooleanMathsSDK) iOS SDK.
9
10
 
10
- ---
11
+ **One import, one API, both platforms.** There are no platform-specific entry
12
+ points — `import { BooleanMaths } from '@booleanmaths/booleanmaths-rn-sdk'` and
13
+ call the same methods everywhere.
11
14
 
12
- ## ⚠️ Platform support
15
+ ---
13
16
 
14
- > **There is no BooleanMaths iOS SDK yet.** The native SDK is published for
15
- > **Android only**. On iOS (and on web) this package installs and builds
16
- > normally, but **every SDK call is a no-op and no events are tracked**.
17
+ ## Platform support
17
18
 
18
19
  | Platform | Native SDK | Behaviour |
19
20
  | :------- | :--------- | :-------- |
20
21
  | Android | ✅ `com.booleanmaths:bm-sdk:1.0.9` | Fully functional |
21
- | iOS | not published | Silent no-op, **never crashes** |
22
+ | iOS | `BooleanMathsSDK 1.0.1` | Event tracking fully functional — see below |
22
23
  | Web | ❌ not published | Silent no-op, **never crashes** |
23
24
 
25
+ ### What iOS supports
26
+
27
+ Event tracking is complete and at parity with Android. Attribution features
28
+ are not — they are deferred to a later release, so their absence is scope
29
+ rather than a bug.
30
+
31
+ | Feature | Android | iOS |
32
+ | :------ | :-----: | :-: |
33
+ | `initialize()` | ✅ | ✅ |
34
+ | `trackEvent()` with nested properties | ✅ | ✅ |
35
+ | `wrapper_type` / `wrapper_version` on every event | ✅ | ✅ |
36
+ | Automatic `app_opened` | ✅ | ✅ |
37
+ | Automatic `FirstOpen` (once per install) | ✅ | ✅ — but with no attribution payload, see below |
38
+ | Visitor ID and 30-minute session handling | ✅ | ✅ |
39
+ | Durable on-device queue surviving app restarts | ✅ | ✅ |
40
+ | Automatic flush when the app backgrounds | ✅ | ✅ |
41
+ | `getHelloMessage()` bridge smoke test | ✅ | ✅ |
42
+ | **Deep links / universal links** (`DeepLinkClick`) | ✅ | ❌ |
43
+ | **Push-notification attribution** (`NotificationClick`) | ✅ | ❌ |
44
+ | **`handleIntent()` / `handleNotificationIntent()`** | ✅ | ❌ no-op |
45
+ | **Install attribution** on `FirstOpen` | ✅ Play Install Referrer | ❌ `data` is `{}` |
46
+
47
+ Notes on the iOS gaps:
48
+
49
+ - **`handleIntent()` is safe to call on iOS.** It reaches a native no-op and
50
+ logs one dev-mode notice. Shared code does not need to branch on platform.
51
+ - **`FirstOpen` still fires on iOS**, once per install — it just carries no
52
+ campaign payload. `data` is `{}` (present but empty), so the wire shape will
53
+ not change when Apple Search Ads attribution lands.
54
+ - On iOS there is **no `DeepLinkClick` or `NotificationClick` event at all**,
55
+ and consequently no `data.attribution` block on subsequent events.
56
+ - **`flush()` is not exposed to JavaScript** on either platform. The iOS SDK
57
+ flushes automatically on `willResignActive` / `didBecomeActive`; it will be
58
+ exposed only once Android has an equivalent, so it can ship as a genuinely
59
+ cross-platform API.
60
+ - macOS and tvOS are out of scope. The native SDK compiles for them; this
61
+ wrapper targets iOS only.
62
+
24
63
  ### Why it does not crash
25
64
 
26
- Two independent guards, so a missing iOS SDK can never take your app down:
65
+ Two independent guards, so an unsupported platform or an incomplete native
66
+ install can never take your app down:
27
67
 
28
68
  1. **JavaScript gate.** `BooleanMaths` checks `Platform.OS` against an
29
- allowlist (currently `['android']`) and returns before touching the native
69
+ allowlist (`['android', 'ios']`) and returns before touching the native
30
70
  bridge. It also verifies the TurboModule actually resolved, which covers a
31
- broken or incomplete native install.
32
- 2. **Native backstop.** iOS still ships a real TurboModule
33
- ([`ios/BooleanmathsRnSdk.mm`](ios/BooleanmathsRnSdk.mm)) whose methods are
34
- no-ops. So even a direct call into the bridge is harmless.
71
+ broken or incomplete native install — most often a JS install without a
72
+ native rebuild.
73
+ 2. **Native backstop.** Every native call is wrapped so failures are logged
74
+ rather than thrown into JavaScript
75
+ ([`safely()`](android/src/main/java/com/booleanmathsrnsdk/BooleanmathsRnSdkModule.kt)
76
+ on Android, `BMSafely()` in
77
+ [`ios/BooleanmathsRnSdk.mm`](ios/BooleanmathsRnSdk.mm)). Analytics should
78
+ never crash the host app.
79
+
80
+ On web the JavaScript gate is the whole story: a separate no-op implementation
81
+ ([`src/BooleanMaths.tsx`](src/BooleanMaths.tsx)) is resolved by the bundler, so
82
+ shared code can call the SDK unconditionally without platform checks.
35
83
 
36
84
  In development (`__DEV__`) you get **one** console warning per app launch, not
37
85
  one per call.
38
86
 
39
- ### The fix: gate your own analytics code
87
+ ### Optionally gate your own analytics code
40
88
 
41
89
  Calling the SDK unconditionally is safe — you do not *need* to branch. But if
42
- you want to avoid dead work, skip the dev warning, or show different UI, branch
43
- on `isSupported`:
90
+ you want to avoid dead work on web, skip the dev warning, or show different UI,
91
+ branch on `isSupported`:
44
92
 
45
93
  ```ts
46
94
  import { BooleanMaths } from '@booleanmaths/booleanmaths-rn-sdk';
@@ -48,21 +96,12 @@ import { BooleanMaths } from '@booleanmaths/booleanmaths-rn-sdk';
48
96
  if (BooleanMaths.isSupported) {
49
97
  BooleanMaths.initialize(API_KEY, PIXEL_ID);
50
98
  } else {
51
- // Fall back to another analytics provider on iOS, or do nothing.
99
+ // Web, or a native install that needs rebuilding. Fall back or do nothing.
52
100
  }
53
101
  ```
54
102
 
55
- `isSupported` is a plain boolean, evaluated at module load cheap to read as
56
- often as you like.
57
-
58
- ### When the iOS SDK ships
59
-
60
- Two changes, no API break for consumers:
61
-
62
- 1. Add the iOS dependency to [`BooleanmathsRnSdk.podspec`](BooleanmathsRnSdk.podspec)
63
- and implement the method bodies in [`ios/BooleanmathsRnSdk.mm`](ios/BooleanmathsRnSdk.mm).
64
- 2. Add `'ios'` to `SUPPORTED_PLATFORMS` in
65
- [`src/BooleanMaths.native.tsx`](src/BooleanMaths.native.tsx).
103
+ `isSupported` is `true` on Android and iOS. It is a plain boolean, evaluated at
104
+ module load — cheap to read as often as you like.
66
105
 
67
106
  ---
68
107
 
@@ -78,14 +117,17 @@ Then rebuild the native app — a Metro reload is **not** enough:
78
117
 
79
118
  ```sh
80
119
  npx react-native run-android
120
+ # and/or
121
+ cd ios && pod install && cd .. && npx react-native run-ios
81
122
  ```
82
123
 
83
124
  ### Requirements
84
125
 
85
126
  - React Native **0.80+** with the **New Architecture** enabled (this is a
86
127
  TurboModule)
87
- - Android **minSdkVersion 24** or higher (the native SDK requires it)
88
- - `compileSdkVersion 36`, Java 17
128
+ - **Android:** `minSdkVersion` 24 or higher (the native SDK requires it),
129
+ `compileSdkVersion 36`, Java 17
130
+ - **iOS:** deployment target **15.1** or higher, and **Xcode 16+**
89
131
 
90
132
  ### Android
91
133
 
@@ -105,13 +147,31 @@ buildscript {
105
147
 
106
148
  ### iOS
107
149
 
108
- Run `pod install` as usual. This installs the no-op stub described above — it
109
- compiles and links, it just does not track anything.
150
+ Autolinking picks the module up; run `pod install` to pull in the native SDK.
110
151
 
111
152
  ```sh
112
153
  cd ios && pod install
113
154
  ```
114
155
 
156
+ That resolves `BooleanMathsSDK` from CocoaPods Trunk. Points worth knowing:
157
+
158
+ - **Minimum deployment target is iOS 15.1**, matching React Native's own floor,
159
+ so adopting this SDK does not raise your app's minimum iOS version.
160
+ - **The pod is pinned to `~> 1.0.1`,** deliberately not `~> 1.0`. Version 1.0.0
161
+ is still published with an iOS 17.0 floor, and resolving to it would break
162
+ the install for apps below iOS 17. Confirm your `Podfile.lock` shows
163
+ `BooleanMathsSDK (1.0.1)` or newer.
164
+ - **The SDK ships as a closed-source, vendored *dynamic* XCFramework.**
165
+ CocoaPods embeds and re-signs it with your app's identity. If you use
166
+ `use_frameworks!`, both `:linkage => :static` and `:linkage => :dynamic`
167
+ are supported.
168
+ - **No privacy manifest work needed.** The SDK bundles its own
169
+ `PrivacyInfo.xcprivacy` inside the XCFramework, so App Store
170
+ privacy-manifest requirements are covered by the pod.
171
+ - **Xcode 16+ is required.** The SDK's `.swiftinterface` is emitted at Swift 6.
172
+ Library evolution is enabled, so it is not pinned to the exact Xcode that
173
+ built it — but the toolchain must understand Swift 6.
174
+
115
175
  ---
116
176
 
117
177
  ## Usage
@@ -146,9 +206,16 @@ ignored.
146
206
 
147
207
  ### `BooleanMaths.initialize(apiKey, pixelId): void`
148
208
 
149
- Initializes the native SDK. Also registers this wrapper with the native SDK so
150
- every event carries `wrapper_type: "react-native"` and the wrapper version, and
151
- forwards the launch intent (see [Deep links](#deep-links-and-notification-attribution)).
209
+ Initializes the native SDK. Also registers this wrapper with the native SDK
210
+ **before** initializing, so that even the automatic `FirstOpen` and `app_opened`
211
+ events emitted during initialization already carry
212
+ `wrapper_type: "react-native"` and the wrapper version. That version is read
213
+ from `package.json` on both platforms, so it cannot drift from the published npm
214
+ version.
215
+
216
+ On Android it additionally forwards the launch intent (see
217
+ [Deep links](#deep-links-and-notification-attribution)); there is no iOS
218
+ equivalent.
152
219
 
153
220
  ### `BooleanMaths.trackEvent(name, properties?): void`
154
221
 
@@ -157,14 +224,19 @@ numbers, booleans, nested objects, and arrays.
157
224
 
158
225
  ### `BooleanMaths.handleIntent(): void`
159
226
 
160
- **Android only** (no-op elsewhere). Forwards the current Activity's intent to
161
- the native SDK so ad deep links, app links and push-notification campaign data
162
- are attributed. Safe to call repeatedly — the native SDK de-duplicates intents
163
- it has already seen.
227
+ **Android only.** Forwards the current Activity's intent to the native SDK so
228
+ ad deep links, app links and push-notification campaign data are attributed.
229
+ Safe to call repeatedly — the native SDK de-duplicates intents it has already
230
+ seen.
164
231
 
165
232
  This is the single entry point for every kind of launch intent; native SDK
166
233
  1.0.9 unified them behind one method.
167
234
 
235
+ **A no-op on iOS**, and safe to call there — it logs one dev-mode notice and
236
+ returns. The reason is scope, not platform support: intents are an Android
237
+ concept, and iOS deep links / universal links are deferred to a later release.
238
+ You do not need to branch on platform before calling it.
239
+
168
240
  ### `BooleanMaths.handleNotificationIntent(): void`
169
241
 
170
242
  Deprecated alias of `handleIntent()`, kept so existing callers keep working.
@@ -174,16 +246,26 @@ behavioural difference — prefer `handleIntent()` in new code.
174
246
  ### `BooleanMaths.getHelloMessage(): string | null`
175
247
 
176
248
  Bridge smoke test. Returns the native SDK's hello string, or `null` where there
177
- is no native SDK.
249
+ is no native SDK. The string comes from the native SDK itself on both platforms
250
+ (not from this wrapper), so a correct value also proves the native artifact
251
+ actually linked — which on iOS is the quickest way to confirm the XCFramework
252
+ was embedded.
178
253
 
179
254
  ### `BooleanMaths.isSupported: boolean`
180
255
 
181
- `true` only where a real native SDK is linked. Currently Android only.
256
+ `true` only where a real native SDK is linked Android and iOS. `false` on
257
+ web, and on any platform where the native module failed to resolve (typically a
258
+ JS install without a native rebuild).
182
259
 
183
260
  ---
184
261
 
185
262
  ## Deep links and notification attribution
186
263
 
264
+ > **Android only.** This entire section does not apply to iOS, where
265
+ > `handleIntent()` is a no-op and no `DeepLinkClick` / `NotificationClick`
266
+ > events are produced. The code below is still safe to run unchanged on iOS —
267
+ > the calls simply do nothing.
268
+
187
269
  The native SDK registers its `ActivityLifecycleCallbacks` **inside**
188
270
  `initialize()`. In a React Native app that runs long after `MainActivity`'s
189
271
  `onActivityCreated` has already fired, so **the intent that cold-started your
@@ -277,17 +359,25 @@ intent without `ACTION_VIEW` takes the `NotificationClick` path instead — see
277
359
 
278
360
  The native SDK tracks these without any call from you:
279
361
 
280
- | Event | When |
281
- | :---- | :--- |
282
- | `app_opened` | First Activity creation / SDK initialization |
283
- | `FirstOpen` | Once per install, with Google Play Install Referrer attribution |
284
- | `DeepLinkClick` | An `ACTION_VIEW` intent is handled — ad deep links and app links. Campaign fields nest under `data.link` |
285
- | `NotificationClick` | Any other intent carrying campaign data is handled. Fields nest under `data.notification` |
362
+ | Event | When | Android | iOS |
363
+ | :---- | :--- | :-----: | :-: |
364
+ | `app_opened` | First Activity creation / SDK initialization | ✅ | ✅ |
365
+ | `FirstOpen` | Once per install | ✅ with Google Play Install Referrer attribution | ✅ but `data` is `{}` |
366
+ | `DeepLinkClick` | An `ACTION_VIEW` intent is handled — ad deep links and app links. Campaign fields nest under `data.link` | ✅ | ❌ |
367
+ | `NotificationClick` | Any other intent carrying campaign data is handled. Fields nest under `data.notification` | ✅ | ❌ |
368
+
369
+ On both platforms `FirstOpen` precedes `app_opened`, so a new install's stream
370
+ reads in order. `FirstOpen` is PascalCase on the wire on purpose — the two
371
+ platforms match byte-for-byte and backend install reporting keys off that exact
372
+ string.
286
373
 
287
374
  Visitor ID and session ID (30-minute timeout) are generated and persisted
288
- natively. Campaign data from a handled intent is also persisted by
289
- `AttributionManager` and attached to **every subsequent event** as
290
- `data.attribution` natively, so `trackEvent` needs no extra work from you.
375
+ natively on both platforms.
376
+
377
+ **Attribution is Android-only.** Campaign data from a handled intent is
378
+ persisted by `AttributionManager` and attached to **every subsequent event** as
379
+ `data.attribution`. iOS produces no `data.attribution` block, since it handles
380
+ no intents; Apple Search Ads attribution is deferred to a later release.
291
381
 
292
382
  ### What counts as a campaign intent (native SDK v1.0.9)
293
383
 
@@ -320,7 +410,34 @@ de-duplicated in a `WeakHashMap`, so re-forwarding the same intent is a no-op.
320
410
 
321
411
  ## How properties cross the bridge
322
412
 
323
- Two normalizations happen on the Android side, both worth knowing:
413
+ Each platform normalizes `properties` at the native boundary before handing it
414
+ to the SDK. The rules differ because the two SDKs serialize differently — the
415
+ observable payload is the same in the cases that matter.
416
+
417
+ | Input | Android | iOS |
418
+ | :---- | :------ | :-- |
419
+ | `null` / `undefined` object value | Key dropped | Key dropped |
420
+ | `null` inside an array | Preserved (indices must not shift) | Preserved |
421
+ | Nested objects and arrays | Recursed | Recursed |
422
+ | Whole numbers (`3`) | Coerced to integer — see the Gson caveat below | No action needed; `JSONSerialization` already emits `3`, not `3.0` |
423
+ | Fractions (`999.5`) | Untouched | Untouched |
424
+ | Booleans | Untouched | Untouched |
425
+ | **`NaN` / `Infinity`** | Passed through as-is | **Key dropped** — see below |
426
+
427
+ ### iOS: why `NaN` is dropped rather than passed through
428
+
429
+ This one is load-bearing, not cosmetic. The iOS SDK guards its writes with
430
+ `JSONSerialization.isValidJSONObject(...)` and **silently discards the payload**
431
+ when that returns false. A JS `NaN` or `Infinity` arrives as a non-finite
432
+ `NSNumber` and invalidates the whole object — so a single bad property value
433
+ could discard the event, or the entire outgoing batch. The wrapper strips those
434
+ keys at the boundary so the rest of the event survives.
435
+
436
+ `NSDate` and anything else not JSON-representable is dropped for the same
437
+ reason. Everything is handled recursively, so a `NaN` nested three objects deep
438
+ costs you that one key and nothing else.
439
+
440
+ ### Android normalization
324
441
 
325
442
  - **Integral numbers are converted to integers.** React Native passes every JS
326
443
  number across the bridge as a `Double`, so `{ count: 3 }` would otherwise be
@@ -346,28 +463,50 @@ Two normalizations happen on the Android side, both worth knowing:
346
463
  serializes with Gson, which omits null map values anyway, so the emitted JSON
347
464
  is identical. Nulls **inside arrays** are preserved so indices do not shift.
348
465
 
349
- Failures in the native bridge are logged (`adb logcat -s BooleanmathsRnSdk`)
350
- rather than thrown into JavaScript — analytics should never crash the host app.
466
+ Failures in the native bridge are logged rather than thrown into JavaScript —
467
+ analytics should never crash the host app. Android logs to
468
+ `adb logcat -s BooleanmathsRnSdk`; iOS logs through `RCTLogError`, visible in
469
+ Xcode's console and Metro.
351
470
 
352
471
  ---
353
472
 
354
473
  ## Troubleshooting
355
474
 
356
475
  **No events arriving.** Confirm `BooleanMaths.isSupported` is `true`, then check
357
- `adb logcat -s BooleanMathsSDK:D BooleanmathsRnSdk:D`. The native SDK logs each
358
- persisted event and the full payload it sends.
476
+ the native logs — `adb logcat -s BooleanMathsSDK:D BooleanmathsRnSdk:D` on
477
+ Android, or Xcode's console on iOS. The native SDK logs each persisted event and
478
+ the full payload it sends.
359
479
 
360
480
  **Warning: "native module could not be found".** The JS installed but the native
361
- side did not. Rebuild the app (`npx react-native run-android`) rather than just
362
- reloading Metro; on iOS run `pod install`.
363
-
364
- **Events show a delay.** By design. Each `trackEvent` enqueues an immediate
365
- sync attempt, but WorkManager also runs a periodic 15-minute batch job and
366
- requires network connectivity.
367
-
368
- **Deep link not attributed.** See
369
- [Deep links](#deep-links-and-notification-attribution) — the launch intent needs
370
- explicit forwarding, and `singleTask` activities need `setIntent`.
481
+ side did not. Rebuild the app (`npx react-native run-android`, or `pod install`
482
+ followed by `npx react-native run-ios`) rather than just reloading Metro.
483
+
484
+ **iOS: crash at launch with a dyld "Library not loaded" / "image not found"
485
+ error naming `BooleanMathsSDK`.** The XCFramework is a *dynamic* framework and
486
+ was not embedded. Confirm the `[CP] Embed Pods Frameworks` build phase exists on
487
+ your app target, then `pod deintegrate && pod install`. This is the one iOS
488
+ failure mode that builds cleanly and only shows up at runtime.
489
+
490
+ **iOS: `getHelloMessage()` returns an empty string.** The bridge resolved but the
491
+ native SDK call failed — check the Xcode console for a
492
+ `BooleanMaths getHelloMessage failed` error. An empty string here specifically
493
+ means the XCFramework did not link correctly.
494
+
495
+ **iOS: `pod install` fails to resolve `BooleanMathsSDK`.** Run
496
+ `pod repo update`, and confirm your app's deployment target is 15.1 or higher.
497
+ If the lockfile pinned `1.0.0`, delete that entry and reinstall — 1.0.0 carries
498
+ an iOS 17.0 floor.
499
+
500
+ **Events show a delay.** By design on both platforms. Each `trackEvent`
501
+ enqueues an immediate sync attempt; Android's WorkManager also runs a periodic
502
+ 15-minute batch job and requires network connectivity, and iOS flushes when the
503
+ app backgrounds.
504
+
505
+ **Deep link not attributed.** Android only — see
506
+ [Deep links](#deep-links-and-notification-attribution); the launch intent needs
507
+ explicit forwarding, and `singleTask` activities need `setIntent`. On iOS deep
508
+ link attribution is not implemented at all
509
+ ([what iOS supports](#what-ios-supports)).
371
510
 
372
511
  ---
373
512
 
@@ -0,0 +1,146 @@
1
+ import Foundation
2
+ import BooleanMathsSDK
3
+
4
+ /// Objective-C-visible shim over the pure Swift `BooleanMathsSDK`.
5
+ ///
6
+ /// This file is mandatory, not a stylistic layer. `BooleanMaths` is declared
7
+ /// `@MainActor public final class` — it is not `@objc` and not `NSObject`-derived
8
+ /// (and cannot trivially become `@objc`, which requires `NSObject` inheritance).
9
+ /// The SDK's generated `BooleanMathsSDK-Swift.h` therefore contains zero
10
+ /// `@interface` declarations, so `#import <BooleanMathsSDK/...>` from
11
+ /// `BooleanmathsRnSdk.mm` resolves to an empty module and the TurboModule cannot
12
+ /// see the SDK at all.
13
+ ///
14
+ /// Everything here takes only Objective-C-representable types and is reached
15
+ /// from the `.mm` through the pod's generated `BooleanmathsRnSdk-Swift.h`.
16
+ ///
17
+ /// Behaviour is specified by the Android module
18
+ /// (`android/src/main/java/com/booleanmathsrnsdk/BooleanmathsRnSdkModule.kt`),
19
+ /// which is the reference implementation for this wrapper.
20
+ @objc(BMBooleanMathsBridge)
21
+ public final class BMBooleanMathsBridge: NSObject {
22
+
23
+ /// Matches Android's `BuildConfig.WRAPPER_TYPE`.
24
+ private static let wrapperType = "react-native"
25
+
26
+ // MARK: - Public API
27
+
28
+ /// Initializes the native SDK and stamps this wrapper's identity onto it.
29
+ ///
30
+ /// `@MainActor` because `BooleanMaths.initialize` is main-actor isolated. The
31
+ /// caller in `BooleanmathsRnSdk.mm` hops with `dispatch_async` before invoking
32
+ /// this — never `dispatch_sync`, which deadlocks when the JS thread is the
33
+ /// main thread.
34
+ ///
35
+ /// Named `initializeSdk` rather than `initialize` for the same reason the
36
+ /// codegen spec is: `+[NSObject initialize]` already exists.
37
+ @MainActor
38
+ @objc(initializeSdkWithApiKey:pixelId:wrapperVersion:)
39
+ public static func initializeSdk(
40
+ apiKey: String,
41
+ pixelId: String,
42
+ wrapperVersion: String
43
+ ) {
44
+ // Before `initialize`, not after: `initialize` itself emits `FirstOpen` and
45
+ // `app_opened` internally, and those must already carry wrapper_type /
46
+ // wrapper_version. Same ordering rationale as Android.
47
+ //
48
+ // Do NOT port Android's second, post-initialize call. Android needs it
49
+ // because its native SDK can only persist to SharedPreferences once it holds
50
+ // an application context, which it acquires inside initialize(). iOS has no
51
+ // such gap: `WrapperConfigStore.set` writes to UserDefaults unconditionally,
52
+ // and `initialize` calls `rehydrate()`, which fills each field only where it
53
+ // is still nil — so a value set earlier in the process outranks the
54
+ // persisted one. One call, before initialize, is correct and sufficient.
55
+ BooleanMaths.shared.setWrapperConfig(type: wrapperType, version: wrapperVersion)
56
+
57
+ BooleanMaths.shared.initialize(apiKey: apiKey, pixelId: pixelId)
58
+
59
+ // There is no iOS equivalent of Android's forwardCurrentIntent(): deep links
60
+ // are deferred on iOS, so nothing is forwarded here. See `handleIntent` in
61
+ // BooleanmathsRnSdk.mm.
62
+ }
63
+
64
+ /// Records a custom event. `properties` is sanitized first — see
65
+ /// `sanitized(value:allowNull:)` for why that is load-bearing.
66
+ ///
67
+ /// `@MainActor` because `BooleanMaths.track` is main-actor isolated.
68
+ @MainActor
69
+ @objc(trackEventWithName:properties:)
70
+ public static func trackEvent(name: String, properties: [String: Any]?) {
71
+ BooleanMaths.shared.track(name, properties: sanitized(dictionary: properties ?? [:]))
72
+ }
73
+
74
+ /// Bridge smoke test. Deliberately not main-actor isolated: the TurboModule
75
+ /// method is synchronous and returns the value directly, so it must not hop
76
+ /// threads. `BooleanMaths.getHelloMessage()` is `nonisolated static` in the SDK
77
+ /// specifically to allow this.
78
+ ///
79
+ /// A real SDK call rather than a wrapper-side literal, so it also proves the
80
+ /// XCFramework actually linked.
81
+ @objc(getHelloMessage)
82
+ public static func getHelloMessage() -> String {
83
+ BooleanMaths.getHelloMessage()
84
+ }
85
+
86
+ // MARK: - Properties sanitization
87
+
88
+ // `EventDispatcher` and `EventStore` both guard on
89
+ // `JSONSerialization.isValidJSONObject(...)` and silently drop the payload when
90
+ // it returns false. A JS `NaN` or `Infinity` arrives here as a non-finite
91
+ // NSNumber and makes the whole object invalid — so one bad property value can
92
+ // discard the event, or the entire outgoing batch. Android guards the same case
93
+ // in `normalizeValue`.
94
+
95
+ private static func sanitized(dictionary: [String: Any]) -> [String: Any] {
96
+ var result = [String: Any](minimumCapacity: dictionary.count)
97
+
98
+ for (key, value) in dictionary {
99
+ guard let clean = sanitized(value: value, allowNull: false) else { continue }
100
+ result[key] = clean
101
+ }
102
+
103
+ return result
104
+ }
105
+
106
+ /// Nulls are preserved inside arrays — dropping them would shift indices.
107
+ /// Android preserves them deliberately for the same reason.
108
+ private static func sanitized(array: [Any]) -> [Any] {
109
+ array.map { sanitized(value: $0, allowNull: true) ?? NSNull() }
110
+ }
111
+
112
+ private static func sanitized(value: Any, allowNull: Bool) -> Any? {
113
+ switch value {
114
+ case is NSNull:
115
+ // Dropped as an object value (matching Android, whose Gson omits null map
116
+ // values anyway), kept inside an array.
117
+ return allowNull ? NSNull() : nil
118
+
119
+ case let number as NSNumber:
120
+ // Must be tested before any `as Bool` cast: `NSNumber(1) as? Bool`
121
+ // succeeds under Swift bridging, so checking Bool first would silently
122
+ // turn `{ count: 1 }` into `{ count: true }`. Booleans arrive from React
123
+ // Native as CFBoolean, match here, and serialize as true/false unchanged.
124
+ //
125
+ // No whole-number coercion: Android's `toWholeNumberOrSelf` fixes a
126
+ // Gson-specific artifact, whereas JSONSerialization already emits `3`
127
+ // rather than `3.0`.
128
+ return number.doubleValue.isFinite ? number : nil
129
+
130
+ case let string as String:
131
+ return string
132
+
133
+ case let nested as [String: Any]:
134
+ return sanitized(dictionary: nested)
135
+
136
+ case let nested as [Any]:
137
+ return sanitized(array: nested)
138
+
139
+ default:
140
+ // NSDate and anything else not JSON-representable, plus dictionaries with
141
+ // non-String keys. React Native never produces these from a JS object, but
142
+ // passing one through would invalidate the payload.
143
+ return nil
144
+ }
145
+ }
146
+ }
@@ -1,19 +1,20 @@
1
1
  #import <BooleanmathsRnSdkSpec/BooleanmathsRnSdkSpec.h>
2
2
 
3
3
  /**
4
- * iOS no-op implementation of the BooleanMaths SDK.
4
+ * iOS implementation of the BooleanMaths TurboModule, backed by the
5
+ * `BooleanMathsSDK` pod (>= 1.0.1, minimum iOS 15.1).
5
6
  *
6
- * There is no native BooleanMaths iOS SDK published yet (the native SDK ships
7
- * for Android only, as `com.booleanmaths:bm-sdk`). This class exists so that
8
- * the TurboModule still resolves on iOS and every call degrades into a
9
- * harmless no-op instead of crashing the host app.
7
+ * The SDK itself is pure Swift and exposes no Objective-C surface, so this class
8
+ * does not talk to it directly every call goes through
9
+ * `BMBooleanMathsBridge` (`ios/BMBooleanMathsBridge.swift`), which explains why
10
+ * in detail.
10
11
  *
11
- * In practice these methods are never reached: the JavaScript layer gates on
12
- * `Platform.OS` and returns before touching the bridge. They are implemented
13
- * anyway as a backstop.
12
+ * `initializeSdk` and `trackEvent` hop to the main queue because the SDK is
13
+ * `@MainActor`; `getHelloMessage` deliberately does not, being synchronous and
14
+ * value-returning. Failures are logged and swallowed rather than thrown into JS.
14
15
  *
15
- * When an iOS SDK becomes available, replace the bodies below and flip
16
- * `SUPPORTED_PLATFORMS` in `src/BooleanMaths.native.tsx` to include 'ios'.
16
+ * `handleIntent` and `handleNotificationIntent` remain no-ops: intents are an
17
+ * Android concept and iOS deep links are out of scope for this release.
17
18
  */
18
19
  @interface BooleanmathsRnSdk : NSObject <NativeBooleanmathsRnSdkSpec>
19
20
 
@@ -2,50 +2,104 @@
2
2
 
3
3
  #import <React/RCTLog.h>
4
4
 
5
+ // The generated header for BMBooleanMathsBridge.swift. Both paths are needed so
6
+ // this builds under static and dynamic pod linkage alike.
7
+ #if __has_include(<BooleanmathsRnSdk/BooleanmathsRnSdk-Swift.h>)
8
+ #import <BooleanmathsRnSdk/BooleanmathsRnSdk-Swift.h>
9
+ #else
10
+ #import "BooleanmathsRnSdk-Swift.h"
11
+ #endif
12
+
13
+ // Injected by BooleanmathsRnSdk.podspec from package.json's version, mirroring
14
+ // Android's BuildConfig.WRAPPER_VERSION. Only reached if that xcconfig failed to
15
+ // apply, in which case a wrong-looking wrapper_version is far better than a
16
+ // build error in a consumer's app.
17
+ #ifndef BMRN_WRAPPER_VERSION
18
+ #define BMRN_WRAPPER_VERSION "unknown"
19
+ #endif
20
+
5
21
  @implementation BooleanmathsRnSdk
6
22
 
7
23
  /**
8
- * Logs the "no iOS SDK" warning at most once per app launch so it is visible
9
- * without flooding the console if something does call through.
24
+ * Analytics must never take the host app down. Mirrors Android's `safely()`
25
+ * helper: log the failure and swallow it rather than surfacing it to JS.
26
+ */
27
+ static void BMSafely(NSString *operation, void (^block)(void))
28
+ {
29
+ @try {
30
+ block();
31
+ } @catch (NSException *exception) {
32
+ RCTLogError(@"[@booleanmaths/booleanmaths-rn-sdk] BooleanMaths %@ failed: %@", operation, exception);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Logs the "deep links are Android-only" notice at most once per app launch, so
38
+ * it is visible without flooding the console.
10
39
  */
11
- static void BMWarnUnsupportedOnce(void)
40
+ static void BMWarnIntentUnsupportedOnce(void)
12
41
  {
13
42
  static dispatch_once_t onceToken;
14
43
  dispatch_once(&onceToken, ^{
15
- RCTLogWarn(@"[@booleanmaths/booleanmaths-rn-sdk] The BooleanMaths native SDK is not available on iOS "
16
- @"(Android only for now). All SDK calls are no-ops and no events will be "
17
- @"tracked on this platform.");
44
+ RCTLogWarn(@"[@booleanmaths/booleanmaths-rn-sdk] Deep-link and notification intent forwarding is "
45
+ @"Android-only; handleIntent() is a no-op on iOS. Event tracking is fully supported — "
46
+ @"only intent attribution is unavailable.");
18
47
  });
19
48
  }
20
49
 
21
50
  - (void)initializeSdk:(NSString *)apiKey pixelId:(NSString *)pixelId
22
51
  {
23
- BMWarnUnsupportedOnce();
52
+ // Captured outside the block: it is a compile-time constant, not thread state.
53
+ NSString *wrapperVersion = @BMRN_WRAPPER_VERSION;
54
+
55
+ // BooleanMaths is @MainActor. dispatch_async, never dispatch_sync — the latter
56
+ // deadlocks whenever the JS thread is the main thread.
57
+ dispatch_async(dispatch_get_main_queue(), ^{
58
+ BMSafely(@"initialize", ^{
59
+ [BMBooleanMathsBridge initializeSdkWithApiKey:apiKey
60
+ pixelId:pixelId
61
+ wrapperVersion:wrapperVersion];
62
+ });
63
+ });
24
64
  }
25
65
 
26
66
  - (void)trackEvent:(NSString *)name properties:(NSDictionary *)properties
27
67
  {
28
- BMWarnUnsupportedOnce();
68
+ dispatch_async(dispatch_get_main_queue(), ^{
69
+ BMSafely(@"trackEvent", ^{
70
+ [BMBooleanMathsBridge trackEventWithName:name properties:properties];
71
+ });
72
+ });
29
73
  }
30
74
 
31
75
  - (void)handleIntent
32
76
  {
33
- // Android-only concept; nothing to do on iOS even once an iOS SDK exists.
34
- BMWarnUnsupportedOnce();
77
+ // Deliberately still a no-op. Intents are an Android concept, and iOS deep
78
+ // links / universal links are deferred — their absence here is scope, not a
79
+ // missing implementation.
80
+ BMWarnIntentUnsupportedOnce();
35
81
  }
36
82
 
37
83
  - (void)handleNotificationIntent
38
84
  {
39
85
  // Alias of handleIntent, mirroring the Android bridge.
40
- BMWarnUnsupportedOnce();
86
+ BMWarnIntentUnsupportedOnce();
41
87
  }
42
88
 
43
89
  - (NSString *)getHelloMessage
44
90
  {
45
- BMWarnUnsupportedOnce();
91
+ // No dispatch: this is synchronous and returns a value, so it must not hop
92
+ // threads. The SDK's getHelloMessage() is `nonisolated static` to allow it.
93
+ @try {
94
+ return [BMBooleanMathsBridge getHelloMessage];
95
+ } @catch (NSException *exception) {
96
+ RCTLogError(@"[@booleanmaths/booleanmaths-rn-sdk] BooleanMaths getHelloMessage failed: %@", exception);
46
97
 
47
- // Deliberately not nil: the codegen spec declares a non-optional string.
48
- return @"BooleanMaths SDK is not available on iOS";
98
+ // Empty rather than a plausible-looking message: this method exists to prove
99
+ // the native SDK linked, so a failure must read as a failure. The codegen
100
+ // spec declares a non-optional string, so it cannot be nil.
101
+ return @"";
102
+ }
49
103
  }
50
104
 
51
105
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
@@ -3,11 +3,15 @@
3
3
  import { Platform } from 'react-native';
4
4
  import NativeBooleanmathsRnSdk from "./NativeBooleanmathsRnSdk.js";
5
5
  /**
6
- * The BooleanMaths native SDK currently ships for Android only. There is no
7
- * iOS artifact yet, so on iOS every call below short-circuits and the SDK
8
- * behaves as a silent no-op rather than crashing the app.
6
+ * The BooleanMaths native SDK ships for Android (`com.booleanmaths:bm-sdk`) and
7
+ * iOS (`BooleanMathsSDK`). Any other platform including out-of-tree React
8
+ * Native targets has no native artifact, so every call below short-circuits
9
+ * and the SDK behaves as a silent no-op rather than crashing the app.
10
+ *
11
+ * Note that `handleIntent` is a no-op on iOS even though iOS is supported here:
12
+ * intents are an Android concept. See its JSDoc in `./types`.
9
13
  */
10
- const SUPPORTED_PLATFORMS = ['android'];
14
+ const SUPPORTED_PLATFORMS = ['android', 'ios'];
11
15
  const isPlatformSupported = SUPPORTED_PLATFORMS.includes(Platform.OS);
12
16
 
13
17
  /**
@@ -23,7 +27,7 @@ function warnOnce() {
23
27
  }
24
28
  hasWarned = true;
25
29
  if (!isPlatformSupported) {
26
- console.warn(`[@booleanmaths/booleanmaths-rn-sdk] The BooleanMaths native SDK is not available on ${Platform.OS} ` + '(Android only for now). All SDK calls are no-ops on this platform and no ' + 'events will be tracked. Gate your calls on `BooleanMaths.isSupported` to ' + 'silence this warning — see the README\'s "Platform support" section.');
30
+ console.warn(`[@booleanmaths/booleanmaths-rn-sdk] The BooleanMaths native SDK is not available on ${Platform.OS} ` + '(Android and iOS only). All SDK calls are no-ops on this platform and no ' + 'events will be tracked. Gate your calls on `BooleanMaths.isSupported` to ' + 'silence this warning — see the README\'s "Platform support" section.');
27
31
  return;
28
32
  }
29
33
  console.warn('[@booleanmaths/booleanmaths-rn-sdk] The native module could not be found on ' + `${Platform.OS}. Rebuild the app after installing the package (a Metro ` + 'reload is not enough), and on iOS run `pod install`. All SDK calls are ' + 'no-ops until this is fixed.');
@@ -1 +1 @@
1
- {"version":3,"names":["Platform","NativeBooleanmathsRnSdk","SUPPORTED_PLATFORMS","isPlatformSupported","includes","OS","isSupported","hasWarned","warnOnce","__DEV__","console","warn","forwardCurrentIntent","handleIntent","BooleanMaths","initialize","apiKey","pixelId","initializeSdk","trackEvent","name","properties","handleNotificationIntent","getHelloMessage"],"sourceRoot":"../../src","sources":["BooleanMaths.native.tsx"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AACvC,OAAOC,uBAAuB,MAAM,8BAA2B;AAG/D;AACA;AACA;AACA;AACA;AACA,MAAMC,mBAAsD,GAAG,CAAC,SAAS,CAAC;AAE1E,MAAMC,mBAAmB,GAAGD,mBAAmB,CAACE,QAAQ,CAACJ,QAAQ,CAACK,EAAE,CAAC;;AAErE;AACA;AACA;AACA;AACA;AACA,MAAMC,WAAW,GAAGH,mBAAmB,IAAIF,uBAAuB,IAAI,IAAI;AAE1E,IAAIM,SAAS,GAAG,KAAK;AAErB,SAASC,QAAQA,CAAA,EAAG;EAClB,IAAID,SAAS,IAAI,CAACE,OAAO,EAAE;IACzB;EACF;EAEAF,SAAS,GAAG,IAAI;EAEhB,IAAI,CAACJ,mBAAmB,EAAE;IACxBO,OAAO,CAACC,IAAI,CACV,uFAAuFX,QAAQ,CAACK,EAAE,GAAG,GACnG,2EAA2E,GAC3E,2EAA2E,GAC3E,sEACJ,CAAC;IACD;EACF;EAEAK,OAAO,CAACC,IAAI,CACV,8EAA8E,GAC5E,GAAGX,QAAQ,CAACK,EAAE,0DAA0D,GACxE,yEAAyE,GACzE,6BACJ,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,SAASO,oBAAoBA,CAAA,EAAS;EACpC,IAAI,CAACN,WAAW,EAAE;IAChBE,QAAQ,CAAC,CAAC;IACV;EACF;EAEAP,uBAAuB,CAAEY,YAAY,CAAC,CAAC;AACzC;AAEA,OAAO,MAAMC,YAA6B,GAAG;EAC3CR,WAAW;EAEXS,UAAUA,CAACC,MAAc,EAAEC,OAAe,EAAQ;IAChD,IAAI,CAACX,WAAW,EAAE;MAChBE,QAAQ,CAAC,CAAC;MACV;IACF;IAEAP,uBAAuB,CAAEiB,aAAa,CAACF,MAAM,EAAEC,OAAO,CAAC;EACzD,CAAC;EAEDE,UAAUA,CAACC,IAAY,EAAEC,UAAuC,GAAG,CAAC,CAAC,EAAQ;IAC3E,IAAI,CAACf,WAAW,EAAE;MAChBE,QAAQ,CAAC,CAAC;MACV;IACF;IAEAP,uBAAuB,CAAEkB,UAAU,CAACC,IAAI,EAAEC,UAAU,CAAC;EACvD,CAAC;EAEDR,YAAY,EAAED,oBAAoB;EAElCU,wBAAwB,EAAEV,oBAAoB;EAE9CW,eAAeA,CAAA,EAAkB;IAC/B,IAAI,CAACjB,WAAW,EAAE;MAChBE,QAAQ,CAAC,CAAC;MACV,OAAO,IAAI;IACb;IAEA,OAAOP,uBAAuB,CAAEsB,eAAe,CAAC,CAAC;EACnD;AACF,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["Platform","NativeBooleanmathsRnSdk","SUPPORTED_PLATFORMS","isPlatformSupported","includes","OS","isSupported","hasWarned","warnOnce","__DEV__","console","warn","forwardCurrentIntent","handleIntent","BooleanMaths","initialize","apiKey","pixelId","initializeSdk","trackEvent","name","properties","handleNotificationIntent","getHelloMessage"],"sourceRoot":"../../src","sources":["BooleanMaths.native.tsx"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AACvC,OAAOC,uBAAuB,MAAM,8BAA2B;AAG/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMC,mBAAsD,GAAG,CAC7D,SAAS,EACT,KAAK,CACN;AAED,MAAMC,mBAAmB,GAAGD,mBAAmB,CAACE,QAAQ,CAACJ,QAAQ,CAACK,EAAE,CAAC;;AAErE;AACA;AACA;AACA;AACA;AACA,MAAMC,WAAW,GAAGH,mBAAmB,IAAIF,uBAAuB,IAAI,IAAI;AAE1E,IAAIM,SAAS,GAAG,KAAK;AAErB,SAASC,QAAQA,CAAA,EAAG;EAClB,IAAID,SAAS,IAAI,CAACE,OAAO,EAAE;IACzB;EACF;EAEAF,SAAS,GAAG,IAAI;EAEhB,IAAI,CAACJ,mBAAmB,EAAE;IACxBO,OAAO,CAACC,IAAI,CACV,uFAAuFX,QAAQ,CAACK,EAAE,GAAG,GACnG,2EAA2E,GAC3E,2EAA2E,GAC3E,sEACJ,CAAC;IACD;EACF;EAEAK,OAAO,CAACC,IAAI,CACV,8EAA8E,GAC5E,GAAGX,QAAQ,CAACK,EAAE,0DAA0D,GACxE,yEAAyE,GACzE,6BACJ,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,SAASO,oBAAoBA,CAAA,EAAS;EACpC,IAAI,CAACN,WAAW,EAAE;IAChBE,QAAQ,CAAC,CAAC;IACV;EACF;EAEAP,uBAAuB,CAAEY,YAAY,CAAC,CAAC;AACzC;AAEA,OAAO,MAAMC,YAA6B,GAAG;EAC3CR,WAAW;EAEXS,UAAUA,CAACC,MAAc,EAAEC,OAAe,EAAQ;IAChD,IAAI,CAACX,WAAW,EAAE;MAChBE,QAAQ,CAAC,CAAC;MACV;IACF;IAEAP,uBAAuB,CAAEiB,aAAa,CAACF,MAAM,EAAEC,OAAO,CAAC;EACzD,CAAC;EAEDE,UAAUA,CAACC,IAAY,EAAEC,UAAuC,GAAG,CAAC,CAAC,EAAQ;IAC3E,IAAI,CAACf,WAAW,EAAE;MAChBE,QAAQ,CAAC,CAAC;MACV;IACF;IAEAP,uBAAuB,CAAEkB,UAAU,CAACC,IAAI,EAAEC,UAAU,CAAC;EACvD,CAAC;EAEDR,YAAY,EAAED,oBAAoB;EAElCU,wBAAwB,EAAEV,oBAAoB;EAE9CW,eAAeA,CAAA,EAAkB;IAC/B,IAAI,CAACjB,WAAW,EAAE;MAChBE,QAAQ,CAAC,CAAC;MACV,OAAO,IAAI;IACb;IAEA,OAAOP,uBAAuB,CAAEsB,eAAe,CAAC,CAAC;EACnD;AACF,CAAC","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"BooleanMaths.native.d.ts","sourceRoot":"","sources":["../../../src/BooleanMaths.native.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAA+B,MAAM,YAAS,CAAC;AA4D5E,eAAO,MAAM,YAAY,EAAE,eAiC1B,CAAC"}
1
+ {"version":3,"file":"BooleanMaths.native.d.ts","sourceRoot":"","sources":["../../../src/BooleanMaths.native.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAA+B,MAAM,YAAS,CAAC;AAmE5E,eAAO,MAAM,YAAY,EAAE,eAiC1B,CAAC"}
@@ -28,10 +28,14 @@ export interface BooleanMathsApi {
28
28
  * Forwards the current Activity's intent to the native SDK so ad deep links,
29
29
  * app links and push-notification campaign data are attributed.
30
30
  *
31
- * This is the primary entry point for every kind of launch intent. Android
32
- * only a no-op on every other platform. Call it from your deep-link
33
- * handler; `initialize` already forwards the launch intent itself (see the
34
- * README for why that needs special handling).
31
+ * This is the primary entry point for every kind of launch intent. Call it
32
+ * from your deep-link handler; `initialize` already forwards the launch
33
+ * intent itself (see the README for why that needs special handling).
34
+ *
35
+ * **Android only.** A no-op on iOS — not because iOS is unsupported (event
36
+ * tracking works fully there), but because intents are an Android concept and
37
+ * iOS deep links / universal links are out of scope for now. Safe to call
38
+ * unconditionally from shared code; on iOS it logs one dev-mode notice.
35
39
  *
36
40
  * Safe to call repeatedly — the native SDK de-duplicates intents it has
37
41
  * already processed.
@@ -47,12 +51,17 @@ export interface BooleanMathsApi {
47
51
  handleNotificationIntent(): void;
48
52
  /**
49
53
  * Returns the native SDK's hello message, or `null` when there is no native
50
- * SDK on this platform. Useful as a bridge smoke test.
54
+ * SDK on this platform.
55
+ *
56
+ * Useful as a bridge smoke test: the string comes from the native SDK itself
57
+ * on both Android and iOS, so a correct value also proves the native artifact
58
+ * actually linked.
51
59
  */
52
60
  getHelloMessage(): string | null;
53
61
  /**
54
- * `true` only on platforms where a real BooleanMaths native SDK is linked.
55
- * Currently Android only.
62
+ * `true` only on platforms where a real BooleanMaths native SDK is linked
63
+ * Android and iOS. `false` on web, and on any platform where the native
64
+ * module failed to resolve (typically a JS install without a native rebuild).
56
65
  */
57
66
  readonly isSupported: boolean;
58
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GACjC,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,yBAAyB,EAAE,GAC3B;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,yBAAyB,CAAA;CAAE,CAAC;AAEjD,MAAM,MAAM,2BAA2B,GAAG,MAAM,CAC9C,MAAM,EACN,yBAAyB,CAC1B,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAElD;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAEzE;;;;;;;;;;;OAWG;IACH,YAAY,IAAI,IAAI,CAAC;IAErB;;;;;;OAMG;IACH,wBAAwB,IAAI,IAAI,CAAC;IAEjC;;;OAGG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI,CAAC;IAEjC;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GACjC,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,yBAAyB,EAAE,GAC3B;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,yBAAyB,CAAA;CAAE,CAAC;AAEjD,MAAM,MAAM,2BAA2B,GAAG,MAAM,CAC9C,MAAM,EACN,yBAAyB,CAC1B,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAElD;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAEzE;;;;;;;;;;;;;;;OAeG;IACH,YAAY,IAAI,IAAI,CAAC;IAErB;;;;;;OAMG;IACH,wBAAwB,IAAI,IAAI,CAAC;IAEjC;;;;;;;OAOG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI,CAAC;IAEjC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@booleanmaths/booleanmaths-rn-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "React Native SDK for BooleanMaths — track user events and attribution in mobile apps.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -3,11 +3,18 @@ import NativeBooleanmathsRnSdk from './NativeBooleanmathsRnSdk';
3
3
  import type { BooleanMathsApi, BooleanMathsEventProperties } from './types';
4
4
 
5
5
  /**
6
- * The BooleanMaths native SDK currently ships for Android only. There is no
7
- * iOS artifact yet, so on iOS every call below short-circuits and the SDK
8
- * behaves as a silent no-op rather than crashing the app.
6
+ * The BooleanMaths native SDK ships for Android (`com.booleanmaths:bm-sdk`) and
7
+ * iOS (`BooleanMathsSDK`). Any other platform including out-of-tree React
8
+ * Native targets has no native artifact, so every call below short-circuits
9
+ * and the SDK behaves as a silent no-op rather than crashing the app.
10
+ *
11
+ * Note that `handleIntent` is a no-op on iOS even though iOS is supported here:
12
+ * intents are an Android concept. See its JSDoc in `./types`.
9
13
  */
10
- const SUPPORTED_PLATFORMS: ReadonlyArray<typeof Platform.OS> = ['android'];
14
+ const SUPPORTED_PLATFORMS: ReadonlyArray<typeof Platform.OS> = [
15
+ 'android',
16
+ 'ios',
17
+ ];
11
18
 
12
19
  const isPlatformSupported = SUPPORTED_PLATFORMS.includes(Platform.OS);
13
20
 
@@ -30,7 +37,7 @@ function warnOnce() {
30
37
  if (!isPlatformSupported) {
31
38
  console.warn(
32
39
  `[@booleanmaths/booleanmaths-rn-sdk] The BooleanMaths native SDK is not available on ${Platform.OS} ` +
33
- '(Android only for now). All SDK calls are no-ops on this platform and no ' +
40
+ '(Android and iOS only). All SDK calls are no-ops on this platform and no ' +
34
41
  'events will be tracked. Gate your calls on `BooleanMaths.isSupported` to ' +
35
42
  'silence this warning — see the README\'s "Platform support" section.'
36
43
  );
package/src/types.ts CHANGED
@@ -40,10 +40,14 @@ export interface BooleanMathsApi {
40
40
  * Forwards the current Activity's intent to the native SDK so ad deep links,
41
41
  * app links and push-notification campaign data are attributed.
42
42
  *
43
- * This is the primary entry point for every kind of launch intent. Android
44
- * only a no-op on every other platform. Call it from your deep-link
45
- * handler; `initialize` already forwards the launch intent itself (see the
46
- * README for why that needs special handling).
43
+ * This is the primary entry point for every kind of launch intent. Call it
44
+ * from your deep-link handler; `initialize` already forwards the launch
45
+ * intent itself (see the README for why that needs special handling).
46
+ *
47
+ * **Android only.** A no-op on iOS — not because iOS is unsupported (event
48
+ * tracking works fully there), but because intents are an Android concept and
49
+ * iOS deep links / universal links are out of scope for now. Safe to call
50
+ * unconditionally from shared code; on iOS it logs one dev-mode notice.
47
51
  *
48
52
  * Safe to call repeatedly — the native SDK de-duplicates intents it has
49
53
  * already processed.
@@ -61,13 +65,18 @@ export interface BooleanMathsApi {
61
65
 
62
66
  /**
63
67
  * Returns the native SDK's hello message, or `null` when there is no native
64
- * SDK on this platform. Useful as a bridge smoke test.
68
+ * SDK on this platform.
69
+ *
70
+ * Useful as a bridge smoke test: the string comes from the native SDK itself
71
+ * on both Android and iOS, so a correct value also proves the native artifact
72
+ * actually linked.
65
73
  */
66
74
  getHelloMessage(): string | null;
67
75
 
68
76
  /**
69
- * `true` only on platforms where a real BooleanMaths native SDK is linked.
70
- * Currently Android only.
77
+ * `true` only on platforms where a real BooleanMaths native SDK is linked
78
+ * Android and iOS. `false` on web, and on any platform where the native
79
+ * module failed to resolve (typically a JS install without a native rebuild).
71
80
  */
72
81
  readonly isSupported: boolean;
73
82
  }