@namiml/sdk-core 3.4.4-dev.202607071644 → 3.4.4-dev.202607072319

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 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.202607071644",
101
+ NAMI_SDK_PACKAGE_VERSION: exports.NAMI_SDK_PACKAGE_VERSION = "3.4.4-dev.202607072319",
102
102
  // environments
103
103
  PRODUCTION: exports.PRODUCTION = "production", DEVELOPMENT: exports.DEVELOPMENT = "development",
104
104
  // error messages
@@ -61151,12 +61151,6 @@ class EntitlementRepository {
61151
61151
  if (isAnonymousMode()) {
61152
61152
  logger.debug("Skipping active entitlements update - anonymous mode");
61153
61153
  }
61154
- if (hasCapability(exports.Capabilities.THIRD_PARTY_TRANSACTIONS)) {
61155
- logger.debug("Skipping active entitlements update - paywalls-only mode");
61156
- }
61157
- if (!hasCapability(exports.Capabilities.ENTITLEMENT_MANAGEMENT)) {
61158
- logger.debug("Skipping active entitlements update - no entitlement_management capability");
61159
- }
61160
61154
  }
61161
61155
  return activeEntitlements;
61162
61156
  }
@@ -61475,10 +61469,12 @@ class NamiRefs {
61475
61469
  }
61476
61470
  const paywallsDuration = Date.now() - paywallsStartTime;
61477
61471
  const totalDuration = Date.now() - startTime;
61478
- logger.info(`Paywall fetch telemetry: strategy=${useIndividual ? "individual" : "bulk"}, ` +
61479
- `count=${paywalls.length}, total=${totalDuration}ms, ` +
61480
- `campaigns=${campaignRulesDuration}ms, paywalls=${paywallsDuration}ms` +
61481
- `${useIndividual ? `, retry_count=${retryCount}` : ""}`);
61472
+ if (Nami.instance.maxLogging) {
61473
+ logger.info(`Paywall fetch telemetry: strategy=${useIndividual ? "individual" : "bulk"}, ` +
61474
+ `count=${paywalls.length}, total=${totalDuration}ms, ` +
61475
+ `campaigns=${campaignRulesDuration}ms, paywalls=${paywallsDuration}ms` +
61476
+ `${useIndividual ? `, retry_count=${retryCount}` : ""}`);
61477
+ }
61482
61478
  return campaignRepo.finalizeCampaignRules(rawCampaigns, paywalls);
61483
61479
  }
