@booleanmaths/booleanmaths-rn-sdk 0.2.0 → 0.4.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,39 @@ 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 has declared a 15.1 floor since
14
+ # 1.0.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.1` resolves to >= 1.1, < 2.0. The earlier pin carried a `.1` patch
29
+ # component purely to exclude 1.0.0, which is still published on Trunk carrying
30
+ # an iOS 17.0 deployment target and would break the install for any app below
31
+ # iOS 17. A 1.1 floor excludes it outright, so the patch component is no longer
32
+ # load-bearing and the range can stay open across minors.
33
+ s.dependency "BooleanMathsSDK", "~> 1.1"
34
+
35
+ # Mirrors Android's BuildConfig.WRAPPER_VERSION, which is read from
36
+ # package.json so the reported wrapper_version cannot drift from the published
37
+ # npm version — s.version is that same value. Consumed by the .mm as an
38
+ # Objective-C preprocessor define; it does not reach Swift.
39
+ #
40
+ # $(inherited) is load-bearing: without it this replaces the pod target's
41
+ # inherited defines instead of appending, dropping DEBUG, RCT_NEW_ARCH_ENABLED
42
+ # and the FOLLY_* flags that React Native's own xcconfig sets.
43
+ s.pod_target_xcconfig = {
44
+ "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) BMRN_WRAPPER_VERSION=\\\"#{s.version}\\\""
45
+ }
46
+
19
47
  install_modules_dependencies(s)
20
48
  end
package/README.md CHANGED
@@ -5,42 +5,92 @@ 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
- | Android | ✅ `com.booleanmaths:bm-sdk:1.0.9` | Fully functional |
21
- | iOS | not published | Silent no-op, **never crashes** |
21
+ | Android | ✅ `com.booleanmaths:bm-sdk:1.0.12` | Fully functional |
22
+ | iOS | `BooleanMathsSDK 1.1.0` | 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
+ | `isDebug` → `environment` on every event | ✅ | ✅ |
37
+ | `app_info` (package, version, install/update times) on every event | ✅ | ✅ |
38
+ | Automatic `app_opened` | ✅ | ✅ |
39
+ | Automatic `FirstOpen` (once per install) | ✅ | ✅ — but with no attribution payload, see below |
40
+ | Visitor ID and 30-minute session handling | ✅ | ✅ |
41
+ | Durable on-device queue surviving app restarts | ✅ | ✅ |
42
+ | Automatic flush when the app backgrounds | ✅ | ✅ |
43
+ | `getHelloMessage()` bridge smoke test | ✅ | ✅ |
44
+ | **Deep links / universal links** (`DeepLinkClick`) | ✅ | ❌ |
45
+ | **Push-notification attribution** (`NotificationClick`) | ✅ | ❌ |
46
+ | **`handleIntent()` / `handleNotificationIntent()`** | ✅ | ❌ no-op |
47
+ | **Install attribution** on `FirstOpen` | ✅ Play Install Referrer | ❌ `data` is `{}` |
48
+
49
+ Notes on the iOS gaps:
50
+
51
+ - **`handleIntent()` is safe to call on iOS.** It reaches a native no-op and
52
+ logs one dev-mode notice. Shared code does not need to branch on platform.
53
+ - **`FirstOpen` still fires on iOS**, once per install — it just carries no
54
+ campaign payload. `data` is `{}` (present but empty), so the wire shape will
55
+ not change when Apple Search Ads attribution lands.
56
+ - On iOS there is **no `DeepLinkClick` or `NotificationClick` event at all**,
57
+ and consequently no `data.attribution` block on subsequent events.
58
+ - **`flush()` is not exposed to JavaScript** on either platform. The iOS SDK
59
+ flushes automatically on `willResignActive` / `didBecomeActive`; it will be
60
+ exposed only once Android has an equivalent, so it can ship as a genuinely
61
+ cross-platform API.
62
+ - macOS and tvOS are out of scope. The native SDK compiles for them; this
63
+ wrapper targets iOS only.
64
+
24
65
  ### Why it does not crash
25
66
 
26
- Two independent guards, so a missing iOS SDK can never take your app down:
67
+ Two independent guards, so an unsupported platform or an incomplete native
68
+ install can never take your app down:
27
69
 
28
70
  1. **JavaScript gate.** `BooleanMaths` checks `Platform.OS` against an
29
- allowlist (currently `['android']`) and returns before touching the native
71
+ allowlist (`['android', 'ios']`) and returns before touching the native
30
72
  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.
73
+ broken or incomplete native install — most often a JS install without a
74
+ native rebuild.
75
+ 2. **Native backstop.** Every native call is wrapped so failures are logged
76
+ rather than thrown into JavaScript
77
+ ([`safely()`](android/src/main/java/com/booleanmathsrnsdk/BooleanmathsRnSdkModule.kt)
78
+ on Android, `BMSafely()` in
79
+ [`ios/BooleanmathsRnSdk.mm`](ios/BooleanmathsRnSdk.mm)). Analytics should
80
+ never crash the host app.
81
+
82
+ On web the JavaScript gate is the whole story: a separate no-op implementation
83
+ ([`src/BooleanMaths.tsx`](src/BooleanMaths.tsx)) is resolved by the bundler, so
84
+ shared code can call the SDK unconditionally without platform checks.
35
85
 
36
86
  In development (`__DEV__`) you get **one** console warning per app launch, not
37
87
  one per call.
38
88
 
39
- ### The fix: gate your own analytics code
89
+ ### Optionally gate your own analytics code
40
90
 
41
91
  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`:
92
+ you want to avoid dead work on web, skip the dev warning, or show different UI,
93
+ branch on `isSupported`:
44
94
 
45
95
  ```ts
46
96
  import { BooleanMaths } from '@booleanmaths/booleanmaths-rn-sdk';
@@ -48,21 +98,12 @@ import { BooleanMaths } from '@booleanmaths/booleanmaths-rn-sdk';
48
98
  if (BooleanMaths.isSupported) {
49
99
  BooleanMaths.initialize(API_KEY, PIXEL_ID);
50
100
  } else {
51
- // Fall back to another analytics provider on iOS, or do nothing.
101
+ // Web, or a native install that needs rebuilding. Fall back or do nothing.
52
102
  }
