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.
- package/.orbit-output/config/MASTER.md +129 -0
- package/.orbit-output/config/architecture/API_DOCS.md +342 -0
- package/.orbit-output/config/architecture/ARCHITECTURE.md +209 -0
- package/.orbit-output/config/architecture/CODE-CATALOG.md +172 -0
- package/.orbit-output/config/architecture/project.yaml +138 -0
- package/.orbit-output/config/automation/AUTOMATION-SETUP.md +50 -0
- package/.orbit-output/config/enforcement/ENFORCEMENT-RULES.md +41 -0
- package/.orbit-output/config/enforcement/validate.sh +49 -0
- package/.orbit-output/skills/custom/academy-doc-generator.md +598 -0
- package/LICENSE +4 -2
- package/README.md +5 -5
- package/RNInsider.podspec +2 -2
- package/android/build.gradle +1 -1
- package/docs/app-cards/app-cards_new.md +569 -0
- package/package.json +3 -2
|
@@ -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)
|