@namiml/expo-sdk 3.4.0-dev.202605060437

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 (60) hide show
  1. package/dist/index.cjs +4000 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.ts +151 -0
  4. package/dist/index.mjs +3966 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/nami-expo-nami-iap.tgz +0 -0
  7. package/package.json +92 -0
  8. package/src/adapters/expo-device.adapter.ts +106 -0
  9. package/src/adapters/expo-purchase.adapter.ts +79 -0
  10. package/src/adapters/expo-storage.adapter.ts +92 -0
  11. package/src/adapters/expo-ui.adapter.ts +57 -0
  12. package/src/adapters/index.ts +33 -0
  13. package/src/amazon-kepler.d.ts +7 -0
  14. package/src/components/NamiView.tsx +1006 -0
  15. package/src/components/PaywallScreen.tsx +245 -0
  16. package/src/components/TemplateRenderer.tsx +243 -0
  17. package/src/components/containers/NamiBackgroundContainer.tsx +103 -0
  18. package/src/components/containers/NamiCarousel.tsx +217 -0
  19. package/src/components/containers/NamiCollapseContainer.tsx +116 -0
  20. package/src/components/containers/NamiContainer.tsx +315 -0
  21. package/src/components/containers/NamiContentContainer.tsx +140 -0
  22. package/src/components/containers/NamiFooter.tsx +35 -0
  23. package/src/components/containers/NamiHeader.tsx +45 -0
  24. package/src/components/containers/NamiProductContainer.tsx +248 -0
  25. package/src/components/containers/NamiRepeatingGrid.tsx +81 -0
  26. package/src/components/containers/NamiResponsiveGrid.tsx +75 -0
  27. package/src/components/containers/NamiStack.tsx +69 -0
  28. package/src/components/elements/NamiButton.tsx +285 -0
  29. package/src/components/elements/NamiCountdownTimer.tsx +123 -0
  30. package/src/components/elements/NamiImage.tsx +177 -0
  31. package/src/components/elements/NamiPlayPauseButton.tsx +93 -0
  32. package/src/components/elements/NamiProgressBar.tsx +90 -0
  33. package/src/components/elements/NamiProgressIndicator.tsx +41 -0
  34. package/src/components/elements/NamiQRCode.tsx +51 -0
  35. package/src/components/elements/NamiRadioButton.tsx +62 -0
  36. package/src/components/elements/NamiSegmentPicker.tsx +67 -0
  37. package/src/components/elements/NamiSegmentPickerItem.tsx +184 -0
  38. package/src/components/elements/NamiSpacer.tsx +23 -0
  39. package/src/components/elements/NamiSymbol.tsx +104 -0
  40. package/src/components/elements/NamiText.tsx +311 -0
  41. package/src/components/elements/NamiToggleButton.tsx +102 -0
  42. package/src/components/elements/NamiToggleSwitch.tsx +64 -0
  43. package/src/components/elements/NamiVideo.kepler.tsx +638 -0
  44. package/src/components/elements/NamiVideo.tsx +133 -0
  45. package/src/components/elements/NamiVolumeButton.tsx +93 -0
  46. package/src/context/FocusContext.tsx +169 -0
  47. package/src/context/PaywallContext.tsx +343 -0
  48. package/src/global.d.ts +5 -0
  49. package/src/index.ts +62 -0
  50. package/src/nami.ts +24 -0
  51. package/src/react-native-qrcode-svg.d.ts +4 -0
  52. package/src/utils/actionHandler.ts +281 -0
  53. package/src/utils/fonts.ts +359 -0
  54. package/src/utils/iconMap.ts +67 -0
  55. package/src/utils/impression.ts +39 -0
  56. package/src/utils/rendering.ts +197 -0
  57. package/src/utils/smartText.ts +148 -0
  58. package/src/utils/styles.ts +668 -0
  59. package/src/utils/tvFocus.ts +31 -0
  60. package/src/utils/videoControls.ts +49 -0
