@namiml/sdk-core 3.4.0-dev.202605141520 → 3.4.0-dev.202605180118
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 +226 -1
- package/dist/index.d.ts +77 -1
- package/dist/index.mjs +226 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -98,7 +98,7 @@ const {
|
|
|
98
98
|
// version — stamped by scripts/version.sh
|
|
99
99
|
NAMI_SDK_VERSION: exports.NAMI_SDK_VERSION = "3.4.0",
|
|
100
100
|
// full package version including dev suffix — stamped by scripts/version.sh
|
|
101
|
-
NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.0-dev.
|
|
101
|
+
NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.0-dev.202605180118",
|
|
102
102
|
// environments
|
|
103
103
|
PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
|
|
104
104
|
// error messages
|
|
@@ -63913,6 +63913,230 @@ const convertLocale = (locale) => {
|
|
|
63913
63913
|
return intlLocale.language + (intlLocale.region ?? intlLocale.script ?? '');
|
|
63914
63914
|
};
|
|
63915
63915
|
|
|
63916
|
+
/**
|
|
63917
|
+
* Components that are skipped entirely (they and their subtree do not
|
|
63918
|
+
* contribute text to the page-level announcement).
|
|
63919
|
+
*
|
|
63920
|
+
* - Buttons / interactive controls: their label is appended as the
|
|
63921
|
+
* focused-button suffix, not collected mid-tree. Recursing into them
|
|
63922
|
+
* would double-count the label and pull in non-spoken decorative text.
|
|
63923
|
+
* - Image-like leaves: explicitly skipped per spec.
|
|
63924
|
+
* - Form pickers + QR codes + raw video: not informational text.
|
|
63925
|
+
*/
|
|
63926
|
+
const SKIP_COMPONENTS = new Set([
|
|
63927
|
+
'button',
|
|
63928
|
+
'playPauseButton',
|
|
63929
|
+
'volumeButton',
|
|
63930
|
+
'toggleButton',
|
|
63931
|
+
'toggleSwitch',
|
|
63932
|
+
'radio',
|
|
63933
|
+
'image',
|
|
63934
|
+
'svgImage',
|
|
63935
|
+
'symbol',
|
|
63936
|
+
'qrCode',
|
|
63937
|
+
'videoUrl',
|
|
63938
|
+
'segmentPicker',
|
|
63939
|
+
'segmentPickerItem',
|
|
63940
|
+
]);
|
|
63941
|
+
/** Component values in {@link SKIP_COMPONENTS} that are interactive controls
|
|
63942
|
+
* (as opposed to images / media / non-text leaves). When a text component
|
|
63943
|
+
* shares a container with one of these, the text is treated as that
|
|
63944
|
+
* button's accessible label and excluded from the page composite. */
|
|
63945
|
+
const BUTTON_COMPONENTS = new Set([
|
|
63946
|
+
'button',
|
|
63947
|
+
'playPauseButton',
|
|
63948
|
+
'volumeButton',
|
|
63949
|
+
'toggleButton',
|
|
63950
|
+
'toggleSwitch',
|
|
63951
|
+
'radio',
|
|
63952
|
+
]);
|
|
63953
|
+
/** `namiComponentType` values that are always included in the page
|
|
63954
|
+
* composite — even when their structural position (e.g. a sibling of a
|
|
63955
|
+
* utility button) would otherwise mark them for exclusion. Driven by the
|
|
63956
|
+
* spec's schema table:
|
|
63957
|
+
*
|
|
63958
|
+
* | `namiComponentType: "legalText"` | Legal/T&C content (included in tree-walk) |
|
|
63959
|
+
*/
|
|
63960
|
+
const ALWAYS_INCLUDE_NAMI_TYPES = new Set(['legalText']);
|
|
63961
|
+
/**
|
|
63962
|
+
* Whether a text leaf inside `parent` should be skipped because it acts as
|
|
63963
|
+
* the accessible label for a sibling button.
|
|
63964
|
+
*
|
|
63965
|
+
* Schema pattern this catches: a container whose direct children include
|
|
63966
|
+
* BOTH a text/text-list AND an interactive button (e.g. Page 3's "Login
|
|
63967
|
+
* Group" — `[ text "Already an NFL+ subscriber?", loginButton "Sign In" ]`).
|
|
63968
|
+
* The text describes the button and is announced when the button is
|
|
63969
|
+
* focused, NOT as page-level content. Without this rule, the composite
|
|
63970
|
+
* leaks "Already an NFL+ subscriber? Subscribe to NFL plus button" instead
|
|
63971
|
+
* of "Subscribe to NFL plus button".
|
|
63972
|
+
*
|
|
63973
|
+
* Texts marked `namiComponentType: "legalText"` are exempt — the spec
|
|
63974
|
+
* explicitly opts legal copy in regardless of its container's other
|
|
63975
|
+
* children (e.g. Page 5's "Bottom Wrapper" mixes a body text, a restore
|
|
63976
|
+
* button, and the legal text — the legal text must still be spoken).
|
|
63977
|
+
*/
|
|
63978
|
+
function isButtonSiblingLabel(node, parent) {
|
|
63979
|
+
if (!parent || !Array.isArray(parent.components))
|
|
63980
|
+
return false;
|
|
63981
|
+
if (typeof node.namiComponentType === 'string' &&
|
|
63982
|
+
ALWAYS_INCLUDE_NAMI_TYPES.has(node.namiComponentType)) {
|
|
63983
|
+
return false;
|
|
63984
|
+
}
|
|
63985
|
+
for (const sibling of parent.components) {
|
|
63986
|
+
if (sibling && typeof sibling === 'object') {
|
|
63987
|
+
const s = sibling;
|
|
63988
|
+
if (typeof s.component === 'string' && BUTTON_COMPONENTS.has(s.component)) {
|
|
63989
|
+
return true;
|
|
63990
|
+
}
|
|
63991
|
+
}
|
|
63992
|
+
}
|
|
63993
|
+
return false;
|
|
63994
|
+
}
|
|
63995
|
+
/**
|
|
63996
|
+
* Recursively collect spoken text from a component subtree following the
|
|
63997
|
+
* TV Full-Page Announcement contract.
|
|
63998
|
+
*
|
|
63999
|
+
* Rules (in order):
|
|
64000
|
+
* 1. `null` / `undefined` → contribute nothing.
|
|
64001
|
+
* 2. `hidden: true` → skip the node AND its subtree.
|
|
64002
|
+
* 3. `component` in {@link SKIP_COMPONENTS} → skip the node AND its subtree.
|
|
64003
|
+
* 4. `screenreaderText` set on this container → take that string verbatim
|
|
64004
|
+
* and do NOT recurse (server-supplied override).
|
|
64005
|
+
* 5. `component: "text"` or `"text-list"` → collect, UNLESS the text is a
|
|
64006
|
+
* sibling label for an adjacent button (see
|
|
64007
|
+
* {@link isButtonSiblingLabel}). `namiComponentType: "legalText"` is
|
|
64008
|
+
* exempted from the sibling-label exclusion.
|
|
64009
|
+
* 6. Otherwise → recurse into `components`.
|
|
64010
|
+
*/
|
|
64011
|
+
function collectText(node, parent = null) {
|
|
64012
|
+
if (!node || typeof node !== 'object')
|
|
64013
|
+
return [];
|
|
64014
|
+
const n = node;
|
|
64015
|
+
if (n.hidden === true)
|
|
64016
|
+
return [];
|
|
64017
|
+
if (typeof n.component === 'string' && SKIP_COMPONENTS.has(n.component))
|
|
64018
|
+
return [];
|
|
64019
|
+
// Container-level override: when an enclosing container pre-supplies its own
|
|
64020
|
+
// screenreader text, take it verbatim and stop descending — the customer
|
|
64021
|
+
// has authored a polished announcement for this subtree.
|
|
64022
|
+
if (typeof n.screenreaderText === 'string' && n.screenreaderText.trim().length > 0) {
|
|
64023
|
+
return [n.screenreaderText.trim()];
|
|
64024
|
+
}
|
|
64025
|
+
if (n.component === 'text') {
|
|
64026
|
+
if (isButtonSiblingLabel(n, parent))
|
|
64027
|
+
return [];
|
|
64028
|
+
return typeof n.text === 'string' && n.text.length > 0 ? [n.text] : [];
|
|
64029
|
+
}
|
|
64030
|
+
if (n.component === 'text-list') {
|
|
64031
|
+
if (isButtonSiblingLabel(n, parent))
|
|
64032
|
+
return [];
|
|
64033
|
+
if (!Array.isArray(n.texts))
|
|
64034
|
+
return [];
|
|
64035
|
+
return n.texts.filter((t) => typeof t === 'string' && t.length > 0);
|
|
64036
|
+
}
|
|
64037
|
+
if (Array.isArray(n.components)) {
|
|
64038
|
+
const out = [];
|
|
64039
|
+
for (const child of n.components) {
|
|
64040
|
+
out.push(...collectText(child, n));
|
|
64041
|
+
}
|
|
64042
|
+
return out;
|
|
64043
|
+
}
|
|
64044
|
+
return [];
|
|
64045
|
+
}
|
|
64046
|
+
function collectHeaderFooter(slot) {
|
|
64047
|
+
if (!Array.isArray(slot))
|
|
64048
|
+
return [];
|
|
64049
|
+
// The slot itself acts as the synthetic parent so a top-level text inside
|
|
64050
|
+
// a header/footer that sits alongside a top-level button is treated as a
|
|
64051
|
+
// button label and excluded.
|
|
64052
|
+
const syntheticParent = { components: slot };
|
|
64053
|
+
const out = [];
|
|
64054
|
+
for (const node of slot) {
|
|
64055
|
+
out.push(...collectText(node, syntheticParent));
|
|
64056
|
+
}
|
|
64057
|
+
return out;
|
|
64058
|
+
}
|
|
64059
|
+
/**
|
|
64060
|
+
* Join an ordered list of collected text fragments into a single speakable
|
|
64061
|
+
* string. Each fragment is trimmed; empty fragments are dropped. Author
|
|
64062
|
+
* terminators (`.`, `!`, `?`) are preserved so emphasis carries through to
|
|
64063
|
+
* the screenreader; only fragments that lack a terminator get `". "`
|
|
64064
|
+
* inserted before the next fragment. The result is a string that reads
|
|
64065
|
+
* naturally without double-punctuation like `"...REACH!. Watch..."`.
|
|
64066
|
+
*/
|
|
64067
|
+
function joinFragments(fragments) {
|
|
64068
|
+
let out = '';
|
|
64069
|
+
for (const raw of fragments) {
|
|
64070
|
+
const trimmed = raw.trim();
|
|
64071
|
+
if (!trimmed)
|
|
64072
|
+
continue;
|
|
64073
|
+
if (!out) {
|
|
64074
|
+
out = trimmed;
|
|
64075
|
+
continue;
|
|
64076
|
+
}
|
|
64077
|
+
const last = out[out.length - 1];
|
|
64078
|
+
const endsWithTerminator = last === '.' || last === '!' || last === '?';
|
|
64079
|
+
out += (endsWithTerminator ? ' ' : '. ') + trimmed;
|
|
64080
|
+
}
|
|
64081
|
+
return out;
|
|
64082
|
+
}
|
|
64083
|
+
/**
|
|
64084
|
+
* Build the full-screen announcement string for a paywall page per the
|
|
64085
|
+
* TV Full-Page Announcement contract
|
|
64086
|
+
* (https://linear.app/nami-product-development/document/tv-full-page-announcement-980f61b96e7c).
|
|
64087
|
+
*
|
|
64088
|
+
* Behaviour:
|
|
64089
|
+
* 1. **Override path.** If `page.screenreaderText` is set, that string is
|
|
64090
|
+
* returned verbatim (followed by `focusedButtonLabel` if provided and
|
|
64091
|
+
* not already present at the tail). Server-authored polished
|
|
64092
|
+
* announcements win over client-side aggregation.
|
|
64093
|
+
*
|
|
64094
|
+
* 2. **Tree-walk fallback.** Otherwise, walk the page's containers in
|
|
64095
|
+
* reading order — `header` → `backgroundContainer` → `contentContainer`
|
|
64096
|
+
* → `footer` — collecting visible `text` / `text-list` descendants in
|
|
64097
|
+
* source order. `image`, `hidden: true` subtrees, interactive controls
|
|
64098
|
+
* (buttons / toggles), and non-text leaves are skipped. The
|
|
64099
|
+
* `focusedButtonLabel` is appended as the final sentence.
|
|
64100
|
+
*
|
|
64101
|
+
* SDKs invoke this once per page on the first focus of the default CTA, and
|
|
64102
|
+
* speak the result via their platform TTS API. On subsequent focuses within
|
|
64103
|
+
* the same page, the SDK announces only the focused element's own label —
|
|
64104
|
+
* this helper is NOT re-called.
|
|
64105
|
+
*
|
|
64106
|
+
* @param page The paywall page to announce.
|
|
64107
|
+
* @param focusedButtonLabel The resolved label of the button that
|
|
64108
|
+
* triggered the announcement (typically the
|
|
64109
|
+
* page's default-focused CTA). For
|
|
64110
|
+
* subscription-plan CTAs this is the dynamic
|
|
64111
|
+
* `"{price} {period} subscription plan button"`
|
|
64112
|
+
* string the backend already resolved.
|
|
64113
|
+
* @returns A single string ready to pass to a platform TTS API. Empty when
|
|
64114
|
+
* the page has no spoken content and no focused button label.
|
|
64115
|
+
*/
|
|
64116
|
+
function aggregateScreenreaderText(page, focusedButtonLabel = '') {
|
|
64117
|
+
const trimmedLabel = focusedButtonLabel.trim();
|
|
64118
|
+
// Override path
|
|
64119
|
+
if (typeof page.screenreaderText === 'string' && page.screenreaderText.trim().length > 0) {
|
|
64120
|
+
const override = page.screenreaderText.trim();
|
|
64121
|
+
if (!trimmedLabel)
|
|
64122
|
+
return override;
|
|
64123
|
+
// If the server-authored override already ends with the focused button
|
|
64124
|
+
// label, don't append it again.
|
|
64125
|
+
if (override.toLowerCase().endsWith(trimmedLabel.toLowerCase()))
|
|
64126
|
+
return override;
|
|
64127
|
+
return joinFragments([override, trimmedLabel]);
|
|
64128
|
+
}
|
|
64129
|
+
// Tree-walk path
|
|
64130
|
+
const fragments = [];
|
|
64131
|
+
fragments.push(...collectHeaderFooter(page.header));
|
|
64132
|
+
fragments.push(...collectText(page.backgroundContainer));
|
|
64133
|
+
fragments.push(...collectText(page.contentContainer));
|
|
64134
|
+
fragments.push(...collectHeaderFooter(page.footer));
|
|
64135
|
+
if (trimmedLabel)
|
|
64136
|
+
fragments.push(trimmedLabel);
|
|
64137
|
+
return joinFragments(fragments);
|
|
64138
|
+
}
|
|
64139
|
+
|
|
63916
64140
|
function namiBuySKU(skuRefId) {
|
|
63917
64141
|
if (!hasPurchaseManagement("NamiPurchaseManager.buySKU")) {
|
|
63918
64142
|
return;
|
|
@@ -63982,6 +64206,7 @@ exports.SimpleEventTarget = SimpleEventTarget;
|
|
|
63982
64206
|
exports.StorageService = StorageService;
|
|
63983
64207
|
exports.activateEntitlementByPurchase = activateEntitlementByPurchase;
|
|
63984
64208
|
exports.activeEntitlements = activeEntitlements;
|
|
64209
|
+
exports.aggregateScreenreaderText = aggregateScreenreaderText;
|
|
63985
64210
|
exports.allCampaigns = allCampaigns;
|
|
63986
64211
|
exports.allPaywalls = allPaywalls;
|
|
63987
64212
|
exports.applyEntitlementActivation = applyEntitlementActivation;
|
package/dist/index.d.ts
CHANGED
|
@@ -772,6 +772,31 @@ interface TBaseComponent {
|
|
|
772
772
|
context?: {
|
|
773
773
|
[key: string]: any;
|
|
774
774
|
};
|
|
775
|
+
/**
|
|
776
|
+
* Text spoken by the platform screenreader. Plays two roles depending on
|
|
777
|
+
* where it is set, per the TV Full-Page Announcement contract
|
|
778
|
+
* (https://linear.app/nami-product-development/document/tv-full-page-announcement-980f61b96e7c):
|
|
779
|
+
*
|
|
780
|
+
* - **On focusable elements** (buttons, toggles, etc.): the element's own
|
|
781
|
+
* label, spoken when the element receives focus. For subscription-plan
|
|
782
|
+
* CTAs the backend resolves this dynamically to
|
|
783
|
+
* `"{price} {period} [{tier}] subscription plan button"`.
|
|
784
|
+
*
|
|
785
|
+
* - **On containers** (any TBaseComponent-derived container, and TPages):
|
|
786
|
+
* an optional *server-supplied composite override*. When set, SDKs
|
|
787
|
+
* announce this verbatim on first focus of the page's default CTA
|
|
788
|
+
* instead of tree-walking the page's text. When omitted, SDKs derive
|
|
789
|
+
* the composite by walking visible `text` / `text-list` descendants in
|
|
790
|
+
* source order (skipping `image`, `hidden: true`, and non-text leaves)
|
|
791
|
+
* and appending the focused button's label.
|
|
792
|
+
*/
|
|
793
|
+
screenreaderText?: string;
|
|
794
|
+
/**
|
|
795
|
+
* Supplementary hint announced after `screenreaderText` on the element it
|
|
796
|
+
* is set on (e.g. "Double-tap to subscribe"). Optional; SDKs that lack a
|
|
797
|
+
* hint affordance may omit it.
|
|
798
|
+
*/
|
|
799
|
+
screenreaderHint?: string;
|
|
775
800
|
moveX?: number | string;
|
|
776
801
|
moveY?: string | number;
|
|
777
802
|
direction?: DirectionType;
|
|
@@ -1701,6 +1726,22 @@ type TPages = {
|
|
|
1701
1726
|
contentContainer: TContainer | null;
|
|
1702
1727
|
footer: THeaderFooter;
|
|
1703
1728
|
backgroundContainer: TContainer | null;
|
|
1729
|
+
/**
|
|
1730
|
+
* Optional server-supplied composite announcement for the page. When set,
|
|
1731
|
+
* TV SDKs speak this verbatim on first focus of the default CTA instead of
|
|
1732
|
+
* tree-walking the page's text content. When omitted, SDKs derive the
|
|
1733
|
+
* composite client-side from visible `text` / `text-list` descendants (in
|
|
1734
|
+
* source order, skipping `image`, `hidden: true`, and non-text leaves) and
|
|
1735
|
+
* append the focused button's label.
|
|
1736
|
+
*
|
|
1737
|
+
* Video pages are detected by the presence of `component: "videoUrl"` in
|
|
1738
|
+
* `backgroundContainer` with `autoplayVideo: true` and `loopVideo: false`
|
|
1739
|
+
* — TTS is deferred until playback completes. No additional schema flag is
|
|
1740
|
+
* required for that suppression.
|
|
1741
|
+
*
|
|
1742
|
+
* Contract: https://linear.app/nami-product-development/document/tv-full-page-announcement-980f61b96e7c
|
|
1743
|
+
*/
|
|
1744
|
+
screenreaderText?: string;
|
|
1704
1745
|
};
|
|
1705
1746
|
type TInitialState = {
|
|
1706
1747
|
slides?: TCarouselSlidesState;
|
|
@@ -2623,6 +2664,41 @@ interface INamiRefsInstance {
|
|
|
2623
2664
|
*/
|
|
2624
2665
|
declare function isAnonymousMode(): boolean;
|
|
2625
2666
|
|
|
2667
|
+
/**
|
|
2668
|
+
* Build the full-screen announcement string for a paywall page per the
|
|
2669
|
+
* TV Full-Page Announcement contract
|
|
2670
|
+
* (https://linear.app/nami-product-development/document/tv-full-page-announcement-980f61b96e7c).
|
|
2671
|
+
*
|
|
2672
|
+
* Behaviour:
|
|
2673
|
+
* 1. **Override path.** If `page.screenreaderText` is set, that string is
|
|
2674
|
+
* returned verbatim (followed by `focusedButtonLabel` if provided and
|
|
2675
|
+
* not already present at the tail). Server-authored polished
|
|
2676
|
+
* announcements win over client-side aggregation.
|
|
2677
|
+
*
|
|
2678
|
+
* 2. **Tree-walk fallback.** Otherwise, walk the page's containers in
|
|
2679
|
+
* reading order — `header` → `backgroundContainer` → `contentContainer`
|
|
2680
|
+
* → `footer` — collecting visible `text` / `text-list` descendants in
|
|
2681
|
+
* source order. `image`, `hidden: true` subtrees, interactive controls
|
|
2682
|
+
* (buttons / toggles), and non-text leaves are skipped. The
|
|
2683
|
+
* `focusedButtonLabel` is appended as the final sentence.
|
|
2684
|
+
*
|
|
2685
|
+
* SDKs invoke this once per page on the first focus of the default CTA, and
|
|
2686
|
+
* speak the result via their platform TTS API. On subsequent focuses within
|
|
2687
|
+
* the same page, the SDK announces only the focused element's own label —
|
|
2688
|
+
* this helper is NOT re-called.
|
|
2689
|
+
*
|
|
2690
|
+
* @param page The paywall page to announce.
|
|
2691
|
+
* @param focusedButtonLabel The resolved label of the button that
|
|
2692
|
+
* triggered the announcement (typically the
|
|
2693
|
+
* page's default-focused CTA). For
|
|
2694
|
+
* subscription-plan CTAs this is the dynamic
|
|
2695
|
+
* `"{price} {period} subscription plan button"`
|
|
2696
|
+
* string the backend already resolved.
|
|
2697
|
+
* @returns A single string ready to pass to a platform TTS API. Empty when
|
|
2698
|
+
* the page has no spoken content and no focused button label.
|
|
2699
|
+
*/
|
|
2700
|
+
declare function aggregateScreenreaderText(page: TPages, focusedButtonLabel?: string): string;
|
|
2701
|
+
|
|
2626
2702
|
/**
|
|
2627
2703
|
* Returns the translated string for the given key in the current SDK language.
|
|
2628
2704
|
* Uses Nami language code from storage (set via Nami.configure({ namiLanguageCode })).
|
|
@@ -2985,5 +3061,5 @@ declare const getBillingPeriodNumber: (billingPeriod: string) => number;
|
|
|
2985
3061
|
declare const formattedPrice: (price: number) => number;
|
|
2986
3062
|
declare function toDouble(num: number): number;
|
|
2987
3063
|
|
|
2988
|
-
export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, activateEntitlementByPurchase, activeEntitlements, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateMinSDKVersion };
|
|
3064
|
+
export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateMinSDKVersion };
|
|
2989
3065
|
export type { AlignmentType, AmazonProduct, ApiResponse, AppleProduct, AvailableCampaignsResponseHandler, BorderLocationType, BorderSideType, Callback$1 as Callback, CloseHandler, CustomerJourneyState, DeepLinkUrlHandler, Device, DevicePayload, DeviceProfile, DirectionType, ExtendedPlatformInfo, FlexDirectionObject, FlowNavigationOptions, FontCollection, FontDetails, FormFactor, GoogleProduct, IConfig, IDeviceAdapter, IEntitlements$1 as IEntitlements, IPaywall, IPlatformAdapters, IProductsWithComponents, IPurchaseAdapter, ISkuMenu, IStorageAdapter, IUIAdapter, Impression, InitialConfig, InitialConfigCompressed, InitiateStateGroup, LoginResponse, NamiAnimation, NamiAnimationObjectSpec, NamiAnimationSpec, NamiAnonymousCampaign, NamiAppSuppliedVideoDetails, NamiCampaign, NamiCampaignSegment, NamiConfiguration, NamiConfigurationState, NamiEntitlement$1 as NamiEntitlement, NamiFlowAction, NamiFlowAnimation, NamiFlowCampaign, NamiFlowDTO, NamiFlowEventHandler, NamiFlowHandoffStepHandler, NamiFlowObjectDTO, NamiFlowOn, NamiFlowStep, NamiFlowTransition, NamiFlowTransitionDirection, NamiFlowWithObject, NamiInitialConfig, NamiLanguageCodes, NamiLogLevel, NamiPaywallActionHandler, NamiPaywallComponentChange, NamiPaywallEvent, NamiPaywallEventVideoMetadata, NamiPaywallLaunchContext, NamiPresentationStyle, NamiProductDetails, NamiProductOffer, NamiProfile, NamiPurchase, NamiPurchaseCompleteResult, NamiPurchaseDetails, NamiPurchasesState, NamiSKU, NamiSKUType, NamiSubscriptionInterval, NamiSubscriptionPeriod, None, NoneSpec, PaywallActionEvent, PaywallHandle, PaywallResultHandler, PaywallSKU, PricingPhase, ProductGroup, Pulse, PulseSpec, PurchaseContext, PurchaseResult, PurchaseValidationRequest, SKU, SKUActionHandler, ScreenInfo, Session, TBaseComponent, TButtonContainer, TCarouselContainer, TCarouselSlide, TCarouselSlidesState, TCollapseContainer, TComponent, TConditionalAttributes, TConditionalComponent, TContainer, TContainerPosition, TCountdownTimerTextComponent, TDevice, TDisabledButton, TField, TFieldSettings, TFlexProductContainer, THeaderFooter, TImageComponent, TInitialState, TMediaTypes, TOffer, TPages, TPaywallContext, TPaywallLaunchContext, TPaywallMedia, TPaywallTemplate, TPlayPauseButton, TProductContainer, TProductGroup, TProgressBarComponent, TProgressIndicatorComponent, TQRCodeComponent, TRadioButton, TRepeatingGrid, TResponsiveGrid, TSegmentPicker, TSegmentPickerItem, TSemverObj, TSpacerComponent, TStack, TSvgImageComponent, TSymbolComponent, TTestObject, TTextComponent, TTextLikeComponent, TTextListComponent, TToggleButtonComponent, TToggleSwitch, TVariablePattern, TVideoComponent, TVolumeButton, TimerState, TransactionRequest, UserAction, UserActionParameters, Wave, WaveSpec };
|
package/dist/index.mjs
CHANGED
|
@@ -96,7 +96,7 @@ const {
|
|
|
96
96
|
// version — stamped by scripts/version.sh
|
|
97
97
|
NAMI_SDK_VERSION = "3.4.0",
|
|
98
98
|
// full package version including dev suffix — stamped by scripts/version.sh
|
|
99
|
-
NAMI_SDK_PACKAGE_VERSION = "3.4.0-dev.
|
|
99
|
+
NAMI_SDK_PACKAGE_VERSION = "3.4.0-dev.202605180118",
|
|
100
100
|
// environments
|
|
101
101
|
PRODUCTION = "production", DEVELOPMENT = "development",
|
|
102
102
|
// error messages
|
|
@@ -63911,6 +63911,230 @@ const convertLocale = (locale) => {
|
|
|
63911
63911
|
return intlLocale.language + (intlLocale.region ?? intlLocale.script ?? '');
|
|
63912
63912
|
};
|
|
63913
63913
|
|
|
63914
|
+
/**
|
|
63915
|
+
* Components that are skipped entirely (they and their subtree do not
|
|
63916
|
+
* contribute text to the page-level announcement).
|
|
63917
|
+
*
|
|
63918
|
+
* - Buttons / interactive controls: their label is appended as the
|
|
63919
|
+
* focused-button suffix, not collected mid-tree. Recursing into them
|
|
63920
|
+
* would double-count the label and pull in non-spoken decorative text.
|
|
63921
|
+
* - Image-like leaves: explicitly skipped per spec.
|
|
63922
|
+
* - Form pickers + QR codes + raw video: not informational text.
|
|
63923
|
+
*/
|
|
63924
|
+
const SKIP_COMPONENTS = new Set([
|
|
63925
|
+
'button',
|
|
63926
|
+
'playPauseButton',
|
|
63927
|
+
'volumeButton',
|
|
63928
|
+
'toggleButton',
|
|
63929
|
+
'toggleSwitch',
|
|
63930
|
+
'radio',
|
|
63931
|
+
'image',
|
|
63932
|
+
'svgImage',
|
|
63933
|
+
'symbol',
|
|
63934
|
+
'qrCode',
|
|
63935
|
+
'videoUrl',
|
|
63936
|
+
'segmentPicker',
|
|
63937
|
+
'segmentPickerItem',
|
|
63938
|
+
]);
|
|
63939
|
+
/** Component values in {@link SKIP_COMPONENTS} that are interactive controls
|
|
63940
|
+
* (as opposed to images / media / non-text leaves). When a text component
|
|
63941
|
+
* shares a container with one of these, the text is treated as that
|
|
63942
|
+
* button's accessible label and excluded from the page composite. */
|
|
63943
|
+
const BUTTON_COMPONENTS = new Set([
|
|
63944
|
+
'button',
|
|
63945
|
+
'playPauseButton',
|
|
63946
|
+
'volumeButton',
|
|
63947
|
+
'toggleButton',
|
|
63948
|
+
'toggleSwitch',
|
|
63949
|
+
'radio',
|
|
63950
|
+
]);
|
|
63951
|
+
/** `namiComponentType` values that are always included in the page
|
|
63952
|
+
* composite — even when their structural position (e.g. a sibling of a
|
|
63953
|
+
* utility button) would otherwise mark them for exclusion. Driven by the
|
|
63954
|
+
* spec's schema table:
|
|
63955
|
+
*
|
|
63956
|
+
* | `namiComponentType: "legalText"` | Legal/T&C content (included in tree-walk) |
|
|
63957
|
+
*/
|
|
63958
|
+
const ALWAYS_INCLUDE_NAMI_TYPES = new Set(['legalText']);
|
|
63959
|
+
/**
|
|
63960
|
+
* Whether a text leaf inside `parent` should be skipped because it acts as
|
|
63961
|
+
* the accessible label for a sibling button.
|
|
63962
|
+
*
|
|
63963
|
+
* Schema pattern this catches: a container whose direct children include
|
|
63964
|
+
* BOTH a text/text-list AND an interactive button (e.g. Page 3's "Login
|
|
63965
|
+
* Group" — `[ text "Already an NFL+ subscriber?", loginButton "Sign In" ]`).
|
|
63966
|
+
* The text describes the button and is announced when the button is
|
|
63967
|
+
* focused, NOT as page-level content. Without this rule, the composite
|
|
63968
|
+
* leaks "Already an NFL+ subscriber? Subscribe to NFL plus button" instead
|
|
63969
|
+
* of "Subscribe to NFL plus button".
|
|
63970
|
+
*
|
|
63971
|
+
* Texts marked `namiComponentType: "legalText"` are exempt — the spec
|
|
63972
|
+
* explicitly opts legal copy in regardless of its container's other
|
|
63973
|
+
* children (e.g. Page 5's "Bottom Wrapper" mixes a body text, a restore
|
|
63974
|
+
* button, and the legal text — the legal text must still be spoken).
|
|
63975
|
+
*/
|
|
63976
|
+
function isButtonSiblingLabel(node, parent) {
|
|
63977
|
+
if (!parent || !Array.isArray(parent.components))
|
|
63978
|
+
return false;
|
|
63979
|
+
if (typeof node.namiComponentType === 'string' &&
|
|
63980
|
+
ALWAYS_INCLUDE_NAMI_TYPES.has(node.namiComponentType)) {
|
|
63981
|
+
return false;
|
|
63982
|
+
}
|
|
63983
|
+
for (const sibling of parent.components) {
|
|
63984
|
+
if (sibling && typeof sibling === 'object') {
|
|
63985
|
+
const s = sibling;
|
|
63986
|
+
if (typeof s.component === 'string' && BUTTON_COMPONENTS.has(s.component)) {
|
|
63987
|
+
return true;
|
|
63988
|
+
}
|
|
63989
|
+
}
|
|
63990
|
+
}
|
|
63991
|
+
return false;
|
|
63992
|
+
}
|
|
63993
|
+
/**
|
|
63994
|
+
* Recursively collect spoken text from a component subtree following the
|
|
63995
|
+
* TV Full-Page Announcement contract.
|
|
63996
|
+
*
|
|
63997
|
+
* Rules (in order):
|
|
63998
|
+
* 1. `null` / `undefined` → contribute nothing.
|
|
63999
|
+
* 2. `hidden: true` → skip the node AND its subtree.
|
|
64000
|
+
* 3. `component` in {@link SKIP_COMPONENTS} → skip the node AND its subtree.
|
|
64001
|
+
* 4. `screenreaderText` set on this container → take that string verbatim
|
|
64002
|
+
* and do NOT recurse (server-supplied override).
|
|
64003
|
+
* 5. `component: "text"` or `"text-list"` → collect, UNLESS the text is a
|
|
64004
|
+
* sibling label for an adjacent button (see
|
|
64005
|
+
* {@link isButtonSiblingLabel}). `namiComponentType: "legalText"` is
|
|
64006
|
+
* exempted from the sibling-label exclusion.
|
|
64007
|
+
* 6. Otherwise → recurse into `components`.
|
|
64008
|
+
*/
|
|
64009
|
+
function collectText(node, parent = null) {
|
|
64010
|
+
if (!node || typeof node !== 'object')
|
|
64011
|
+
return [];
|
|
64012
|
+
const n = node;
|
|
64013
|
+
if (n.hidden === true)
|
|
64014
|
+
return [];
|
|
64015
|
+
if (typeof n.component === 'string' && SKIP_COMPONENTS.has(n.component))
|
|
64016
|
+
return [];
|
|
64017
|
+
// Container-level override: when an enclosing container pre-supplies its own
|
|
64018
|
+
// screenreader text, take it verbatim and stop descending — the customer
|
|
64019
|
+
// has authored a polished announcement for this subtree.
|
|
64020
|
+
if (typeof n.screenreaderText === 'string' && n.screenreaderText.trim().length > 0) {
|
|
64021
|
+
return [n.screenreaderText.trim()];
|
|
64022
|
+
}
|
|
64023
|
+
if (n.component === 'text') {
|
|
64024
|
+
if (isButtonSiblingLabel(n, parent))
|
|
64025
|
+
return [];
|
|
64026
|
+
return typeof n.text === 'string' && n.text.length > 0 ? [n.text] : [];
|
|
64027
|
+
}
|
|
64028
|
+
if (n.component === 'text-list') {
|
|
64029
|
+
if (isButtonSiblingLabel(n, parent))
|
|
64030
|
+
return [];
|
|
64031
|
+
if (!Array.isArray(n.texts))
|
|
64032
|
+
return [];
|
|
64033
|
+
return n.texts.filter((t) => typeof t === 'string' && t.length > 0);
|
|
64034
|
+
}
|
|
64035
|
+
if (Array.isArray(n.components)) {
|
|
64036
|
+
const out = [];
|
|
64037
|
+
for (const child of n.components) {
|
|
64038
|
+
out.push(...collectText(child, n));
|
|
64039
|
+
}
|
|
64040
|
+
return out;
|
|
64041
|
+
}
|
|
64042
|
+
return [];
|
|
64043
|
+
}
|
|
64044
|
+
function collectHeaderFooter(slot) {
|
|
64045
|
+
if (!Array.isArray(slot))
|
|
64046
|
+
return [];
|
|
64047
|
+
// The slot itself acts as the synthetic parent so a top-level text inside
|
|
64048
|
+
// a header/footer that sits alongside a top-level button is treated as a
|
|
64049
|
+
// button label and excluded.
|
|
64050
|
+
const syntheticParent = { components: slot };
|
|
64051
|
+
const out = [];
|
|
64052
|
+
for (const node of slot) {
|
|
64053
|
+
out.push(...collectText(node, syntheticParent));
|
|
64054
|
+
}
|
|
64055
|
+
return out;
|
|
64056
|
+
}
|
|
64057
|
+
/**
|
|
64058
|
+
* Join an ordered list of collected text fragments into a single speakable
|
|
64059
|
+
* string. Each fragment is trimmed; empty fragments are dropped. Author
|
|
64060
|
+
* terminators (`.`, `!`, `?`) are preserved so emphasis carries through to
|
|
64061
|
+
* the screenreader; only fragments that lack a terminator get `". "`
|
|
64062
|
+
* inserted before the next fragment. The result is a string that reads
|
|
64063
|
+
* naturally without double-punctuation like `"...REACH!. Watch..."`.
|
|
64064
|
+
*/
|
|
64065
|
+
function joinFragments(fragments) {
|
|
64066
|
+
let out = '';
|
|
64067
|
+
for (const raw of fragments) {
|
|
64068
|
+
const trimmed = raw.trim();
|
|
64069
|
+
if (!trimmed)
|
|
64070
|
+
continue;
|
|
64071
|
+
if (!out) {
|
|
64072
|
+
out = trimmed;
|
|
64073
|
+
continue;
|
|
64074
|
+
}
|
|
64075
|
+
const last = out[out.length - 1];
|
|
64076
|
+
const endsWithTerminator = last === '.' || last === '!' || last === '?';
|
|
64077
|
+
out += (endsWithTerminator ? ' ' : '. ') + trimmed;
|
|
64078
|
+
}
|
|
64079
|
+
return out;
|
|
64080
|
+
}
|
|
64081
|
+
/**
|
|
64082
|
+
* Build the full-screen announcement string for a paywall page per the
|
|
64083
|
+
* TV Full-Page Announcement contract
|
|
64084
|
+
* (https://linear.app/nami-product-development/document/tv-full-page-announcement-980f61b96e7c).
|
|
64085
|
+
*
|
|
64086
|
+
* Behaviour:
|
|
64087
|
+
* 1. **Override path.** If `page.screenreaderText` is set, that string is
|
|
64088
|
+
* returned verbatim (followed by `focusedButtonLabel` if provided and
|
|
64089
|
+
* not already present at the tail). Server-authored polished
|
|
64090
|
+
* announcements win over client-side aggregation.
|
|
64091
|
+
*
|
|
64092
|
+
* 2. **Tree-walk fallback.** Otherwise, walk the page's containers in
|
|
64093
|
+
* reading order — `header` → `backgroundContainer` → `contentContainer`
|
|
64094
|
+
* → `footer` — collecting visible `text` / `text-list` descendants in
|
|
64095
|
+
* source order. `image`, `hidden: true` subtrees, interactive controls
|
|
64096
|
+
* (buttons / toggles), and non-text leaves are skipped. The
|
|
64097
|
+
* `focusedButtonLabel` is appended as the final sentence.
|
|
64098
|
+
*
|
|
64099
|
+
* SDKs invoke this once per page on the first focus of the default CTA, and
|
|
64100
|
+
* speak the result via their platform TTS API. On subsequent focuses within
|
|
64101
|
+
* the same page, the SDK announces only the focused element's own label —
|
|
64102
|
+
* this helper is NOT re-called.
|
|
64103
|
+
*
|
|
64104
|
+
* @param page The paywall page to announce.
|
|
64105
|
+
* @param focusedButtonLabel The resolved label of the button that
|
|
64106
|
+
* triggered the announcement (typically the
|
|
64107
|
+
* page's default-focused CTA). For
|
|
64108
|
+
* subscription-plan CTAs this is the dynamic
|
|
64109
|
+
* `"{price} {period} subscription plan button"`
|
|
64110
|
+
* string the backend already resolved.
|
|
64111
|
+
* @returns A single string ready to pass to a platform TTS API. Empty when
|
|
64112
|
+
* the page has no spoken content and no focused button label.
|
|
64113
|
+
*/
|
|
64114
|
+
function aggregateScreenreaderText(page, focusedButtonLabel = '') {
|
|
64115
|
+
const trimmedLabel = focusedButtonLabel.trim();
|
|
64116
|
+
// Override path
|
|
64117
|
+
if (typeof page.screenreaderText === 'string' && page.screenreaderText.trim().length > 0) {
|
|
64118
|
+
const override = page.screenreaderText.trim();
|
|
64119
|
+
if (!trimmedLabel)
|
|
64120
|
+
return override;
|
|
64121
|
+
// If the server-authored override already ends with the focused button
|
|
64122
|
+
// label, don't append it again.
|
|
64123
|
+
if (override.toLowerCase().endsWith(trimmedLabel.toLowerCase()))
|
|
64124
|
+
return override;
|
|
64125
|
+
return joinFragments([override, trimmedLabel]);
|
|
64126
|
+
}
|
|
64127
|
+
// Tree-walk path
|
|
64128
|
+
const fragments = [];
|
|
64129
|
+
fragments.push(...collectHeaderFooter(page.header));
|
|
64130
|
+
fragments.push(...collectText(page.backgroundContainer));
|
|
64131
|
+
fragments.push(...collectText(page.contentContainer));
|
|
64132
|
+
fragments.push(...collectHeaderFooter(page.footer));
|
|
64133
|
+
if (trimmedLabel)
|
|
64134
|
+
fragments.push(trimmedLabel);
|
|
64135
|
+
return joinFragments(fragments);
|
|
64136
|
+
}
|
|
64137
|
+
|
|
63914
64138
|
function namiBuySKU(skuRefId) {
|
|
63915
64139
|
if (!hasPurchaseManagement("NamiPurchaseManager.buySKU")) {
|
|
63916
64140
|
return;
|
|
@@ -63925,4 +64149,4 @@ function namiBuySKU(skuRefId) {
|
|
|
63925
64149
|
return result;
|
|
63926
64150
|
}
|
|
63927
64151
|
|
|
63928
|
-
export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, activateEntitlementByPurchase, activeEntitlements, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateMinSDKVersion };
|
|
64152
|
+
export { ALREADY_CONFIGURED, ANONYMOUS_MODE, ANONYMOUS_MODE_ALREADY_OFF, ANONYMOUS_MODE_ALREADY_ON, ANONYMOUS_MODE_LOGIN_NOT_ALLOWED, ANONYMOUS_UUID, APIError, API_ACTIVE_ENTITLEMENTS, API_CAMPAIGN_RULES, API_CAMPAIGN_SESSION_TIMESTAMP, API_CONFIG, API_MAX_CALLS_LIMIT, API_PAYWALLS, API_PRODUCTS, API_RETRY_DELAY_SEC, API_TIMEOUT_LIMIT, API_VERSION, AUTH_DEVICE, AVAILABLE_ACTIVE_ENTITLEMENTS_CHANGED, AVAILABLE_CAMPAIGNS_CHANGED, AccountStateAction, AnonymousCDPError, AnonymousLoginError, AnonymousModeAlreadyOffError, AnonymousModeAlreadyOnError, BASE_STAGING_URL, BASE_URL, BASE_URL_PATH, BadRequestError, BasicNamiFlow, BorderMap, BorderSideMap, CAMPAIGN_NOT_AVAILABLE, CUSTOMER_ATTRIBUTES_KEY_PREFIX, CUSTOMER_JOURNEY_STATE_CHANGED, CUSTOM_HOST_PREFIX, CampaignNotAvailableError, CampaignRuleConversionEventType, CampaignRuleRepository, Capabilities, ClientError, ConfigRepository, ConflictError, CustomerJourneyRepository, DEVELOPMENT, DEVICE_API_TIMEOUT_LIMIT, DEVICE_ID_NOT_SET, DEVICE_ID_REQUIRED, DeviceIDRequiredError, DeviceRepository, EXTENDED_CLIENT_INFO_DELIMITER, EXTENDED_CLIENT_INFO_PREFIX, EXTENDED_PLATFORM, EXTENDED_PLATFORM_VERSION, EXTERNAL_ID_REQUIRED, EntitlementRepository, EntitlementUtils, ExternalIDRequiredError, FLOW_SCREENS_NOT_AVAILABLE, FlowScreensNotAvailableError, HTML_REGEX, INITIAL_APP_CONFIG, INITIAL_CAMPAIGN_RULES, INITIAL_PAYWALLS, INITIAL_PRODUCTS, INITIAL_SESSION_COUNTER_VALUE, INITIAL_SUCCESS, InternalServerError, KEY_SESSION_COUNTER, LIQUID_VARIABLE_REGEX, LOCAL_NAMI_ENTITLEMENTS, LOG_HTTP_REQUESTS, LOG_HTTP_TRAFFIC, LaunchCampaignError, LaunchContextResolver, LogLevel, NAMI_CONFIGURATION, NAMI_CUSTOMER_JOURNEY_STATE, NAMI_LANGUAGE_CODE, NAMI_LAST_IMPRESSION_ID, NAMI_LAUNCH_ID, NAMI_PROFILE, NAMI_PURCHASE_CHANNEL, NAMI_PURCHASE_IMPRESSION_ID, NAMI_SDK_PACKAGE_VERSION, NAMI_SDK_VERSION, NAMI_SESSION_ID, NAMI_STORAGE_KEYS, Nami, NamiAPI, NamiAnimationType, NamiCampaignManager, NamiCampaignRuleType, NamiConditionEvaluator, NamiCustomerManager, NamiEntitlementManager, NamiEventEmitter, NamiFlow, NamiFlowActionFunction, NamiFlowManager, NamiFlowStepType, NamiPaywallAction, NamiPaywallManager, PaywallManagerEvents as NamiPaywallManagerEvents, NamiProfileManager, NamiPurchaseManager, NamiRefs, NamiReservedActions, NotFoundError, PAYWALL_ACTION_EVENT, PLATFORM_ID_REQUIRED, PRODUCTION, PaywallManagerEvents, PaywallRepository, PaywallState, PlacementLabelResolver, PlatformIDRequiredError, ProductRepository, RECONFIG_SUCCESS, RetryLimitExceededError, SDKNotInitializedError, SDK_NOT_INITIALIZED, SERVER_NAMI_ENTITLEMENTS, SESSION_REQUIRED, SHOULD_SHOW_LOADING_INDICATOR, SKU_TEXT_REGEX, SMART_TEXT_PATTERN, STATUS_BAD_REQUEST, STATUS_CONFLICT, STATUS_INTERNAL_SERVER_ERROR, STATUS_NOT_FOUND, STATUS_SUCCESS, SessionService, SimpleEventTarget, StorageService, UNABLE_TO_UPDATE_CDP_ID, USE_STAGING_API, VALIDATE_PRODUCT_GROUPS, VAR_REGEX, activateEntitlementByPurchase, activeEntitlements, aggregateScreenreaderText, allCampaigns, allPaywalls, applyEntitlementActivation, audienceSplitPosition, bestUrlCampaignMatch, bigintToUuid, checkAnySkuHasPromoOffer, checkAnySkuHasTrialOffer, convertISO8601PeriodToText, convertLocale, convertOfferToPricingPhase, createNamiEntitlements, currentSku, empty, extractStandardPricingPhases, formatDate, formattedPrice, generateUUID, getApiCampaigns, getApiPaywalls, getBaseUrl, getBillingPeriodNumber, getCurrencyFormat, getDeviceData, getDeviceFormFactor, getDeviceScaleFactor, getEffectiveWebStyle, getEntitlementRefIdsForSku, getExtendedClientInfo, getFreeTrialPeriod, getInitialCampaigns, getInitialPaywalls, getPaywall, getPaywallDataFromLabel, getPercentagePriceDifference, getPeriodNumberInDays, getPeriodNumberInWeeks, getPlatformAdapters, getPriceDifference, getPricePerMonth, getProductDetail, getPurchaseAdapter, getReferenceSku, getSkuProductDetailKeys, getSkuSmartTextValue, getSlideSmartTextValue, getStandardBillingPeriod, getTranslate, getUrlParams, handleErrors, hasAllPaywalls, hasCapability, hasPurchaseManagement, initialState, invokeHandler, isAnonymousMode, isInitialConfigCompressed, isNamiFlowCampaign, isSubscription, isValidISODate, isValidUrl, logger, mapAnonymousCampaigns, namiBuySKU, normalizeLaunchContext, parseToSemver, postConversion, productDetail, registerPlatformAdapters, registerPurchaseAdapter, selectSegment, setActiveNamiEntitlements, shouldValidateProductGroups, skuItems, skuMapFromEntitlements, storageService, toDouble, toNamiEntitlements, toNamiSKU, tryParseB64Gzip, tryParseJson, updateRelatedSKUsForNamiEntitlement, uuidFromSplitPosition, validateMinSDKVersion };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@namiml/sdk-core",
|
|
3
|
-
"version": "3.4.0-dev.
|
|
3
|
+
"version": "3.4.0-dev.202605180118",
|
|
4
4
|
"description": "Platform-agnostic core for the Nami SDK — business logic, API, types, and state management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|