53
103
  ```
54
104
 
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).
105
+ `isSupported` is `true` on Android and iOS. It is a plain boolean, evaluated at
106
+ module load — cheap to read as often as you like.
66
107
 
67
108
  ---
68
109
 
@@ -78,14 +119,17 @@ Then rebuild the native app — a Metro reload is **not** enough:
78
119
 
79
120
  ```sh
80
121
  npx react-native run-android
122
+ # and/or
123
+ cd ios && pod install && cd .. && npx react-native run-ios
81
124
  ```
82
125
 
83
126
  ### Requirements
84
127
 
85
128
  - React Native **0.80+** with the **New Architecture** enabled (this is a
86
129
  TurboModule)
87
- - Android **minSdkVersion 24** or higher (the native SDK requires it)
88
- - `compileSdkVersion 36`, Java 17
130
+ - **Android:** `minSdkVersion` 24 or higher (the native SDK requires it),
131
+ `compileSdkVersion 36`, Java 17
132
+ - **iOS:** deployment target **15.1** or higher, and **Xcode 16+**
89
133
 
90
134
  ### Android
91
135
 
@@ -98,20 +142,44 @@ To pin a different native SDK version, set this in your app's **root**
98
142
  ```gradle
99
143
  buildscript {
100
144
  ext {
101
- BooleanmathsRnSdk_bmSdkVersion = "1.0.9"
145
+ BooleanmathsRnSdk_bmSdkVersion = "1.0.12"
102
146
  }
103
147
  }
104
148
  ```
105
149
 
150
+ > ⚠️ **1.0.10 is the floor.** The wrapper calls
151
+ > `BooleanMathsSDK.initialize(context, apiKey, pixelId, isDebug)`, and the
152
+ > four-argument overload only exists from 1.0.10. Pinning anything older fails
153
+ > the Kotlin compile with an unresolved-overload error rather than degrading at
154
+ > runtime.
155
+
106
156
  ### iOS
107
157
 
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.
158
+ Autolinking picks the module up; run `pod install` to pull in the native SDK.
110
159
 
111
160
  ```sh
112
161
  cd ios && pod install
