import type { NamiFlow, TPaywallContext } from '@namiml/sdk-core'; import { getUrlParams, SMART_TEXT_PATTERN, VAR_REGEX } from '@namiml/sdk-core'; type ReplacementsType = Record; export type SmartTextFlowState = { isLaunched: boolean; previousStepAvailable: boolean; nextStepAvailable: boolean; }; export type SmartTextReplacements = { state: TPaywallContext; urlParams: Record; flow: SmartTextFlowState; launch: TPaywallContext['launch']; sku: any; customer: TPaywallContext['customer']; block?: any; }; function getAttr(object: any, ...attrs: Array): any { let current = object; for (const attr of attrs) { if (current === undefined || current === null) return undefined; current = Array.isArray(current) ? current[+attr] : current[attr]; } return current; } function interpolateString(value: string, replacements: ReplacementsType): any { let output = value; if (typeof value !== 'string') { return output; } if (!value.includes('$')) { return value; } const regex = new RegExp(VAR_REGEX.source, VAR_REGEX.flags); const matches: RegExpExecArray[] = []; let matchResult: RegExpExecArray | null; while ((matchResult = regex.exec(value)) !== null) { matches.push(matchResult); if (!regex.global) break; if (matchResult.index === regex.lastIndex) regex.lastIndex++; } for (const [match, rawPath] of matches) { const path = interpolateString(rawPath, replacements); if (match === value) return replacer(match, path); if (path !== rawPath) { output = output.replace(rawPath, path); } output = output.replace(VAR_REGEX, replacer as any); } return output; function replacer(matchValue: string, pathValue: string): string { const pathParts = String(pathValue).split('.'); const firstKey = pathParts[0]; if (!firstKey) return matchValue; const firstSegment = getAttr(replacements, firstKey); if (firstSegment === undefined) return matchValue; const isEmptyObject = typeof firstSegment === 'object' && firstSegment !== null && Object.keys(firstSegment).length === 0; if (isEmptyObject) return ''; const newValue = getAttr(replacements, ...pathParts); if (newValue === undefined) return ''; if (typeof newValue !== 'string') return newValue as unknown as string; return interpolateString(newValue, replacements); } } export function interpolate(value: T, replacements: ReplacementsType): T { if (Array.isArray(value)) { const output: any[] = []; for (const item of value) { const newValue = interpolate(item, replacements); if (Array.isArray(newValue)) { output.push(...newValue); } else { output.push(newValue); } } return output as unknown as T; } if (value === null || value === undefined) return value; if (typeof value === 'object') { const output: Record = {}; for (const [key, current] of Object.entries(value as Record)) { output[key] = key === 'newRow' ? current : interpolate(current, replacements); } return output as T; } if (typeof value === 'string') return interpolateString(value, replacements); return value; } export function buildSmartTextReplacements( state: TPaywallContext, flow?: NamiFlow, sku?: any, block?: any, ): SmartTextReplacements { return { state, urlParams: getUrlParams(), flow: { isLaunched: !!flow?.manager?.flowOpen, previousStepAvailable: !!flow?.previousStepAvailable, nextStepAvailable: !!flow?.nextStepAvailable, }, launch: state.launch, sku: sku ?? state.sku, customer: state.customer, block, }; } export function resolveSmartText(text: string | undefined, state: TPaywallContext): string { if (!text) return ''; return String(interpolateSmartText(text, buildSmartTextReplacements(state)) ?? ''); } export function interpolateSmartText(value: any, replacements: SmartTextReplacements): any { if (value == null) return value; if (typeof value !== 'string') return value; if (!value.includes(SMART_TEXT_PATTERN)) return value; return interpolateString(value, replacements); } export function interpolateComponent>( component: T, state: TPaywallContext, skuVars?: Record, ): T { const replacements = buildSmartTextReplacements(state, undefined, skuVars); return interpolate(component, replacements); }