react-native-nami-sdk 3.3.8 → 3.3.9-dev.202603131926

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,166 @@
1
+ # React Native SDK Architecture
2
+
3
+ The Nami React Native SDK (`react-native-nami-sdk`) is a TypeScript bridge layer that exposes native Android and iOS SDK functionality to React Native applications via TurboModules.
4
+
5
+ ## Build System
6
+
7
+ - **Language:** TypeScript 5 (strict mode), Kotlin (Android bridge), Swift (iOS bridge)
8
+ - **Package Manager:** npm
9
+ - **TypeScript Output:** Declaration files only (`emitDeclarationOnly`)
10
+ - **Native Code Generation:** React Native Codegen (TurboModule specs)
11
+ - **Linting:** ESLint with @react-native config, Prettier
12
+
13
+ ### Native Dependencies
14
+
15
+ | Platform | SDK |
16
+ |----------|-----|
17
+ | Android (Google Play) | `com.namiml:sdk-android` |
18
+ | Android (Amazon) | `com.namiml:sdk-amazon` |
19
+ | iOS/tvOS | `Nami` CocoaPod |
20
+
21
+ ## Directory Structure
22
+
23
+ ```
24
+ sdk/react-native/
25
+ ├── src/ # TypeScript bridge layer
26
+ │ ├── index.ts # Main exports
27
+ │ ├── types.ts # Type definitions
28
+ │ ├── transformers.ts # Data transformation utilities
29
+ │ ├── version.ts # Auto-generated version constant
30
+ │ ├── Nami.ts # Core SDK initialization
31
+ │ ├── NamiPaywallManager.ts # Paywall display/events
32
+ │ ├── NamiCampaignManager.ts # Campaign management
33
+ │ ├── NamiCustomerManager.ts # Customer identity
34
+ │ ├── NamiEntitlementManager.ts # Entitlement access
35
+ │ ├── NamiPurchaseManager.ts # Purchase operations
36
+ │ ├── NamiFlowManager.ts # Flow management
37
+ │ └── NamiOverlayControl.tsx # React component for overlay UI
38
+ ├── specs/ # TurboModule native interface specs
39
+ ├── android/ # Kotlin native bridge modules
40
+ │ └── src/main/java/com/namiml/reactnative/
41
+ ├── ios/ # Swift + Objective-C native bridge
42
+ ├── dist/ # Built .d.ts type declarations
43
+ ├── examples/
44
+ │ ├── Basic/ # Full-featured sample (with Detox e2e)
45
+ │ └── TestNamiTV/ # tvOS sample
46
+ ├── build-utils/ # Version management scripts
47
+ └── scripts/ # Version generation
48
+ ```
49
+
50
+ ## Bridge Architecture
51
+
52
+ ```
53
+ JavaScript (React Native)
54
+ |
55
+ TurboModule TypeScript Specs (/specs)
56
+ |
57
+ Native Bridge Modules (Kotlin + Swift)
58
+ |
59
+ Native SDKs (com.namiml:sdk-android / Nami CocoaPod)
60
+ |
61
+ Platform APIs (Google Play Billing / StoreKit)
62
+ ```
63
+
64
+ The React Native SDK is a **thin bridge** - it does NOT reimplement networking, IAP handling, paywall rendering, entitlement validation, or purchase tracking. All business logic lives in the native SDKs.
65
+
66
+ ## Manager Classes
67
+
68
+ Each TypeScript manager wraps a corresponding TurboModule:
69
+
70
+ | Manager | Key Methods |
71
+ |---------|-------------|
72
+ | `Nami` | `configure()`, `sdkConfigured()`, `sdkVersion()` |
73
+ | `NamiPaywallManager` | `buySkuComplete()`, handler registration (buy, close, sign-in, restore, deeplink) |
74
+ | `NamiCampaignManager` | `launch()`, `allCampaigns()`, `isCampaignAvailable()`, `isFlow()`, `refresh()`, `getProductGroups()` |
75
+ | `NamiCustomerManager` | `login()`, `logout()`, `journeyState()`, `setCustomerAttribute()`, `setAnonymousMode()`, `deviceId()` |
76
+ | `NamiEntitlementManager` | `active()`, `isEntitlementActive()`, `refresh()`, `clearProvisionalEntitlementGrants()` |
77
+ | `NamiPurchaseManager` | `allPurchases()`, `skuPurchased()`, `restorePurchases()`, `presentCodeRedemptionSheet()` |
78
+ | `NamiFlowManager` | `pause()`, `resume()`, `finish()`, `isFlowOpen()`, `registerStepHandoff()` |
79
+ | `NamiOverlayControl` | `presentOverlay()`, `finishOverlay()`, React `NamiOverlayHost` component |
80
+
81
+ ## Native Bridge Modules
82
+
83
+ ### Android (`android/` - Kotlin)
84
+
85
+ - **NamiBridgePackage.java** - TurboReactPackage registering all modules
86
+ - **NamiBridgeModule.kt** - `RNNami` core configuration
87
+ - **NamiCampaignManagerBridgeModule.kt** - Campaign bridge
88
+ - **NamiPaywallManagerBridgeModule.kt** - Paywall bridge
89
+ - **NamiPurchaseManagerBridge.kt** - Purchase bridge
90
+ - **NamiEntitlementManagerBridge.kt** - Entitlement bridge
91
+ - **NamiCustomerManagerBridge.kt** - Customer bridge
92
+ - **NamiFlowManagerBridge.kt** - Flow bridge
93
+ - **NamiOverlayControlBridge.kt** - Overlay + ReactOverlayActivity
94
+ - **NamiUtil.kt** - Data transformation (WritableMap/WritableArray)
95
+
96
+ Android supports two product flavors: `play` (Google Play) and `amazon` (Amazon Appstore).
97
+
98
+ ### iOS (`ios/` - Swift)
99
+
100
+ Parallel Swift implementations for each bridge module, plus Objective-C `.m` files for Codegen/legacy architecture support.
101
+
102
+ ## Type System (`src/types.ts`)
103
+
104
+ Key types exported to consumers:
105
+
106
+ - **NamiConfiguration** - SDK init params (appPlatformID, logLevel, language)
107
+ - **NamiSKU** - Product details with platform-specific pricing (Apple, Google, Amazon)
108
+ - **NamiPurchase** - Purchase record with timestamps, transaction IDs, source
109
+ - **NamiEntitlement** - Entitlement with active purchases and related SKUs
110
+ - **NamiCampaign** - Campaign metadata (name, rule type, form factors, segment)
111
+ - **CustomerJourneyState** - Subscription lifecycle flags
112
+ - **NamiPaywallEvent** - Comprehensive event (30+ actions)
113
+ - **NamiPaywallAction** - Enum: BUY_SKU, SELECT_SKU, RESTORE_PURCHASES, VIDEO_STARTED, etc.
114
+
115
+ ## Testing
116
+
117
+ ### Unit Tests
118
+
119
+ TypeScript unit tests using Jest + ts-jest, covering the pure data transformation layer in `src/transformers.ts`:
120
+
121
+ - **`parsePurchaseDates`** — converts native timestamp numbers to JS Date objects
122
+ - **`coerceSkuType`** — validates raw strings against `NamiSKUType` union, falls back to `'unknown'`
123
+ - **`mapToNamiPaywallAction`** — validates raw strings against the `NamiPaywallAction` enum (28 values), falls back to `UNKNOWN`
124
+ - **`parseEntitlements`** — transforms raw entitlement arrays, parsing nested purchase dates and defaulting missing SKU arrays
125
+
126
+ ```bash
127
+ # From monorepo root
128
+ make test-react-native
129
+
130
+ # Or directly
131
+ cd sdk/react-native && npm test
132
+ ```
133
+
134
+ Test files live in `src/__tests__/` and are excluded from TypeScript compilation output via `tsconfig.json`.
135
+
136
+ ### E2E Tests
137
+
138
+ - Detox framework in `examples/Basic/e2e/` (iOS simulator + Android emulator)
139
+ - CI via GitHub Actions with artifact upload on failure
140
+
141
+ #### Local Native SDK Resolution
142
+
143
+ In the monorepo, native SDKs may not yet be published when e2e tests run against a PR. The workflow resolves this by building native SDKs locally before the Detox build:
144
+
145
+ **iOS** — The `NAMI_SDK_LOCAL_PATH` environment variable points to `sdk/apple/` in the monorepo. When set, each example app's Podfile overrides the published `Nami` CocoaPod with a local `:path` reference. CI builds the XCFramework first (`make build-apple`), then sets the env var so `pod install` picks up the local artifact.
146
+
147
+ **Android** — CI publishes the SDK to `mavenLocal` via `./gradlew sdk:publishPublicGooglePublicationToMavenLocal sdk:publishPublicAmazonPublicationToMavenLocal`. The example app's `build.gradle` already includes `mavenLocal()` in its repository list, so Gradle resolves the local artifact without any code changes.
148
+
149
+ Both approaches are temporary until dev release publishing is in place, at which point the pre-build steps and env var can be removed.
150
+
151
+ ### Code Quality
152
+
153
+ - ESLint + TypeScript compilation checks in CI
154
+
155
+ ## Peer Dependencies
156
+
157
+ - React >= 18
158
+ - React Native >= 0.73
159
+
160
+ ## Architectural Patterns
161
+
162
+ - **Bridge/Adapter** - TypeScript wrappers over native TurboModules
163
+ - **Event Emitter** - NativeEventEmitter for real-time native-to-JS events
164
+ - **Thin Client** - All business logic delegated to native SDKs
165
+ - **Codegen** - TurboModule specs for typed native module generation (New Architecture)
166
+ - **Transformer** - Data conversion between native types and JS objects
package/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # Changelog - React Native SDK
@@ -15,7 +15,7 @@ apply plugin: 'com.android.library'
15
15
  // apply plugin: 'maven'