113
162
  ```
114
163
 
164
+ That resolves `BooleanMathsSDK` from CocoaPods Trunk. Points worth knowing:
165
+
166
+ - **Minimum deployment target is iOS 15.1**, matching React Native's own floor,
167
+ so adopting this SDK does not raise your app's minimum iOS version.
168
+ - **The pod is constrained to `~> 1.1`** — that is, `>= 1.1, < 2.0`. The floor
169
+ matters: version 1.0.0 is still published with an iOS 17.0 floor, and
170
+ resolving to it would break the install for apps below iOS 17. Confirm your
171
+ `Podfile.lock` shows `BooleanMathsSDK (1.1.0)` or newer.
172
+ - **The SDK ships as a closed-source, vendored *dynamic* XCFramework.**
173
+ CocoaPods embeds and re-signs it with your app's identity. If you use
174
+ `use_frameworks!`, both `:linkage => :static` and `:linkage => :dynamic`
175
+ are supported.
176
+ - **No privacy manifest work needed.** The SDK bundles its own
177
+ `PrivacyInfo.xcprivacy` inside the XCFramework, so App Store
178
+ privacy-manifest requirements are covered by the pod.
179
+ - **Xcode 16+ is required.** The SDK's `.swiftinterface` is emitted at Swift 6.
180
+ Library evolution is enabled, so it is not pinned to the exact Xcode that
181
+ built it — but the toolchain must understand Swift 6.
182
+
115
183
  ---
116
184
 
117
185
  ## Usage
@@ -122,6 +190,9 @@ import { BooleanMaths } from '@booleanmaths/booleanmaths-rn-sdk';
122
190
  // Once, as early as possible — typically in your root component.
123
191
  BooleanMaths.initialize('YOUR_API_KEY', 'YOUR_PIXEL_ID');
124
192
 
193
+ // Or, to mark this build's events as development rather than production:
194
+ BooleanMaths.initialize('YOUR_API_KEY', 'YOUR_PIXEL_ID', __DEV__);
195
+
125
196
  // Anywhere afterwards.
126
197
  BooleanMaths.trackEvent('purchase', {
127
198
  orderId: 'ORD-1024',
@@ -144,11 +215,51 @@ ignored.
144
215
 
145
216
  ## API
146
217
 
147
- ### `BooleanMaths.initialize(apiKey, pixelId): void`
218
+ ### `BooleanMaths.initialize(apiKey, pixelId, isDebug?): void`
219
+
220
+ Initializes the native SDK. Also registers this wrapper with the native SDK
221
+ **before** initializing, so that even the automatic `FirstOpen` and `app_opened`
222
+ events emitted during initialization already carry
223
+ `wrapper_type: "react-native"` and the wrapper version. That version is read
224
+ from `package.json` on both platforms, so it cannot drift from the published npm
225
+ version.
226
+
227
+ On Android it additionally forwards the launch intent (see
228
+ [Deep links](#deep-links-and-notification-attribution)); there is no iOS
229
+ equivalent.
230
+
231
+ `isDebug` defaults to `false`. See [Debug mode](#debug-mode) below.
232
+
233
+ ### Debug mode
234
+
235
+ Passing `isDebug: true` does two things on both platforms:
236
+
237
+ 1. **Every event this process tracks is stamped `environment: "development"`**
238
+ instead of `"production"`, in the `setup` block of the request payload.
239
+ 2. **The native SDK's verbose logging is switched on.** Warnings and errors —
240
+ "SDK is already initialized", network failures, malformed intents — are
241
+ logged *regardless* of this flag, so a broken integration is still
242
+ diagnosable in a production build. Only the routine per-event chatter
243
+ ("Persisted event", "Sending payload") is gated.
244
+
245
+ The flag is recorded **per event, when the event is queued** — not when the
246
+ queue is flushed. An event written by a debug build therefore stays marked as
247
+ development even if it only reaches the network later, after an app restart.
248
+ Both platforms migrated their local queue schemas for this (Room v6 → v7 on
249
+ Android, SQLite v1 → v2 on iOS); the migration is automatic and no events are
250
+ lost.
148
251
 
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)).
252
+ The wrapper deliberately **does not** default this to `__DEV__`. Which
253
+ environment events land in is a backend-routing decision, not a bundler one —
254
+ release builds pointed at a development pixel are a normal QA setup. Pass
255
+ `__DEV__` yourself if that is the behaviour you want:
256
+
257
+ ```ts
258
+ BooleanMaths.initialize(API_KEY, PIXEL_ID, __DEV__);
259
+ ```
260
+
261
+ Requires native SDK `bm-sdk` **1.0.10+** on Android and `BooleanMathsSDK`
262
+ **1.1.0+** on iOS; both are the versions this wrapper depends on.
152
263
 
153
264
  ### `BooleanMaths.trackEvent(name, properties?): void`
154
265
 
@@ -157,14 +268,19 @@ numbers, booleans, nested objects, and arrays.
157
268
 
158
269
  ### `BooleanMaths.handleIntent(): void`
159
270
 
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.
271
+ **Android only.** Forwards the current Activity's intent to the native SDK so
272
+ ad deep links, app links and push-notification campaign data are attributed.
273
+ Safe to call repeatedly — the native SDK de-duplicates intents it has already
274
+ seen.
164
275
 
165
276
  This is the single entry point for every kind of launch intent; native SDK
166
277
  1.0.9 unified them behind one method.
167
278
 
279
+ **A no-op on iOS**, and safe to call there — it logs one dev-mode notice and
280
+ returns. The reason is scope, not platform support: intents are an Android
281
+ concept, and iOS deep links / universal links are deferred to a later release.
282
+ You do not need to branch on platform before calling it.
283
+
168
284
  ### `BooleanMaths.handleNotificationIntent(): void`
169
285
 
170
286
  Deprecated alias of `handleIntent()`, kept so existing callers keep working.
@@ -174,16 +290,26 @@ behavioural difference — prefer `handleIntent()` in new code.
174
290
  ### `BooleanMaths.getHelloMessage(): string | null`
175
291
 
176
292
  Bridge smoke test. Returns the native SDK's hello string, or `null` where there
177
- is no native SDK.
293
+ is no native SDK. The string comes from the native SDK itself on both platforms
294
+ (not from this wrapper), so a correct value also proves the native artifact
295
+ actually linked — which on iOS is the quickest way to confirm the XCFramework
296
+ was embedded.
178
297
 
179
298
  ### `BooleanMaths.isSupported: boolean`
180
299
 
181
- `true` only where a real native SDK is linked. Currently Android only.
300
+ `true` only where a real native SDK is linked Android and iOS. `false` on
301
+ web, and on any platform where the native module failed to resolve (typically a
302
+ JS install without a native rebuild).
182
303
 
183
304
  ---
184
305
 
185
306
  ## Deep links and notification attribution
186
307
 
308
+ > **Android only.** This entire section does not apply to iOS, where
309
+ > `handleIntent()` is a no-op and no `DeepLinkClick` / `NotificationClick`
310
+ > events are produced. The code below is still safe to run unchanged on iOS —
311
+ > the calls simply do nothing.
312
+
187
313
  The native SDK registers its `ActivityLifecycleCallbacks` **inside**
188
314
  `initialize()`. In a React Native app that runs long after `MainActivity`'s
189
315
  `onActivityCreated` has already fired, so **the intent that cold-started your
@@ -277,19 +403,27 @@ intent without `ACTION_VIEW` takes the `NotificationClick` path instead — see
277
403
 
278
404
  The native SDK tracks these without any call from you:
279
405
 
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` |
406
+ | Event | When | Android | iOS |
407
+ | :---- | :--- | :-----: | :-: |
408
+ | `app_opened` | First Activity creation / SDK initialization | ✅ | ✅ |
409
+ | `FirstOpen` | Once per install | ✅ with Google Play Install Referrer attribution | ✅ but `data` is `{}` |
410
+ | `DeepLinkClick` | An `ACTION_VIEW` intent is handled — ad deep links and app links. Campaign fields nest under `data.link` | ✅ | ❌ |
411
+ | `NotificationClick` | Any other intent carrying campaign data is handled. Fields nest under `data.notification` | ✅ | ❌ |
412
+
413
+ On both platforms `FirstOpen` precedes `app_opened`, so a new install's stream
414
+ reads in order. `FirstOpen` is PascalCase on the wire on purpose — the two
415
+ platforms match byte-for-byte and backend install reporting keys off that exact
416
+ string.
286
417
 
287
418
  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.
419
+ natively on both platforms.
420
+
421
+ **Attribution is Android-only.** Campaign data from a handled intent is
422
+ persisted by `AttributionManager` and attached to **every subsequent event** as
423
+ `data.attribution`. iOS produces no `data.attribution` block, since it handles
424
+ no intents; Apple Search Ads attribution is deferred to a later release.
291
425
 
292
- ### What counts as a campaign intent (native SDK v1.0.9)
426
+ ### What counts as a campaign intent (native SDK v1.0.12)
293
427
 
294
428
  `handleIntent` ignores an intent when **all** of these hold, which is what a
295
429
  plain launcher tap looks like:
@@ -320,7 +454,34 @@ de-duplicated in a `WeakHashMap`, so re-forwarding the same intent is a no-op.
320
454
 
321
455
  ## How properties cross the bridge
322
456
 
323
- Two normalizations happen on the Android side, both worth knowing:
457
+ Each platform normalizes `properties` at the native boundary before handing it
458
+ to the SDK. The rules differ because the two SDKs serialize differently — the
459
+ observable payload is the same in the cases that matter.
460
+
461
+ | Input | Android | iOS |
462
+ | :---- | :------ | :-- |
463
+ | `null` / `undefined` object value | Key dropped | Key dropped |
464
+ | `null` inside an array | Preserved (indices must not shift) | Preserved |
465
+ | Nested objects and arrays | Recursed | Recursed |
466
+ | Whole numbers (`3`) | Coerced to integer — see the Gson caveat below | No action needed; `JSONSerialization` already emits `3`, not `3.0` |
467
+ | Fractions (`999.5`) | Untouched | Untouched |
468
+ | Booleans | Untouched | Untouched |
469
+ | **`NaN` / `Infinity`** | Passed through as-is | **Key dropped** — see below |
470
+
471
+ ### iOS: why `NaN` is dropped rather than passed through
472
+
473
+ This one is load-bearing, not cosmetic. The iOS SDK guards its writes with
474
+ `JSONSerialization.isValidJSONObject(...)` and **silently discards the payload**
475
+ when that returns false. A JS `NaN` or `Infinity` arrives as a non-finite
476
+ `NSNumber` and invalidates the whole object — so a single bad property value
477
+ could discard the event, or the entire outgoing batch. The wrapper strips those
478
+ keys at the boundary so the rest of the event survives.
479
+
480
+ `NSDate` and anything else not JSON-representable is dropped for the same
481
+ reason. Everything is handled recursively, so a `NaN` nested three objects deep
482
+ costs you that one key and nothing else.
483
+
484
+ ### Android normalization
324
485
 