61484
61480
  reRenderPaywall() {
@@ -61895,6 +61891,13 @@ const NamiHandoffTag = {
61895
61891
  BuySku: 'buysku',
61896
61892
  UserData: 'userdata',
61897
61893
  };
61894
+ /**
61895
+ * True iff `tag` is a value of the customer-facing `NamiHandoffTag` vocabulary.
61896
+ * Unknown tags are still delivered (pass-through) — this only drives a warning.
61897
+ */
61898
+ function isKnownHandoffTag(tag) {
61899
+ return Object.values(NamiHandoffTag).includes(tag);
61900
+ }
61898
61901
  /**
61899
61902
  * Permission types reportable through a handoff outcome.
61900
61903
  *
@@ -63992,6 +63995,8 @@ function validateForm(validators, formStates) {
63992
63995
  return errors;
63993
63996
  }
63994
63997
 
63998
+ /** Form field key convention for grouped handoffs: `<tag>::<payloadKey>`. */
63999
+ const HANDOFF_GROUP_KEY = /^([^:]+)::(.+)$/;
63995
64000
  class BasicNamiFlow {
63996
64001
  constructor(flowObject = {}) {
63997
64002
  this.id = flowObject.id ?? '';
@@ -64760,13 +64765,25 @@ class NamiFlow extends BasicNamiFlow {
64760
64765
  logger.warn("No formStates available for handoff sequence");
64761
64766
  return;
64762
64767
  }
64763
- const keys = Object.keys(formStates).sort();
64768
+ const keys = Object.keys(formStates);
64764
64769
  if (keys.length === 0) {
64765
64770
  logger.warn("formStates is empty, no handoffs to perform");
64766
64771
  return;
64767
64772
  }
64768
- // filter keys where value === true
64769
- const eligibleKeys = keys.filter((k) => formStates[k] === true);
64773
+ // Grouped path: at least one field uses the `tag::field` convention.
64774
+ if (keys.some((k) => HANDOFF_GROUP_KEY.test(k))) {
64775
+ const groups = this.buildHandoffGroups(formStates);
64776
+ if (groups.length === 0) {
64777
+ logger.info("No handoff groups after grouping");
64778
+ this.manager.resume();
64779
+ return;
64780
+ }
64781
+ this.activeHandoffSequence = { groups };
64782
+ this.resumeNextHandoff();
64783
+ return;
64784
+ }
64785
+ // Legacy path (unchanged): one handoff per true key, tag = field name.
64786
+ const eligibleKeys = keys.sort().filter((k) => formStates[k] === true);
64770
64787
  if (eligibleKeys.length === 0) {
64771
64788
  logger.info("No eligible handoff keys after filtering");
64772
64789
  this.manager.resume();
@@ -64778,6 +64795,34 @@ class NamiFlow extends BasicNamiFlow {
64778
64795
  };
64779
64796
  this.resumeNextHandoff();
64780
64797
  }
64798
+ /**
64799
+ * Reduce a flat `formStates` map into one bucket per distinct handoff tag,
64800
+ * using the `<tag>::<payloadKey>` field-key convention. Insertion order is
64801
+ * preserved (group order = first-appearance). Reserved-prefixed tags are
64802
+ * skipped so they never reach the host handler (NAM-1556). A group is kept
64803
+ * only if at least one of its fields is `true` (a permission the user
64804
+ * declined is not handed off); a kept group's payload rides verbatim,
64805
+ * including its `false` fields.
64806
+ */
64807
+ buildHandoffGroups(formStates) {
64808
+ const buckets = new Map();
64809
+ for (const key of Object.keys(formStates)) {
64810
+ const match = key.match(HANDOFF_GROUP_KEY);
64811
+ if (!match)
64812
+ continue;
64813
+ const [, tag, field] = match;
64814
+ if (tag.startsWith(RESERVED_HANDOFF_TAG_PREFIX)) {
64815
+ logger.warn(`Ignoring reserved handoff group tag '${tag}'`);
64816
+ continue;
64817
+ }
64818
+ if (!buckets.has(tag))
64819
+ buckets.set(tag, {});
64820
+ buckets.get(tag)[field] = formStates[key];
64821
+ }
64822
+ return Array.from(buckets, ([tag, data]) => ({ tag, data }))
64823
+ .filter((group) => Object.values(group.data).some((value) => value === true))
64824
+ .sort((a, b) => a.tag.localeCompare(b.tag));
64825
+ }
64781
64826
  resumeNextHandoff() {
64782
64827
  const sequence = this.activeHandoffSequence;
64783
64828
  if (!sequence) {
@@ -64785,6 +64830,24 @@ class NamiFlow extends BasicNamiFlow {
64785
64830
  this.manager.resume();
64786
64831
  return;
64787
64832
  }
64833
+ // Grouped path.
64834
+ if ("groups" in sequence) {
64835
+ if (sequence.groups.length === 0) {
64836
+ logger.info("All handoffs in the sequence are complete");
64837
+ this.activeHandoffSequence = null;
64838
+ this.manager.resume();
64839
+ return;
64840
+ }
64841
+ const next = sequence.groups.shift(); // safe: length > 0
64842
+ if (!isKnownHandoffTag(next.tag)) {
64843
+ logger.warn(`handoff group tag '${next.tag}' is outside the NamiHandoffTag vocabulary — passing through`);
64844
+ }
64845
+ this.activeHandoffSequence = sequence; // persist mutated groups
64846
+ logger.debug(`Starting grouped handoff for '${next.tag}' → ${JSON.stringify(next.data)}`);
64847
+ this.manager.deliverHandoff(next.tag, next.data);
64848
+ return;
64849
+ }
64850
+ // Legacy path (unchanged).
64788
64851
  if (sequence.remainingKeys.length === 0) {
64789
64852
  logger.info("All handoffs in the sequence are complete");
64790
64853
  this.activeHandoffSequence = null;
@@ -64794,10 +64857,7 @@ class NamiFlow extends BasicNamiFlow {
64794
64857
  const nextKey = sequence.remainingKeys.shift(); // safe: length > 0
64795
64858
  const value = sequence.formStates[nextKey];
64796
64859
  logger.debug(`Starting handoff for ${nextKey} → ${String(value)}`);
64797
- // persist updated sequence (remainingKeys mutated)
64798
64860
  this.activeHandoffSequence = sequence;
64799
- // Route through the manager's single delivery point so v2 handlers get
64800
- // a working complete() for sequence-key handoffs too.
64801
64861
  this.manager.deliverHandoff(`${nextKey}`, undefined);
64802
64862
  }
64803
64863
  }
package/dist/index.d.ts CHANGED
@@ -2249,6 +2249,11 @@ declare class NamiFlow extends BasicNamiFlow {
2249
2249
  activeHandoffSequence: {
2250
2250
  formStates: Record<string, boolean | string>;
2251
2251
  remainingKeys: string[];
2252
+ } | {
2253
+ groups: Array<{
2254
+ tag: string;
2255
+ data: Record<string, boolean | string>;
2256
+ }>;
2252
2257
  } | null;
2253
2258
  currentScreenState: TPaywallContext | null;
2254
2259
  isPaused: boolean;
@@ -2357,6 +2362,16 @@ declare class NamiFlow extends BasicNamiFlow {
2357
2362
  */
2358
2363
  buildUserDataEnvelope(formId: string): NamiUserDataEnvelope;
2359
2364
  flowHandoffFormSequence(): void;
2365
+ /**
2366
+ * Reduce a flat `formStates` map into one bucket per distinct handoff tag,
2367
+ * using the `<tag>::<payloadKey>` field-key convention. Insertion order is
2368
+ * preserved (group order = first-appearance). Reserved-prefixed tags are
2369
+ * skipped so they never reach the host handler (NAM-1556). A group is kept
2370
+ * only if at least one of its fields is `true` (a permission the user
2371
+ * declined is not handed off); a kept group's payload rides verbatim,
2372
+ * including its `false` fields.
2373
+ */
2374
+ private buildHandoffGroups;
2360
2375
  resumeNextHandoff(): void;
2361
2376
  }
2362
2377
 
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.202607071644",
99
+ NAMI_SDK_PACKAGE_VERSION = "3.4.4-dev.202607072319",
100
100
  // environments
101
101
  PRODUCTION = "production", DEVELOPMENT = "development",
102
102
  // error messages
@@ -61149,12 +61149,6 @@ class EntitlementRepository {
61149
61149
  if (isAnonymousMode()) {
61150
61150
  logger.debug("Skipping active entitlements update - anonymous mode");
61151
61151
  }
61152
- if (hasCapability(Capabilities.THIRD_PARTY_TRANSACTIONS)) {
61153
- logger.debug("Skipping active entitlements update - paywalls-only mode");
61154
- }
61155
- if (!hasCapability(Capabilities.ENTITLEMENT_MANAGEMENT)) {
61156
- logger.debug("Skipping active entitlements update - no entitlement_management capability");
61157
- }
61158
61152
  }
61159
61153
  return activeEntitlements;
61160
61154
  }
@@ -61473,10 +61467,12 @@ class NamiRefs {
61473
61467
  }
61474
61468
  const paywallsDuration = Date.now() - paywallsStartTime;
61475
61469
  const totalDuration = Date.now() - startTime;
61476
- logger.info(`Paywall fetch telemetry: strategy=${useIndividual ? "individual" : "bulk"}, ` +
61477
- `count=${paywalls.length}, total=${totalDuration}ms, ` +
61478
- `campaigns=${campaignRulesDuration}ms, paywalls=${paywallsDuration}ms` +
61479
- `${useIndividual ? `, retry_count=${retryCount}` : ""}`);
61470
+ if (Nami.instance.maxLogging) {
61471
+ logger.info(`Paywall fetch telemetry: strategy=${useIndividual ? "individual" : "bulk"}, ` +
61472
+ `count=${paywalls.length}, total=${totalDuration}ms, ` +
61473
+ `campaigns=${campaignRulesDuration}ms, paywalls=${paywallsDuration}ms` +
61474
+ `${useIndividual ? `, retry_count=${retryCount}` : ""}`);
61475
+ }
61480
61476
  return campaignRepo.finalizeCampaignRules(rawCampaigns, paywalls);
61481
61477
  }
61482
61478
  reRenderPaywall() {
@@ -61893,6 +61889,13 @@ const NamiHandoffTag = {
61893
61889
  BuySku: 'buysku',
61894
61890
  UserData: 'userdata',
61895
61891
  };
61892
+ /**
61893
+ * True iff `tag` is a value of the customer-facing `NamiHandoffTag` vocabulary.
61894
+ * Unknown tags are still delivered (pass-through) — this only drives a warning.
61895
+ */
61896
+ function isKnownHandoffTag(tag) {
61897
+ return Object.values(NamiHandoffTag).includes(tag);
61898
+ }
61896
61899
  /**
61897
61900
  * Permission types reportable through a handoff outcome.
61898
61901
  *
@@ -63990,6 +63993,8 @@ function validateForm(validators, formStates) {
63990
63993
  return errors;
63991
63994
  }
63992
63995
 
63996
+ /** Form field key convention for grouped handoffs: `<tag>::<payloadKey>`. */
63997
+ const HANDOFF_GROUP_KEY = /^([^:]+)::(.+)$/;
63993
63998
  class BasicNamiFlow {
63994
63999
  constructor(flowObject = {}) {
63995
64000
  this.id = flowObject.id ?? '';
@@ -64758,13 +64763,25 @@ class NamiFlow extends BasicNamiFlow {
64758
64763
  logger.warn("No formStates available for handoff sequence");
64759
64764
  return;
64760
64765
  }
64761
- const keys = Object.keys(formStates).sort();
64766
+ const keys = Object.keys(formStates);
64762
64767
  if (keys.length === 0) {
64763
64768
  logger.warn("formStates is empty, no handoffs to perform");
64764
64769
  return;
64765
64770
  }
64766
- // filter keys where value === true
64767
- const eligibleKeys = keys.filter((k) => formStates[k] === true);
64771
+ // Grouped path: at least one field uses the `tag::field` convention.
64772
+ if (keys.some((k) => HANDOFF_GROUP_KEY.test(k))) {
64773
+ const groups = this.buildHandoffGroups(formStates);
64774
+ if (groups.length === 0) {
64775
+ logger.info("No handoff groups after grouping");
64776
+ this.manager.resume();
64777
+ return;
64778
+ }
64779
+ this.activeHandoffSequence = { groups };
64780
+ this.resumeNextHandoff();
64781
+ return;
64782
+ }
64783
+ // Legacy path (unchanged): one handoff per true key, tag = field name.
64784
+ const eligibleKeys = keys.sort().filter((k) => formStates[k] === true);
64768
64785
  if (eligibleKeys.length === 0) {
64769
64786
  logger.info("No eligible handoff keys after filtering");
64770
64787
  this.manager.resume();
@@ -64776,6 +64793,34 @@ class NamiFlow extends BasicNamiFlow {
64776
64793
  };
64777
64794
  this.resumeNextHandoff();
64778
64795
  }
64796
+ /**
64797
+ * Reduce a flat `formStates` map into one bucket per distinct handoff tag,
64798
+ * using the `<tag>::<payloadKey>` field-key convention. Insertion order is
64799
+ * preserved (group order = first-appearance). Reserved-prefixed tags are
64800
+ * skipped so they never reach the host handler (NAM-1556). A group is kept
64801
+ * only if at least one of its fields is `true` (a permission the user
64802
+ * declined is not handed off); a kept group's payload rides verbatim,
64803
+ * including its `false` fields.
64804
+ */
64805
+ buildHandoffGroups(formStates) {
64806
+ const buckets = new Map();
64807
+ for (const key of Object.keys(formStates)) {
64808
+ const match = key.match(HANDOFF_GROUP_KEY);
64809
+ if (!match)
64810
+ continue;
64811
+ const [, tag, field] = match;
64812
+ if (tag.startsWith(RESERVED_HANDOFF_TAG_PREFIX)) {
64813
+ logger.warn(`Ignoring reserved handoff group tag '${tag}'`);
64814
+ continue;
64815
+ }
64816
+ if (!buckets.has(tag))
64817
+ buckets.set(tag, {});
64818
+ buckets.get(tag)[field] = formStates[key];
64819
+ }
64820
+ return Array.from(buckets, ([tag, data]) => ({ tag, data }))
64821
+ .filter((group) => Object.values(group.data).some((value) => value === true))
64822
+ .sort((a, b) => a.tag.localeCompare(b.tag));
64823
+ }
64779
64824
  resumeNextHandoff() {
64780
64825
  const sequence = this.activeHandoffSequence;
64781
64826
  if (!sequence) {
@@ -64783,6 +64828,24 @@ class NamiFlow extends BasicNamiFlow {
64783
64828
  this.manager.resume();
64784
64829
  return;
64785
64830
  }
64831
+ // Grouped path.
64832
+ if ("groups" in sequence) {
64833
+ if (sequence.groups.length === 0) {
64834
+ logger.info("All handoffs in the sequence are complete");
64835
+ this.activeHandoffSequence = null;
64836
+ this.manager.resume();
64837
+ return;
64838
+ }
64839
+ const next = sequence.groups.shift(); // safe: length > 0
64840
+ if (!isKnownHandoffTag(next.tag)) {
64841
+ logger.warn(`handoff group tag '${next.tag}' is outside the NamiHandoffTag vocabulary — passing through`);
64842
+ }
64843
+ this.activeHandoffSequence = sequence; // persist mutated groups
64844
+ logger.debug(`Starting grouped handoff for '${next.tag}' → ${JSON.stringify(next.data)}`);
64845
+ this.manager.deliverHandoff(next.tag, next.data);
64846
+ return;
64847
+ }
64848
+ // Legacy path (unchanged).
64786
64849
  if (sequence.remainingKeys.length === 0) {
64787
64850
  logger.info("All handoffs in the sequence are complete");
64788
64851
  this.activeHandoffSequence = null;
@@ -64792,10 +64855,7 @@ class NamiFlow extends BasicNamiFlow {
64792
64855
  const nextKey = sequence.remainingKeys.shift(); // safe: length > 0
64793
64856
  const value = sequence.formStates[nextKey];
64794
64857
  logger.debug(`Starting handoff for ${nextKey} → ${String(value)}`);
64795
- // persist updated sequence (remainingKeys mutated)
64796
64858
  this.activeHandoffSequence = sequence;
64797
- // Route through the manager's single delivery point so v2 handlers get
64798
- // a working complete() for sequence-key handoffs too.
64799
64859
  this.manager.deliverHandoff(`${nextKey}`, undefined);
64800
64860
  }
64801
64861
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/sdk-core",
3
- "version": "3.4.4-dev.202607071644",
3
+ "version": "3.4.4-dev.202607072319",
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",