react-native-insider 8.1.0 → 9.0.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.
Files changed (48) hide show
  1. package/README.md +89 -660
  2. package/RNInsider.podspec +33 -12
  3. package/android/build.gradle +16 -2
  4. package/android/src/main/java/com/useinsider/react/RNInsiderAppFrameViewManager.java +253 -0
  5. package/android/src/main/java/com/useinsider/react/RNInsiderModule.java +238 -174
  6. package/android/src/main/java/com/useinsider/react/RNInsiderPackage.java +5 -1
  7. package/android/src/main/java/com/useinsider/react/RNUtils.java +111 -0
  8. package/babel.config.js +4 -1
  9. package/index.d.ts +196 -161
  10. package/index.js +60 -11
  11. package/ios/RNInsider/RNInsider.m +245 -341
  12. package/ios/RNInsider/RNInsiderAppFrameView.h +33 -0
  13. package/ios/RNInsider/RNInsiderAppFrameView.mm +315 -0
  14. package/ios/RNInsider/RNNotificationHandler.h +4 -1
  15. package/ios/RNInsider/RNNotificationHandler.m +16 -18
  16. package/ios/RNInsider/RNUtils.h +2 -0
  17. package/ios/RNInsider/RNUtils.m +79 -0
  18. package/package.json +43 -12
  19. package/src/EventHandler.js +17 -0
  20. package/src/InsiderAppFrame.d.ts +45 -0
  21. package/src/InsiderAppFrame.js +265 -0
  22. package/src/InsiderAppFrameLogic.d.ts +35 -0
  23. package/src/InsiderAppFrameLogic.js +192 -0
  24. package/src/InsiderAppFrameStatus.d.ts +22 -0
  25. package/src/InsiderAppFrameStatus.js +35 -0
  26. package/src/InsiderAppFramesError.d.ts +25 -0
  27. package/src/InsiderAppFramesError.js +77 -0
  28. package/src/InsiderEvent.js +1 -0
  29. package/src/InsiderEvents.d.ts +93 -0
  30. package/src/InsiderEvents.js +124 -0
  31. package/src/InsiderIdentifier.js +1 -1
  32. package/src/InsiderUser.d.ts +3 -2
  33. package/src/InsiderUser.js +27 -0
  34. package/src/Util.js +2 -1
  35. package/src/specs/InsiderAppFrameNativeComponent.ts +54 -0
  36. package/.orbit-output/config/MASTER.md +0 -129
  37. package/.orbit-output/config/architecture/API_DOCS.md +0 -342
  38. package/.orbit-output/config/architecture/ARCHITECTURE.md +0 -209
  39. package/.orbit-output/config/architecture/CODE-CATALOG.md +0 -172
  40. package/.orbit-output/config/architecture/project.yaml +0 -138
  41. package/.orbit-output/config/automation/AUTOMATION-SETUP.md +0 -50
  42. package/.orbit-output/config/enforcement/ENFORCEMENT-RULES.md +0 -41
  43. package/.orbit-output/config/enforcement/validate.sh +0 -49
  44. package/.orbit-output/skills/custom/academy-doc-generator.md +0 -598
  45. package/android/.settings/org.eclipse.buildship.core.prefs +0 -13
  46. package/android/src/test/java/com/useinsider/react/RNInsiderModuleTest.java +0 -169
  47. package/docs/app-cards/app-cards_new.md +0 -569
  48. package/ios/RNInsiderTests/RNInsiderTriggerPushProcessTests.m +0 -91
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Parameters attached to a recorded Insider event.
3
+ *
4
+ * Sourced from the native SDK — the map is unmodifiable on Android and a defensive copy
5
+ * on iOS. Treat it as READ-ONLY; do not mutate it.
6
+ *
7
+ * Values are normalized for the bridge on both platforms before delivery. In particular a
8
+ * `Date` nested inside the parameters arrives as epoch **MILLISECONDS** (a number),
9
+ * following this SDK's epoch-millis convention for dates crossing the bridge. Note the
10
+ * deliberate contrast with the callback's top-level `timestamp`, which is in SECONDS.
11
+ * Values of a type the bridge cannot represent degrade to their string description rather
12
+ * than being dropped.
13
+ */
14
+ export type InsiderRecordedEventParameters = {
15
+ readonly [key: string]: any;
16
+ };
17
+
18
+ /**
19
+ * Callback invoked for every event recorded by the Insider SDK.
20
+ *
21
+ * ⚠️ `timestamp` IS IN **SECONDS**, NOT MILLISECONDS. ⚠️
22
+ *
23
+ * @param name - The recorded event name.
24
+ * @param parameters - Read-only event parameters.
25
+ * @param timestamp - Whole seconds since the Unix epoch. Convert with
26
+ * `new Date(timestamp * 1000)`.
27
+ */
28
+ export type InsiderEventRecordedCallback = (
29
+ name: string,
30
+ parameters: InsiderRecordedEventParameters,
31
+ timestamp: number
32
+ ) => void;
33
+
34
+ /** Handle returned by `events.addObserver`; call `remove()` to unsubscribe. */
35
+ export interface InsiderEventSubscription {
36
+ /**
37
+ * Unsubscribes this listener. When the last subscriber is removed the underlying
38
+ * native observer is unregistered as well.
39
+ */
40
+ remove(): void;
41
+ }
42
+
43
+ /**
44
+ * Main API for subscribing to the Insider SDK's recorded-event stream.
45
+ *
46
+ * Mirrors the native SDK namespaces — `[[Insider events] addObserver:]` on iOS,
47
+ * `Insider.Instance.events().addObserver(...)` on Android — so the wrapper reads
48
+ * the way the SDK it wraps does.
49
+ */
50
+ interface InsiderEvents {
51
+ /**
52
+ * Subscribes to every event recorded by the Insider SDK.
53
+ *
54
+ * ## ⚠️ `timestamp` is in SECONDS, not milliseconds
55
+ *
56
+ * The `timestamp` argument is passed through from the native SDKs **unchanged**, in
57
+ * **whole seconds** since the Unix epoch (this is deliberate — it keeps parity with the
58
+ * native Android/iOS SDKs). JavaScript's `Date` constructor and `Date.now()` both use
59
+ * **milliseconds**, so you MUST multiply by 1000 before building a `Date`:
60
+ *
61
+ * ```typescript
62
+ * Insider.events.addObserver((name, parameters, timestamp) => {
63
+ * const date = new Date(timestamp * 1000); // ✅ correct — timestamp is in SECONDS
64
+ * // const wrong = new Date(timestamp); // ❌ WRONG — yields a date in 1970
65
+ * });
66
+ * ```
67
+ *
68
+ * ## Threading
69
+ *
70
+ * The native SDK delivers on a dedicated background thread, so the callback may be
71
+ * invoked off the JS main work loop. Keep the callback cheap and non-blocking.
72
+ *
73
+ * ## Parameters are read-only
74
+ *
75
+ * `parameters` is unmodifiable on Android / a defensive copy on iOS. Do not mutate it.
76
+ *
77
+ * ## Unsubscribing
78
+ *
79
+ * Unlike `insiderIDListener`, this method returns a subscription. Call `remove()` on it
80
+ * to unsubscribe — important because this fires on EVERY event. Multiple subscribers are
81
+ * supported; the native observer is registered once and torn down only when the last
82
+ * subscriber is removed.
83
+ *
84
+ * @param callback - Invoked as `(name, parameters, timestamp)` for each recorded event.
85
+ * @returns A subscription handle, or `undefined` if the SDK is unavailable or the
86
+ * callback failed validation.
87
+ */
88
+ addObserver(callback: InsiderEventRecordedCallback): InsiderEventSubscription | undefined;
89
+ }
90
+
91
+ declare const InsiderEvents: InsiderEvents;
92
+
93
+ export default InsiderEvents;
@@ -0,0 +1,124 @@
1
+ import { NativeModules } from 'react-native';
2
+
3
+ import { shouldNotProceed, generateJSONErrorString, checkParameters, showParameterWarningLog } from './Util';
4
+ import eventHandler from './EventHandler';
5
+
6
+ const Insider = NativeModules.RNInsider;
7
+
8
+ const INSIDER_EVENT_RECORDED = 'INSIDER_EVENT_RECORDED';
9
+
10
+ /**
11
+ * JS-side fan-out registry for the native event-recorded stream.
12
+ *
13
+ * NOTE — deliberate deviation from the older `insiderIDListener` API:
14
+ * `insiderIDListener` returns nothing, so a caller can never unsubscribe and every
15
+ * extra call stacks another duplicate native + JS listener. That is tolerable for a
16
+ * rarely-firing ID change, but `events.addObserver` fires on EVERY tagged event,
17
+ * so leaked/duplicated subscriptions are a real correctness and performance problem.
18
+ *
19
+ * Therefore this API registers with the native SDK exactly ONCE (guarded by
20
+ * `eventRecordedNativeSubscription`) and fans out to N JS subscribers itself. Each
21
+ * subscriber gets back a `{ remove() }` handle; when the LAST subscriber is removed we
22
+ * tear the native observer down again. This matters on Android in particular, where the
23
+ * native SDK holds observers STRONGLY and would otherwise keep the listener (and the
24
+ * emitter it captures) alive for the lifetime of the process.
25
+ */
26
+ // Holds one record per SUBSCRIPTION rather than per callback, so that registering the
27
+ // same function twice yields two independent subscriptions (a Set keyed on the function
28
+ // itself would dedupe them, and removing one would silently kill the other).
29
+ const eventRecordedSubscribers = new Set();
30
+ let eventRecordedNativeSubscription = null;
31
+
32
+ function dispatchEventRecorded(payload) {
33
+ if (payload === null || payload === undefined) return;
34
+
35
+ // Copy first: a subscriber may call remove() from inside its own callback, and
36
+ // mutating the Set while iterating it would skip the following subscribers.
37
+ const subscribers = Array.from(eventRecordedSubscribers);
38
+ subscribers.forEach(subscriber => {
39
+ try {
40
+ subscriber.callback(payload.name, payload.parameters || {}, payload.timestamp);
41
+ } catch (error) {
42
+ Insider.putErrorLog(generateJSONErrorString(error));
43
+ }
44
+ });
45
+ }
46
+
47
+ function removeEventRecordedSubscriber(subscriber) {
48
+ try {
49
+ if (!eventRecordedSubscribers.delete(subscriber)) return;
50
+ if (eventRecordedSubscribers.size > 0) return;
51
+
52
+ if (eventRecordedNativeSubscription !== null) {
53
+ eventRecordedNativeSubscription.remove();
54
+ eventRecordedNativeSubscription = null;
55
+ }
56
+
57
+ Insider.unregisterInsiderEventListener();
58
+ } catch (error) {
59
+ Insider.putErrorLog(generateJSONErrorString(error));
60
+ }
61
+ }
62
+
63
+ const InsiderEvents = Object.freeze({
64
+ /**
65
+ * Subscribes to every event recorded by the Insider SDK.
66
+ *
67
+ * IMPORTANT: `timestamp` is delivered in WHOLE SECONDS since the Unix epoch —
68
+ * NOT milliseconds — matching the native SDKs exactly. Use
69
+ * `new Date(timestamp * 1000)` to build a JS Date.
70
+ *
71
+ * Unlike `insiderIDListener`, this returns a subscription; call `remove()` on it to
72
+ * unsubscribe. The native observer is registered once and torn down when the last
73
+ * subscriber leaves.
74
+ *
75
+ * @param {(name: string, parameters: object, timestamp: number) => void} callback
76
+ * @returns {{ remove: () => void } | undefined} subscription, or undefined if setup failed
77
+ */
78
+ addObserver(callback) {
79
+ if (shouldNotProceed()) return;
80
+ if (checkParameters([{ type: 'function', value: callback }])) {
81
+ showParameterWarningLog("events.addObserver", [{ type: 'function', value: callback }]);
82
+ return;
83
+ }
84
+ const subscriber = { callback: callback };
85
+ // Tracks whether THIS call is the one performing native registration. Only a failure
86
+ // during that phase may be rolled back — see the catch below.
87
+ let isRegisteringNatively = false;
88
+
89
+ try {
90
+ eventRecordedSubscribers.add(subscriber);
91
+
92
+ if (eventRecordedNativeSubscription === null) {
93
+ isRegisteringNatively = true;
94
+
95
+ eventRecordedNativeSubscription = eventHandler.addListener(INSIDER_EVENT_RECORDED, dispatchEventRecorded);
96
+
97
+ Insider.registerInsiderEventListener();
98
+ }
99
+
100
+ return {
101
+ remove: () => removeEventRecordedSubscriber(subscriber)
102
+ };
103
+ } catch (error) {
104
+ Insider.putErrorLog(generateJSONErrorString(error));
105
+
106
+ // Roll the failed subscribe back so a later call can retry. Without this the
107
+ // half-built state (non-null `eventRecordedNativeSubscription` + a registered
108
+ // subscriber) would permanently satisfy the guard above, and events would never
109
+ // arrive with nothing left to re-trigger registration.
110
+ eventRecordedSubscribers.delete(subscriber);
111
+
112
+ // ONLY undo the native side when this call is the one that was registering it.
113
+ // If registration already succeeded earlier, a throw here came from somewhere else
114
+ // on a live stream — tearing down and letting a later subscribe re-register would
115
+ // stack a SECOND native observer and double-deliver every event.
116
+ if (isRegisteringNatively && eventRecordedNativeSubscription !== null) {
117
+ eventRecordedNativeSubscription.remove();
118
+ eventRecordedNativeSubscription = null;
119
+ }
120
+ }
121
+ },
122
+ });
123
+
124
+ export default InsiderEvents;
@@ -7,10 +7,10 @@ const Insider = NativeModules.RNInsider;
7
7
  const ADD_EMAIL = 'addEmail';