325
486
  - **Integral numbers are converted to integers.** React Native passes every JS
326
487
  number across the bridge as a `Double`, so `{ count: 3 }` would otherwise be
@@ -332,7 +493,7 @@ Two normalizations happen on the Android side, both worth knowing:
332
493
  { "value": 2499, "items": [{ "quantity": 2, "price": 999.5 }] }
333
494
  ```
334
495
 
335
- > ⚠️ **Known native SDK limitation (still present in v1.0.9).** The integers do *not* survive
496
+ > ⚠️ **Known native SDK limitation (still present in v1.0.12).** The integers do *not* survive
336
497
  > to the wire. `EventDispatcher` re-reads the stored payload with
337
498
  > `gson.fromJson(properties, Map::class.java)`, and Gson coerces every number
338
499
  > in a raw `Map` to `Double` — so the request body ends up with `2499.0` and
@@ -346,28 +507,50 @@ Two normalizations happen on the Android side, both worth knowing:
346
507
  serializes with Gson, which omits null map values anyway, so the emitted JSON
347
508
  is identical. Nulls **inside arrays** are preserved so indices do not shift.
348
509
 
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.
510
+ Failures in the native bridge are logged rather than thrown into JavaScript —
511
+ analytics should never crash the host app. Android logs to
512
+ `adb logcat -s BooleanmathsRnSdk`; iOS logs through `RCTLogError`, visible in
513
+ Xcode's console and Metro.
351
514
 
352
515
  ---
353
516
 
354
517
  ## Troubleshooting
355
518
 
356
519
  **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.
520
+ the native logs — `adb logcat -s BooleanMathsSDK:D BooleanmathsRnSdk:D` on
521
+ Android, or Xcode's console on iOS. The native SDK logs each persisted event and
522
+ the full payload it sends.
359
523
 
360
524
  **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`.