@@ -0,0 +1,343 @@
1
+ import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { Dimensions } from 'react-native';
3
+ import type {
4
+ IPaywall, ISkuMenu, TPaywallContext, NamiPaywallLaunchContext,
5
+ NamiCampaign, NamiProductDetails, NamiSKU, NamiPaywallEvent,
6
+ TDevice, TimerState, TPaywallMedia, NamiAppSuppliedVideoDetails,
7
+ NamiFlow,
8
+ } from '@namiml/sdk-core';
9
+ import { PaywallState, initialState } from '@namiml/sdk-core';
10
+
11
+ type PaywallStateWithPageHistory = PaywallState & {
12
+ canGoBackPage(): boolean;
13
+ goBackPage(): boolean;
14
+ };
15
+
16
+ function asMethod<TArgs extends unknown[], TResult>(
17
+ candidate: unknown,
18
+ ): ((...args: TArgs) => TResult) | undefined {
19
+ return typeof candidate === 'function'
20
+ ? (candidate as (...args: TArgs) => TResult)
21
+ : undefined;
22
+ }
23
+
24
+ export interface PaywallContextValue {
25
+ state: TPaywallContext;
26
+ productDetails: NamiProductDetails[];
27
+ flow?: NamiFlow;
28
+ filteredSkuMenus: ISkuMenu[];
29
+ setPaywall(paywall: IPaywall, context: NamiPaywallLaunchContext, campaign: NamiCampaign): void;
30
+ notifyFirstFocusReady(paywallId: string, page: string, formFactor?: string): void;
31
+ setCurrentPage(page: string): void;
32
+ canGoBackPage(): boolean;
33
+ goBackPage(): boolean;
34
+ setCurrentGroupData(groupId: string, groupName: string): void;
35
+ setSelectedProducts(products: Record<string, string | null>): void;
36
+ setCurrentFormId(formId: string, value?: string): void;
37
+ setFormState(formId: string, value: boolean | string): void;
38
+ setTimerState(timerId: string, remainingSeconds: number, savedAt: number, hasEmittedCompletion: boolean): void;
39
+ getTimerState(timerId: string): TimerState | undefined;
40
+ setProductDetails(details: NamiProductDetails[]): void;
41
+ setPurchaseInProgress(inProgress: boolean): void;
42
+ setPurchase(inProgress: boolean, product?: NamiSKU): void;
43
+ setCustomerAttribute(attributes: Record<string, string>): void;
44
+ removeCustomerAttribute(key: string): void;
45
+ setIsLoggedIn(isLoggedIn: boolean): void;
46
+ setAppSuppliedVideoDetails(details: NamiAppSuppliedVideoDetails): void;
47
+ resetAppSuppliedVideoDetails(): void;
48
+ setMediaList(media: TPaywallMedia[]): void;
49
+ setSafeAreaTop(top: number): void;
50
+ setFullScreenPresentation(full: boolean): void;
51
+ setFormFactor(factor: TDevice): void;
52
+ setUserInteractionEnabled(enabled: boolean): void;
53
+ setUserTags(tags: Record<string, string>): void;
54
+ setLaunchDetails(value: string, type?: string): void;
55
+ setOpenHeaderIds(id: string, sku?: NamiSKU): void;
56
+ setFlow(flow: NamiFlow): void;
57
+ setCurrentSlideIndex(index: number): void;
58
+ getPaywallActionEventData(): Partial<NamiPaywallEvent>;
59
+ getSelectedPaywall(): IPaywall | undefined;
60
+ getSelectedCampaign(): NamiCampaign | undefined;
61
+ }
62
+
63
+ const PaywallCtx = createContext<PaywallContextValue | null>(null);
64
+ const FirstFocusReadyCtx = createContext<{
65
+ firstFocusReadyKey: string | null;
66
+ notifyFirstFocusReady(paywallId: string, page: string, formFactor?: string): void;
67
+ } | null>(null);
68
+
69
+ export function usePaywallContext(): PaywallContextValue {
70
+ const ctx = useContext(PaywallCtx);
71
+ if (!ctx) throw new Error('usePaywallContext must be used within a PaywallProvider');
72
+ return ctx;
73
+ }
74
+
75
+ export function useFirstFocusReadyContext(): {
76
+ firstFocusReadyKey: string | null;
77
+ notifyFirstFocusReady(paywallId: string, page: string, formFactor?: string): void;
78
+ } {
79
+ const ctx = useContext(FirstFocusReadyCtx);
80
+ if (!ctx) throw new Error('useFirstFocusReadyContext must be used within a PaywallProvider');
81
+ return ctx;
82
+ }
83
+
84
+ interface PaywallProviderProps {
85
+ paywall: IPaywall;
86
+ context: NamiPaywallLaunchContext;
87
+ campaign: NamiCampaign;
88
+ flow?: NamiFlow;
89
+ onFirstFocusReady?: (paywallId: string, page: string, formFactor?: string) => void;
90
+ children: React.ReactNode;
91
+ }
92
+
93
+ export const PaywallProvider: React.FC<PaywallProviderProps> = ({
94
+ paywall, context, campaign, flow, onFirstFocusReady, children,
95
+ }) => {
96
+ const providerRef = useRef<PaywallStateWithPageHistory | null>(null);
97
+ const pageHistoryRef = useRef<string[]>([]);
98
+ const [firstFocusReadyKey, setFirstFocusReadyKey] = useState<string | null>(null);
99
+
100
+ const [state, setState] = useState<TPaywallContext>(() => {
101
+ const provider = PaywallState.create(paywall, context, campaign) as PaywallStateWithPageHistory;
102
+ if (flow) provider.setFlow(flow);
103
+ providerRef.current = provider;
104
+ pageHistoryRef.current = [provider.state.currentPage ?? paywall.template?.initialState?.currentPage ?? 'page1'];
105
+ return cloneStateWithDynamicProps(provider.state, provider);
106
+ });
107
+
108
+ useEffect(() => {
109
+ const provider = providerRef.current;
110
+ if (!provider) return;
111
+ const unsubscribe = provider.subscribe(() => {
112
+ setState(cloneStateWithDynamicProps(provider.state, provider));
113
+ });
114
+ return () => {
115
+ unsubscribe();
116
+ PaywallState.remove(provider);
117
+ };
118
+ }, []);
119
+
120
+ useEffect(() => {
121
+ const provider = providerRef.current;
122
+ if (!provider) return;
123
+ setFirstFocusReadyKey(null);
124
+ provider.setPaywall(paywall, context, campaign);
125
+ if (flow) provider.setFlow(flow);
126
+ pageHistoryRef.current = [provider.state.currentPage ?? paywall.template?.initialState?.currentPage ?? 'page1'];
127
+ setState(cloneStateWithDynamicProps(provider.state, provider));
128
+ }, [paywall, context, campaign, flow]);
129
+
130
+ const methods = useMemo(() => ({
131
+ setPaywall: (nextPaywall: IPaywall, nextContext: NamiPaywallLaunchContext, nextCampaign: NamiCampaign) => {
132
+ providerRef.current?.setPaywall(nextPaywall, nextContext, nextCampaign);
133
+ const provider = providerRef.current;
134
+ pageHistoryRef.current = [provider?.state.currentPage ?? nextPaywall.template?.initialState?.currentPage ?? 'page1'];
135
+ },
136
+ setCurrentPage: (page: string) => {
137
+ const provider = providerRef.current;
138
+ const currentPage = provider?.state.currentPage ?? pageHistoryRef.current[pageHistoryRef.current.length - 1] ?? 'page1';
139
+ if (!page || page === currentPage) {
140
+ return;
141
+ }
142
+ const currentHistory = pageHistoryRef.current.length ? pageHistoryRef.current : [currentPage];
143
+ pageHistoryRef.current = [...currentHistory, page];
144
+ provider?.setCurrentPage(page);
145
+ },
146
+ canGoBackPage: () => {
147
+ if (pageHistoryRef.current.length > 1) {
148
+ return true;
149
+ }
150
+ return asMethod<[], boolean>(providerRef.current?.canGoBackPage)?.call(providerRef.current) ?? false;
151
+ },
152
+ goBackPage: () => {
153
+ const provider = providerRef.current;
154
+ if (pageHistoryRef.current.length > 1) {
155
+ const nextHistory = [...pageHistoryRef.current];
156
+ nextHistory.pop();
157
+ const previousPage = nextHistory[nextHistory.length - 1];
158
+ if (!previousPage) {
159
+ return false;
160
+ }
161
+
162
+ pageHistoryRef.current = nextHistory;
163
+ const providerHandled =
164
+ asMethod<[], boolean>(provider?.goBackPage)?.call(provider) ?? false;
165
+ if (!providerHandled) {
166
+ provider?.setCurrentPage(previousPage);
167
+ }
168
+ return true;
169
+ }
170
+ return asMethod<[], boolean>(provider?.goBackPage)?.call(provider) ?? false;
171
+ },
172
+ setCurrentGroupData: (groupId: string, groupName: string) => {
173
+ providerRef.current?.setCurrentGroupData(groupId, groupName);
174
+ },
175
+ setSelectedProducts: (products: Record<string, string | null>) => {
176
+ providerRef.current?.setSelectedProducts(products);
177
+ },
178
+ setCurrentFormId: (formId: string, value?: string) => {
179
+ providerRef.current?.setCurrentFormId(formId, value);
180
+ },
181
+ setFormState: (formId: string, value: boolean | string) => {
182
+ providerRef.current?.setFormState(formId, value);
183
+ },
184
+ setTimerState: (timerId: string, remainingSeconds: number, savedAt: number, hasEmittedCompletion: boolean) => {
185
+ providerRef.current?.setTimerState(timerId, remainingSeconds, savedAt, hasEmittedCompletion);
186
+ },
187
+ getTimerState: (timerId: string) => {
188
+ return providerRef.current?.getTimerState(timerId);
189
+ },
190
+ setProductDetails: (details: NamiProductDetails[]) => {
191
+ providerRef.current?.setProductDetails(details);
192
+ },
193
+ setPurchaseInProgress: (inProgress: boolean) => {
194
+ providerRef.current?.setPurchaseInProgress(inProgress);
195
+ },
196
+ setPurchase: (inProgress: boolean, product?: NamiSKU) => {
197
+ providerRef.current?.setPurchase(inProgress, product);
198
+ },
199
+ setCustomerAttribute: (attributes: Record<string, string>) => {
200
+ providerRef.current?.setCustomerAttribute(attributes);
201
+ },
202
+ removeCustomerAttribute: (key: string) => {
203
+ providerRef.current?.removeCustomerAttribute(key);
204
+ },
205
+ setIsLoggedIn: (isLoggedIn: boolean) => {
206
+ providerRef.current?.setIsLoggedIn(isLoggedIn);
207
+ },
208
+ setAppSuppliedVideoDetails: (details: NamiAppSuppliedVideoDetails) => {
209
+ providerRef.current?.setAppSuppliedVideoDetails(details);
210
+ },
211
+ resetAppSuppliedVideoDetails: () => {
212
+ providerRef.current?.resetAppSuppliedVideoDetails();
213
+ },
214
+ setMediaList: (media: TPaywallMedia[]) => {
215
+ providerRef.current?.setMediaList(media);
216
+ },
217
+ setSafeAreaTop: (top: number) => {
218
+ providerRef.current?.setSafeAreaTop(top);
219
+ },
220
+ setFullScreenPresentation: (full: boolean) => {
221
+ providerRef.current?.setFullScreenPresentation(full);
222
+ },
223
+ setFormFactor: (factor: TDevice) => {
224
+ providerRef.current?.setFormFactor(factor);
225
+ },
226
+ setUserInteractionEnabled: (enabled: boolean) => {
227
+ providerRef.current?.setUserInteractionEnabled(enabled);
228
+ },
229
+ setUserTags: (tags: Record<string, string>) => {
230
+ providerRef.current?.setUserTags(tags);
231
+ },
232
+ setLaunchDetails: (value: string, type?: string) => {
233
+ providerRef.current?.setLaunchDetails(value, type);
234
+ },
235
+ setOpenHeaderIds: (id: string, sku?: NamiSKU) => {
236
+ providerRef.current?.setOpenHeaderIds(id, sku);
237
+ },
238
+ setFlow: (nextFlow: NamiFlow) => {
239
+ providerRef.current?.setFlow(nextFlow);
240
+ },
241
+ notifyFirstFocusReady: (paywallId: string, page: string, formFactor?: string) => {
242
+ setFirstFocusReadyKey(`${paywallId}:${page}:${formFactor ?? ''}`);
243
+ onFirstFocusReady?.(paywallId, page, formFactor);
244
+ },
245
+ setCurrentSlideIndex: (index: number) => {
246
+ providerRef.current?.setCurrentSlideIndex(index);
247
+ },
248
+ getPaywallActionEventData: () => {
249
+ return providerRef.current?.getPaywallActionEventData() ?? {};
250
+ },
251
+ getSelectedPaywall: () => {
252
+ return providerRef.current?.getSelectedPaywall();
253
+ },
254
+ getSelectedCampaign: () => {
255
+ return providerRef.current?.getSelectedCampaign();
256
+ },
257
+ }), [onFirstFocusReady]);
258
+
259
+ const focusReadyValue = useMemo(
260
+ () => ({
261
+ firstFocusReadyKey,
262
+ notifyFirstFocusReady: methods.notifyFirstFocusReady,
263
+ }),
264
+ [firstFocusReadyKey, methods],
265
+ );
266
+
267
+ const value = useMemo<PaywallContextValue>(() => {
268
+ const provider = providerRef.current;
269
+ return {
270
+ state,
271
+ productDetails: provider?.getProductDetails() ?? provider?.productDetails ?? [],
272
+ flow: provider?.flow,
273
+ filteredSkuMenus: provider?.filteredSkuMenus ?? [],
274
+ ...methods,
275
+ };
276
+ }, [methods, state]);
277
+
278
+ return (
279
+ <PaywallCtx.Provider value={value}>
280
+ <FirstFocusReadyCtx.Provider value={focusReadyValue}>
281
+ {children}
282
+ </FirstFocusReadyCtx.Provider>
283
+ </PaywallCtx.Provider>
284
+ );
285
+ };
286
+
287
+ export default PaywallCtx;
288
+
289
+ function cloneStateWithDynamicProps(
290
+ state: TPaywallContext | undefined,
291
+ provider: PaywallState,
292
+ ): TPaywallContext {
293
+ const nextState = { ...(state ?? initialState) } as TPaywallContext;
294
+ defineDynamicStateProps(nextState, provider);
295
+ return nextState;
296
+ }
297
+
298
+ function defineDynamicStateProps(state: TPaywallContext, provider: PaywallState): void {
299
+ Object.defineProperty(state, 'tvQuality', {
300
+ get: () => getTVQuality(provider),
301
+ enumerable: false,
302
+ configurable: true,
303
+ });
304
+ Object.defineProperty(state, 'viewportWidth', {
305
+ get: getViewportWidth,
306
+ enumerable: false,
307
+ configurable: true,
308
+ });
309
+ Object.defineProperty(state, 'viewportHeight', {
310
+ get: getViewportHeight,
311
+ enumerable: false,
312
+ configurable: true,
313
+ });
314
+ }
315
+
316
+ function getTVQuality(provider: PaywallState): string {
317
+ if (provider.getFormFactor() !== 'television') {
318
+ return '';
319
+ }
320
+
321
+ const { width, height, scale } = Dimensions.get('window');
322
+ const maxDimension = Math.max(width, height) * (scale || 1);
323
+ const minDimension = Math.min(width, height) * (scale || 1);
324
+
325
+ if (!maxDimension || !minDimension) {
326
+ return '720p';
327
+ }
328
+ if (maxDimension >= 3840 || minDimension >= 2160) {
329
+ return '4K';
330
+ }
331
+ if (maxDimension >= 1920 || minDimension >= 1080) {
332
+ return '1080p';
333
+ }
334
+ return '720p';
335
+ }
336
+
337
+ function getViewportWidth(): number {
338
+ return Dimensions.get('window').width;
339
+ }
340
+
341
+ function getViewportHeight(): number {
342
+ return Dimensions.get('window').height;
343
+ }
@@ -0,0 +1,5 @@
1
+ declare global {
2
+ const NAMI_SDK_ENV: string;
3
+ }
4
+
5
+ export {};
package/src/index.ts ADDED
@@ -0,0 +1,62 @@
1
+ const runtimeGlobals = globalThis as Record<string, unknown>;
2
+ const sdkEnvKey = 'NAMI_SDK' + '_ENV';
3
+
4
+ if (typeof runtimeGlobals[sdkEnvKey] !== 'string' || runtimeGlobals[sdkEnvKey] === '') {
5
+ const devFlag = typeof __DEV__ === 'boolean' ? __DEV__ : false;
6
+ runtimeGlobals[sdkEnvKey] = devFlag ? 'development' : 'production';
7
+ }
8
+
9
+ // ─── Side-effect: register adapters ──────────────────────────────────
10
+ import './adapters';
11
+
12
+ // ─── Nami wrapper ────────────────────────────────────────────────────
13
+ export { Nami } from './nami';
14
+
15
+ // ─── Primary component ──────────────────────────────────────────────
16
+ export { NamiView } from './components/NamiView';
17
+
18
+ // ─── Managers (re-exported from core) ────────────────────────────────
19
+ export {
20
+ NamiCampaignManager,
21
+ NamiCustomerManager,
22
+ NamiPaywallManager,
23
+ NamiEntitlementManager,
24
+ NamiFlowManager,
25
+ NamiProfileManager,
26
+ } from '@namiml/sdk-core';
27
+
28
+ // ─── Types (re-exported from core) ──────────────────────────────────
29
+ export type {
30
+ NamiConfiguration,
31
+ NamiCampaign,
32
+ NamiSKU,
33
+ NamiSKUType,
34
+ NamiEntitlement,
35
+ NamiPurchase,
36
+ NamiPaywallEvent,
37
+ NamiPaywallAction,
38
+ NamiPaywallLaunchContext,
39
+ NamiLogLevel,
40
+ NamiLanguageCodes,
41
+ CustomerJourneyState,
42
+ NamiProductDetails,
43
+ NamiFlow,
44
+ IPurchaseAdapter,
45
+ PurchaseContext,
46
+ PurchaseResult,
47
+ } from '@namiml/sdk-core';
48
+
49
+ // ─── Adapters (advanced use) ────────────────────────────────────────
50
+ export {
51
+ ExpoStorageAdapter,
52
+ ExpoDeviceAdapter,
53
+ ExpoUIAdapter,
54
+ ExpoPurchaseAdapter,
55
+ } from './adapters';
56
+
57
+ // ─── Context (advanced use) ─────────────────────────────────────────
58
+ export { PaywallProvider, usePaywallContext } from './context/PaywallContext';
59
+
60
+ // ─── Internal components (for customization) ────────────────────────
61
+ export { PaywallScreen } from './components/PaywallScreen';
62
+ export { TemplateRenderer } from './components/TemplateRenderer';
package/src/nami.ts ADDED
@@ -0,0 +1,24 @@
1
+ import './adapters';
2
+ import { Nami as CoreNami } from '@namiml/sdk-core';
3
+
4
+ type ResettableCoreNami = typeof CoreNami & {
5
+ reset?: () => Promise<void>;
6
+ };
7
+
8
+ export class Nami {
9
+ static get sdkVersion(): string {
10
+ return CoreNami.sdkVersion();
11
+ }
12
+
13
+ static sdkPackageVersion(): string {
14
+ return CoreNami.sdkPackageVersion();
15
+ }
16
+
17
+ static async configure(config: Parameters<typeof CoreNami.configure>[0]) {
18
+ return CoreNami.configure(config);
19
+ }
20
+
21
+ static async reset(): Promise<void> {
22
+ await (CoreNami as ResettableCoreNami).reset?.();
23
+ }
24
+ }
@@ -0,0 +1,4 @@
1
+ declare module 'react-native-qrcode-svg' {
2
+ const QRCode: any;
3
+ export default QRCode;
4
+ }