16
16
  apply plugin: "kotlin-android"
17
17
  buildscript {
18
- ext.kotlin_version = '1.9.20'
18
+ ext.kotlin_version = '2.1.0'
19
19
  // The Android Gradle plugin is only required when opening the android folder stand-alone.
20
20
  // This avoids unnecessary downloads and potential conflicts when the library is included as a
21
21
  // module dependency in an application project.
@@ -85,8 +85,8 @@ dependencies {
85
85
  implementation fileTree(dir: 'libs', include: ['*.jar'])
86
86
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
87
87
 
88
- playImplementation "com.namiml:sdk-android:3.3.8"
89
- amazonImplementation "com.namiml:sdk-amazon:3.3.8"
88
+ playImplementation "com.namiml:sdk-android:3.3.9-dev.202603131926"
89
+ amazonImplementation "com.namiml:sdk-amazon:3.3.9-dev.202603131926"
90
90
 
91
91
  implementation "com.facebook.react:react-native:+" // From node_modules
92
92
  coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.4"
@@ -140,9 +140,9 @@ class NamiCampaignManagerBridgeModule internal constructor(
140
140
  }
141
141
 
142
142
  if (context.hasKey("productGroups")) {
143
- paywallLaunchContext = PaywallLaunchContext(productGroups.toList(), customAttributes, customObject)
143
+ paywallLaunchContext = PaywallLaunchContext(productGroups = productGroups.toList(), customAttributes = customAttributes, customObject = customObject)
144
144
  } else {
145
- paywallLaunchContext = PaywallLaunchContext(null, customAttributes, customObject)
145
+ paywallLaunchContext = PaywallLaunchContext(productGroups = null, customAttributes = customAttributes, customObject = customObject)
146
146
  }
147
147
  }
148
148
 
@@ -1,3 +1,6 @@
1
- import type { NamiPurchase, NamiSKUType } from './types';
1
+ import type { NamiEntitlement, NamiPurchase, NamiSKUType } from './types';
2
+ import { NamiPaywallAction } from './types';
2
3
  export declare function parsePurchaseDates(purchase: any): NamiPurchase;
3
4
  export declare function coerceSkuType(raw: string): NamiSKUType;
5
+ export declare function mapToNamiPaywallAction(action: string): NamiPaywallAction;
6
+ export declare function parseEntitlements(entitlements: any[]): NamiEntitlement[];
@@ -2,4 +2,4 @@
2
2
  * Auto-generated file. Do not edit manually.
3
3
  * React Native Nami SDK version.
4
4
  */
5
- export declare const NAMI_REACT_NATIVE_VERSION = "3.3.8";
5
+ export declare const NAMI_REACT_NATIVE_VERSION = "3.3.9-dev.202603131926";
package/jest.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { Config } from 'jest';
2
+
3
+ const config: Config = {
4
+ preset: 'ts-jest',
5
+ testEnvironment: 'node',
6
+ roots: ['<rootDir>/src'],
7
+ testMatch: ['**/__tests__/**/*.test.ts'],
8
+ };
9
+
10
+ export default config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nami-sdk",
3
- "version": "3.3.8",
3
+ "version": "3.3.9-dev.202603131926",
4
4
  "description": "React Native SDK for Nami - No-code paywall and onboarding flows with A/B testing.",
5
5
  "main": "index.ts",
6
6
  "types": "dist/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "build": "tsc",
24
24
  "generate:version": "ts-node scripts/generate-version.ts",
25
25
  "prepare": "npm run generate:version && npm run build",
26
- "test": "echo \"Error: no test specified\" && exit 1",
26
+ "test": "jest",
27
27
  "android": "react-native run-android",
28
28
  "android-clean": "cd android && rm -rf .gradle && rm -rf .idea && rm -rf android.iml && rm -rf local.properties",
29
29
  "ios": "react-native run-ios",
@@ -73,19 +73,14 @@
73
73
  "eslint": "^8.57.1",
74
74
  "eslint-plugin-prettier": "^5.5.0",
75
75
  "eslint-plugin-react-hooks": "^5.2.0",
76
+ "jest": "^30.2.0",
76
77
  "prettier": "^3.6.0",
77
78
  "react": "^18.2.0",
78
79
  "react-native": "^0.73.0",
79
80
  "react-native-codegen": "^0.0.12",
81
+ "ts-jest": "^29.4.6",
80
82
  "ts-node": "^10.9.2",
81
83
  "typescript": "^5.0.2"
82
84
  },