525
+ side did not. Rebuild the app (`npx react-native run-android`, or `pod install`
526
+ followed by `npx react-native run-ios`) rather than just reloading Metro.
527
+
528
+ **iOS: crash at launch with a dyld "Library not loaded" / "image not found"
529
+ error naming `BooleanMathsSDK`.** The XCFramework is a *dynamic* framework and
530
+ was not embedded. Confirm the `[CP] Embed Pods Frameworks` build phase exists on
531
+ your app target, then `pod deintegrate && pod install`. This is the one iOS
532
+ failure mode that builds cleanly and only shows up at runtime.
533
+
534
+ **iOS: `getHelloMessage()` returns an empty string.** The bridge resolved but the
535
+ native SDK call failed — check the Xcode console for a
536
+ `BooleanMaths getHelloMessage failed` error. An empty string here specifically
537
+ means the XCFramework did not link correctly.
538
+
539
+ **iOS: `pod install` fails to resolve `BooleanMathsSDK`.** Run
540
+ `pod repo update`, and confirm your app's deployment target is 15.1 or higher.
541
+ If the lockfile pinned `1.0.0`, delete that entry and reinstall — 1.0.0 carries
542
+ an iOS 17.0 floor.
543
+
544
+ **Events show a delay.** By design on both platforms. Each `trackEvent`
545
+ enqueues an immediate sync attempt; Android's WorkManager also runs a periodic
546
+ 15-minute batch job and requires network connectivity, and iOS flushes when the
547
+ app backgrounds.
548
+
549
+ **Deep link not attributed.** Android only — see
550
+ [Deep links](#deep-links-and-notification-attribution); the launch intent needs
551
+ explicit forwarding, and `singleTask` activities need `setIntent`. On iOS deep
552
+ link attribution is not implemented at all
553
+ ([what iOS supports](#what-ios-supports)).
371
554
 
372
555
  ---
373
556
 
@@ -6,7 +6,7 @@ buildscript {
6
6
  // Version of the native Android SDK (com.booleanmaths:bm-sdk) this wrapper
7
7
  // is built against. Override with `ext.BooleanmathsRnSdk_bmSdkVersion` in
8
8
  // your app's root build.gradle to pin a different one.
9
- bmSdkVersion: "1.0.9"
9
+ bmSdkVersion: "1.0.12"
10
10
  ]
11
11
 
12
12
  ext.getExtOrDefault = { prop ->
@@ -8,17 +8,22 @@ import com.facebook.react.bridge.ReadableMap
8
8
  class BooleanmathsRnSdkModule(reactContext: ReactApplicationContext) :
9
9
  NativeBooleanmathsRnSdkSpec(reactContext) {
10
10
 
11
- override fun initializeSdk(apiKey: String, pixelId: String) {
11
+ override fun initializeSdk(apiKey: String, pixelId: String, isDebug: Boolean) {
12
12
  safely("initialize") {
13
13
  // Stamp the wrapper identity *before* initialize() so the automatic
14
14
  // `app_opened` and `FirstOpen` events that the native SDK tracks during
15
15
  // initialization already carry wrapper_type / wrapper_version.
16
16
  applyWrapperConfig()
17
17
 
18
+ // `isDebug` is passed explicitly rather than left to the native default:
19
+ // it decides the `environment` field ("development" / "production") that
20
+ // every event carries, and it gates the native SDK's verbose logging.
21
+ // The JS wrapper always supplies it, defaulting to false.
18
22
  BooleanMathsSDK.initialize(
19
23
  reactApplicationContext.applicationContext,
20
24
  apiKey,
21
- pixelId
25
+ pixelId,
26
+ isDebug
22
27
  )
23
28
 
24
29
  // ...and again afterwards, because the native SDK can only persist the
@@ -0,0 +1,151 @@
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:isDebug:wrapperVersion:)
39
+ public static func initializeSdk(
40
+ apiKey: String,
41
+ pixelId: String,
42
+ isDebug: Bool,
43
+ wrapperVersion: String
44
+ ) {
45
+ // Before `initialize`, not after: `initialize` itself emits `FirstOpen` and
46
+ // `app_opened` internally, and those must already carry wrapper_type /
47
+ // wrapper_version. Same ordering rationale as Android.
48
+ //
49
+ // Do NOT port Android's second, post-initialize call. Android needs it
50
+ // because its native SDK can only persist to SharedPreferences once it holds
51
+ // an application context, which it acquires inside initialize(). iOS has no
52
+ // such gap: `WrapperConfigStore.set` writes to UserDefaults unconditionally,
53
+ // and `initialize` calls `rehydrate()`, which fills each field only where it
54
+ // is still nil — so a value set earlier in the process outranks the
55
+ // persisted one. One call, before initialize, is correct and sufficient.
56
+ BooleanMaths.shared.setWrapperConfig(type: wrapperType, version: wrapperVersion)
57
+
58
+ // `isDebug` is passed explicitly rather than left to the SDK's default: it
59
+ // decides the `environment` field ("development" / "production") stamped on
60
+ // every event, and it gates the SDK's verbose logging. The JS wrapper always
61
+ // supplies it, defaulting to false. Same contract as Android.
62
+ BooleanMaths.shared.initialize(apiKey: apiKey, pixelId: pixelId, isDebug: isDebug)
63
+
64
+ // There is no iOS equivalent of Android's forwardCurrentIntent(): deep links
65
+ // are deferred on iOS, so nothing is forwarded here. See `handleIntent` in
66
+ // BooleanmathsRnSdk.mm.
67
+ }
68
+
69
+ /// Records a custom event. `properties` is sanitized first — see
70
+ /// `sanitized(value:allowNull:)` for why that is load-bearing.
71
+ ///
72
+ /// `@MainActor` because `BooleanMaths.track` is main-actor isolated.
73
+ @MainActor
74
+ @objc(trackEventWithName:properties:)
75
+ public static func trackEvent(name: String, properties: [String: Any]?) {
76
+ BooleanMaths.shared.track(name, properties: sanitized(dictionary: properties ?? [:]))
77
+ }
78
+
79
+ /// Bridge smoke test. Deliberately not main-actor isolated: the TurboModule
80
+ /// method is synchronous and returns the value directly, so it must not hop
81
+ /// threads. `BooleanMaths.getHelloMessage()` is `nonisolated static` in the SDK
82
+ /// specifically to allow this.
83
+ ///
84
+ /// A real SDK call rather than a wrapper-side literal, so it also proves the
85
+ /// XCFramework actually linked.
86
+ @objc(getHelloMessage)
87
+ public static func getHelloMessage() -> String {
88
+ BooleanMaths.getHelloMessage()
89
+ }
90
+
91
+ // MARK: - Properties sanitization
92
+
93
+ // `EventDispatcher` and `EventStore` both guard on
94
+ // `JSONSerialization.isValidJSONObject(...)` and silently drop the payload when
95
+ // it returns false. A JS `NaN` or `Infinity` arrives here as a non-finite
96
+ // NSNumber and makes the whole object invalid — so one bad property value can
97
+ // discard the event, or the entire outgoing batch. Android guards the same case
98
+ // in `normalizeValue`.
99
+
100
+ private static func sanitized(dictionary: [String: Any]) -> [String: Any] {
101
+ var result = [String: Any](minimumCapacity: dictionary.count)
102
+
103
+ for (key, value) in dictionary {
104
+ guard let clean = sanitized(value: value, allowNull: false) else { continue }
105
+ result[key] = clean
106
+ }
107
+
108
+ return result
109
+ }
110
+
111
+ /// Nulls are preserved inside arrays — dropping them would shift indices.
112
+ /// Android preserves them deliberately for the same reason.
113
+ private static func sanitized(array: [Any]) -> [Any] {
114
+ array.map { sanitized(value: $0, allowNull: true) ?? NSNull() }
115
+ }
116
+
117
+ private static func sanitized(value: Any, allowNull: Bool) -> Any? {
118
+ switch value {
119
+ case is NSNull:
120
+ // Dropped as an object value (matching Android, whose Gson omits null map
121
+ // values anyway), kept inside an array.
122
+ return allowNull ? NSNull() : nil
123
+
124
+ case let number as NSNumber:
125
+ // Must be tested before any `as Bool` cast: `NSNumber(1) as? Bool`
126
+ // succeeds under Swift bridging, so checking Bool first would silently
127
+ // turn `{ count: 1 }` into `{ count: true }`. Booleans arrive from React
128
+ // Native as CFBoolean, match here, and serialize as true/false unchanged.
129
+ //
130
+ // No whole-number coercion: Android's `toWholeNumberOrSelf` fixes a
131
+ // Gson-specific artifact, whereas JSONSerialization already emits `3`
132
+ // rather than `3.0`.
133
+ return number.doubleValue.isFinite ? number : nil
134
+
135
+ case let string as String:
136
+ return string
137
+
138
+ case let nested as [String: Any]:
139
+ return sanitized(dictionary: nested)
140
+
141
+ case let nested as [Any]:
142
+ return sanitized(array: nested)
143
+
144
+ default:
145
+ // NSDate and anything else not JSON-representable, plus dictionaries with
146
+ // non-String keys. React Native never produces these from a JS object, but
147
+ // passing one through would invalidate the payload.
148
+ return nil
149
+ }
150
+ }
151
+ }
@@ -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,105 @@
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
- - (void)initializeSdk:(NSString *)apiKey pixelId:(NSString *)pixelId
50
+ - (void)initializeSdk:(NSString *)apiKey pixelId:(NSString *)pixelId isDebug:(BOOL)isDebug
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
+ isDebug:isDebug
62
+ wrapperVersion:wrapperVersion];
63
+ });
64
+ });
24
65
  }
25
66
 
26
67
  - (void)trackEvent:(NSString *)name properties:(NSDictionary *)properties
27
68
  {
28
- BMWarnUnsupportedOnce();
69
+ dispatch_async(dispatch_get_main_queue(), ^{
70
+ BMSafely(@"trackEvent", ^{
71
+ [BMBooleanMathsBridge trackEventWithName:name properties:properties];
72
+ });
73
+ });
29
74
  }