8
8
  const ADD_PHONE_NUMBER = 'addPhoneNumber';
9
9
  const ADD_USER_ID = 'addUserID';
10
- const ADD_CUSTOM_IDENTIFIER = 'addCustomIdentifier';
11
10
 
12
11
  export default class RNInsiderIdentifier {
13
12
  identifiers = {};
13
+ // Stryker disable next-line BlockStatement: the field initializer above already sets identifiers to {}
14
14
  constructor() {
15
15
  this.identifiers = {};
16
16
  }
@@ -17,10 +17,11 @@ declare class RNInsiderUser {
17
17
  setPushOptin(value: boolean): this;
18
18
  setLocationOptin(value: boolean): this;
19
19
  setWhatsappOptin(value: boolean): this;
20
+ setPromotionalOptin(value: boolean): this;
20
21
 
21
- login(identifiers: RNInsiderIdentifier, insiderIDResult?: (insiderID: string) => void): this;
22
+ login(identifiers: RNInsiderIdentifier, insiderIDResult?: (insiderID: string | null) => void): this;
22
23
  logout(): this;
23
- logoutResettingInsiderID(identifiers?: RNInsiderIdentifier[] | null, insiderIDResult?: (insiderID: string) => void): this;
24
+ logoutResettingInsiderID(identifiers?: RNInsiderIdentifier[] | null, insiderIDResult?: (insiderID: string | null) => void): this;
24
25
 
25
26
  setCustomAttributeWithString(key: string, value: string): this;
26
27
  setCustomAttributeWithInt(key: string, value: number): this;
@@ -229,10 +229,30 @@ export default class RNInsiderUser {
229
229
  return this;
230
230
  }
231
231
 
232
+ setPromotionalOptin(value) {
233
+ if (shouldNotProceed()) return this;
234
+ if (checkParameters([{ type: 'boolean', value }])) {
235
+ showParameterWarningLog("setPromotionalOptin", [{ type: 'boolean', value }]);
236
+ return this;
237
+ }
238
+ try {
239
+ Insider.setPromotionalOptin(value);
240
+ } catch (error) {
241
+ Insider.putErrorLog(generateJSONErrorString(error));
242
+ }
243
+ return this;
244
+ }
245
+
232
246
  login(identifiers, insiderIDResult) {
233
247
  if (shouldNotProceed()) { return this; }
234
248
  try {
235
249
  if (insiderIDResult !== null && insiderIDResult !== undefined) {
250
+ if (checkParameters([{ type: 'function', value: insiderIDResult }])) {
251
+ showParameterWarningLog("login", [{ type: 'function', value: insiderIDResult }]);
252
+
253
+ return this;
254
+ }
255
+
236
256
  Insider.loginWithReturningID(identifiers.identifiers, insiderIDResult);
237
257
 
238
258
  return;
@@ -258,6 +278,13 @@ export default class RNInsiderUser {
258
278
  logoutResettingInsiderID(identifiers, insiderIDResult) {
259
279
  if (shouldNotProceed()) { return this; }
260
280
  try {
281
+ if (insiderIDResult !== null && insiderIDResult !== undefined
282
+ && checkParameters([{ type: 'function', value: insiderIDResult }])) {
283
+ showParameterWarningLog("logoutResettingInsiderID", [{ type: 'function', value: insiderIDResult }]);
284
+
285
+ return this;
286
+ }
287
+
261
288
  const identifiersArray = (identifiers && Array.isArray(identifiers))
262
289
  ? identifiers.map(id => id.identifiers)
263
290
  : null;
package/src/Util.js CHANGED
@@ -13,6 +13,7 @@ export function generateJSONErrorString(error) {
13
13
  export function checkParameters(parameters) {
14
14
  return parameters.some(param =>
15
15
  param.value === null ||
16
+ // Stryker disable next-line ConditionalExpression: typeof undefined never equals a declared type, so the type check below already rejects it
16
17
  param.value === undefined ||
17
18
  param.type !== typeof param.value
18
19
  );
@@ -25,7 +26,7 @@ export function showParameterWarningLog(functionName, parameters = []) {
25
26
  }
26
27
 
27
28
  const invalidParams = parameters
28
- .filter(param => param.value === null || param.value === undefined || param.type !== typeof param.value)
29
+ .filter(param => checkParameters([param]))
29
30
  .map(param => `Expected ${param.type} but got ${typeof param.value} (${param.value})`);
30
31
 
31
32
  console.warn(
@@ -0,0 +1,54 @@
1
+ import type {HostComponent, ViewProps} from 'react-native';
2
+ import type {
3
+ DirectEventHandler,
4
+ Double,
5
+ Int32,
6
+ } from 'react-native/Libraries/Types/CodegenTypes';
7
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
8
+
9
+ export type InsiderAppFrameDismissEvent = Readonly<{
10
+ placementId: string;
11
+ }>;
12
+
13
+ export type InsiderAppFrameStatusEvent = Readonly<{
14
+ placementId: string;
15
+ status: string;
16
+ previousStatus: string;
17
+ }>;
18
+
19
+ export type InsiderAppFrameErrorEvent = Readonly<{
20
+ placementId: string;
21
+ code: string;
22
+ /** Always the canonical text for `code`; the originating error travels in `cause`. */
23
+ message: string;
24
+ /**
25
+ * The originating platform error's message, or an empty string — codegen event payloads
26
+ * are non-nullable.
27
+ */
28
+ cause: string;
29
+ dismissCode: Int32;
30
+ }>;
31
+
32
+ export type InsiderAppFrameHeightEvent = Readonly<{
33
+ placementId: string;
34
+ height: Double;
35
+ }>;
36
+
37
+ export type InsiderAppFrameActionEvent = Readonly<{
38
+ placementId: string;
39
+ data: string;
40
+ }>;
41
+
42
+ export interface NativeProps extends ViewProps {
43
+ placementId?: string;
44
+
45
+ onStatusChange?: DirectEventHandler<InsiderAppFrameStatusEvent>;
46
+ onLoadError?: DirectEventHandler<InsiderAppFrameErrorEvent>;
47
+ onHeightChange?: DirectEventHandler<InsiderAppFrameHeightEvent>;
48
+ onDismissRequest?: DirectEventHandler<InsiderAppFrameDismissEvent>;
49
+ onAction?: DirectEventHandler<InsiderAppFrameActionEvent>;
50
+ }
51
+
52
+ export default codegenNativeComponent<NativeProps>(
53
+ 'InsiderAppFrameView',
54
+ ) as HostComponent<NativeProps>;
@@ -1,129 +0,0 @@
1
- # MASTER.md — Single Source of Truth
2
-
3
- > react-native-insider v8.0.3 | Generated by Orbit Init | 2026-04-16 (synced 2026-06-29)
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.1.0 |
22
- | iOS Geofence | InsiderGeofence | 1.2.4 |
23
- | iOS Hybrid | InsiderHybrid | 1.7.6 |
24
- | Android SDK | com.useinsider:insider | 16.0.7 |
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.3)
120
- - iOS: `RNInsider.podspec` reads from package.json
121
- - Android: `build.gradle` has pinned native SDK versions (insider:16.0.7)
122
- - SDK string: `'RN-' + version` (e.g., RN-8.0.3)
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 (synced 2026-06-12)*