83
- "repository": {
84
- "type": "git",
85
- "url": "git+https://github.com/namiml/react-native-nami-sdk.git"
86
- },
87
- "homepage": "https://www.namiml.com",
88
- "bugs": {
89
- "url": "https://github.com/namiml/react-native-nami-sdk/issues"
90
- }
85
+ "homepage": "https://www.nami.ml"
91
86
  }
@@ -21,7 +21,7 @@ Pod::Spec.new do |s|
21
21
  s.requires_arc = true
22
22
  s.swift_version = '5.0' # or your supported version
23
23
 
24
- s.dependency 'Nami', '3.3.8'
24
+ s.dependency 'Nami', '3.3.9'
25
25
  s.dependency 'React'
26
26
 
27
27
  pod_target_xcconfig = {
@@ -10,7 +10,7 @@ import type {
10
10
  NamiCampaign,
11
11
  NamiError,
12
12
  } from './types';
13
- import { NamiPaywallAction } from './types';
13
+ import { mapToNamiPaywallAction } from './transformers';
14
14
 
15
15
  const RNNamiCampaignManager: Spec =
16
16
  TurboModuleRegistry.getEnforcing<Spec>('RNNamiCampaignManager') ??
@@ -21,16 +21,6 @@ export enum NamiCampaignManagerEvents {
21
21
  NamiPaywallEvent = 'NamiPaywallEvent',
22
22
  }
23
23
 
24
- const validPaywallActions = new Set(
25
- Object.values(NamiPaywallAction) as NamiPaywallAction[],
26
- );
27
-
28
- function mapToNamiPaywallAction(action: string): NamiPaywallAction {
29
- return validPaywallActions.has(action as NamiPaywallAction)
30
- ? (action as NamiPaywallAction)
31
- : NamiPaywallAction.UNKNOWN;
32
- }
33
-
34
24
  const emitter = new NativeEventEmitter(NativeModules.RNNamiCampaignManager);
35
25
 
36
26
  export const NamiCampaignManager = {
@@ -6,7 +6,7 @@ import {
6
6
  } from 'react-native';
7
7
  import type { Spec } from '../specs/NativeNamiEntitlementManager';
8
8
  import type { NamiEntitlement } from './types';
9
- import { parsePurchaseDates } from './transformers';
9
+ import { parseEntitlements } from './transformers';
10
10
 
11
11
  const RNNamiEntitlementManager: Spec =
12
12
  TurboModuleRegistry.getEnforcing?.<Spec>('RNNamiEntitlementManager') ??
@@ -18,15 +18,6 @@ export enum NamiEntitlementManagerEvents {
18
18
  EntitlementsChanged = 'EntitlementsChanged',
19
19
  }
20
20
 
21
- function parseEntitlements(entitlements: any[]): NamiEntitlement[] {
22
- return entitlements.map((ent) => ({
23
- ...ent,
24
- activePurchases: ent.activePurchases.map(parsePurchaseDates),
25
- relatedSkus: ent.relatedSkus ?? [],
26
- purchasedSkus: ent.purchasedSkus ?? [],
27
- }));
28
- }
29
-
30
21
  export const NamiEntitlementManager = {
31
22
  emitter,
32
23
 
@@ -0,0 +1,177 @@
1
+ import {
2
+ parsePurchaseDates,
3
+ coerceSkuType,
4
+ mapToNamiPaywallAction,
5
+ parseEntitlements,
6
+ } from '../transformers';
7
+ import { NamiPaywallAction } from '../types';
8
+
9
+ describe('parsePurchaseDates', () => {
10
+ it('converts timestamp numbers to Date objects', () => {
11
+ const raw = {
12
+ skuId: 'com.example.monthly',
13
+ purchaseInitiatedTimestamp: 1700000000000,
14
+ expires: 1700086400000,
15
+ };
16
+ const result = parsePurchaseDates(raw);
17
+
18
+ expect(result.purchaseInitiatedTimestamp).toBeInstanceOf(Date);
19
+ expect(result.purchaseInitiatedTimestamp.getTime()).toBe(1700000000000);
20
+ expect(result.expires).toBeInstanceOf(Date);
21
+ expect(result.expires!.getTime()).toBe(1700086400000);
22
+ });
23
+
24
+ it('sets expires to undefined when absent', () => {
25
+ const raw = {
26
+ skuId: 'com.example.lifetime',
27
+ purchaseInitiatedTimestamp: 1700000000000,
28
+ };
29
+ const result = parsePurchaseDates(raw);
30
+
31
+ expect(result.expires).toBeUndefined();
32
+ });
33
+
34
+ it('sets expires to undefined when explicitly null', () => {
35
+ const raw = {
36
+ skuId: 'com.example.lifetime',
37
+ purchaseInitiatedTimestamp: 1700000000000,
38
+ expires: null,
39
+ };
40
+ const result = parsePurchaseDates(raw);
41
+
42
+ expect(result.expires).toBeUndefined();
43
+ });
44
+
45
+ it('preserves all other fields via spread', () => {
46
+ const raw = {
47
+ skuId: 'com.example.monthly',
48
+ transactionIdentifier: 'txn_123',
49
+ purchaseSource: 'CAMPAIGN' as const,
50
+ purchaseInitiatedTimestamp: 1700000000000,
51
+ sku: { id: 'sku_1', skuId: 'com.example.monthly', type: 'subscription' },
52
+ };
53
+ const result = parsePurchaseDates(raw);
54
+
55
+ expect(result.skuId).toBe('com.example.monthly');
56
+ expect(result.transactionIdentifier).toBe('txn_123');
57
+ expect(result.purchaseSource).toBe('CAMPAIGN');
58
+ expect(result.sku).toEqual(raw.sku);
59
+ });
60
+ });
61
+
62
+ describe('coerceSkuType', () => {
63
+ it.each(['unknown', 'one_time_purchase', 'subscription'] as const)(
64
+ 'accepts valid type "%s"',
65
+ (type) => {
66
+ expect(coerceSkuType(type)).toBe(type);
67
+ },
68
+ );
69
+
70
+ it('returns "unknown" for invalid types', () => {
71
+ expect(coerceSkuType('invalid')).toBe('unknown');
72
+ expect(coerceSkuType('')).toBe('unknown');
73
+ expect(coerceSkuType('SUBSCRIPTION')).toBe('unknown');
74
+ });
75
+ });
76
+
77
+ describe('mapToNamiPaywallAction', () => {
78
+ it.each(Object.values(NamiPaywallAction))(
79
+ 'accepts valid action "%s"',
80
+ (action) => {
81
+ expect(mapToNamiPaywallAction(action)).toBe(action);
82
+ },
83
+ );
84
+
85
+ it('returns UNKNOWN for unrecognized actions', () => {
86
+ expect(mapToNamiPaywallAction('NOT_A_REAL_ACTION')).toBe(
87
+ NamiPaywallAction.UNKNOWN,
88
+ );
89
+ expect(mapToNamiPaywallAction('')).toBe(NamiPaywallAction.UNKNOWN);
90
+ expect(mapToNamiPaywallAction('buy_sku')).toBe(NamiPaywallAction.UNKNOWN);
91
+ });
92
+ });
93
+
94
+ describe('parseEntitlements', () => {
95
+ const rawPurchase = {
96
+ skuId: 'com.example.monthly',
97
+ purchaseInitiatedTimestamp: 1700000000000,
98
+ expires: 1700086400000,
99
+ };
100
+
101
+ it('converts activePurchases dates', () => {
102
+ const raw = [
103
+ {
104
+ name: 'Premium',
105
+ desc: 'Premium access',
106
+ referenceId: 'premium',
107
+ activePurchases: [rawPurchase],
108
+ purchasedSkus: [{ id: 'sku_1', skuId: 'com.example.monthly', type: 'subscription' }],
109
+ relatedSkus: [],
110
+ },
111
+ ];
112
+ const result = parseEntitlements(raw);
113
+
114
+ expect(result).toHaveLength(1);
115
+ expect(result[0].activePurchases[0].purchaseInitiatedTimestamp).toBeInstanceOf(Date);
116
+ expect(result[0].activePurchases[0].expires).toBeInstanceOf(Date);
117
+ });
118
+
119
+ it('defaults relatedSkus and purchasedSkus to empty arrays when missing', () => {
120
+ const raw = [
121
+ {
122
+ name: 'Basic',
123
+ desc: 'Basic access',
124
+ referenceId: 'basic',
125
+ activePurchases: [],
126
+ },
127
+ ];
128
+ const result = parseEntitlements(raw);
129
+
130
+ expect(result[0].relatedSkus).toEqual([]);
131
+ expect(result[0].purchasedSkus).toEqual([]);
132
+ });
133
+
134
+ it('preserves existing relatedSkus and purchasedSkus', () => {
135
+ const sku = { id: 'sku_1', skuId: 'com.example.monthly', type: 'subscription' };
136
+ const raw = [
137
+ {
138
+ name: 'Premium',
139
+ desc: 'Premium access',
140
+ referenceId: 'premium',
141
+ activePurchases: [],
142
+ purchasedSkus: [sku],
143
+ relatedSkus: [sku],
144
+ },
145
+ ];
146
+ const result = parseEntitlements(raw);
147
+
148
+ expect(result[0].purchasedSkus).toEqual([sku]);
149
+ expect(result[0].relatedSkus).toEqual([sku]);
150
+ });
151
+
152
+ it('handles empty array', () => {
153
+ expect(parseEntitlements([])).toEqual([]);
154
+ });
155
+
156
+ it('handles multiple entitlements', () => {
157
+ const raw = [
158
+ {
159
+ name: 'A',
160
+ desc: '',
161
+ referenceId: 'a',
162
+ activePurchases: [rawPurchase],
163
+ },
164
+ {
165
+ name: 'B',
166
+ desc: '',
167
+ referenceId: 'b',
168
+ activePurchases: [rawPurchase, rawPurchase],
169
+ },
170
+ ];
171
+ const result = parseEntitlements(raw);
172
+
173
+ expect(result).toHaveLength(2);
174
+ expect(result[0].activePurchases).toHaveLength(1);
175
+ expect(result[1].activePurchases).toHaveLength(2);
176
+ });
177
+ });
@@ -1,4 +1,5 @@
1
- import type { NamiPurchase, NamiSKUType } from './types';
1
+ import type { NamiEntitlement, NamiPurchase, NamiSKUType } from './types';
2
+ import { NamiPaywallAction } from './types';
2
3
 
3
4
  export function parsePurchaseDates(purchase: any): NamiPurchase {
4
5
  return {
@@ -19,3 +20,22 @@ export function coerceSkuType(raw: string): NamiSKUType {
19
20
  ? (raw as NamiSKUType)
20
21
  : 'unknown';
21
22
  }
23
+
24
+ const validPaywallActions = new Set(
25
+ Object.values(NamiPaywallAction) as NamiPaywallAction[],
26
+ );
27
+
28
+ export function mapToNamiPaywallAction(action: string): NamiPaywallAction {
29
+ return validPaywallActions.has(action as NamiPaywallAction)
30
+ ? (action as NamiPaywallAction)
31
+ : NamiPaywallAction.UNKNOWN;
32
+ }
33
+
34
+ export function parseEntitlements(entitlements: any[]): NamiEntitlement[] {
35
+ return entitlements.map((ent) => ({
36
+ ...ent,
37
+ activePurchases: ent.activePurchases.map(parsePurchaseDates),
38
+ relatedSkus: ent.relatedSkus ?? [],
39
+ purchasedSkus: ent.purchasedSkus ?? [],
40
+ }));
41
+ }
package/src/version.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Auto-generated file. Do not edit manually.
3
3
  * React Native Nami SDK version.
4
4
  */
5
- export const NAMI_REACT_NATIVE_VERSION = '3.3.8';
5
+ export const NAMI_REACT_NATIVE_VERSION = '3.3.9-dev.202603131926';
package/tsconfig.json CHANGED
@@ -67,6 +67,8 @@
67
67
  "./build-utils",
68
68
  "./specs/*",
69
69
  "**/*.spec.*",
70
+ "**/*.test.*",
71
+ "**/__tests__/**",
70
72
  "**/*.d.ts"
71
73
  ]
72
74
  }