30
75
 
31
76
  - (void)handleIntent
32
77
  {
33
- // Android-only concept; nothing to do on iOS even once an iOS SDK exists.
34
- BMWarnUnsupportedOnce();
78
+ // Deliberately still a no-op. Intents are an Android concept, and iOS deep
79
+ // links / universal links are deferred — their absence here is scope, not a
80
+ // missing implementation.
81
+ BMWarnIntentUnsupportedOnce();
35
82
  }
36
83
 
37
84
  - (void)handleNotificationIntent
38
85
  {
39
86
  // Alias of handleIntent, mirroring the Android bridge.
40
- BMWarnUnsupportedOnce();
87
+ BMWarnIntentUnsupportedOnce();
41
88
  }
42
89
 
43
90
  - (NSString *)getHelloMessage
44
91
  {
45
- BMWarnUnsupportedOnce();
92
+ // No dispatch: this is synchronous and returns a value, so it must not hop
93
+ // threads. The SDK's getHelloMessage() is `nonisolated static` to allow it.
94
+ @try {
95
+ return [BMBooleanMathsBridge getHelloMessage];
96
+ } @catch (NSException *exception) {
97
+ RCTLogError(@"[@booleanmaths/booleanmaths-rn-sdk] BooleanMaths getHelloMessage failed: %@", exception);
46
98
 
47
- // Deliberately not nil: the codegen spec declares a non-optional string.
48
- return @"BooleanMaths SDK is not available on iOS";
99
+ // Empty rather than a plausible-looking message: this method exists to prove
100
+ // the native SDK linked, so a failure must read as a failure. The codegen
101
+ // spec declares a non-optional string, so it cannot be nil.
102
+ return @"";
103
+ }
49
104
  }
50
105
 
51
106
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
@@ -17,7 +17,7 @@ function warnOnce() {
17
17
  }
