@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.
- package/dist/index.cjs +4000 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +151 -0
- package/dist/index.mjs +3966 -0
- package/dist/index.mjs.map +1 -0
- package/nami-expo-nami-iap.tgz +0 -0
- package/package.json +92 -0
- package/src/adapters/expo-device.adapter.ts +106 -0
- package/src/adapters/expo-purchase.adapter.ts +79 -0
- package/src/adapters/expo-storage.adapter.ts +92 -0
- package/src/adapters/expo-ui.adapter.ts +57 -0
- package/src/adapters/index.ts +33 -0
- package/src/amazon-kepler.d.ts +7 -0
- package/src/components/NamiView.tsx +1006 -0
- package/src/components/PaywallScreen.tsx +245 -0
- package/src/components/TemplateRenderer.tsx +243 -0
- package/src/components/containers/NamiBackgroundContainer.tsx +103 -0
- package/src/components/containers/NamiCarousel.tsx +217 -0
- package/src/components/containers/NamiCollapseContainer.tsx +116 -0
- package/src/components/containers/NamiContainer.tsx +315 -0
- package/src/components/containers/NamiContentContainer.tsx +140 -0
- package/src/components/containers/NamiFooter.tsx +35 -0
- package/src/components/containers/NamiHeader.tsx +45 -0
- package/src/components/containers/NamiProductContainer.tsx +248 -0
- package/src/components/containers/NamiRepeatingGrid.tsx +81 -0
- package/src/components/containers/NamiResponsiveGrid.tsx +75 -0
- package/src/components/containers/NamiStack.tsx +69 -0
- package/src/components/elements/NamiButton.tsx +285 -0
- package/src/components/elements/NamiCountdownTimer.tsx +123 -0
- package/src/components/elements/NamiImage.tsx +177 -0
- package/src/components/elements/NamiPlayPauseButton.tsx +93 -0
- package/src/components/elements/NamiProgressBar.tsx +90 -0
- package/src/components/elements/NamiProgressIndicator.tsx +41 -0
- package/src/components/elements/NamiQRCode.tsx +51 -0
- package/src/components/elements/NamiRadioButton.tsx +62 -0
- package/src/components/elements/NamiSegmentPicker.tsx +67 -0
- package/src/components/elements/NamiSegmentPickerItem.tsx +184 -0
- package/src/components/elements/NamiSpacer.tsx +23 -0
- package/src/components/elements/NamiSymbol.tsx +104 -0
- package/src/components/elements/NamiText.tsx +311 -0
- package/src/components/elements/NamiToggleButton.tsx +102 -0
- package/src/components/elements/NamiToggleSwitch.tsx +64 -0
- package/src/components/elements/NamiVideo.kepler.tsx +638 -0
- package/src/components/elements/NamiVideo.tsx +133 -0
- package/src/components/elements/NamiVolumeButton.tsx +93 -0
- package/src/context/FocusContext.tsx +169 -0
- package/src/context/PaywallContext.tsx +343 -0
- package/src/global.d.ts +5 -0
- package/src/index.ts +62 -0
- package/src/nami.ts +24 -0
- package/src/react-native-qrcode-svg.d.ts +4 -0
- package/src/utils/actionHandler.ts +281 -0
- package/src/utils/fonts.ts +359 -0
- package/src/utils/iconMap.ts +67 -0
- package/src/utils/impression.ts +39 -0
- package/src/utils/rendering.ts +197 -0
- package/src/utils/smartText.ts +148 -0
- package/src/utils/styles.ts +668 -0
- package/src/utils/tvFocus.ts +31 -0
- package/src/utils/videoControls.ts +49 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getUrlParams,
|
|
3
|
+
Impression,
|
|
4
|
+
isAnonymousMode,
|
|
5
|
+
logger,
|
|
6
|
+
NamiAPI,
|
|
7
|
+
storageService,
|
|
8
|
+
} from '@namiml/sdk-core';
|
|
9
|
+
|
|
10
|
+
function buildImpressionPayload(impression: Impression): Impression {
|
|
11
|
+
const payload: Impression = { ...impression };
|
|
12
|
+
const sessionId = storageService.getSessionId();
|
|
13
|
+
if (sessionId) {
|
|
14
|
+
payload.session = sessionId;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const launchId = storageService.getLaunchId();
|
|
18
|
+
if (launchId) {
|
|
19
|
+
payload.launch_id = launchId;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
payload.app_env = NAMI_SDK_ENV;
|
|
23
|
+
payload.url_params = getUrlParams();
|
|
24
|
+
return payload;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const postImpression = async (impression: Impression): Promise<void> => {
|
|
28
|
+
if (isAnonymousMode()) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const payload = buildImpressionPayload(impression);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await NamiAPI.instance.postImpression(payload);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
logger.error('Error posting impression', error);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LIQUID_VARIABLE_REGEX,
|
|
3
|
+
SMART_TEXT_PATTERN,
|
|
4
|
+
type TComponent,
|
|
5
|
+
} from '@namiml/sdk-core';
|
|
6
|
+
import { buildSmartTextReplacements, interpolate, interpolateSmartText } from './smartText';
|
|
7
|
+
|
|
8
|
+
const DEEP_SMART_TEXT_PRESERVE_KEYS = new Set([
|
|
9
|
+
'components',
|
|
10
|
+
'productBaseComponents',
|
|
11
|
+
'productFeaturedComponents',
|
|
12
|
+
'collapseHeader',
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function componentSmartTextSku(component: any): any {
|
|
16
|
+
return component?.smartTextSku ?? component?.sku;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function conditionComponentMatches(
|
|
20
|
+
ctx: any,
|
|
21
|
+
condition: any,
|
|
22
|
+
sku: any = componentSmartTextSku(condition),
|
|
23
|
+
): boolean {
|
|
24
|
+
if (condition.assertions) {
|
|
25
|
+
return condition.assertions.every((test: any) => testObjectMatches(ctx, test, undefined, sku));
|
|
26
|
+
}
|
|
27
|
+
if (condition.orAssertions) {
|
|
28
|
+
return !condition.orAssertions.length || condition.orAssertions.some((test: any) => testObjectMatches(ctx, test, undefined, sku));
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function withOverrides<T extends Record<string, any>>(
|
|
34
|
+
ctx: any,
|
|
35
|
+
{ conditionAttributes, ...component }: T,
|
|
36
|
+
sku: any = componentSmartTextSku(component),
|
|
37
|
+
): T {
|
|
38
|
+
if (!conditionAttributes) return component as T;
|
|
39
|
+
return conditionAttributes.reduce((nextComponent: T, condition: any): T => {
|
|
40
|
+
if (!conditionComponentMatches(ctx, condition, sku)) return nextComponent;
|
|
41
|
+
const resolvedAttributes = Object.fromEntries(
|
|
42
|
+
Object.entries(condition.attributes ?? {}).map(([key, value]) => [
|
|
43
|
+
key,
|
|
44
|
+
valueFromSmartText(ctx, value, sku),
|
|
45
|
+
]),
|
|
46
|
+
);
|
|
47
|
+
return { ...nextComponent, ...resolvedAttributes };
|
|
48
|
+
}, component as T);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function valueFromSmartText(ctx: any, value: any, sku?: any, block: any = null): any {
|
|
52
|
+
const replacements = buildSmartTextReplacements(
|
|
53
|
+
ctx.state,
|
|
54
|
+
ctx.flow,
|
|
55
|
+
sku ?? ctx.state?.sku,
|
|
56
|
+
block,
|
|
57
|
+
);
|
|
58
|
+
if (typeof value === 'string' && value.includes(SMART_TEXT_PATTERN)) {
|
|
59
|
+
return interpolateSmartText(value, replacements);
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function resolveComponentSmartText(component: any, ctx: any): any {
|
|
65
|
+
const smartTextSku = componentSmartTextSku(component);
|
|
66
|
+
const replacements = buildSmartTextReplacements(ctx.state, ctx.flow, smartTextSku);
|
|
67
|
+
const preservedEntries: Array<[string, any]> = [];
|
|
68
|
+
const shallowComponent = Object.entries(component ?? {}).reduce((output, [key, value]) => {
|
|
69
|
+
if (DEEP_SMART_TEXT_PRESERVE_KEYS.has(key)) {
|
|
70
|
+
// Child component trees need to keep their original template strings so they can
|
|
71
|
+
// be resolved later with their own component/SKU context.
|
|
72
|
+
preservedEntries.push([key, value]);
|
|
73
|
+
return output;
|
|
74
|
+
}
|
|
75
|
+
output[key] = valueFromSmartText(ctx, value, smartTextSku);
|
|
76
|
+
return output;
|
|
77
|
+
}, {} as Record<string, any>);
|
|
78
|
+
|
|
79
|
+
const resolvedComponent = interpolate(shallowComponent, replacements);
|
|
80
|
+
preservedEntries.forEach(([key, value]) => {
|
|
81
|
+
resolvedComponent[key] = value;
|
|
82
|
+
});
|
|
83
|
+
return resolvedComponent;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function expandRenderableComponents(
|
|
87
|
+
ctx: any,
|
|
88
|
+
components: TComponent[] | undefined,
|
|
89
|
+
): TComponent[] {
|
|
90
|
+
return (components ?? []).flatMap((component) =>
|
|
91
|
+
expandRenderableComponent(ctx, component as TComponent),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function expandRenderableComponent(ctx: any, component: TComponent | undefined): TComponent[] {
|
|
96
|
+
if (!component || (component as any).hidden) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const smartTextSku = componentSmartTextSku(component);
|
|
101
|
+
|
|
102
|
+
if ((component as any).component === 'condition') {
|
|
103
|
+
if (!conditionComponentMatches(ctx, component, smartTextSku)) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return expandRenderableComponents(ctx, (component as any).components);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let resolvedComponent: any = component;
|
|
111
|
+
if ((component as any).conditionAttributes?.length) {
|
|
112
|
+
resolvedComponent = withOverrides(ctx, resolvedComponent, smartTextSku);
|
|
113
|
+
}
|
|
114
|
+
resolvedComponent = resolveComponentSmartText(resolvedComponent, ctx);
|
|
115
|
+
|
|
116
|
+
return resolvedComponent?.hidden ? [] : [resolvedComponent as TComponent];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getRepeatingListBlocks(ctx: any, component: any): TComponent[][] {
|
|
120
|
+
if (!component.loopSource) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const source = typeof component.loopSource === 'string'
|
|
125
|
+
? JSON.parse(component.loopSource)
|
|
126
|
+
: component.loopSource;
|
|
127
|
+
|
|
128
|
+
return (source ?? [])
|
|
129
|
+
.map((block: any) => {
|
|
130
|
+
const matchesConditions =
|
|
131
|
+
!component.loopSourceConditions?.length ||
|
|
132
|
+
component.loopSourceConditions.every((value: any) =>
|
|
133
|
+
testObjectMatches(ctx, value, block),
|
|
134
|
+
);
|
|
135
|
+
if (!matchesConditions) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
return interpolate(component.components, interpolate({ block }, { block }));
|
|
139
|
+
})
|
|
140
|
+
.filter((item: TComponent[] | undefined) => item != null);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function testObjectMatches(
|
|
144
|
+
ctx: any,
|
|
145
|
+
{ value: rawValue, expected, operator }: { value: any; expected: any; operator: string },
|
|
146
|
+
block?: any,
|
|
147
|
+
sku?: any,
|
|
148
|
+
): boolean {
|
|
149
|
+
let value = valueFromSmartText(ctx, rawValue, sku, block);
|
|
150
|
+
expected = valueFromSmartText(ctx, expected, sku, block);
|
|
151
|
+
|
|
152
|
+
if (operator === 'equals') return value === expected;
|
|
153
|
+
if (operator === 'notEquals') return value !== expected;
|
|
154
|
+
if (operator === 'contains') {
|
|
155
|
+
if (typeof rawValue === 'string' && rawValue.includes('launch.productGroups')) {
|
|
156
|
+
const launchGroups = ctx?.state?.launch?.productGroups;
|
|
157
|
+
const fallbackGroups = Array.isArray(ctx?.state?.groups)
|
|
158
|
+
? ctx.state.groups.map((group: any) => group?.ref).filter(Boolean)
|
|
159
|
+
: [];
|
|
160
|
+
const unresolvedValue =
|
|
161
|
+
value == null
|
|
162
|
+
|| value === ''
|
|
163
|
+
|| value === rawValue
|
|
164
|
+
|| (Array.isArray(value) && value.length === 0);
|
|
165
|
+
if (unresolvedValue) {
|
|
166
|
+
value = Array.isArray(launchGroups) && launchGroups.length ? launchGroups : fallbackGroups;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!Array.isArray(value) && typeof value !== 'string' && !(value instanceof String)) {
|
|
170
|
+
value = value?.toString?.();
|
|
171
|
+
}
|
|
172
|
+
if (expected === 'collapse') {
|
|
173
|
+
expected += `_${ctx.getCurrentCollapsibleSku?.()?.skuId ?? ''}`;
|
|
174
|
+
}
|
|
175
|
+
return value?.includes?.(expected);
|
|
176
|
+
}
|
|
177
|
+
if (operator === 'notContains') {
|
|
178
|
+
if (!Array.isArray(value) && typeof value !== 'string' && !(value instanceof String)) {
|
|
179
|
+
value = value?.toString?.();
|
|
180
|
+
}
|
|
181
|
+
return !value?.includes?.(expected);
|
|
182
|
+
}
|
|
183
|
+
if (operator === 'set') {
|
|
184
|
+
if (typeof expected === 'boolean') {
|
|
185
|
+
return !!value && (!(value.toString().match(LIQUID_VARIABLE_REGEX)) === expected);
|
|
186
|
+
}
|
|
187
|
+
return !!value && !value.toString().match(LIQUID_VARIABLE_REGEX);
|
|
188
|
+
}
|
|
189
|
+
if (operator === 'notSet') {
|
|
190
|
+
return !value || !!value.toString().match(LIQUID_VARIABLE_REGEX);
|
|
191
|
+
}
|
|
192
|
+
if (operator === 'gt') return value > expected;
|
|
193
|
+
if (operator === 'gte') return value >= expected;
|
|
194
|
+
if (operator === 'lt') return value < expected;
|
|
195
|
+
if (operator === 'lte') return value <= expected;
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { NamiFlow, TPaywallContext } from '@namiml/sdk-core';
|
|
2
|
+
import { getUrlParams, SMART_TEXT_PATTERN, VAR_REGEX } from '@namiml/sdk-core';
|
|
3
|
+
|
|
4
|
+
type ReplacementsType = Record<string, any>;
|
|
5
|
+
|
|
6
|
+
export type SmartTextFlowState = {
|
|
7
|
+
isLaunched: boolean;
|
|
8
|
+
previousStepAvailable: boolean;
|
|
9
|
+
nextStepAvailable: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type SmartTextReplacements = {
|
|
13
|
+
state: TPaywallContext;
|
|
14
|
+
urlParams: Record<string, any>;
|
|
15
|
+
flow: SmartTextFlowState;
|
|
16
|
+
launch: TPaywallContext['launch'];
|
|
17
|
+
sku: any;
|
|
18
|
+
customer: TPaywallContext['customer'];
|
|
19
|
+
block?: any;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function getAttr(object: any, ...attrs: Array<string | number>): any {
|
|
23
|
+
let current = object;
|
|
24
|
+
for (const attr of attrs) {
|
|
25
|
+
if (current === undefined || current === null) return undefined;
|
|
26
|
+
current = Array.isArray(current) ? current[+attr] : current[attr];
|
|
27
|
+
}
|
|
28
|
+
return current;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function interpolateString(value: string, replacements: ReplacementsType): any {
|
|
32
|
+
let output = value;
|
|
33
|
+
if (typeof value !== 'string') {
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
36
|
+
if (!value.includes('$')) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const regex = new RegExp(VAR_REGEX.source, VAR_REGEX.flags);
|
|
41
|
+
const matches: RegExpExecArray[] = [];
|
|
42
|
+
let matchResult: RegExpExecArray | null;
|
|
43
|
+
while ((matchResult = regex.exec(value)) !== null) {
|
|
44
|
+
matches.push(matchResult);
|
|
45
|
+
if (!regex.global) break;
|
|
46
|
+
if (matchResult.index === regex.lastIndex) regex.lastIndex++;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const [match, rawPath] of matches) {
|
|
50
|
+
const path = interpolateString(rawPath, replacements);
|
|
51
|
+
if (match === value) return replacer(match, path);
|
|
52
|
+
if (path !== rawPath) {
|
|
53
|
+
output = output.replace(rawPath, path);
|
|
54
|
+
}
|
|
55
|
+
output = output.replace(VAR_REGEX, replacer as any);
|
|
56
|
+
}
|
|
57
|
+
return output;
|
|
58
|
+
|
|
59
|
+
function replacer(matchValue: string, pathValue: string): string {
|
|
60
|
+
const pathParts = String(pathValue).split('.');
|
|
61
|
+
const firstKey = pathParts[0];
|
|
62
|
+
if (!firstKey) return matchValue;
|
|
63
|
+
|
|
64
|
+
const firstSegment = getAttr(replacements, firstKey);
|
|
65
|
+
if (firstSegment === undefined) return matchValue;
|
|
66
|
+
|
|
67
|
+
const isEmptyObject =
|
|
68
|
+
typeof firstSegment === 'object'
|
|
69
|
+
&& firstSegment !== null
|
|
70
|
+
&& Object.keys(firstSegment).length === 0;
|
|
71
|
+
if (isEmptyObject) return '';
|
|
72
|
+
|
|
73
|
+
const newValue = getAttr(replacements, ...pathParts);
|
|
74
|
+
if (newValue === undefined) return '';
|
|
75
|
+
if (typeof newValue !== 'string') return newValue as unknown as string;
|
|
76
|
+
return interpolateString(newValue, replacements);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function interpolate<T>(value: T, replacements: ReplacementsType): T {
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
const output: any[] = [];
|
|
83
|
+
for (const item of value) {
|
|
84
|
+
const newValue = interpolate(item, replacements);
|
|
85
|
+
if (Array.isArray(newValue)) {
|
|
86
|
+
output.push(...newValue);
|
|
87
|
+
} else {
|
|
88
|
+
output.push(newValue);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return output as unknown as T;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (value === null || value === undefined) return value;
|
|
95
|
+
|
|
96
|
+
if (typeof value === 'object') {
|
|
97
|
+
const output: Record<string, any> = {};
|
|
98
|
+
for (const [key, current] of Object.entries(value as Record<string, any>)) {
|
|
99
|
+
output[key] = key === 'newRow' ? current : interpolate(current, replacements);
|
|
100
|
+
}
|
|
101
|
+
return output as T;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof value === 'string') return interpolateString(value, replacements);
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function buildSmartTextReplacements(
|
|
109
|
+
state: TPaywallContext,
|
|
110
|
+
flow?: NamiFlow,
|
|
111
|
+
sku?: any,
|
|
112
|
+
block?: any,
|
|
113
|
+
): SmartTextReplacements {
|
|
114
|
+
return {
|
|
115
|
+
state,
|
|
116
|
+
urlParams: getUrlParams(),
|
|
117
|
+
flow: {
|
|
118
|
+
isLaunched: !!flow?.manager?.flowOpen,
|
|
119
|
+
previousStepAvailable: !!flow?.previousStepAvailable,
|
|
120
|
+
nextStepAvailable: !!flow?.nextStepAvailable,
|
|
121
|
+
},
|
|
122
|
+
launch: state.launch,
|
|
123
|
+
sku: sku ?? state.sku,
|
|
124
|
+
customer: state.customer,
|
|
125
|
+
block,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function resolveSmartText(text: string | undefined, state: TPaywallContext): string {
|
|
130
|
+
if (!text) return '';
|
|
131
|
+
return String(interpolateSmartText(text, buildSmartTextReplacements(state)) ?? '');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function interpolateSmartText(value: any, replacements: SmartTextReplacements): any {
|
|
135
|
+
if (value == null) return value;
|
|
136
|
+
if (typeof value !== 'string') return value;
|
|
137
|
+
if (!value.includes(SMART_TEXT_PATTERN)) return value;
|
|
138
|
+
return interpolateString(value, replacements);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function interpolateComponent<T extends Record<string, any>>(
|
|
142
|
+
component: T,
|
|
143
|
+
state: TPaywallContext,
|
|
144
|
+
skuVars?: Record<string, any>,
|
|
145
|
+
): T {
|
|
146
|
+
const replacements = buildSmartTextReplacements(state, undefined, skuVars);
|
|
147
|
+
return interpolate(component, replacements);
|
|
148
|
+
}
|