@namiml/sdk-core 3.4.4-dev.202606300501 → 3.4.4-dev.202606302327
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 +125 -7
- package/dist/index.d.ts +12 -1
- package/dist/index.mjs +125 -7
- 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.4",
|
|
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.4-dev.
|
|
101
|
+
NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.4-dev.202606302327",
|
|
102
102
|
// environments
|
|
103
103
|
PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
|
|
104
104
|
// error messages
|
|
@@ -12392,6 +12392,38 @@ let NamiProfileManager$1 = class NamiProfileManager {
|
|
|
12392
12392
|
};
|
|
12393
12393
|
NamiProfileManager$1.instance = new NamiProfileManager$1();
|
|
12394
12394
|
|
|
12395
|
+
/**
|
|
12396
|
+
* Two-slot, session-scoped store of the most recent launches.
|
|
12397
|
+
*
|
|
12398
|
+
* On `record(rec)` the existing `current` slot is demoted to `previous` and
|
|
12399
|
+
* `rec` becomes the new `current`. Flow conditions read `previous`, which
|
|
12400
|
+
* structurally excludes the placement that is launching right now (the PPO
|
|
12401
|
+
* requirement): the first launch in a session leaves `previous` undefined.
|
|
12402
|
+
*
|
|
12403
|
+
* State is in-memory only and cleared via `reset()` (wired into `Nami.reset()`).
|
|
12404
|
+
*/
|
|
12405
|
+
class LastLaunchStore {
|
|
12406
|
+
static get shared() {
|
|
12407
|
+
if (!this._shared)
|
|
12408
|
+
this._shared = new LastLaunchStore();
|
|
12409
|
+
return this._shared;
|
|
12410
|
+
}
|
|
12411
|
+
/** The Last Launch — the launch immediately before the current one. */
|
|
12412
|
+
get previous() {
|
|
12413
|
+
return this._previous;
|
|
12414
|
+
}
|
|
12415
|
+
/** Demote `current` → `previous`, then set `current` to `rec`. */
|
|
12416
|
+
record(rec) {
|
|
12417
|
+
this._previous = this._current;
|
|
12418
|
+
this._current = rec;
|
|
12419
|
+
}
|
|
12420
|
+
/** Clear both slots. */
|
|
12421
|
+
reset() {
|
|
12422
|
+
this._current = undefined;
|
|
12423
|
+
this._previous = undefined;
|
|
12424
|
+
}
|
|
12425
|
+
}
|
|
12426
|
+
|
|
12395
12427
|
var _Nami_isInitialized;
|
|
12396
12428
|
// NamiFlowManager is intentionally NOT imported at top level — it
|
|
12397
12429
|
// transitively imports back to this module (`Nami` for logging), and
|
|
@@ -12432,6 +12464,7 @@ class Nami {
|
|
|
12432
12464
|
PaywallState.reset();
|
|
12433
12465
|
NamiAPI.reset();
|
|
12434
12466
|
CampaignRuleRepository.instance.reset();
|
|
12467
|
+
LastLaunchStore.shared.reset();
|
|
12435
12468
|
// Lazy import to avoid a load-order cycle (NamiFlowManager imports
|
|
12436
12469
|
// back to this module for `Nami.instance.maxLogging`).
|
|
12437
12470
|
const { NamiFlowManager } = await Promise.resolve().then(function () { return NamiFlowManager$3; });
|
|
@@ -12732,6 +12765,7 @@ const FilterOperator = {
|
|
|
12732
12765
|
NOT_EQUALS: 'not_equals',
|
|
12733
12766
|
NOT_I_EQUALS: 'not_i_equals',
|
|
12734
12767
|
NOT_CONTAINS: 'not_contains',
|
|
12768
|
+
CONTAINS: 'contains',
|
|
12735
12769
|
SET: 'set',
|
|
12736
12770
|
NOT_SET: 'not_set',
|
|
12737
12771
|
GREATER_THAN: 'greater_than',
|
|
@@ -12864,14 +12898,24 @@ class NamiConditionEvaluator {
|
|
|
12864
12898
|
case FilterOperator.NOT_I_EQUALS: {
|
|
12865
12899
|
return filter.values.every(expected => !this.strictEquals(resolvedValue, expected, true));
|
|
12866
12900
|
}
|
|
12901
|
+
case FilterOperator.CONTAINS: {
|
|
12902
|
+
// Array (tags): ANY-match by exact membership. String: case-sensitive substring.
|
|
12903
|
+
// `includes` covers both (Array.prototype.includes / String.prototype.includes).
|
|
12904
|
+
if (Array.isArray(resolvedValue) || typeof resolvedValue === 'string') {
|
|
12905
|
+
return filter.values.some(expected => typeof expected === 'string' && resolvedValue.includes(expected));
|
|
12906
|
+
}
|
|
12907
|
+
return false;
|
|
12908
|
+
}
|
|
12867
12909
|
case FilterOperator.NOT_CONTAINS: {
|
|
12868
|
-
|
|
12910
|
+
if (Array.isArray(resolvedValue)) {
|
|
12911
|
+
return filter.values.every(expected => !(typeof expected === 'string' && resolvedValue.includes(expected)));
|
|
12912
|
+
}
|
|
12913
|
+
return filter.values.every(expected => {
|
|
12869
12914
|
if (typeof resolvedValue === 'string' && typeof expected === 'string') {
|
|
12870
12915
|
return !resolvedValue.includes(expected);
|
|
12871
12916
|
}
|
|
12872
12917
|
return true;
|
|
12873
12918
|
});
|
|
12874
|
-
return result;
|
|
12875
12919
|
}
|
|
12876
12920
|
case FilterOperator.GREATER_THAN:
|
|
12877
12921
|
case FilterOperator.GREATER_THAN_OR_EQUAL_TO:
|
|
@@ -13016,14 +13060,22 @@ class BaseNamespaceResolver {
|
|
|
13016
13060
|
|
|
13017
13061
|
// import { logger } from "../../services/logger.service";
|
|
13018
13062
|
class LaunchContextResolver extends BaseNamespaceResolver {
|
|
13019
|
-
constructor(context) {
|
|
13063
|
+
constructor(context, campaign) {
|
|
13020
13064
|
super();
|
|
13021
13065
|
this.namespace = 'LaunchContext';
|
|
13022
13066
|
this.context = context;
|
|
13067
|
+
this.campaign = campaign;
|
|
13023
13068
|
this.register();
|
|
13024
13069
|
}
|
|
13025
13070
|
resolveValue(keyPath) {
|
|
13026
13071
|
// logger.debug('[LaunchContextResolver]', 'Resolving keyPath', keyPath);
|
|
13072
|
+
// Current-launch placement metadata sourced from the launching campaign.
|
|
13073
|
+
if (keyPath === 'placement') {
|
|
13074
|
+
return this.campaign?.value ?? undefined;
|
|
13075
|
+
}
|
|
13076
|
+
if (keyPath === 'tags') {
|
|
13077
|
+
return this.campaign?.placement_tags ?? undefined;
|
|
13078
|
+
}
|
|
13027
13079
|
if (keyPath.startsWith('customAttributes.')) {
|
|
13028
13080
|
const innerPath = keyPath.substring('customAttributes.'.length);
|
|
13029
13081
|
return this.resolveKeyPath(innerPath, this.context.customAttributes);
|
|
@@ -13446,6 +13498,50 @@ class PlacementLabelResolver extends BaseNamespaceResolver {
|
|
|
13446
13498
|
}
|
|
13447
13499
|
}
|
|
13448
13500
|
|
|
13501
|
+
/**
|
|
13502
|
+
* Resolves flow conditions against the *previous* launch (the "Last Launch"),
|
|
13503
|
+
* exposed under the `LastLaunchContext` namespace (NAM-1937):
|
|
13504
|
+
*
|
|
13505
|
+
* - `LastLaunchContext.placement` → previous launch's placement label
|
|
13506
|
+
* - `LastLaunchContext.tags` → previous launch's placement tags
|
|
13507
|
+
* - `LastLaunchContext.customAttributes.<k>` → a custom attribute from the previous launch
|
|
13508
|
+
*
|
|
13509
|
+
* When there is no previous launch (first launch of the session), every key
|
|
13510
|
+
* resolves to `undefined`. The condition evaluator's existing null-state
|
|
13511
|
+
* handling then makes `equals`/`set` false and `not_set` true — so the
|
|
13512
|
+
* resolver deliberately does not special-case any operator.
|
|
13513
|
+
*/
|
|
13514
|
+
class LastLaunchContextResolver extends BaseNamespaceResolver {
|
|
13515
|
+
constructor() {
|
|
13516
|
+
super(...arguments);
|
|
13517
|
+
this.namespace = "LastLaunchContext";
|
|
13518
|
+
}
|
|
13519
|
+
// Exposed publicly so callers register explicitly (consistent with how this
|
|
13520
|
+
// resolver is wired from NamiFlow and exercised in tests). Unlike the other
|
|
13521
|
+
// resolvers it does not auto-register from its constructor because it reads
|
|
13522
|
+
// live store state rather than capturing per-launch context.
|
|
13523
|
+
register() {
|
|
13524
|
+
super.register();
|
|
13525
|
+
}
|
|
13526
|
+
resolveValue(keyPath) {
|
|
13527
|
+
const previous = LastLaunchStore.shared.previous;
|
|
13528
|
+
if (!previous) {
|
|
13529
|
+
return undefined;
|
|
13530
|
+
}
|
|
13531
|
+
if (keyPath === "placement") {
|
|
13532
|
+
return previous.placementLabel;
|
|
13533
|
+
}
|
|
13534
|
+
if (keyPath === "tags") {
|
|
13535
|
+
return previous.tags;
|
|
13536
|
+
}
|
|
13537
|
+
if (keyPath.startsWith("customAttributes.")) {
|
|
13538
|
+
const innerPath = keyPath.substring("customAttributes.".length);
|
|
13539
|
+
return this.resolveKeyPath(innerPath, previous.customAttributes);
|
|
13540
|
+
}
|
|
13541
|
+
return undefined;
|
|
13542
|
+
}
|
|
13543
|
+
}
|
|
13544
|
+
|
|
13449
13545
|
/**
|
|
13450
13546
|
* Represents the possible account actions states that can be returned by callback registered from
|
|
13451
13547
|
* [NamiCustomerManager.registerAccountStateHandler]
|
|
@@ -14749,7 +14845,7 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14749
14845
|
applyLaunchContextAttributes(attrs) {
|
|
14750
14846
|
if (!this.context) {
|
|
14751
14847
|
this.context = { customAttributes: {} };
|
|
14752
|
-
new LaunchContextResolver(this.context);
|
|
14848
|
+
new LaunchContextResolver(this.context, this.campaign);
|
|
14753
14849
|
}
|
|
14754
14850
|
// Guard against a runtime-supplied context that omits customAttributes
|
|
14755
14851
|
// (the field is required by the type but may be absent in untyped JSON).
|
|
@@ -14759,12 +14855,13 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14759
14855
|
}
|
|
14760
14856
|
registerResolvers(context) {
|
|
14761
14857
|
if (context) {
|
|
14762
|
-
new LaunchContextResolver(context);
|
|
14858
|
+
new LaunchContextResolver(context, this.campaign);
|
|
14763
14859
|
}
|
|
14764
14860
|
new DeviceResolver();
|
|
14765
14861
|
new URLParamsResolver();
|
|
14766
14862
|
new FormStateResolver();
|
|
14767
14863
|
new PlacementLabelResolver(this.campaign);
|
|
14864
|
+
new LastLaunchContextResolver().register();
|
|
14768
14865
|
NamiConditionEvaluator.shared.registerNamespaceResolver('Flow', (identifier) => {
|
|
14769
14866
|
switch (identifier) {
|
|
14770
14867
|
case 'Flow.stepcrumbs':
|
|
@@ -15617,7 +15714,11 @@ let NamiCampaignManager$2 = class NamiCampaignManager {
|
|
|
15617
15714
|
const data = startupTelemetry.firstLaunchLookup(value, () => getPaywallDataFromLabel(value, type));
|
|
15618
15715
|
let paywall = data.paywall;
|
|
15619
15716
|
const campaign = data.campaign;
|
|
15620
|
-
|
|
15717
|
+
// Captured before the degenerate branch below clobbers `paywall` to `{}`,
|
|
15718
|
+
// so a not-found launch is correctly distinguishable from a real one when
|
|
15719
|
+
// deciding whether to record into LastLaunchStore (NAM-1937).
|
|
15720
|
+
const campaignNotFound = !campaign || (!paywall && !campaign.flow);
|
|
15721
|
+
if (campaignNotFound) {
|
|
15621
15722
|
let error;
|
|
15622
15723
|
if (!label && !withUrl) {
|
|
15623
15724
|
error = exports.LaunchCampaignError.DEFAULT_CAMPAIGN_NOT_FOUND;
|
|
@@ -15668,6 +15769,23 @@ let NamiCampaignManager$2 = class NamiCampaignManager {
|
|
|
15668
15769
|
resultCallback(false, exports.LaunchCampaignError.FLOW_SCREEN_DATA_UNAVILABLE);
|
|
15669
15770
|
throw new FlowScreensNotAvailableError();
|
|
15670
15771
|
}
|
|
15772
|
+
// Record this launch into the two-slot Last Launch store BEFORE the flow
|
|
15773
|
+
// resolvers read it. The just-launched placement becomes `current`; the
|
|
15774
|
+
// prior launch is demoted to `previous`, which flow conditions read via
|
|
15775
|
+
// the LastLaunchContext namespace (NAM-1937).
|
|
15776
|
+
//
|
|
15777
|
+
// Only record when a real placement is actually being launched. The
|
|
15778
|
+
// not-found/degenerate path above sets `paywall = {}` and falls through
|
|
15779
|
+
// (the throw is commented out); recording there would store a bogus
|
|
15780
|
+
// failed-launch record AND demote the real `current`, breaking the
|
|
15781
|
+
// two-slot/PPO guarantee for the next real launch.
|
|
15782
|
+
if (!campaignNotFound) {
|
|
15783
|
+
LastLaunchStore.shared.record({
|
|
15784
|
+
placementLabel: campaign?.value ?? value ?? "",
|
|
15785
|
+
tags: campaign?.placement_tags ?? [],
|
|
15786
|
+
customAttributes: (context?.customAttributes ?? {}),
|
|
15787
|
+
});
|
|
15788
|
+
}
|
|
15671
15789
|
const component = getPlatformAdapters().ui.createPaywall(type, value, context);
|
|
15672
15790
|
// Generate and store launch ID for successful campaign launch
|
|
15673
15791
|
const launchId = generateUUID();
|
package/dist/index.d.ts
CHANGED
|
@@ -91,6 +91,7 @@ declare const FilterOperator: {
|
|
|
91
91
|
readonly NOT_EQUALS: "not_equals";
|
|
92
92
|
readonly NOT_I_EQUALS: "not_i_equals";
|
|
93
93
|
readonly NOT_CONTAINS: "not_contains";
|
|
94
|
+
readonly CONTAINS: "contains";
|
|
94
95
|
readonly SET: "set";
|
|
95
96
|
readonly NOT_SET: "not_set";
|
|
96
97
|
readonly GREATER_THAN: "greater_than";
|
|
@@ -1055,6 +1056,7 @@ interface NamiCampaign {
|
|
|
1055
1056
|
page_urls?: Record<string, string> | null;
|
|
1056
1057
|
type: string | NamiCampaignRuleType;
|
|
1057
1058
|
value?: string | null;
|
|
1059
|
+
placement_tags?: string[];
|
|
1058
1060
|
form_factors: FormFactor[];
|
|
1059
1061
|
external_segment: string | null;
|
|
1060
1062
|
conversion_event_type?: CampaignRuleConversionEventType | null;
|
|
@@ -1486,6 +1488,10 @@ type TResponsiveGrid = TBaseComponent & {
|
|
|
1486
1488
|
groupBy?: string;
|
|
1487
1489
|
groupHeaderTemplate?: TComponent;
|
|
1488
1490
|
timeline?: TTimelineRail;
|
|
1491
|
+
/** Flat lists only (no groupBy): render at most the first N eligible items. Ignored when groupBy is present. */
|
|
1492
|
+
max?: number;
|
|
1493
|
+
/** Grouped lists only (groupBy present): render at most the first N eligible items per group. Ignored when groupBy is absent. */
|
|
1494
|
+
groupMax?: number;
|
|
1489
1495
|
};
|
|
1490
1496
|
type TRepeatingGrid = TBaseComponent & {
|
|
1491
1497
|
component: "repeatingGrid";
|
|
@@ -1499,6 +1505,10 @@ type TRepeatingGrid = TBaseComponent & {
|
|
|
1499
1505
|
itemAlignment?: string;
|
|
1500
1506
|
groupBy?: string;
|
|
1501
1507
|
groupHeaderTemplate?: TComponent;
|
|
1508
|
+
/** Flat lists only (no groupBy): render at most the first N eligible items. Ignored when groupBy is present. */
|
|
1509
|
+
max?: number;
|
|
1510
|
+
/** Grouped lists only (groupBy present): render at most the first N eligible items per group. Ignored when groupBy is absent. */
|
|
1511
|
+
groupMax?: number;
|
|
1502
1512
|
};
|
|
1503
1513
|
type TToggleSwitch = TBaseComponent & {
|
|
1504
1514
|
component: 'toggleSwitch';
|
|
@@ -3505,7 +3515,8 @@ declare abstract class BaseNamespaceResolver {
|
|
|
3505
3515
|
declare class LaunchContextResolver extends BaseNamespaceResolver {
|
|
3506
3516
|
protected readonly namespace = "LaunchContext";
|
|
3507
3517
|
private context;
|
|
3508
|
-
|
|
3518
|
+
private campaign?;
|
|
3519
|
+
constructor(context: NamiPaywallLaunchContext, campaign?: NamiCampaign);
|
|
3509
3520
|
protected resolveValue(keyPath: string): any;
|
|
3510
3521
|
}
|
|
3511
3522
|
|
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.4",
|
|
98
98
|
// full package version including dev suffix — stamped by scripts/version.sh
|
|
99
|
-
NAMI_SDK_PACKAGE_VERSION = "3.4.4-dev.
|
|
99
|
+
NAMI_SDK_PACKAGE_VERSION = "3.4.4-dev.202606302327",
|
|
100
100
|
// environments
|
|
101
101
|
PRODUCTION = "production", DEVELOPMENT = "development",
|
|
102
102
|
// error messages
|
|
@@ -12390,6 +12390,38 @@ let NamiProfileManager$1 = class NamiProfileManager {
|
|
|
12390
12390
|
};
|
|
12391
12391
|
NamiProfileManager$1.instance = new NamiProfileManager$1();
|
|
12392
12392
|
|
|
12393
|
+
/**
|
|
12394
|
+
* Two-slot, session-scoped store of the most recent launches.
|
|
12395
|
+
*
|
|
12396
|
+
* On `record(rec)` the existing `current` slot is demoted to `previous` and
|
|
12397
|
+
* `rec` becomes the new `current`. Flow conditions read `previous`, which
|
|
12398
|
+
* structurally excludes the placement that is launching right now (the PPO
|
|
12399
|
+
* requirement): the first launch in a session leaves `previous` undefined.
|
|
12400
|
+
*
|
|
12401
|
+
* State is in-memory only and cleared via `reset()` (wired into `Nami.reset()`).
|
|
12402
|
+
*/
|
|
12403
|
+
class LastLaunchStore {
|
|
12404
|
+
static get shared() {
|
|
12405
|
+
if (!this._shared)
|
|
12406
|
+
this._shared = new LastLaunchStore();
|
|
12407
|
+
return this._shared;
|
|
12408
|
+
}
|
|
12409
|
+
/** The Last Launch — the launch immediately before the current one. */
|
|
12410
|
+
get previous() {
|
|
12411
|
+
return this._previous;
|
|
12412
|
+
}
|
|
12413
|
+
/** Demote `current` → `previous`, then set `current` to `rec`. */
|
|
12414
|
+
record(rec) {
|
|
12415
|
+
this._previous = this._current;
|
|
12416
|
+
this._current = rec;
|
|
12417
|
+
}
|
|
12418
|
+
/** Clear both slots. */
|
|
12419
|
+
reset() {
|
|
12420
|
+
this._current = undefined;
|
|
12421
|
+
this._previous = undefined;
|
|
12422
|
+
}
|
|
12423
|
+
}
|
|
12424
|
+
|
|
12393
12425
|
var _Nami_isInitialized;
|
|
12394
12426
|
// NamiFlowManager is intentionally NOT imported at top level — it
|
|
12395
12427
|
// transitively imports back to this module (`Nami` for logging), and
|
|
@@ -12430,6 +12462,7 @@ class Nami {
|
|
|
12430
12462
|
PaywallState.reset();
|
|
12431
12463
|
NamiAPI.reset();
|
|
12432
12464
|
CampaignRuleRepository.instance.reset();
|
|
12465
|
+
LastLaunchStore.shared.reset();
|
|
12433
12466
|
// Lazy import to avoid a load-order cycle (NamiFlowManager imports
|
|
12434
12467
|
// back to this module for `Nami.instance.maxLogging`).
|
|
12435
12468
|
const { NamiFlowManager } = await Promise.resolve().then(function () { return NamiFlowManager$3; });
|
|
@@ -12730,6 +12763,7 @@ const FilterOperator = {
|
|
|
12730
12763
|
NOT_EQUALS: 'not_equals',
|
|
12731
12764
|
NOT_I_EQUALS: 'not_i_equals',
|
|
12732
12765
|
NOT_CONTAINS: 'not_contains',
|
|
12766
|
+
CONTAINS: 'contains',
|
|
12733
12767
|
SET: 'set',
|
|
12734
12768
|
NOT_SET: 'not_set',
|
|
12735
12769
|
GREATER_THAN: 'greater_than',
|
|
@@ -12862,14 +12896,24 @@ class NamiConditionEvaluator {
|
|
|
12862
12896
|
case FilterOperator.NOT_I_EQUALS: {
|
|
12863
12897
|
return filter.values.every(expected => !this.strictEquals(resolvedValue, expected, true));
|
|
12864
12898
|
}
|
|
12899
|
+
case FilterOperator.CONTAINS: {
|
|
12900
|
+
// Array (tags): ANY-match by exact membership. String: case-sensitive substring.
|
|
12901
|
+
// `includes` covers both (Array.prototype.includes / String.prototype.includes).
|
|
12902
|
+
if (Array.isArray(resolvedValue) || typeof resolvedValue === 'string') {
|
|
12903
|
+
return filter.values.some(expected => typeof expected === 'string' && resolvedValue.includes(expected));
|
|
12904
|
+
}
|
|
12905
|
+
return false;
|
|
12906
|
+
}
|
|
12865
12907
|
case FilterOperator.NOT_CONTAINS: {
|
|
12866
|
-
|
|
12908
|
+
if (Array.isArray(resolvedValue)) {
|
|
12909
|
+
return filter.values.every(expected => !(typeof expected === 'string' && resolvedValue.includes(expected)));
|
|
12910
|
+
}
|
|
12911
|
+
return filter.values.every(expected => {
|
|
12867
12912
|
if (typeof resolvedValue === 'string' && typeof expected === 'string') {
|
|
12868
12913
|
return !resolvedValue.includes(expected);
|
|
12869
12914
|
}
|
|
12870
12915
|
return true;
|
|
12871
12916
|
});
|
|
12872
|
-
return result;
|
|
12873
12917
|
}
|
|
12874
12918
|
case FilterOperator.GREATER_THAN:
|
|
12875
12919
|
case FilterOperator.GREATER_THAN_OR_EQUAL_TO:
|
|
@@ -13014,14 +13058,22 @@ class BaseNamespaceResolver {
|
|
|
13014
13058
|
|
|
13015
13059
|
// import { logger } from "../../services/logger.service";
|
|
13016
13060
|
class LaunchContextResolver extends BaseNamespaceResolver {
|
|
13017
|
-
constructor(context) {
|
|
13061
|
+
constructor(context, campaign) {
|
|
13018
13062
|
super();
|
|
13019
13063
|
this.namespace = 'LaunchContext';
|
|
13020
13064
|
this.context = context;
|
|
13065
|
+
this.campaign = campaign;
|
|
13021
13066
|
this.register();
|
|
13022
13067
|
}
|
|
13023
13068
|
resolveValue(keyPath) {
|
|
13024
13069
|
// logger.debug('[LaunchContextResolver]', 'Resolving keyPath', keyPath);
|
|
13070
|
+
// Current-launch placement metadata sourced from the launching campaign.
|
|
13071
|
+
if (keyPath === 'placement') {
|
|
13072
|
+
return this.campaign?.value ?? undefined;
|
|
13073
|
+
}
|
|
13074
|
+
if (keyPath === 'tags') {
|
|
13075
|
+
return this.campaign?.placement_tags ?? undefined;
|
|
13076
|
+
}
|
|
13025
13077
|
if (keyPath.startsWith('customAttributes.')) {
|
|
13026
13078
|
const innerPath = keyPath.substring('customAttributes.'.length);
|
|
13027
13079
|
return this.resolveKeyPath(innerPath, this.context.customAttributes);
|
|
@@ -13444,6 +13496,50 @@ class PlacementLabelResolver extends BaseNamespaceResolver {
|
|
|
13444
13496
|
}
|
|
13445
13497
|
}
|
|
13446
13498
|
|
|
13499
|
+
/**
|
|
13500
|
+
* Resolves flow conditions against the *previous* launch (the "Last Launch"),
|
|
13501
|
+
* exposed under the `LastLaunchContext` namespace (NAM-1937):
|
|
13502
|
+
*
|
|
13503
|
+
* - `LastLaunchContext.placement` → previous launch's placement label
|
|
13504
|
+
* - `LastLaunchContext.tags` → previous launch's placement tags
|
|
13505
|
+
* - `LastLaunchContext.customAttributes.<k>` → a custom attribute from the previous launch
|
|
13506
|
+
*
|
|
13507
|
+
* When there is no previous launch (first launch of the session), every key
|
|
13508
|
+
* resolves to `undefined`. The condition evaluator's existing null-state
|
|
13509
|
+
* handling then makes `equals`/`set` false and `not_set` true — so the
|
|
13510
|
+
* resolver deliberately does not special-case any operator.
|
|
13511
|
+
*/
|
|
13512
|
+
class LastLaunchContextResolver extends BaseNamespaceResolver {
|
|
13513
|
+
constructor() {
|
|
13514
|
+
super(...arguments);
|
|
13515
|
+
this.namespace = "LastLaunchContext";
|
|
13516
|
+
}
|
|
13517
|
+
// Exposed publicly so callers register explicitly (consistent with how this
|
|
13518
|
+
// resolver is wired from NamiFlow and exercised in tests). Unlike the other
|
|
13519
|
+
// resolvers it does not auto-register from its constructor because it reads
|
|
13520
|
+
// live store state rather than capturing per-launch context.
|
|
13521
|
+
register() {
|
|
13522
|
+
super.register();
|
|
13523
|
+
}
|
|
13524
|
+
resolveValue(keyPath) {
|
|
13525
|
+
const previous = LastLaunchStore.shared.previous;
|
|
13526
|
+
if (!previous) {
|
|
13527
|
+
return undefined;
|
|
13528
|
+
}
|
|
13529
|
+
if (keyPath === "placement") {
|
|
13530
|
+
return previous.placementLabel;
|
|
13531
|
+
}
|
|
13532
|
+
if (keyPath === "tags") {
|
|
13533
|
+
return previous.tags;
|
|
13534
|
+
}
|
|
13535
|
+
if (keyPath.startsWith("customAttributes.")) {
|
|
13536
|
+
const innerPath = keyPath.substring("customAttributes.".length);
|
|
13537
|
+
return this.resolveKeyPath(innerPath, previous.customAttributes);
|
|
13538
|
+
}
|
|
13539
|
+
return undefined;
|
|
13540
|
+
}
|
|
13541
|
+
}
|
|
13542
|
+
|
|
13447
13543
|
/**
|
|
13448
13544
|
* Represents the possible account actions states that can be returned by callback registered from
|
|
13449
13545
|
* [NamiCustomerManager.registerAccountStateHandler]
|
|
@@ -14747,7 +14843,7 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14747
14843
|
applyLaunchContextAttributes(attrs) {
|
|
14748
14844
|
if (!this.context) {
|
|
14749
14845
|
this.context = { customAttributes: {} };
|
|
14750
|
-
new LaunchContextResolver(this.context);
|
|
14846
|
+
new LaunchContextResolver(this.context, this.campaign);
|
|
14751
14847
|
}
|
|
14752
14848
|
// Guard against a runtime-supplied context that omits customAttributes
|
|
14753
14849
|
// (the field is required by the type but may be absent in untyped JSON).
|
|
@@ -14757,12 +14853,13 @@ class NamiFlow extends BasicNamiFlow {
|
|
|
14757
14853
|
}
|
|
14758
14854
|
registerResolvers(context) {
|
|
14759
14855
|
if (context) {
|
|
14760
|
-
new LaunchContextResolver(context);
|
|
14856
|
+
new LaunchContextResolver(context, this.campaign);
|
|
14761
14857
|
}
|
|
14762
14858
|
new DeviceResolver();
|
|
14763
14859
|
new URLParamsResolver();
|
|
14764
14860
|
new FormStateResolver();
|
|
14765
14861
|
new PlacementLabelResolver(this.campaign);
|
|
14862
|
+
new LastLaunchContextResolver().register();
|
|
14766
14863
|
NamiConditionEvaluator.shared.registerNamespaceResolver('Flow', (identifier) => {
|
|
14767
14864
|
switch (identifier) {
|
|
14768
14865
|
case 'Flow.stepcrumbs':
|
|
@@ -15615,7 +15712,11 @@ let NamiCampaignManager$2 = class NamiCampaignManager {
|
|
|
15615
15712
|
const data = startupTelemetry.firstLaunchLookup(value, () => getPaywallDataFromLabel(value, type));
|
|
15616
15713
|
let paywall = data.paywall;
|
|
15617
15714
|
const campaign = data.campaign;
|
|
15618
|
-
|
|
15715
|
+
// Captured before the degenerate branch below clobbers `paywall` to `{}`,
|
|
15716
|
+
// so a not-found launch is correctly distinguishable from a real one when
|
|
15717
|
+
// deciding whether to record into LastLaunchStore (NAM-1937).
|
|
15718
|
+
const campaignNotFound = !campaign || (!paywall && !campaign.flow);
|
|
15719
|
+
if (campaignNotFound) {
|
|
15619
15720
|
let error;
|
|
15620
15721
|
if (!label && !withUrl) {
|
|
15621
15722
|
error = LaunchCampaignError.DEFAULT_CAMPAIGN_NOT_FOUND;
|
|
@@ -15666,6 +15767,23 @@ let NamiCampaignManager$2 = class NamiCampaignManager {
|
|
|
15666
15767
|
resultCallback(false, LaunchCampaignError.FLOW_SCREEN_DATA_UNAVILABLE);
|
|
15667
15768
|
throw new FlowScreensNotAvailableError();
|
|
15668
15769
|
}
|
|
15770
|
+
// Record this launch into the two-slot Last Launch store BEFORE the flow
|
|
15771
|
+
// resolvers read it. The just-launched placement becomes `current`; the
|
|
15772
|
+
// prior launch is demoted to `previous`, which flow conditions read via
|
|
15773
|
+
// the LastLaunchContext namespace (NAM-1937).
|
|
15774
|
+
//
|
|
15775
|
+
// Only record when a real placement is actually being launched. The
|
|
15776
|
+
// not-found/degenerate path above sets `paywall = {}` and falls through
|
|
15777
|
+
// (the throw is commented out); recording there would store a bogus
|
|
15778
|
+
// failed-launch record AND demote the real `current`, breaking the
|
|
15779
|
+
// two-slot/PPO guarantee for the next real launch.
|
|
15780
|
+
if (!campaignNotFound) {
|
|
15781
|
+
LastLaunchStore.shared.record({
|
|
15782
|
+
placementLabel: campaign?.value ?? value ?? "",
|
|
15783
|
+
tags: campaign?.placement_tags ?? [],
|
|
15784
|
+
customAttributes: (context?.customAttributes ?? {}),
|
|
15785
|
+
});
|
|
15786
|
+
}
|
|
15669
15787
|
const component = getPlatformAdapters().ui.createPaywall(type, value, context);
|
|
15670
15788
|
// Generate and store launch ID for successful campaign launch
|
|
15671
15789
|
const launchId = generateUUID();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@namiml/sdk-core",
|
|
3
|
-
"version": "3.4.4-dev.
|
|
3
|
+
"version": "3.4.4-dev.202606302327",
|
|
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",
|