18
18
  export const BooleanMaths = {
19
19
  isSupported: false,
20
- initialize(_apiKey, _pixelId) {
20
+ initialize(_apiKey, _pixelId, _isDebug) {
21
21
  warnOnce();
22
22
  },
23
23
  trackEvent(_name, _properties) {
@@ -1 +1 @@
1
- {"version":3,"names":["hasWarned","warnOnce","__DEV__","console","warn","BooleanMaths","isSupported","initialize","_apiKey","_pixelId","trackEvent","_name","_properties","handleIntent","handleNotificationIntent","getHelloMessage"],"sourceRoot":"../../src","sources":["BooleanMaths.tsx"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAIA,SAAS,GAAG,KAAK;AAErB,SAASC,QAAQA,CAAA,EAAG;EAClB,IAAID,SAAS,IAAI,OAAOE,OAAO,KAAK,WAAW,IAAI,CAACA,OAAO,EAAE;IAC3D;EACF;EAEAF,SAAS,GAAG,IAAI;EAEhBG,OAAO,CAACC,IAAI,CACV,sFAAsF,GACpF,2EAA2E,GAC3E,8DACJ,CAAC;AACH;AAEA,OAAO,MAAMC,YAA6B,GAAG;EAC3CC,WAAW,EAAE,KAAK;EAElBC,UAAUA,CAACC,OAAe,EAAEC,QAAgB,EAAQ;IAClDR,QAAQ,CAAC,CAAC;EACZ,CAAC;EAEDS,UAAUA,CAACC,KAAa,EAAEC,WAAyC,EAAQ;IACzEX,QAAQ,CAAC,CAAC;EACZ,CAAC;EAEDY,YAAYA,CAAA,EAAS;IACnBZ,QAAQ,CAAC,CAAC;EACZ,CAAC;EAEDa,wBAAwBA,CAAA,EAAS;IAC/Bb,QAAQ,CAAC,CAAC;EACZ,CAAC;EAEDc,eAAeA,CAAA,EAAkB;IAC/Bd,QAAQ,CAAC,CAAC;IACV,OAAO,IAAI;EACb;AACF,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["hasWarned","warnOnce","__DEV__","console","warn","BooleanMaths","isSupported","initialize","_apiKey","_pixelId","_isDebug","trackEvent","_name","_properties","handleIntent","handleNotificationIntent","getHelloMessage"],"sourceRoot":"../../src","sources":["BooleanMaths.tsx"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAIA,SAAS,GAAG,KAAK;AAErB,SAASC,QAAQA,CAAA,EAAG;EAClB,IAAID,SAAS,IAAI,OAAOE,OAAO,KAAK,WAAW,IAAI,CAACA,OAAO,EAAE;IAC3D;EACF;EAEAF,SAAS,GAAG,IAAI;EAEhBG,OAAO,CAACC,IAAI,CACV,sFAAsF,GACpF,2EAA2E,GAC3E,8DACJ,CAAC;AACH;AAEA,OAAO,MAAMC,YAA6B,GAAG;EAC3CC,WAAW,EAAE,KAAK;EAElBC,UAAUA,CAACC,OAAe,EAAEC,QAAgB,EAAEC,QAAkB,EAAQ;IACtET,QAAQ,CAAC,CAAC;EACZ,CAAC;EAEDU,UAAUA,CAACC,KAAa,EAAEC,WAAyC,EAAQ;IACzEZ,QAAQ,CAAC,CAAC;EACZ,CAAC;EAEDa,YAAYA,CAAA,EAAS;IACnBb,QAAQ,CAAC,CAAC;EACZ,CAAC;EAEDc,wBAAwBA,CAAA,EAAS;IAC/Bd,QAAQ,CAAC,CAAC;EACZ,CAAC;EAEDe,eAAeA,CAAA,EAAkB;IAC/Bf,QAAQ,CAAC,CAAC;IACV,OAAO,IAAI;EACb;AACF,CAAC","ignoreList":[]}
@@ -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.');
@@ -44,12 +48,16 @@ function forwardCurrentIntent() {
44
48
  }
45
49
  export const BooleanMaths = {
46
50
  isSupported,
47
- initialize(apiKey, pixelId) {
51
+ initialize(apiKey, pixelId, isDebug = false) {
48
52
  if (!isSupported) {
49
53
  warnOnce();
50
54
  return;
51
55
  }
52
- NativeBooleanmathsRnSdk.initializeSdk(apiKey, pixelId);
56
+
57
+ // Coerced rather than passed through: the codegen spec declares a
58
+ // non-optional boolean, and a truthy non-boolean from untyped JS would
59
+ // arrive at the native side as a type mismatch rather than a flag.
60
+ NativeBooleanmathsRnSdk.initializeSdk(apiKey, pixelId, Boolean(isDebug));
53
61
  },
54
62
  trackEvent(name, properties = {}) {
55
63
  if (!isSupported) {
@@ -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","isDebug","initializeSdk","Boolean","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,EAAEC,OAAgB,GAAG,KAAK,EAAQ;IAC1E,IAAI,CAACZ,WAAW,EAAE;MAChBE,QAAQ,CAAC,CAAC;MACV;IACF;;IAEA;IACA;IACA;IACAP,uBAAuB,CAAEkB,aAAa,CAACH,MAAM,EAAEC,OAAO,EAAEG,OAAO,CAACF,OAAO,CAAC,CAAC;EAC3E,CAAC;EAEDG,UAAUA,CAACC,IAAY,EAAEC,UAAuC,GAAG,CAAC,CAAC,EAAQ;IAC3E,IAAI,CAACjB,WAAW,EAAE;MAChBE,QAAQ,CAAC,CAAC;MACV;IACF;IAEAP,uBAAuB,CAAEoB,UAAU,CAACC,IAAI,EAAEC,UAAU,CAAC;EACvD,CAAC;EAEDV,YAAY,EAAED,oBAAoB;EAElCY,wBAAwB,EAAEZ,oBAAoB;EAE9Ca,eAAeA,CAAA,EAAkB;IAC/B,IAAI,CAACnB,WAAW,EAAE;MAChBE,QAAQ,CAAC,CAAC;MACV,OAAO,IAAI;IACb;IAEA,OAAOP,uBAAuB,CAAEwB,eAAe,CAAC,CAAC;EACnD;AACF,CAAC","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"names":["TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeBooleanmathsRnSdk.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAsBpE;AACA;AACA;AACA;AACA,eAAeA,mBAAmB,CAACC,GAAG,CAAO,mBAAmB,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeBooleanmathsRnSdk.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AA2BpE;AACA;AACA;AACA;AACA,eAAeA,mBAAmB,CAACC,GAAG,CAAO,mBAAmB,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,eAoC1B,CAAC"}
@@ -5,8 +5,13 @@ export interface Spec extends TurboModule {
5
5
  * already taken by `NativeModule.initialize()` on Android and by
6
6
  * `+[NSObject initialize]` on iOS. The public JS API exposes this as
7
7
  * `BooleanMaths.initialize()`.
8
+ *
9
+ * `isDebug` is required here even though it is optional on the public API —
10
+ * codegen has no notion of a default, so `BooleanMaths.initialize` always
11
+ * passes an explicit boolean. Same arrangement as `trackEvent`'s
12
+ * `properties`.
8
13
  */
9
- initializeSdk(apiKey: string, pixelId: string): void;
14
+ initializeSdk(apiKey: string, pixelId: string, isDebug: boolean): void;
10
15
  trackEvent(name: string, properties: Object): void;
11
16
  /** Android only. No-op elsewhere. */
12
17
  handleIntent(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"NativeBooleanmathsRnSdk.d.ts","sourceRoot":"","sources":["../../../src/NativeBooleanmathsRnSdk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC;;;;;OAKG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACrD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,qCAAqC;IACrC,YAAY,IAAI,IAAI,CAAC;IACrB;;;OAGG;IACH,wBAAwB,IAAI,IAAI,CAAC;IACjC,qDAAqD;IACrD,eAAe,IAAI,MAAM,CAAC;CAC3B;AAED;;;GAGG;;AACH,wBAAkE"}
1
+ {"version":3,"file":"NativeBooleanmathsRnSdk.d.ts","sourceRoot":"","sources":["../../../src/NativeBooleanmathsRnSdk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACvE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,qCAAqC;IACrC,YAAY,IAAI,IAAI,CAAC;IACrB;;;OAGG;IACH,wBAAwB,IAAI,IAAI,CAAC;IACjC,qDAAqD;IACrD,eAAe,IAAI,MAAM,CAAC;CAC3B;AAED;;;GAGG;;AACH,wBAAkE"}
@@ -15,8 +15,21 @@ export interface BooleanMathsApi {
15
15
  *
16
16
  * Calling this more than once is harmless — the native SDK ignores
17
17
  * subsequent calls.
18
+ *
19
+ * @param isDebug Marks this process as a development build. Every event it
20
+ * tracks is stamped `environment: "development"` instead of `"production"`,
21
+ * and the native SDKs' verbose logging is switched on. Defaults to `false`.
22
+ *
23
+ * The flag is recorded per event when the event is queued, not when the
24
+ * queue is flushed, so events written by a debug build stay marked as
25
+ * development even if they only sync later.
26
+ *
27
+ * It is deliberately *not* derived from `__DEV__` inside the wrapper —
28
+ * where an event lands is a backend-routing decision, not a bundler one, and
29
+ * some teams want release builds pointed at development. Pass `__DEV__`
30
+ * yourself if that is the behaviour you want.
18
31
  */
19
- initialize(apiKey: string, pixelId: string): void;
32
+ initialize(apiKey: string, pixelId: string, isDebug?: boolean): void;
20
33
  /**
21
34
  * Records a custom event. Events are persisted locally and synced in the
22
35
  * background, so this never blocks and never throws on network failure.
@@ -28,10 +41,14 @@ export interface BooleanMathsApi {
28
41
  * Forwards the current Activity's intent to the native SDK so ad deep links,
29
42
  * app links and push-notification campaign data are attributed.
30
43
  *
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).
44
+ * This is the primary entry point for every kind of launch intent. Call it
45
+ * from your deep-link handler; `initialize` already forwards the launch
46
+ * intent itself (see the README for why that needs special handling).
47
+ *
48
+ * **Android only.** A no-op on iOS — not because iOS is unsupported (event
49
+ * tracking works fully there), but because intents are an Android concept and
50
+ * iOS deep links / universal links are out of scope for now. Safe to call
51
+ * unconditionally from shared code; on iOS it logs one dev-mode notice.
35
52
  *
36
53
  * Safe to call repeatedly — the native SDK de-duplicates intents it has
37
54
  * already processed.
@@ -47,12 +64,17 @@ export interface BooleanMathsApi {
47
64
  handleNotificationIntent(): void;
48
65
  /**
49
66
  * 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.
67
+ * SDK on this platform.
68
+ *
69
+ * Useful as a bridge smoke test: the string comes from the native SDK itself
70
+ * on both Android and iOS, so a correct value also proves the native artifact
71
+ * actually linked.
51
72
  */
52
73
  getHelloMessage(): string | null;
53
74
  /**
54
- * `true` only on platforms where a real BooleanMaths native SDK is linked.
55
- * Currently Android only.
75
+ * `true` only on platforms where a real BooleanMaths native SDK is linked
76
+ * Android and iOS. `false` on web, and on any platform where the native
77
+ * module failed to resolve (typically a JS install without a native rebuild).
56
78
  */
57
79
  readonly isSupported: boolean;
58
80
  }
@@ -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;;;;;;;;;;;;;;;;;;;OAmBG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAErE;;;;;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.4.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
  );
@@ -63,13 +70,16 @@ function forwardCurrentIntent(): void {
63
70
  export const BooleanMaths: BooleanMathsApi = {
64
71
  isSupported,
65
72
 
66
- initialize(apiKey: string, pixelId: string): void {
73
+ initialize(apiKey: string, pixelId: string, isDebug: boolean = false): void {
67
74
  if (!isSupported) {
68
75
  warnOnce();
69
76
  return;
70
77
  }
71
78
 
72
- NativeBooleanmathsRnSdk!.initializeSdk(apiKey, pixelId);
79
+ // Coerced rather than passed through: the codegen spec declares a
80
+ // non-optional boolean, and a truthy non-boolean from untyped JS would
81
+ // arrive at the native side as a type mismatch rather than a flag.
82
+ NativeBooleanmathsRnSdk!.initializeSdk(apiKey, pixelId, Boolean(isDebug));
73
83
  },
74
84
 
75
85
  trackEvent(name: string, properties: BooleanMathsEventProperties = {}): void {
@@ -26,7 +26,7 @@ function warnOnce() {
26
26
  export const BooleanMaths: BooleanMathsApi = {
27
27
  isSupported: false,
28
28
 
29
- initialize(_apiKey: string, _pixelId: string): void {
29
+ initialize(_apiKey: string, _pixelId: string, _isDebug?: boolean): void {
30
30
  warnOnce();
31
31
  },
32
32
 
@@ -6,8 +6,13 @@ export interface Spec extends TurboModule {
6
6
  * already taken by `NativeModule.initialize()` on Android and by
7
7
  * `+[NSObject initialize]` on iOS. The public JS API exposes this as
8
8
  * `BooleanMaths.initialize()`.
9
+ *
10
+ * `isDebug` is required here even though it is optional on the public API —
11
+ * codegen has no notion of a default, so `BooleanMaths.initialize` always
12
+ * passes an explicit boolean. Same arrangement as `trackEvent`'s
13
+ * `properties`.
9
14
  */
10
- initializeSdk(apiKey: string, pixelId: string): void;
15
+ initializeSdk(apiKey: string, pixelId: string, isDebug: boolean): void;
11
16
  trackEvent(name: string, properties: Object): void;
12
17
  /** Android only. No-op elsewhere. */
13
18
  handleIntent(): void;
package/src/types.ts CHANGED
@@ -25,8 +25,21 @@ export interface BooleanMathsApi {
25
25
  *
26
26
  * Calling this more than once is harmless — the native SDK ignores
27
27
  * subsequent calls.
28
+ *
29
+ * @param isDebug Marks this process as a development build. Every event it
30
+ * tracks is stamped `environment: "development"` instead of `"production"`,
31
+ * and the native SDKs' verbose logging is switched on. Defaults to `false`.
32
+ *
33
+ * The flag is recorded per event when the event is queued, not when the
34
+ * queue is flushed, so events written by a debug build stay marked as
35
+ * development even if they only sync later.
36
+ *
37
+ * It is deliberately *not* derived from `__DEV__` inside the wrapper —
38
+ * where an event lands is a backend-routing decision, not a bundler one, and
39
+ * some teams want release builds pointed at development. Pass `__DEV__`
40
+ * yourself if that is the behaviour you want.
28
41
  */
29
- initialize(apiKey: string, pixelId: string): void;
42
+ initialize(apiKey: string, pixelId: string, isDebug?: boolean): void;
30
43
 
31
44
  /**
32
45
  * Records a custom event. Events are persisted locally and synced in the
@@ -40,10 +53,14 @@ export interface BooleanMathsApi {
40
53
  * Forwards the current Activity's intent to the native SDK so ad deep links,
41
54
  * app links and push-notification campaign data are attributed.
42
55
  *
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).
56
+ * This is the primary entry point for every kind of launch intent. Call it
57
+ * from your deep-link handler; `initialize` already forwards the launch
58
+ * intent itself (see the README for why that needs special handling).
59
+ *
60
+ * **Android only.** A no-op on iOS — not because iOS is unsupported (event
61
+ * tracking works fully there), but because intents are an Android concept and
62
+ * iOS deep links / universal links are out of scope for now. Safe to call
63
+ * unconditionally from shared code; on iOS it logs one dev-mode notice.
47
64
  *
48
65
  * Safe to call repeatedly — the native SDK de-duplicates intents it has
49
66
  * already processed.
@@ -61,13 +78,18 @@ export interface BooleanMathsApi {
61
78
 
62
79
  /**
63
80
  * 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.
81
+ * SDK on this platform.
82
+ *
83
+ * Useful as a bridge smoke test: the string comes from the native SDK itself
84
+ * on both Android and iOS, so a correct value also proves the native artifact
85
+ * actually linked.
65
86
  */
66
87
  getHelloMessage(): string | null;
67
88
 
68
89
  /**
69
- * `true` only on platforms where a real BooleanMaths native SDK is linked.
70
- * Currently Android only.
90
+ * `true` only on platforms where a real BooleanMaths native SDK is linked
91
+ * Android and iOS. `false` on web, and on any platform where the native
92
+ * module failed to resolve (typically a JS install without a native rebuild).
71
93
  */
72
94
  readonly isSupported: boolean;
73
95
  }