react-native-insider 8.0.1 → 8.0.2

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.
@@ -0,0 +1,129 @@
1
+ # MASTER.md — Single Source of Truth
2
+
3
+ > react-native-insider | Generated by Orbit Init | 2026-04-16
4
+
5
+ ---
6
+
7
+ ## Project Overview
8
+
9
+ React Native bridge SDK (`react-native-insider`) wrapping native Insider iOS/Android SDKs via NativeModules. Published to npm as a library — not an application. v8.0.0 introduces the **App Cards** module and **identifier-scoped Message Center**.
10
+
11
+ ## Technology Stack
12
+
13
+ | Layer | Technology | Version |
14
+ |-------|-----------|---------|
15
+ | JS Bridge | JavaScript (ES6+, ES private fields) | — |
16
+ | Type Declarations | TypeScript (.d.ts) | — |
17
+ | iOS Native | Objective-C | — |
18
+ | Android Native | Java | — |
19
+ | Framework | React Native | 0.83.0 |
20
+ | React | React | 19.2.0 |
21
+ | iOS SDK | InsiderMobile | 15.0.0 |
22
+ | iOS Geofence | InsiderGeofence | 1.2.4 |
23
+ | iOS Hybrid | InsiderHybrid | 1.7.6 |
24
+ | Android SDK | com.useinsider:insider | 16.0.1 |
25
+ | Android Hybrid | com.useinsider:insiderhybrid | 1.3.4 |
26
+ | Testing | Jest | 29.7.0 |
27
+ | Build | Babel + @react-native/babel-preset | 0.83.0 |
28
+ | Android Build | Android Gradle Plugin | 8.4.2 |
29
+ | CI/CD | GitHub Actions | 3 workflows |
30
+
31
+ ## Project Structure
32
+
33
+ ```
34
+ ./
35
+ ├── index.js / index.d.ts # Main entry point (re-exports AppCardsError)
36
+ ├── src/ # JS modules
37
+ │ ├── InsiderUser/Product/Event/Identifier (fluent builders)
38
+ │ ├── InsiderAppCards / InsiderAppCard / InsiderAppCardsError (App Cards)
39
+ │ ├── Util.js # validation + resolveWithCallback
40
+ │ ├── *Enum* (Gender, CallbackType, ContentOptimizerDataType, CloseButtonPosition)
41
+ │ └── __tests__/ # per-module tests
42
+ ├── ios/RNInsider/ # Objective-C bridge
43
+ ├── android/src/main/java/ # Java bridge
44
+ ├── __tests__/ + __mocks__/ # Jest tests
45
+ ├── docs/ # internal feature specs
46
+ ├── example/ # Demo React Native app
47
+ ├── .github/workflows/ # CI/CD
48
+ ├── .orbit-output/ # Orbit architecture docs (below)
49
+ └── devops/ # Release scripts
50
+ ```
51
+
52
+ ## .orbit Structure
53
+
54
+ ```
55
+ .orbit-output/
56
+ ├── config/
57
+ │ ├── MASTER.md # THIS FILE — single source of truth
58
+ │ └── architecture/
59
+ │ ├── ARCHITECTURE.md # Architecture overview + diagrams
60
+ │ ├── CODE-CATALOG.md # All classes, functions, modules
61
+ │ ├── API_DOCS.md # Bridge API reference
62
+ │ └── project.yaml # Machine-readable config
63
+ ```
64
+
65
+ **When to read:**
66
+ - New to project → ARCHITECTURE.md
67
+ - Looking for a class/function → CODE-CATALOG.md
68
+ - Understanding bridge API → API_DOCS.md
69
+ - Quick reference → this file (MASTER.md)
70
+
71
+ ## Code Patterns
72
+
73
+ ### Naming
74
+ - Classes: `PascalCase` (RNInsider, RNInsiderProduct, InsiderAppCard)
75
+ - Methods: `camelCase` (tagEvent, getCurrentUser, markAsRead)
76
+ - Constants: `UPPER_SNAKE_CASE` (NOTIFICATION_OPEN, INVALID_PARAMETER)
77
+ - Product field keys: `snake_case` strings (product_id, sale_price)
78
+ - Private fields: `#field` (App Card domain models)
79
+
80
+ ### Architecture Pattern
81
+ - **Static facade** (RNInsider) + **fluent builders** (Product, Event, User, Identifier) + **frozen singleton** (AppCards)
82
+ - Every public method: `shouldNotProceed()` → `checkParameters()` → try/catch → `putErrorLog()`
83
+ - Products serialized as 3 bridge args: requiredFields, optionalFields, customParameters
84
+ - Dates as epoch-millis strings (bridge int64 limitation)
85
+ - Callback events indexed by array position matching InsiderCallbackType enum
86
+ - App Cards: Promise + completion-callback duality via `Util.resolveWithCallback`
87
+ - Polymorphic action types via `InsiderAppCardAction.fromData()` factory
88
+ - Polymorphic 2nd arg in `itemRemovedFromCart` and `visitCartPage` (saleID OR customParameters)
89
+
90
+ ### Testing
91
+ - Jest with react-native preset
92
+ - Global mock in `__mocks__/react-native.js`
93
+ - Test pattern: `describe('Class.method') → it('should ...')`
94
+ - Per-module suites under `src/__tests__/` plus top-level `__tests__/`
95
+
96
+ ## Common Commands
97
+
98
+ | Command | Purpose |
99
+ |---------|---------|
100
+ | `npm test` | Run all Jest tests |
101
+ | `npx jest __tests__/Util.test.js` | Run single test file |
102
+ | `npm install` | Install dependencies |
103
+ | `npm pack` | Pack for local testing |
104
+ | `cd example && npm install` | Install example app deps |
105
+ | `cd example/ios && pod install` | Install iOS pods for example |
106
+
107
+ ## Key Entry Points
108
+
109
+ | File | Role |
110
+ |------|------|
111
+ | `index.js` | Main SDK entry — RNInsider static class |
112
+ | `src/InsiderAppCards.js` | App Cards module (Promise + callback API) |
113
+ | `ios/RNInsider/RNInsider.m` | iOS bridge module |
114
+ | `android/.../RNInsiderModule.java` | Android bridge module |
115
+ | `example/App.tsx` | Example app entry |
116
+
117
+ ## Version Management
118
+
119
+ - Source of truth: `package.json` → version (currently 8.0.0)
120
+ - iOS: `RNInsider.podspec` reads from package.json
121
+ - Android: `build.gradle` has pinned native SDK versions (insider:16.0.1)
122
+ - SDK string: `'RN-' + version` (e.g., RN-8.0.0)
123
+ - Release: `release/X.Y.Z` branch → master merge → npm publish
124
+ - Dual-track: main + `-nh` (non-Huawei) variant
125
+ - PR titles must contain `MOB-[0-9]+`
126
+
127
+ ---
128
+
129
+ *Auto-generated by Orbit Init v3.11 | 2026-04-16*
@@ -0,0 +1,342 @@
1
+ # API_DOCS.md
2
+
3
+ > Generated by Orbit Init | 2026-04-16 | react-native-insider v8.0.0
4
+
5
+ ## Overview
6
+
7
+ This SDK exposes a **React Native NativeModules bridge** — not HTTP endpoints. The JS layer (`RNInsider`) wraps native method calls that communicate with the Insider platform's native iOS/Android SDKs.
8
+
9
+ ---
10
+
11
+ ## Bridge Architecture
12
+
13
+ ```
14
+ ┌─────────────────────────┐
15
+ │ Consumer App (JS/TS) │
16
+ │ import Insider from │
17
+ │ 'react-native-insider'│
18
+ └──────────┬──────────────┘
19
+ │ NativeModules.RNInsider
20
+
21
+ ┌─────────────────────────┐
22
+ │ index.js (RNInsider) │
23
+ │ Static facade class │
24
+ │ Parameter validation │
25
+ │ Product serialization │
26
+ │ AppCards getter │
27
+ └──────────┬──────────────┘
28
+ │ RN Bridge
29
+ ┌─────┴─────┐
30
+ ▼ ▼
31
+ ┌──────────┐ ┌──────────┐
32
+ │ iOS │ │ Android │
33
+ │ RNInsider│ │ RNInsider│
34
+ │ .m │ │ Module │
35
+ │ │ │ .java │
36
+ └────┬─────┘ └────┬─────┘
37
+ │ │
38
+ ▼ ▼
39
+ ┌──────────┐ ┌──────────┐
40
+ │ Insider │ │ Insider │
41
+ │ Mobile │ │ SDK │
42
+ │ 15.0.0 │ │ 16.0.1 │
43
+ └──────────┘ └──────────┘
44
+ ```
45
+
46
+ **Event Flow (native → JS):**
47
+ ```
48
+ Native SDK event → RNNotificationHandler (iOS) / RCTDeviceEventEmitter (Android)
49
+ → NativeEventEmitter listener in JS
50
+ → insiderCallback(typeIndex, data)
51
+ ```
52
+
53
+ ---
54
+
55
+ ## RNInsider — Static API Methods
56
+
57
+ ### Static Properties
58
+
59
+ | Property | Type | Description |
60
+ |----------|------|-------------|
61
+ | `closeButtonPosition` | `CloseButtonPosition` | Enum re-exported for `setInternalBrowserCloseButtonPosition` |
62
+ | `AppCardsError` | `typeof InsiderAppCardsError` | Typed error class for App Cards operations |
63
+ | `AppCardsErrorCode` | `typeof InsiderAppCardsErrorCode` | Error-code enum for App Cards |
64
+ | `appCards` | `RNInsiderAppCards` (getter) | Singleton App Cards module |
65
+
66
+ ### Initialization
67
+
68
+ | Method | Parameters | Returns | Platform | Description |
69
+ |--------|-----------|---------|----------|-------------|
70
+ | `init` | `partnerName: string, appGroup: string, callback: (type, data) => void` | `void` | Both | Initialize SDK. Registers event listeners before native init. iOS uses appGroup, Android ignores it |
71
+ | `initWithCustomEndpoint` | `partnerName: string, appGroup: string, endpoint: string, callback: (type, data) => void` | `void` | Both | Init with custom API endpoint. iOS: single native call. Android: init() + setCustomEndpoint() |
72
+ | `reinitWithPartnerName` | `partnerName: string` | `void` | Both | Re-initialize with different partner |
73
+
74
+ ### User Management
75
+
76
+ | Method | Parameters | Returns | Description |
77
+ |--------|-----------|---------|-------------|
78
+ | `getCurrentUser()` | — | `RNInsiderUser` | Returns singleton user instance |
79
+
80
+ ### Event Tracking
81
+
82
+ | Method | Parameters | Returns | Description |
83
+ |--------|-----------|---------|-------------|
84
+ | `tagEvent` | `name: string` | `RNInsiderEvent` | Creates event builder. Call `.build()` to dispatch |
85
+
86
+ ### Product & E-commerce
87
+
88
+ | Method | Parameters | Returns | Description |
89
+ |--------|-----------|---------|-------------|
90
+ | `createNewProduct` | `productID, name, taxonomy[], imageURL, price, currency` | `RNInsiderProduct` | Creates product builder |
91
+ | `itemPurchased` | `uniqueSaleID: string, product: RNInsiderProduct, customParameters?: CustomParameters` | `void` | Track purchase. Serializes product as 3 args |
92
+ | `itemAddedToCart` | `product: RNInsiderProduct, customParameters?: CustomParameters` | `void` | Track add-to-cart |
93
+ | `itemRemovedFromCart` | `productID: string, saleIDOrCustomParameters?: string \| CustomParameters, customParameters?: CustomParameters` | `void` | Polymorphic 2nd arg: a string is treated as saleID; a plain object is treated as customParameters |
94
+ | `cartCleared` | `customParameters?: CustomParameters` | `void` | Track cart clear |
95
+ | `itemAddedToWishlist` | `product: RNInsiderProduct, customParameters?: CustomParameters` | `void` | Track wishlist add |
96
+ | `itemRemovedFromWishlist` | `productID: string, customParameters?: CustomParameters` | `void` | Track wishlist removal |
97
+ | `wishlistCleared` | `customParameters?: CustomParameters` | `void` | Track wishlist clear |
98
+
99
+ ### Page Visit Tracking
100
+
101
+ | Method | Parameters | Description |
102
+ |--------|-----------|-------------|
103
+ | `visitHomePage` | `customParameters?: CustomParameters` | Track home page visit |
104
+ | `visitListingPage` | `taxonomy: string[], customParameters?: CustomParameters` | Track listing/category page |
105
+ | `visitProductDetailPage` | `product: RNInsiderProduct, customParameters?: CustomParameters` | Track PDP visit |
106
+ | `visitCartPage` | `products: RNInsiderProduct[], saleIDOrCustomParameters?: string \| CustomParameters, customParameters?: CustomParameters` | Polymorphic 2nd arg (saleID or customParameters). Maps products through 3-part serialization |
107
+ | `visitWishlistPage` | `products: RNInsiderProduct[], customParameters?: CustomParameters` | Track wishlist page |
108
+
109
+ ### Smart Recommendations
110
+
111
+ | Method | Parameters | Returns |
112
+ |--------|-----------|---------|
113
+ | `getSmartRecommendation` | `recommendationID: number, locale: string, currency: string, callback: (data) => void` | `void` |
114
+ | `getSmartRecommendationWithProduct` | `product: RNInsiderProduct, recommendationID: number, locale: string, callback: (data) => void` | `void` |
115
+ | `getSmartRecommendationWithProductIDs` | `productIDs: string[], recommendationID: number, locale: string, currency: string, callback: (data) => void` | `void` |
116
+ | `clickSmartRecommendationProduct` | `recommendationID: number, product: RNInsiderProduct` | `void` |
117
+
118
+ ### Content Optimizer
119
+
120
+ | Method | Parameters | Description |
121
+ |--------|-----------|-------------|
122
+ | `getContentStringWithName` | `variableName, defaultValue: string, dataType: number, callback` | Get string content |
123
+ | `getContentBoolWithName` | `variableName, defaultValue: boolean, dataType: number, callback` | Get boolean content |
124
+ | `getContentIntWithName` | `variableName, defaultValue: number, dataType: number, callback` | Get integer content |
125
+ | `getContentStringWithoutCache` | Same as above | String without cache |
126
+ | `getContentBoolWithoutCache` | Same as above | Boolean without cache |
127
+ | `getContentIntWithoutCache` | Same as above | Integer without cache |
128
+
129
+ ### Message Center
130
+
131
+ | Method | Parameters | Notes |
132
+ |--------|-----------|-------|
133
+ | `getMessageCenterData` | `limit: number, startDate: Date, endDate: Date, callback` | Dates passed as epoch-millis strings. iOS wraps response in `{data: [...]}`, JS unwraps |
134
+ | `getMessageCenterDataWithIdentifiers` | `limit: number, startDate: Date, endDate: Date, identifiers: RNInsiderIdentifier, callback` | NEW. Identifier-scoped variant. Passes `identifiers.identifiers` flat dict to native; callback receives raw response |
135
+
136
+ ### Push Notifications
137
+
138
+ | Method | Parameters | Platform | Description |
139
+ |--------|-----------|----------|-------------|
140
+ | `registerWithQuietPermission` | `enabled: boolean` | iOS only | Enable quiet push permission |
141
+ | `setForegroundPushCallback` | `callback: (notification) => void` | Both | Register foreground push listener (separate from init callback) |
142
+ | `setActiveForegroundPushView` | — | Both | Enable active foreground push view |
143
+ | `setHybridPushToken` | `token: string` | Android only | Set FCM/HMS token |
144
+ | `handleNotification` | `notification: object` | Both | Manual notification handling |
145
+ | `handleUniversalLink` | `url: string` | Android only | Handle deep link URLs |
146
+
147
+ ### In-App Messages
148
+
149
+ | Method | Description |
150
+ |--------|-------------|
151
+ | `removeInapp()` | Remove current in-app message |
152
+ | `disableInAppMessages()` | Disable in-app messages |
153
+ | `enableInAppMessages()` | Enable in-app messages |
154
+
155
+ ### iOS Templates
156
+
157
+ | Method | Platform | Description |
158
+ |--------|----------|-------------|
159
+ | `disableTemplatesForIOS()` | iOS only | Disable in-app message templates |
160
+ | `reenableTemplatesForIOS()` | iOS only | Re-enable templates |
161
+
162
+ ### Geofencing
163
+
164
+ | Method | Parameters | Description |
165
+ |--------|-----------|-------------|
166
+ | `startTrackingGeofence` | — | Start geofence monitoring |
167
+ | `setAllowsBackgroundLocationUpdates` | `enabled: boolean` | iOS: enable background location. Android: no-op |
168
+
169
+ ### GDPR & Privacy
170
+
171
+ | Method | Parameters | Description |
172
+ |--------|-----------|-------------|
173
+ | `setGDPRConsent` | `consent: boolean` | Set GDPR consent status |
174
+ | `setMobileAppAccess` | `access: boolean` | Set mobile app access |
175
+ | `enableLocationCollection` | `status: boolean` | Enable/disable location |
176
+ | `enableIpCollection` | `status: boolean` | Enable/disable IP collection |
177
+ | `enableCarrierCollection` | `status: boolean` | Enable/disable carrier info |
178
+ | `enableIDFACollection` | `enabled: boolean` | iOS: IDFA collection. Android: no-op |
179
+
180
+ ### Identity
181
+
182
+ | Method | Returns | Description |
183
+ |--------|---------|-------------|
184
+ | `getInsiderID()` | `Promise<string>` | Get current Insider ID |
185
+ | `insiderIDListener` | `callback: (insiderID) => void` | Listen for Insider ID changes |
186
+
187
+ ### Miscellaneous
188
+
189
+ | Method | Description |
190
+ |--------|-------------|
191
+ | `showNativeAppReview()` | Show native app review dialog |
192
+ | `signUpConfirmation(customParameters?)` | Confirm sign-up event |
193
+ | `setInternalBrowserCloseButtonPosition(position)` | Android only. Values: LEFT, RIGHT, NONE |
194
+ | `handleURL(url)` | iOS only. Handle deep link URLs from Insider QR codes |
195
+
196
+ ---
197
+
198
+ ## RNInsiderAppCards (NEW)
199
+
200
+ Accessed via `Insider.appCards`. Frozen singleton. Every async method accepts an optional `completion(error, value)` callback **or** returns a Promise.
201
+
202
+ | Method | Signature | Description |
203
+ |--------|-----------|-------------|
204
+ | `getCampaigns(completion?)` | `() => Promise<InsiderAppCardsCampaignResponse>` | Fetch all app cards. On native error, rejects with `InsiderAppCardsError` (parsed from `{code, message}` or string) |
205
+ | `markAsRead(appCardIds, completion?)` | `(string[]) => Promise<void>` | Mark cards as read. Validates non-empty string-array; rejects with `INVALID_PARAMETER` otherwise |
206
+ | `markAsUnread(appCardIds, completion?)` | `(string[]) => Promise<void>` | Mark cards as unread. Same validation as `markAsRead` |
207
+ | `delete(appCardIds, completion?)` | `(string[]) => Promise<void>` | Delete cards by id. Same validation |
208
+ | `view(appCard)` | `(InsiderAppCard) => void` | Record a view event. Throws `InsiderAppCardsError(INVALID_PARAMETER)` if not an `InsiderAppCard` |
209
+ | `click(appCard)` | `(InsiderAppCard) => void` | Record a click. Same instance check |
210
+ | `clickButton(button)` | `(InsiderAppCardButton) => void` | Record button click. Throws if not an `InsiderAppCardButton` |
211
+
212
+ ### Native Bridge Methods Backing App Cards
213
+
214
+ iOS / Android exports:
215
+ - `getAppCardsCampaigns(callback)`
216
+ - `markAppCardsAsRead(ids, callback)` / `markAppCardsAsUnread(ids, callback)`
217
+ - `deleteAppCards(ids, callback)`
218
+ - `viewAppCard(data)` / `clickAppCard(data)` / `clickAppCardButton(appCardId, data)`
219
+
220
+ ### Domain Models
221
+
222
+ | Class | Public Surface |
223
+ |-------|----------------|
224
+ | `InsiderAppCard` | `id`, `type`, `isRead`, `images: InsiderAppCardImage[]`, `content: InsiderAppCardContent`, `buttons: InsiderAppCardButton[]`, `action: InsiderAppCardAction`, raw `data` (deep-cloned), `markAsRead/Unread/delete/view/click` |
225
+ | `InsiderAppCardButton` | `id`, `appCardId`, `text`, `action`, raw `data`, `click()` |
226
+ | `InsiderAppCardContent` | `title`, `description` |
227
+ | `InsiderAppCardImage` | `url` |
228
+ | `InsiderAppCardAction` (base) | `type`. Static `fromData(data)` factory dispatches on `data.type` |
229
+ | `InsiderAppCardDeeplinkAction` | `url` (preferring url_scheme → internal_browser_url → external_browser_url), `deeplinkType`, `json` (parsed), `keysAndValues` |
230
+ | `InsiderAppCardFeedbackAction` | type marker |
231
+ | `InsiderAppCardOpenSettingsAction` | type marker |
232
+ | `InsiderAppCardsCampaignResponse` | `appCards: InsiderAppCard[]` (mapped from `items`) |
233
+
234
+ ### Error Type
235
+
236
+ ```typescript
237
+ class InsiderAppCardsError extends Error {
238
+ readonly code: InsiderAppCardsErrorCodeType;
239
+ static from(error: { code: string; message: string } | string | unknown): InsiderAppCardsError;
240
+ }
241
+ ```
242
+
243
+ `InsiderAppCardsErrorCode` values: `UNKNOWN`, `SDK_NOT_INITIALIZED`, `INVALID_PARAMETER`, `NETWORK_ERROR`, `SERVER_ERROR`, `PARSE_ERROR`.
244
+
245
+ ---
246
+
247
+ ## RNInsiderUser — Fluent Builder
248
+
249
+ All methods return `this` for chaining. Singleton instance from `getCurrentUser()`.
250
+
251
+ ### Demographics
252
+ `setName(s)`, `setSurname(s)`, `setAge(n)`, `setGender(InsiderGender)`, `setBirthday(Date)`, `setEmail(s)`, `setPhoneNumber(s)`, `setLanguage(s)`, `setLocale(s)`, `setFacebookID(s)`, `setTwitterID(s)`
253
+
254
+ ### Marketing Optins
255
+ `setEmailOptin(b)`, `setSMSOptin(b)`, `setPushOptin(b)`, `setWhatsappOptin(b)`, `setLocationOptin(b)`
256
+
257
+ ### Custom Attributes
258
+ `setCustomAttributeWithString(key, value)`, `setCustomAttributeWithInt(key, value)`, `setCustomAttributeWithDouble(key, value)`, `setCustomAttributeWithBoolean(key, value)`, `setCustomAttributeWithDate(key, date)`, `setCustomAttributeWithArray(key, array)`
259
+ `unsetCustomAttribute(key)`
260
+
261
+ ### Identity
262
+ - `login(identifiers: InsiderIdentifier, callback?)` — Login with identifiers, optional callback receives Insider ID via `loginWithReturningID`
263
+ - `logout()` — Logout current user
264
+ - `logoutResettingInsiderID(identifiers, insiderIDResult)` — NEW. Logs out and resets Insider ID. `identifiers` is an array of `InsiderIdentifier` instances; `insiderIDResult` callback receives the new ID
265
+
266
+ ---
267
+
268
+ ## RNInsiderProduct — Fluent Builder
269
+
270
+ Created via `Insider.createNewProduct(productID, name, taxonomy, imageURL, price, currency)`.
271
+
272
+ ### Required Fields (set in constructor)
273
+ `product_id`, `name`, `taxonomy`, `image_url`, `price`, `currency`
274
+
275
+ ### Optional Setters
276
+ `setColor(s)`, `setSize(s)`, `setBrand(s)`, `setSku(s)`, `setGender(s)`, `setDescription(s)`, `setGroupCode(s)`, `setMultipack(s)`, `setProductType(s)`, `setGtin(s)`, `setVoucherName(s)`, `setPromotionName(s)`, `setProductURL(s)`, `setSalePrice(n)`, `setShippingCost(n)`, `setVoucherDiscount(n)`, `setPromotionDiscount(n)`, `setStock(n)`, `setQuantity(n)`, `setTags(string[])`, `setInStock(boolean)`
277
+
278
+ ### Custom Attributes
279
+ `setCustomAttributeWithString(key, value)`, `setCustomAttributeWithInt(key, value)`, `setCustomAttributeWithDouble(key, value)`, `setCustomAttributeWithBoolean(key, value)`, `setCustomAttributeWithDate(key, date)`, `setCustomAttributeWithArray(key, array)`, `setCustomAttributeWithStringArray(key, array)`, `setCustomAttributeWithNumericArray(key, array)`
280
+
281
+ ### Serialization
282
+ Product is passed to native as **3 separate bridge arguments**: `requiredFields` (object), `optionalFields` (object), `customParameters` (typed array).
283
+
284
+ ---
285
+
286
+ ## RNInsiderEvent — Fluent Builder
287
+
288
+ Created via `Insider.tagEvent(name)`. Call `.build()` to dispatch.
289
+
290
+ ### Parameter Setters
291
+ `addParameterWithString(key, value)`, `addParameterWithInt(key, value)`, `addParameterWithDouble(key, value)`, `addParameterWithBoolean(key, value)`, `addParameterWithDate(key, date)`, `addParameterWithArray(key, array)`, `addParameterWithStringArray(key, array)`, `addParameterWithNumericArray(key, array)`
292
+
293
+ ### Terminal
294
+ `build()` — Sends event name + typed parameters array to native SDK
295
+
296
+ ---
297
+
298
+ ## RNInsiderIdentifier — Builder
299
+
300
+ Created with `new InsiderIdentifier()`.
301
+
302
+ ### Methods
303
+ `addEmail(email)`, `addPhoneNumber(phone)`, `addUserID(userID)`, `addCustomIdentifier(key, value)`
304
+
305
+ ### Serialization
306
+ Passed to native as `identifiers.identifiers` (a flat key-value object).
307
+
308
+ ---
309
+
310
+ ## CustomParameters Type
311
+
312
+ ```typescript
313
+ type CustomParameters = {
314
+ [key: string]: string | number | boolean | Date | number[] | string[];
315
+ };
316
+ ```
317
+
318
+ Processed by `parseObjectWithTypes()` which auto-detects types and converts to typed array `[{key, type, value}]` for native bridge. Dates converted to epoch millis. Unknown types silently filtered.
319
+
320
+ ---
321
+
322
+ ## Promise/Callback Bridging Pattern
323
+
324
+ App Cards methods use the `resolveWithCallback(promise, completion)` helper from `src/Util.js`. If a `completion` function is provided, it's invoked as `completion(error, value)`; otherwise the underlying Promise is returned. This enables `await` syntax and Node-style callbacks from the same method signature.
325
+
326
+ ---
327
+
328
+ ## Native Event Types (Callback)
329
+
330
+ | Index | Constant | Description |
331
+ |-------|----------|-------------|
332
+ | 0 | `NOTIFICATION_OPEN` | Push notification opened |
333
+ | 1 | `INAPP_BUTTON_CLICK` | In-app message button clicked |
334
+ | 2 | `TEMP_STORE_PURCHASE` | Temporary store purchase |
335
+ | 3 | `TEMP_STORE_ADDED_TO_CART` | Temporary store add-to-cart |
336
+ | 4 | `TEMP_STORE_CUSTOM_ACTION` | Temporary store custom action |
337
+ | 5 | `INAPP_SEEN` | In-app message displayed |
338
+ | 6 | `SESSION_STARTED` | New session started |
339
+
340
+ Additional listeners (registered separately):
341
+ - `FOREGROUND_PUSH` — via `setForegroundPushCallback()`
342
+ - `INSIDER_ID_LISTENER` — via `insiderIDListener()`
@@ -0,0 +1,209 @@
1
+ # ARCHITECTURE.md
2
+
3
+ > Generated by Orbit Init | 2026-04-16 | react-native-insider v8.0.0
4
+
5
+ ## Project Summary
6
+
7
+ | Property | Value |
8
+ |----------|-------|
9
+ | Name | react-native-insider |
10
+ | Version | 8.0.0 |
11
+ | Type | React Native Bridge SDK (npm library) |
12
+ | Architecture | Single service (one npm package) |
13
+ | Main Languages | JavaScript, TypeScript (declarations), Objective-C (iOS), Java (Android) |
14
+ | Frameworks | React Native 0.83.0, React 19.2.0 |
15
+ | Native SDKs | InsiderMobile 15.0.0 (iOS), Insider 16.0.1 (Android) |
16
+ | Testing | Jest 29.7.0 (react-native preset) |
17
+ | CI/CD | GitHub Actions (3 workflows) |
18
+ | Publish | npm registry (dual-track: main + -nh variant) |
19
+
20
+ ---
21
+
22
+ ## Architecture Overview
23
+
24
+ ```
25
+ ┌──────────────────────────────────────────────────────────┐
26
+ │ Consumer App │
27
+ │ import Insider from │
28
+ │ 'react-native-insider' │
29
+ └──────────────────────┬───────────────────────────────────┘
30
+
31
+ ┌──────────────────────▼───────────────────────────────────┐
32
+ │ JS Bridge Layer │
33
+ │ │
34
+ │ index.js ─── RNInsider (static facade) │
35
+ │ │ + appCards getter │
36
+ │ │ + AppCardsError / AppCardsErrorCode │
37
+ │ │
38
+ │ src/ ─── InsiderUser (fluent builder, singleton) │
39
+ │ ├── InsiderProduct (fluent builder, per-instance) │
40
+ │ ├── InsiderEvent (fluent builder, per-instance) │
41
+ │ ├── InsiderIdentifier (builder, per-instance) │
42
+ │ ├── InsiderAppCards (frozen singleton, Promise+cb) │
43
+ │ │ └── InsiderAppCard / Button / Action models │
44
+ │ │ └── InsiderAppCardsError (typed Error) │
45
+ │ ├── Util.js (validation, type detect, resolveWithCb)│
46
+ │ └── Enums (Gender, CallbackType, ContentOptimizer, │
47
+ │ CloseButtonPosition, │
48
+ │ InsiderAppCardsErrorCode) │
49
+ │ │
50
+ │ TypeScript: index.d.ts + src/*.d.ts │
51
+ └──────────────────────┬───────────────────────────────────┘
52
+ │ NativeModules Bridge
53
+ ┌────────┴────────┐
54
+ ▼ ▼
55
+ ┌─────────────────┐ ┌─────────────────────┐
56
+ │ iOS Native │ │ Android Native │
57
+ │ │ │ │
58
+ │ RNInsider.m │ │ RNInsiderModule.java│
59
+ │ RNNotification │ │ RNInsiderPackage │
60
+ │ Handler.m │ │ .java │
61
+ │ RNUtils.m │ │ RNUtils.java │
62
+ │ │ │ │
63
+ │ InsiderMobile │ │ insider:16.0.1 │
64
+ │ 15.0.0 │ │ insiderhybrid:1.3.4 │
65
+ │ InsiderGeofence │ │ firebase-messaging │
66
+ │ 1.2.4 │ │ play-services │
67
+ │ InsiderHybrid │ │ huawei-hms │
68
+ │ 1.7.6 │ │ │
69
+ └─────────────────┘ └─────────────────────┘
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Key Architectural Patterns
75
+
76
+ ### 1. Static Facade + Builder Pattern
77
+ - `RNInsider` is an all-static class — no JS-side state, all state lives in native singletons
78
+ - Products, Events, Identifiers are builder instances that accumulate state then serialize for the bridge
79
+ - User is a singleton builder returned by `getCurrentUser()`
80
+ - App Cards is a frozen singleton object (`Object.freeze`) accessed via `Insider.appCards` getter
81
+
82
+ ### 2. Three-Part Product Serialization
83
+ Products cross the bridge as **3 separate arguments** (not a single object):
84
+ - `requiredFields` — fixed dict with product_id, name, taxonomy, image_url, price, currency
85
+ - `optionalFields` — accumulated dict from setter methods
86
+ - `customParameters` — typed array `[{type, key, value}]` preserving JS type distinctions
87
+
88
+ ### 3. Index-Based Callback Mapping
89
+ SDK callbacks use array-position-based type discrimination:
90
+ - `callbackActions` array order matches `InsiderCallbackType` enum values (0-6)
91
+ - `forEach` index naturally produces the numeric type identifier
92
+ - Consumer checks `type === InsiderCallbackType.NOTIFICATION_OPEN` etc.
93
+
94
+ ### 4. Epoch-Millis-as-String Date Handling
95
+ All dates converted to `date.getTime().toString()` before crossing the bridge because React Native's bridge doesn't support 64-bit integers. Native side parses long from string.
96
+
97
+ ### 5. Uniform Parameter Validation
98
+ Every public method follows: `shouldNotProceed()` → `checkParameters()` → `showParameterWarningLog()` → try/catch with `putErrorLog()`. Returns safe defaults on failure (empty builders, early return).
99
+
100
+ ### 6. Platform-Aware Initialization
101
+ - iOS: `initWithLaunchOptions(null, partnerName, appGroup, sdkVersion)`
102
+ - Android: `init(partnerName, sdkVersion)` (no appGroup)
103
+ - Event listeners registered BEFORE native init to prevent race conditions
104
+
105
+ ### 7. Promise + Callback Hybrid (App Cards)
106
+ - App Cards methods use `Util.resolveWithCallback(promise, completion)`:
107
+ - With completion → Node-style `(err, value)` callback
108
+ - Without completion → returns a Promise (await-friendly)
109
+ - Errors are wrapped via `InsiderAppCardsError.from()`, normalizing `{code, message}` objects or plain strings into typed errors
110
+
111
+ ### 8. Polymorphic Action Dispatch (App Cards)
112
+ - `InsiderAppCardAction.fromData(data)` factory dispatches on `data.type` string:
113
+ - `deep_link` → `InsiderAppCardDeeplinkAction`
114
+ - `feedback` → `InsiderAppCardFeedbackAction`
115
+ - `open_settings` → `InsiderAppCardOpenSettingsAction`
116
+ - Domain models use ES private fields (`#`) for true encapsulation
117
+
118
+ ### 9. Polymorphic Method Signatures
119
+ - `itemRemovedFromCart(productID, saleIDOrCustomParameters?, customParameters?)` and `visitCartPage(products, saleIDOrCustomParameters?, customParameters?)` accept either a saleID string or a customParameters object as the second argument; runtime branches on `typeof === 'string'` vs `isPlainObject`
120
+
121
+ ---
122
+
123
+ ## Directory Layout
124
+
125
+ ```
126
+ ./
127
+ ├── index.js # Main entry — RNInsider static class
128
+ ├── index.d.ts # TypeScript module declaration
129
+ ├── package.json # npm config, version source of truth (8.0.0)
130
+ ├── RNInsider.podspec # CocoaPods spec (reads version from package.json)
131
+ ├── babel.config.js # Babel config
132
+ ├── CLAUDE.md # Claude Code guidance
133
+
134
+ ├── src/ # JS source modules
135
+ │ ├── InsiderUser.js/.d.ts
136
+ │ ├── InsiderProduct.js/.d.ts
137
+ │ ├── InsiderEvent.js/.d.ts
138
+ │ ├── InsiderIdentifier.js/.d.ts
139
+ │ ├── InsiderAppCards.js/.d.ts # NEW
140
+ │ ├── InsiderAppCard.js/.d.ts # NEW (domain models)
141
+ │ ├── InsiderAppCardsError.js # NEW (typed error)
142
+ │ ├── InsiderGender.js/.d.ts
143
+ │ ├── InsiderCallbackType.js/.d.ts
144
+ │ ├── ContentOptimizerDataType.js/.d.ts
145
+ │ ├── CloseButtonPosition.js
146
+ │ ├── Util.js # + resolveWithCallback
147
+ │ └── __tests__/ # NEW (per-module tests)
148
+ │ ├── InsiderAppCard.test.js
149
+ │ ├── InsiderAppCards.test.js
150
+ │ ├── InsiderAppCardsError.test.js
151
+ │ └── Util.test.js
152
+
153
+ ├── ios/RNInsider/ # Objective-C native bridge
154
+ │ ├── RNInsider.h/.m # ~80 RCT_EXPORT_METHOD entries
155
+ │ ├── RNNotificationHandler.h/.m
156
+ │ └── RNUtils.h/.m
157
+ ├── ios/InsiderMobile.xcframework # bundled native framework
158
+
159
+ ├── android/ # Java native bridge
160
+ │ ├── build.gradle
161
+ │ └── src/main/java/com/useinsider/react/
162
+ │ ├── RNInsiderModule.java # ~80 @ReactMethod entries
163
+ │ ├── RNInsiderPackage.java
164
+ │ └── RNUtils.java
165
+
166
+ ├── __tests__/ # Top-level Jest tests
167
+ │ ├── Insider.test.js
168
+ │ ├── Util.test.js
169
+ │ ├── getMessageCenterData.test.js
170
+ │ ├── getMessageCenterDataWithIdentifiers.test.js # NEW
171
+ │ └── setInternalBrowserCloseButtonPosition.test.js
172
+ ├── __mocks__/
173
+ │ └── react-native.js # Global NativeModules mock
174
+
175
+ ├── docs/ # NEW — internal feature specs
176
+ │ ├── app-cards/
177
+ │ └── message-center/
178
+
179
+ ├── example/ # Demo React Native app (excluded from npm)
180
+ │ ├── App.tsx
181
+ │ ├── android/
182
+ │ └── ios/
183
+
184
+ ├── .github/workflows/ # CI/CD
185
+ │ ├── check-pull-request.yml
186
+ │ ├── create-release.yml
187
+ │ └── publish-release.yml
188
+
189
+ ├── devops/ # Release/deployment scripts
190
+ └── .orbit-output/ # Orbit architecture docs (this dir)
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Related Documents
196
+
197
+ - [CODE-CATALOG.md](./CODE-CATALOG.md) — Full catalog of all classes, functions, modules, and dependencies
198
+ - [API_DOCS.md](./API_DOCS.md) — Complete bridge API reference with serialization patterns
199
+
200
+ ---
201
+
202
+ ## Version Management
203
+
204
+ - **Source of truth**: `package.json` → `version` field
205
+ - **iOS sync**: `RNInsider.podspec` reads version from `package.json`
206
+ - **SDK version string**: JS prepends `'RN-'` prefix (e.g., `RN-8.0.0`)
207
+ - **Release flow**: `release/X.Y.Z` branch → merge to master → GPG-signed tag → npm publish
208
+ - **Dual-track**: Main release + `-nh` variant (non-Huawei) from `develop-nh` branch
209
+ - **PR title format**: Must contain `MOB-[0-9]+` (enforced by CI)