react-native-nami-sdk
Version:
React Native SDK for Nami - No-code paywall and onboarding flows with A/B testing.
529 lines (484 loc) • 13.1 kB
text/typescript
export type NamiConfiguration = {
appPlatformID: string;
logLevel: string;
namiCommands?: string[];
namiLanguageCode?: NamiLanguageCodes;
initialConfig?: string;
};
export type NamiLanguageCodes =
| 'af'
| 'ar'
| 'ar-dz'
| 'ast'
| 'az'
| 'bg'
| 'be'
| 'bn'
| 'br'
| 'bs'
| 'ca'
| 'cs'
| 'cy'
| 'da'
| 'de'
| 'dsb'
| 'el'
| 'en'
| 'en-au'
| 'en-gb'
| 'eo'
| 'es'
| 'es-ar'
| 'es-co'
| 'es-mx'
| 'es-ni'
| 'es-ve'
| 'et'
| 'eu'
| 'fa'
| 'fi'
| 'fr'
| 'fy'
| 'ga'
| 'gd'
| 'gl'
| 'he'
| 'hi'
| 'hr'
| 'hsb'
| 'hu'
| 'hy'
| 'ia'
| 'id'
| 'ig'
| 'io'
| 'is'
| 'it'
| 'ja'
| 'ka'
| 'kab'
| 'kk'
| 'km'
| 'kn'
| 'ko'
| 'ky'
| 'lb'
| 'lt'
| 'lv'
| 'mk'
| 'ml'
| 'mn'
| 'mr'
| 'my'
| 'nb'
| 'ne'
| 'nl'
| 'nn'
| 'os'
| 'pa'
| 'pl'
| 'pt'
| 'pt-br'
| 'ro'
| 'ru'
| 'sk'
| 'sl'
| 'sq'
| 'sr'
| 'sr-latn'
| 'sv'
| 'sw'
| 'ta'
| 'te'
| 'tg'
| 'th'
| 'tk'
| 'tr'
| 'tt'
| 'udm'
| 'uk'
| 'ur'
| 'uz'
| 'vi'
| 'zh-hans'
| 'zh-hant';
export type NamiSKU = {
id: string;
name?: string;
skuId: string;
appleProduct?: AppleProduct;
googleProduct?: GoogleProduct;
amazonProduct?: AmazonProduct;
type: NamiSKUType;
promoId?: string | null;
promoToken?: string | null;
promoOffer?: { [key: string]: string } | null;
};
export enum NamiPurchaseState {
PENDING = 'pending',
PURCHASED = 'purchased',
CONSUMED = 'consumed',
RESUBSCRIBED = 'resubscribed',
UNSUBSCRIBED = 'unsubscribed',
DEFERRED = 'deferred',
FAILED = 'failed',
CANCELLED = 'cancelled',
UNKNOWN = 'unknown',
}
export enum NamiRestorePurchasesState {
STARTED = 'started',
FINISHED = 'finished',
ERROR = 'error',
}
export type NamiSKUType = 'unknown' | 'one_time_purchase' | 'subscription';
export type AppleProduct = {
localizedDescription: string;
localizedMultipliedPrice: string;
localizedPrice: string;
localizedTitle: string;
price: string;
priceCurrency: string;
priceLanguage: string;
};
export type GoogleProduct = Record<string, never>;
export type AmazonProduct = Record<string, never>;
// NamiCampaignManager
export type NamiCampaign = {
name: string;
rule: string;
segment: string;
paywall: string;
type: NamiCampaignRuleType;
value?: string | null;
form_factors: NamiFormFactor[];
external_segment: string | null;
};
export enum NamiCampaignRuleType {
DEFAULT = 'default',
LABEL = 'label',
UNKNOWN = 'unknown',
URL = 'url',
}
type NamiFormFactor = {
form_factor?: string;
supports_portrait?: boolean;
supports_landscape?: boolean;
};
export enum LaunchCampaignError {
DEFAULT_CAMPAIGN_NOT_FOUND = 0,
LABELED_CAMPAIGN_NOT_FOUND = 1,
CAMPAIGN_DATA_NOT_FOUND = 2,
PAYWALL_ALREADY_DISPLAYED = 3,
SDK_NOT_INITIALIZED = 4,
PAYWALL_COULD_NOT_DISPLAY = 5,
URL_CAMPAIGN_NOT_FOUND = 6,
PRODUCT_DATA_NOT_FOUND = 7,
PRODUCT_GROUPS_NOT_FOUND = 8,
}
export type NamiError = {
domain: string;
code: number;
message: string;
};
export enum LaunchCampaignResultAction {
FAILURE = 'FAILURE',
SUCCESS = 'SUCCESS',
}
export type FailureResultObject = {
error: string;
};
/**
* Permitted value types for entries in {@link PaywallLaunchContext.customAttributes}.
*
* Values are matched strictly by type: the string "true" and the boolean true are
* distinct values that never compare equal. A key present with any non-null value —
* including false or "false" — is considered "set". Passing `null` (or omitting the
* key) means the attribute is not set; the native bridge omits null values entirely.
*/
export type CustomAttributeValue = string | boolean | number | null;
export type PaywallLaunchContext = {
// Can contain multiple product group identifiers
productGroups?: string[];
// Key-value pairs used to override template values
customAttributes: {
[key: string]: CustomAttributeValue;
};
// Custom object used as data source for advanced paywall components
customObject?: {
[key: string]: unknown;
};
};
export type CustomerJourneyState = {
formerSubscriber: boolean;
inGracePeriod: boolean;
inTrialPeriod: boolean;
inIntroOfferPeriod: boolean;
isCancelled: boolean;
inPause: boolean;
inAccountHold: boolean;
};
export type AccountStateAction =
| 'login'
| 'logout'
| 'advertising_id_set'
| 'vendor_id_set'
| 'customer_data_platform_id_set'
| 'nami_device_id_set'
| 'advertising_id_cleared'
| 'vendor_id_cleared'
| 'customer_data_platform_id_cleared'
| 'nami_device_id_cleared'
| 'anonymous_mode_on'
| 'anonymous_mode_off';
// NamiEntitlementManager
export type NamiEntitlement = {
activePurchases: NamiPurchase[];
desc: string;
name: string;
purchasedSkus: NamiSKU[];
referenceId: string;
relatedSkus: NamiSKU[];
};
/**
* Host-managed purchase payload handed back to the SDK on a successful
* `buysku` handoff (`complete({ kind: 'purchase', result: 'success', details })`).
* Fields are a superset across stores; the native bridge maps them onto the
* platform's own `NamiPurchaseSuccess` (Apple flat class / Android sealed
* GooglePlay|Amazon) at the boundary. Renamed from `NamiPurchaseDetails` for
* cross-SDK naming consistency (NAM-1560 §2.3).
*/
export type NamiPurchaseSuccess = {
product: NamiSKU;
transactionID?: string;
originalTransactionID?: string;
orderId?: string;
purchaseToken?: string;
receiptId?: string;
localizedPrice?: string;
price?: string;
currencyCode?: string;
userId?: string;
marketplace?: string;
storeType?: string; // 'Apple', 'GooglePlay', 'Amazon'
};
/**
* @deprecated Renamed to {@link NamiPurchaseSuccess} for cross-SDK naming
* consistency. Structurally identical; kept so existing host code keeps
* compiling. Prefer `NamiPurchaseSuccess` in new code.
*/
export type NamiPurchaseDetails = NamiPurchaseSuccess;
export type NamiPurchaseSuccessApple = {
product: NamiSKU;
transactionID: string;
originalTransactionID: string;
price: string;
currencyCode: string;
};
export type NamiPurchaseSuccessGooglePlay = {
product: NamiSKU;
orderId: string;
purchaseToken: string;
};
export type NamiPurchaseSuccessAmazon = {
product: NamiSKU;
receiptId: string;
localizedPrice: string;
userId: string;
marketplace: string;
};
export enum NamiPaywallAction {
BUY_SKU = 'BUY_SKU',
SELECT_SKU = 'SELECT_SKU',
RESTORE_PURCHASES = 'RESTORE_PURCHASES',
SIGN_IN = 'SIGN_IN',
CLOSE_PAYWALL = 'CLOSE_PAYWALL',
SHOW_PAYWALL = 'SHOW_PAYWALL',
PURCHASE_SELECTED_SKU = 'PURCHASE_SELECTED_SKU',
PURCHASE_SUCCESS = 'PURCHASE_SUCCESS',
PURCHASE_FAILED = 'PURCHASE_FAILED',
PURCHASE_CANCELLED = 'PURCHASE_CANCELLED',
PURCHASE_PENDING = 'PURCHASE_PENDING',
PURCHASE_UNKNOWN = 'PURCHASE_UNKNOWN',
PURCHASE_DEFERRED = 'PURCHASE_DEFERRED',
DEEPLINK = 'DEEPLINK',
TOGGLE_CHANGE = 'TOGGLE_CHANGE',
PAGE_CHANGE = 'PAGE_CHANGE',
SLIDE_CHANGE = 'SLIDE_CHANGE',
COLLAPSIBLE_DRAWER_OPEN = 'COLLAPSIBLE_DRAWER_OPEN',
COLLAPSIBLE_DRAWER_CLOSE = 'COLLAPSIBLE_DRAWER_CLOSE',
VIDEO_STARTED = 'VIDEO_STARTED',
VIDEO_PAUSED = 'VIDEO_PAUSED',
VIDEO_RESUMED = 'VIDEO_RESUMED',
VIDEO_ENDED = 'VIDEO_ENDED',
VIDEO_CHANGED = 'VIDEO_CHANGED',
VIDEO_MUTED = 'VIDEO_MUTED',
VIDEO_UNMUTED = 'VIDEO_UNMUTED',
UNKNOWN = 'UNKNOWN',
}
// NamiPurchaseManager
export type NamiPurchase = {
sku?: NamiSKU;
skuId: string;
transactionIdentifier?: string;
originalTransactionIdentifier?: string;
purchaseToken?: string;
expires?: Date;
purchaseInitiatedTimestamp: Date;
purchaseSource?: 'CAMPAIGN' | 'MARKETPLACE' | 'UNKNOWN';
};
export type NamiPurchaseFromBridge = {
sku?: NamiSKU;
skuId: string;
transactionIdentifier?: string;
originalTransactionIdentifier?: string;
purchaseToken?: string;
expires?: number;
purchaseInitiatedTimestamp: number;
purchaseSource?: 'CAMPAIGN' | 'MARKETPLACE' | 'UNKNOWN';
};
export type NamiPurchasesState =
| 'pending'
| 'purchased'
| 'consumed'
| 'resubscribed'
| 'unsubscribed'
| 'deferred'
| 'failed'
| 'cancelled'
| 'unknown';
export type NamiPaywallEvent = {
action: NamiPaywallAction;
campaignId?: string;
campaignName?: string;
campaignType?: string;
campaignLabel?: string;
campaignUrl?: string;
paywallId?: string;
paywallName?: string;
componentChange?: NamiPaywallComponentChange;
segmentId?: string;
externalSegmentId?: string;
deeplinkUrl?: string;
sku?: NamiSKU;
purchaseError?: string;
purchases?: NamiPurchase[];
videoMetadata?: NamiPaywallEventVideoMetadata;
timeSpentOnPaywall?: number;
};
export type NamiPaywallActionHandler = (event: NamiPaywallEvent) => void;
export type NamiPaywallComponentChange = {
id?: string;
name?: string;
};
export type NamiPaywallEventVideoMetadata = {
id?: string;
name?: string;
url?: string;
loopVideo: boolean;
muteByDefault: boolean;
autoplayVideo: boolean;
contentTimecode?: number;
contentDuration?: number;
};
/**
* Native → JS handoff delivery payload. `handoffId` correlates the eventual
* `complete(outcome)` back to the native pending handoff (NAM-1560 §2.8).
*/
export type NamiFlowHandoffPayload = {
handoffId: string;
handoffTag: string;
handoffData?: Record<string, unknown>;
};
// ---------------------------------------------------------------------------
// Variant D — structured handoff outcomes (NAM-1560). Mirrors the core TS
// reference (sdk/core, NAM-1557 / PR #392). Keep the shapes in sync; the native
// bridges decode these dicts into the platform `NamiHandoffOutcome`.
// ---------------------------------------------------------------------------
/** Open-ended host-asserted attribute values — primitives only (no nesting). */
export type NamiHandoffAttributeValue = string | number | boolean;
/**
* Host-asserted login result. Only `loginId` is required. `attributes` is
* open-ended experience data (e.g. `{ subscriberStatus, subscribedTier }`) read
* by branch conditions and smart text via `login.<hostKey>`; never merged into
* Nami subscriber state and never touches entitlements.
*/
export type NamiLoginSuccess = {
loginId: string;
cdpId?: string;
attributes?: Record<string, NamiHandoffAttributeValue>;
};
/** Permission types reportable from a handoff. Tier-1 (push/location/tracking)
* carry completion wire values natively; the rest are state/lifecycle only. */
export const NamiPermissionOutcome = {
Push: 'push',
Location: 'location',
Tracking: 'tracking',
Camera: 'camera',
Microphone: 'microphone',
Photos: 'photos',
Contacts: 'contacts',
Bluetooth: 'bluetooth',
Calendar: 'calendar',
Biometrics: 'biometrics',
Health: 'health',
Motion: 'motion',
Speech: 'speech',
LocalNetwork: 'localNetwork',
} as const;
export type NamiPermissionOutcome =
(typeof NamiPermissionOutcome)[keyof typeof NamiPermissionOutcome];
/** Typed outcome reported by the host's `complete(...)`. Discriminated on `kind`
* (purchase/login further discriminated on `result`). */
export type NamiHandoffOutcome =
| { kind: 'done' }
| {
kind: 'permission';
permission: NamiPermissionOutcome;
granted: boolean;
detail?: string;
}
| { kind: 'purchase'; result: 'success'; details: NamiPurchaseSuccess }
| { kind: 'purchase'; result: 'cancelled' }
| { kind: 'purchase'; result: 'failed'; error?: unknown }
| { kind: 'login'; result: 'success'; details: NamiLoginSuccess }
| { kind: 'login'; result: 'cancelled' }
| { kind: 'login'; result: 'failed'; error?: unknown };
/** Per-handoff completion callback. Bare `complete()` ≙ `complete({kind:'done'})`. */
export type NamiHandoffComplete = (outcome?: NamiHandoffOutcome) => void;
/** The single handoff handler shape (NAM-1560 §2.1 — no dual registration). */
export type NamiFlowHandoffStepHandler = (
handoffTag: string,
handoffData: Record<string, unknown> | undefined,
complete: NamiHandoffComplete,
) => void;
/** Closed-but-extensible handoff tag vocabulary (mirrors the builder's
* `FlowHandoffTag`, minus `__*__` internals). Unknown future tags still pass. */
export const NamiHandoffTag = {
Push: 'push',
Location: 'location',
Tracking: 'att',
Deeplink: 'deeplink',
Complete: 'complete',
SignIn: 'signin',
Restore: 'restore',
SignInMvpd: 'signin_mvpd',
BuySku: 'buysku',
UserData: 'userdata',
} as const;
export type NamiKnownHandoffTag =
(typeof NamiHandoffTag)[keyof typeof NamiHandoffTag];
// eslint-disable-next-line @typescript-eslint/ban-types
export type NamiHandoffTagValue = NamiKnownHandoffTag | (string & {});
/** Outbound `userdata` envelope (Nami → customer) delivered with the `userdata` tag. */
export type NamiUserDataEnvelope = {
form_id: string;
step_id: string;
/** ISO-8601 UTC timestamp of delivery. */
collected_at: string;
fields: Record<string, boolean | string>;
};
export type NamiAccountStateEvent = {
action: AccountStateAction;
success: boolean;
error?: number;
};