@virex-tech/paywallo-sdk 2.2.7 → 2.4.0

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.d.mts CHANGED
@@ -166,6 +166,7 @@ interface PaywallConfig {
166
166
  content?: string | Record<string, unknown>;
167
167
  primaryProductId?: string;
168
168
  secondaryProductId?: string;
169
+ tertiaryProductId?: string;
169
170
  }
170
171
  interface PaywallBlockConfig {
171
172
  type: string;
@@ -1601,6 +1602,7 @@ interface PaywallContextValue {
1601
1602
  selectedProductId: string | null;
1602
1603
  primaryProductId: string | null;
1603
1604
  secondaryProductId: string | null;
1605
+ tertiaryProductId: string | null;
1604
1606
  currentNavigationIndex: number;
1605
1607
  currentTabIndex: number;
1606
1608
  trialEnabled: boolean;
@@ -1649,6 +1651,7 @@ interface PaywallModalProps {
1649
1651
  config: Record<string, unknown>;
1650
1652
  primaryProductId?: string;
1651
1653
  secondaryProductId?: string;
1654
+ tertiaryProductId?: string;
1652
1655
  };
1653
1656
  preloadedProducts?: Map<string, Product>;
1654
1657
  variantKey?: string;
@@ -1859,6 +1862,7 @@ interface VariableContext {
1859
1862
  selectedProductId?: string;
1860
1863
  primaryProductId?: string;
1861
1864
  secondaryProductId?: string;
1865
+ tertiaryProductId?: string;
1862
1866
  products: Map<string, Product>;
1863
1867
  customVariables?: Record<string, string>;
1864
1868
  deviceInfo?: {
package/dist/index.d.ts CHANGED
@@ -166,6 +166,7 @@ interface PaywallConfig {
166
166
  content?: string | Record<string, unknown>;
167
167
  primaryProductId?: string;
168
168
  secondaryProductId?: string;
169
+ tertiaryProductId?: string;
169
170
  }
170
171
  interface PaywallBlockConfig {
171
172
  type: string;
@@ -1601,6 +1602,7 @@ interface PaywallContextValue {
1601
1602
  selectedProductId: string | null;
1602
1603
  primaryProductId: string | null;
1603
1604
  secondaryProductId: string | null;
1605
+ tertiaryProductId: string | null;
1604
1606
  currentNavigationIndex: number;
1605
1607
  currentTabIndex: number;
1606
1608
  trialEnabled: boolean;
@@ -1649,6 +1651,7 @@ interface PaywallModalProps {
1649
1651
  config: Record<string, unknown>;
1650
1652
  primaryProductId?: string;
1651
1653
  secondaryProductId?: string;
1654
+ tertiaryProductId?: string;
1652
1655
  };
1653
1656
  preloadedProducts?: Map<string, Product>;
1654
1657
  variantKey?: string;
@@ -1859,6 +1862,7 @@ interface VariableContext {
1859
1862
  selectedProductId?: string;
1860
1863
  primaryProductId?: string;
1861
1864
  secondaryProductId?: string;
1865
+ tertiaryProductId?: string;
1862
1866
  products: Map<string, Product>;
1863
1867
  customVariables?: Record<string, string>;
1864
1868
  deviceInfo?: {
package/dist/index.js CHANGED
@@ -428,11 +428,28 @@ function parseProperties(json) {
428
428
  return {};
429
429
  }
430
430
  }
431
- var DEVICE_ID_KEY, ANON_ID_KEY, USER_EMAIL_KEY, USER_PROPERTIES_KEY, USER_DISTINCT_ID_KEY, USER_NAME_KEY, USER_COUNTRY_KEY, USER_LOCALE_KEY, USER_PHONE_KEY, USER_FIRST_NAME_KEY, USER_LAST_NAME_KEY, USER_DOB_KEY, USER_GENDER_KEY, LEGACY_USER_PHONE_KEY, LEGACY_USER_FIRST_NAME_KEY, LEGACY_USER_LAST_NAME_KEY, LEGACY_USER_DOB_KEY, LEGACY_USER_GENDER_KEY, LEGACY_DEVICE_ID_KEY, LEGACY_ANON_ID_KEY, LEGACY_EMAIL_KEY, LEGACY_PROPERTIES_KEY, LEGACY_DISTINCT_ID_KEY, LEGACY_NAME_KEY, LEGACY_COUNTRY_KEY, LEGACY_LOCALE_KEY;
431
+ async function persistAnonIdDurably(storage, anonId) {
432
+ let stored = await storage.set(ANON_ID_KEY, anonId);
433
+ for (let i = 0; i < ANON_ID_SET_RETRIES && !stored; i++) {
434
+ await new Promise((r) => setTimeout(r, ANON_ID_RETRY_DELAY_MS));
435
+ stored = await storage.set(ANON_ID_KEY, anonId);
436
+ }
437
+ if (!stored) {
438
+ await nativeStorage.set(ANON_ID_FALLBACK_KEY, anonId).catch(() => void 0);
439
+ }
440
+ }
441
+ async function readAnonIdDurably(storage) {
442
+ const secure = await readWithMigration(storage, ANON_ID_KEY, LEGACY_ANON_ID_KEY);
443
+ if (secure) return secure;
444
+ return nativeStorage.get(ANON_ID_FALLBACK_KEY);
445
+ }
446
+ var DEVICE_ID_KEY, ANON_ID_FALLBACK_KEY, ANON_ID_KEY, USER_EMAIL_KEY, USER_PROPERTIES_KEY, USER_DISTINCT_ID_KEY, USER_NAME_KEY, USER_COUNTRY_KEY, USER_LOCALE_KEY, USER_PHONE_KEY, USER_FIRST_NAME_KEY, USER_LAST_NAME_KEY, USER_DOB_KEY, USER_GENDER_KEY, LEGACY_USER_PHONE_KEY, LEGACY_USER_FIRST_NAME_KEY, LEGACY_USER_LAST_NAME_KEY, LEGACY_USER_DOB_KEY, LEGACY_USER_GENDER_KEY, LEGACY_DEVICE_ID_KEY, LEGACY_ANON_ID_KEY, LEGACY_EMAIL_KEY, LEGACY_PROPERTIES_KEY, LEGACY_DISTINCT_ID_KEY, LEGACY_NAME_KEY, LEGACY_COUNTRY_KEY, LEGACY_LOCALE_KEY, ANON_ID_SET_RETRIES, ANON_ID_RETRY_DELAY_MS;
432
447
  var init_IdentityStorage = __esm({
433
448
  "src/domains/identity/IdentityStorage.ts"() {
434
449
  "use strict";
450
+ init_NativeStorage();
435
451
  DEVICE_ID_KEY = "@paywallo:device_id";
452
+ ANON_ID_FALLBACK_KEY = "@paywallo:anon_id_fallback";
436
453
  ANON_ID_KEY = "@paywallo:anon_id";
437
454
  USER_EMAIL_KEY = "@paywallo:user_email";
438
455
  USER_PROPERTIES_KEY = "@paywallo:user_properties";
@@ -458,6 +475,8 @@ var init_IdentityStorage = __esm({
458
475
  LEGACY_NAME_KEY = "user_name";
459
476
  LEGACY_COUNTRY_KEY = "user_country";
460
477
  LEGACY_LOCALE_KEY = "user_locale";
478
+ ANON_ID_SET_RETRIES = 2;
479
+ ANON_ID_RETRY_DELAY_MS = 50;
461
480
  }
462
481
  });
463
482
 
@@ -537,8 +556,7 @@ var init_IdentityManager = __esm({
537
556
  }
538
557
  if (!this.anonId) {
539
558
  this.anonId = generateUUID();
540
- await this.secureStorage.set(ANON_ID_KEY, this.anonId).catch(() => {
541
- });
559
+ await persistAnonIdDurably(this.secureStorage, this.anonId);
542
560
  }
543
561
  this.initialized = true;
544
562
  return this.deviceId ?? "";
@@ -598,7 +616,8 @@ var init_IdentityManager = __esm({
598
616
  dateOfBirth: this.dateOfBirth ?? void 0,
599
617
  gender: this.gender ?? void 0
600
618
  });
601
- } catch {
619
+ } catch (err) {
620
+ if (this.debug) console.log("[Paywallo] identify dispatch error", err);
602
621
  }
603
622
  }
604
623
  }
@@ -622,7 +641,8 @@ var init_IdentityManager = __esm({
622
641
  if (this.apiClient && this.anonId) {
623
642
  try {
624
643
  await this.apiClient.identify(this.anonId, this.properties, this.email ?? void 0, this.deviceId ?? void 0);
625
- } catch {
644
+ } catch (err) {
645
+ if (this.debug) console.log("[Paywallo] identify dispatch error", err);
626
646
  }
627
647
  }
628
648
  }
@@ -665,18 +685,20 @@ var init_IdentityManager = __esm({
665
685
  }
666
686
  async loadPersistedState() {
667
687
  if (!this.secureStorage) return;
668
- const state = await loadPersistedIdentity(this.secureStorage);
688
+ const [state, rawAnonId] = await Promise.all([
689
+ loadPersistedIdentity(this.secureStorage),
690
+ readAnonIdDurably(this.secureStorage)
691
+ ]);
669
692
  const deviceId = state.deviceId ?? await readDeviceIdWithFallback(this.secureStorage);
670
693
  if (deviceId) this.deviceId = deviceId;
671
- if (state.anonId) {
694
+ if (rawAnonId) {
672
695
  const legacyPrefix = "$paywallo_anon:";
673
- if (state.anonId.startsWith(legacyPrefix)) {
674
- const stripped = state.anonId.slice(legacyPrefix.length);
696
+ if (rawAnonId.startsWith(legacyPrefix)) {
697
+ const stripped = rawAnonId.slice(legacyPrefix.length);
675
698
  this.anonId = stripped;
676
- await this.secureStorage.set(ANON_ID_KEY, stripped).catch(() => {
677
- });
699
+ await this.secureStorage.set(ANON_ID_KEY, stripped).catch(() => void 0);
678
700
  } else {
679
- this.anonId = state.anonId;
701
+ this.anonId = rawAnonId;
680
702
  }
681
703
  }
682
704
  if (state.email) this.email = state.email;
@@ -901,21 +923,23 @@ var init_AdvertisingIdManager = __esm({
901
923
  if (import_react_native3.Platform.OS === "ios") {
902
924
  if (!tracking.isAvailable()) {
903
925
  const idfv2 = await this.collectIdfv();
904
- this.cached = { ...fallback, idfv: idfv2 };
905
- return this.cached;
926
+ const result2 = { ...fallback, idfv: idfv2 };
927
+ if (idfv2 !== null) this.cached = result2;
928
+ return result2;
906
929
  }
907
930
  const current = await tracking.getTrackingPermissionsAsync();
908
931
  let status = current.status;
909
932
  let granted = current.granted;
910
933
  if (status === "undetermined" && requestATT) {
911
- const result = await tracking.requestTrackingPermissionsAsync();
912
- status = result.status;
913
- granted = result.granted;
934
+ const result2 = await tracking.requestTrackingPermissionsAsync();
935
+ status = result2.status;
936
+ granted = result2.granted;
914
937
  }
915
938
  const idfv = await this.collectIdfv();
916
939
  const idfa = granted ? this.sanitizeId(tracking.getAdvertisingId()) : null;
917
- this.cached = { idfv, idfa, gaid: null, attStatus: status };
918
- return this.cached;
940
+ const result = { idfv, idfa, gaid: null, attStatus: status };
941
+ if (idfv !== null) this.cached = result;
942
+ return result;
919
943
  }
920
944
  if (import_react_native3.Platform.OS === "android") {
921
945
  const rawGaid = tracking.getAdvertisingId();
@@ -931,12 +955,20 @@ var init_AdvertisingIdManager = __esm({
931
955
  }
932
956
  }
933
957
  async collectIdfv() {
934
- try {
935
- const info = await nativeDeviceInfo.getDeviceInfo();
936
- return this.sanitizeId(info.idfv);
937
- } catch {
938
- return null;
958
+ const RETRY_DELAY_MS = 80;
959
+ const MAX_ATTEMPTS = 3;
960
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
961
+ try {
962
+ const info = await nativeDeviceInfo.getDeviceInfo();
963
+ const idfv = this.sanitizeId(info.idfv);
964
+ if (idfv !== null) return idfv;
965
+ } catch {
966
+ }
967
+ if (attempt < MAX_ATTEMPTS - 1) {
968
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
969
+ }
939
970
  }
971
+ return null;
940
972
  }
941
973
  sanitizeId(id) {
942
974
  if (!id || id === ZERO_IDFA) return null;
@@ -1374,6 +1406,8 @@ var init_AttributionTracker = __esm({
1374
1406
 
1375
1407
  // src/domains/identity/InstallIdempotency.ts
1376
1408
  async function checkAndArmInstallGuard(storage) {
1409
+ if (armedThisLaunch) return true;
1410
+ armedThisLaunch = true;
1377
1411
  const alreadyTracked = await storage.get(INSTALL_KEYS.INSTALL_TRACKED);
1378
1412
  if (alreadyTracked) return true;
1379
1413
  let fallbackTracked = null;
@@ -1407,7 +1441,7 @@ async function getOrCreateInstallEventId() {
1407
1441
  });
1408
1442
  return id;
1409
1443
  }
1410
- var INSTALL_KEYS;
1444
+ var INSTALL_KEYS, armedThisLaunch;
1411
1445
  var init_InstallIdempotency = __esm({
1412
1446
  "src/domains/identity/InstallIdempotency.ts"() {
1413
1447
  "use strict";
@@ -1432,6 +1466,7 @@ var init_InstallIdempotency = __esm({
1432
1466
  /** Legacy anon ID key written by SDK versions that used @panel: prefix. */
1433
1467
  LEGACY_ANON_ID: "@panel:anon_id"
1434
1468
  };
1469
+ armedThisLaunch = false;
1435
1470
  }
1436
1471
  });
1437
1472
 
@@ -2386,8 +2421,8 @@ var init_NotificationHandlerSetup = __esm({
2386
2421
  // src/core/version.ts
2387
2422
  function resolveVersion() {
2388
2423
  try {
2389
- if ("2.2.7") {
2390
- return "2.2.7";
2424
+ if ("2.4.0") {
2425
+ return "2.4.0";
2391
2426
  }
2392
2427
  } catch {
2393
2428
  }
@@ -3403,6 +3438,7 @@ function buildPaywallInjectionScript(data) {
3403
3438
  products: data.products,
3404
3439
  primaryProductId: data.primaryProductId,
3405
3440
  secondaryProductId: data.secondaryProductId,
3441
+ tertiaryProductId: data.tertiaryProductId,
3406
3442
  currentLanguage: getCurrentLanguage(),
3407
3443
  defaultLanguage: getDefaultLanguage()
3408
3444
  }
@@ -3487,6 +3523,7 @@ function PaywallWebView({
3487
3523
  products,
3488
3524
  primaryProductId,
3489
3525
  secondaryProductId,
3526
+ tertiaryProductId,
3490
3527
  isPurchasing,
3491
3528
  webUrl: webUrlProp,
3492
3529
  onPurchase,
@@ -3558,10 +3595,11 @@ function PaywallWebView({
3558
3595
  craftData,
3559
3596
  products: Array.from(products.values()),
3560
3597
  primaryProductId,
3561
- secondaryProductId
3598
+ secondaryProductId,
3599
+ tertiaryProductId
3562
3600
  });
3563
3601
  injectScript(script);
3564
- }, [craftData, products, primaryProductId, secondaryProductId, injectScript]);
3602
+ }, [craftData, products, primaryProductId, secondaryProductId, tertiaryProductId, injectScript]);
3565
3603
  const processedIdsRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
3566
3604
  const dedupeTimersRef = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
3567
3605
  (0, import_react4.useEffect)(() => {
@@ -4246,10 +4284,10 @@ var init_OfflineQueueStorage = __esm({
4246
4284
  init_NativeStorage();
4247
4285
  init_QueueJournal();
4248
4286
  OfflineQueueStorage = class {
4249
- constructor(config, emit, log2) {
4287
+ constructor(config, emit, log3) {
4250
4288
  this.config = config;
4251
4289
  this.emit = emit;
4252
- this.log = log2;
4290
+ this.log = log3;
4253
4291
  }
4254
4292
  async loadFromStorage(queue) {
4255
4293
  const { merged, recovered, hadJournal } = await mergeJournalIntoMain(
@@ -5507,7 +5545,8 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
5507
5545
  placement: snap.placement,
5508
5546
  config: snap.config,
5509
5547
  primaryProductId: snap.primaryProductId,
5510
- secondaryProductId: snap.secondaryProductId
5548
+ secondaryProductId: snap.secondaryProductId,
5549
+ tertiaryProductId: snap.tertiaryProductId
5511
5550
  };
5512
5551
  } else {
5513
5552
  const fetched = await apiClient.getPaywall(placement);
@@ -5519,6 +5558,7 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
5519
5558
  const productIds = [];
5520
5559
  if (config.primaryProductId) productIds.push(config.primaryProductId);
5521
5560
  if (config.secondaryProductId) productIds.push(config.secondaryProductId);
5561
+ if (config.tertiaryProductId) productIds.push(config.tertiaryProductId);
5522
5562
  const snapProducts = preloadedProductsRef.current;
5523
5563
  if (snapProducts && snapProducts.size > 0) {
5524
5564
  if (!cancelled) setProducts(snapProducts);
@@ -5702,6 +5742,7 @@ function PaywallModal({
5702
5742
  products,
5703
5743
  primaryProductId: paywallConfig.primaryProductId,
5704
5744
  secondaryProductId: paywallConfig.secondaryProductId,
5745
+ tertiaryProductId: paywallConfig.tertiaryProductId,
5705
5746
  isPurchasing,
5706
5747
  onReady: handleWebViewReady,
5707
5748
  onClose: handleClose,
@@ -5883,6 +5924,7 @@ var init_PaywallPreloadService = __esm({
5883
5924
  const productIds = [];
5884
5925
  if (config.primaryProductId) productIds.push(config.primaryProductId);
5885
5926
  if (config.secondaryProductId) productIds.push(config.secondaryProductId);
5927
+ if (config.tertiaryProductId) productIds.push(config.tertiaryProductId);
5886
5928
  if (productIds.length === 0) return /* @__PURE__ */ new Map();
5887
5929
  try {
5888
5930
  const loaded = await getIAPService().loadProducts(productIds);
@@ -7930,7 +7972,36 @@ var init_http = __esm({
7930
7972
  });
7931
7973
 
7932
7974
  // src/core/ApiClient.ts
7933
- var import_react_native25, ApiClient;
7975
+ function normalizeDateOfBirth(raw) {
7976
+ if (!raw) return void 0;
7977
+ if (DATE_OF_BIRTH_RE.test(raw)) return raw;
7978
+ const d = new Date(raw);
7979
+ if (isNaN(d.getTime())) return void 0;
7980
+ const hasTimeComponent = raw.includes("T") || raw.includes("Z");
7981
+ const yyyy = hasTimeComponent ? d.getUTCFullYear() : d.getFullYear();
7982
+ const mm = String((hasTimeComponent ? d.getUTCMonth() : d.getMonth()) + 1).padStart(2, "0");
7983
+ const dd = String(hasTimeComponent ? d.getUTCDate() : d.getDate()).padStart(2, "0");
7984
+ return `${yyyy}-${mm}-${dd}`;
7985
+ }
7986
+ function buildPiiPayload(pii) {
7987
+ const result = {};
7988
+ if (pii.phone) result.phone = pii.phone;
7989
+ if (pii.firstName) result.firstName = pii.firstName;
7990
+ if (pii.lastName) result.lastName = pii.lastName;
7991
+ const dob = normalizeDateOfBirth(pii.dateOfBirth);
7992
+ if (dob) result.dateOfBirth = dob;
7993
+ const g = normalizeGender(pii.gender);
7994
+ if (g) result.gender = g;
7995
+ return result;
7996
+ }
7997
+ function normalizeGender(raw) {
7998
+ if (!raw) return void 0;
7999
+ const lower = raw.toLowerCase();
8000
+ if (lower === "m" || lower === "male") return "m";
8001
+ if (lower === "f" || lower === "female") return "f";
8002
+ return void 0;
8003
+ }
8004
+ var import_react_native25, DATE_OF_BIRTH_RE, ApiClient;
7934
8005
  var init_ApiClient = __esm({
7935
8006
  "src/core/ApiClient.ts"() {
7936
8007
  "use strict";
@@ -7944,6 +8015,7 @@ var init_ApiClient = __esm({
7944
8015
  init_http();
7945
8016
  init_queue();
7946
8017
  init_version();
8018
+ DATE_OF_BIRTH_RE = /^\d{4}-\d{2}-\d{2}$/;
7947
8019
  ApiClient = class {
7948
8020
  constructor(appKey, apiUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
7949
8021
  this.cache = new ApiCache();
@@ -8102,6 +8174,8 @@ var init_ApiClient = __esm({
8102
8174
  }
8103
8175
  }
8104
8176
  const attribution = Object.keys(rawAttribution).length > 0 ? rawAttribution : void 0;
8177
+ const rawUserId = properties?.["userId"];
8178
+ const externalUserId = typeof rawUserId === "string" && rawUserId.length > 0 ? rawUserId : void 0;
8105
8179
  await postWithQueue(
8106
8180
  {
8107
8181
  isOfflineQueueEnabled: () => this.offlineQueueEnabled,
@@ -8112,14 +8186,11 @@ var init_ApiClient = __esm({
8112
8186
  "/sdk/identity/identify",
8113
8187
  {
8114
8188
  distinct_id: distinctId,
8189
+ ...externalUserId && { external_user_id: externalUserId },
8115
8190
  traits,
8116
8191
  ...attribution && { attribution },
8117
8192
  ...deviceId && { deviceId },
8118
- ...pii?.phone && { phone: pii.phone },
8119
- ...pii?.firstName && { firstName: pii.firstName },
8120
- ...pii?.lastName && { lastName: pii.lastName },
8121
- ...pii?.dateOfBirth && { dateOfBirth: pii.dateOfBirth },
8122
- ...pii?.gender && { gender: pii.gender }
8193
+ ...pii ? buildPiiPayload(pii) : {}
8123
8194
  },
8124
8195
  "identify"
8125
8196
  );
@@ -9759,6 +9830,88 @@ init_paywall();
9759
9830
  init_paywall();
9760
9831
  init_paywall();
9761
9832
 
9833
+ // src/domains/paywall/SuperwallAttributeBridge.ts
9834
+ init_identity();
9835
+ var lastSignature = null;
9836
+ function log(debug, msg, data) {
9837
+ if (!debug) return;
9838
+ if (data !== void 0) console.log(`[Paywallo:SuperwallAttr] ${msg}`, data);
9839
+ else console.log(`[Paywallo:SuperwallAttr] ${msg}`);
9840
+ }
9841
+ var PAID_MEDIUM = /cpc|ppc|cpm|cpa|paid|display/i;
9842
+ function deriveIsPaid(a) {
9843
+ if (a.fbclid || a.gclid || a.ttclid || a.tiktokCampaignId) return true;
9844
+ return Boolean(a.utmMedium && PAID_MEDIUM.test(a.utmMedium));
9845
+ }
9846
+ function deriveAdNetwork(a) {
9847
+ if (a.fbclid) return "meta";
9848
+ if (a.ttclid || a.tiktokCampaignId) return "tiktok";
9849
+ if (a.gclid) return "google";
9850
+ return a.utmSource;
9851
+ }
9852
+ function buildSuperwallAttributes(a) {
9853
+ const out = {};
9854
+ if (!a) {
9855
+ out.pw_is_paid = false;
9856
+ return out;
9857
+ }
9858
+ const set2 = (key, value) => {
9859
+ if (value) out[key] = value;
9860
+ };
9861
+ set2("pw_utm_source", a.utmSource);
9862
+ set2("pw_utm_medium", a.utmMedium);
9863
+ set2("pw_utm_campaign", a.utmCampaign);
9864
+ set2("pw_utm_content", a.utmContent);
9865
+ set2("pw_utm_term", a.utmTerm);
9866
+ set2("pw_fbclid", a.fbclid);
9867
+ set2("pw_gclid", a.gclid);
9868
+ set2("pw_ttclid", a.ttclid);
9869
+ set2("pw_tiktok_campaign_id", a.tiktokCampaignId);
9870
+ set2("pw_tiktok_adgroup_id", a.tiktokAdgroupId);
9871
+ set2("pw_tiktok_ad_id", a.tiktokAdId);
9872
+ set2("pw_referrer", a.referrer);
9873
+ set2("pw_install_referrer_source", a.installReferrerSource);
9874
+ set2("pw_ad_network", deriveAdNetwork(a));
9875
+ out.pw_is_paid = deriveIsPaid(a);
9876
+ out.pw_attributed_at = a.capturedAt;
9877
+ return out;
9878
+ }
9879
+ function pickShared(mod) {
9880
+ const shared = mod?.default?.shared ?? mod?.shared;
9881
+ return shared && typeof shared.setUserAttributes === "function" ? shared : null;
9882
+ }
9883
+ async function resolveSuperwallShared(debug) {
9884
+ try {
9885
+ const shared = pickShared(await import("expo-superwall/compat"));
9886
+ if (shared) return shared;
9887
+ } catch {
9888
+ }
9889
+ try {
9890
+ const shared = pickShared(await import("expo-superwall"));
9891
+ if (shared) return shared;
9892
+ } catch {
9893
+ }
9894
+ log(debug, "expo-superwall not available \u2014 attribute push skipped");
9895
+ return null;
9896
+ }
9897
+ async function pushAttributionToSuperwall(debug = false) {
9898
+ const attrs = buildSuperwallAttributes(attributionTracker.get());
9899
+ const signature = JSON.stringify(attrs);
9900
+ if (signature === lastSignature) {
9901
+ log(debug, "attributes unchanged \u2014 skipping push");
9902
+ return;
9903
+ }
9904
+ const shared = await resolveSuperwallShared(debug);
9905
+ if (!shared) return;
9906
+ try {
9907
+ await shared.setUserAttributes(attrs);
9908
+ lastSignature = signature;
9909
+ log(debug, "attributes pushed to Superwall", attrs);
9910
+ } catch (err) {
9911
+ log(debug, "setUserAttributes error", { err: String(err) });
9912
+ }
9913
+ }
9914
+
9762
9915
  // src/domains/paywall/SuperwallAutoBridge.ts
9763
9916
  init_PaywalloClient();
9764
9917
 
@@ -9792,7 +9945,7 @@ function buildDeterministicEventId(parts) {
9792
9945
  var bridgeStarted = false;
9793
9946
  var unsubscribe = null;
9794
9947
  var debugEnabled = false;
9795
- function log(msg, data) {
9948
+ function log2(msg, data) {
9796
9949
  if (!debugEnabled) return;
9797
9950
  if (data !== void 0) {
9798
9951
  console.log(`[Paywallo:SuperwallBridge] ${msg}`, data);
@@ -9822,7 +9975,7 @@ async function trackPaywallEvent(type, identifier, ts, extra) {
9822
9975
  }
9823
9976
  });
9824
9977
  } catch (err) {
9825
- log("track error", { type, identifier, eventId: _eventId, err: String(err) });
9978
+ log2("track error", { type, identifier, eventId: _eventId, err: String(err) });
9826
9979
  }
9827
9980
  }
9828
9981
  function mapDismissToCloseReason(resultType) {
@@ -9842,7 +9995,7 @@ function buildCallbacks() {
9842
9995
  return {
9843
9996
  onPaywallPresent: (info) => {
9844
9997
  const ts = Date.now();
9845
- log("onPaywallPresent", { identifier: info.identifier });
9998
+ log2("onPaywallPresent", { identifier: info.identifier });
9846
9999
  void trackPaywallEvent("viewed", info.identifier, ts, {
9847
10000
  placement: info.presentedByEventWithName,
9848
10001
  variant_id: info.experiment?.variant?.id,
@@ -9851,7 +10004,7 @@ function buildCallbacks() {
9851
10004
  },
9852
10005
  onPaywallDismiss: (info, result) => {
9853
10006
  const ts = Date.now();
9854
- log("onPaywallDismiss", { identifier: info.identifier, result: result.type });
10007
+ log2("onPaywallDismiss", { identifier: info.identifier, result: result.type });
9855
10008
  void trackPaywallEvent("closed", info.identifier, ts, {
9856
10009
  placement: info.presentedByEventWithName,
9857
10010
  variant_id: info.experiment?.variant?.id,
@@ -9865,7 +10018,7 @@ function buildCallbacks() {
9865
10018
  const safe = params !== null && typeof params === "object" ? params : {};
9866
10019
  const productId = "productId" in safe && typeof safe.productId === "string" ? safe.productId : "unknown";
9867
10020
  const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
9868
- log("onPurchase", { productId, identifier });
10021
+ log2("onPurchase", { productId, identifier });
9869
10022
  void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
9870
10023
  },
9871
10024
  // Global Superwall event listener — used specifically for `transactionComplete`
@@ -9880,7 +10033,7 @@ function buildCallbacks() {
9880
10033
  const product = ev.product;
9881
10034
  const transaction = ev.transaction;
9882
10035
  if (!product) {
9883
- log("transactionComplete missing product \u2014 skipping transaction track");
10036
+ log2("transactionComplete missing product \u2014 skipping transaction track");
9884
10037
  return;
9885
10038
  }
9886
10039
  const productId = product.productIdentifier ?? product.id ?? "unknown";
@@ -9889,7 +10042,7 @@ function buildCallbacks() {
9889
10042
  const transactionId = transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
9890
10043
  const isTrial = product.hasFreeTrial === true || (product.trialPeriodPrice ?? 0) > 0;
9891
10044
  const paywallIdentifier = ev.paywallInfo?.identifier;
9892
- log("onSuperwallEvent: transactionComplete", { productId, currency, price, transactionId, isTrial });
10045
+ log2("onSuperwallEvent: transactionComplete", { productId, currency, price, transactionId, isTrial });
9893
10046
  void (async () => {
9894
10047
  try {
9895
10048
  await PaywalloClient.track("transaction", {
@@ -9907,7 +10060,7 @@ function buildCallbacks() {
9907
10060
  }
9908
10061
  });
9909
10062
  } catch (err) {
9910
- log("transactionComplete track error", { err: String(err) });
10063
+ log2("transactionComplete track error", { err: String(err) });
9911
10064
  }
9912
10065
  })();
9913
10066
  }
@@ -9916,7 +10069,7 @@ function buildCallbacks() {
9916
10069
  function startSuperwallAutoBridge(debug = false) {
9917
10070
  debugEnabled = debug;
9918
10071
  if (bridgeStarted) {
9919
- log("already started, skipping");
10072
+ log2("already started, skipping");
9920
10073
  return;
9921
10074
  }
9922
10075
  void detectAndStart();
@@ -9928,7 +10081,7 @@ async function detectAndStart() {
9928
10081
  if (!mod || typeof mod.subscribeToSuperwallEvents !== "function") {
9929
10082
  const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
9930
10083
  if (typeof internal.subscribeToSuperwallEvents !== "function") {
9931
- log("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
10084
+ log2("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
9932
10085
  return;
9933
10086
  }
9934
10087
  subscribeFn = internal.subscribeToSuperwallEvents;
@@ -9936,16 +10089,16 @@ async function detectAndStart() {
9936
10089
  subscribeFn = mod.subscribeToSuperwallEvents;
9937
10090
  }
9938
10091
  } catch {
9939
- log("expo-superwall not installed \u2014 bridge inactive");
10092
+ log2("expo-superwall not installed \u2014 bridge inactive");
9940
10093
  return;
9941
10094
  }
9942
10095
  try {
9943
10096
  const callbacks = buildCallbacks();
9944
10097
  unsubscribe = subscribeFn(void 0, () => callbacks);
9945
10098
  bridgeStarted = true;
9946
- log("bridge started");
10099
+ log2("bridge started");
9947
10100
  } catch (err) {
9948
- log("subscribe failed", { err: String(err) });
10101
+ log2("subscribe failed", { err: String(err) });
9949
10102
  }
9950
10103
  }
9951
10104
 
@@ -10518,6 +10671,7 @@ function PaywalloProvider({ children, config }) {
10518
10671
  if (!mounted) return;
10519
10672
  setDistinctId(PaywalloClient.getDistinctId());
10520
10673
  startSuperwallAutoBridge(configRef.current.debug);
10674
+ void pushAttributionToSuperwall(configRef.current.debug);
10521
10675
  setIsInitialized(true);
10522
10676
  if (!autoPreloadTriggeredRef.current) {
10523
10677
  autoPreloadTriggeredRef.current = true;
@@ -10553,7 +10707,8 @@ function PaywalloProvider({ children, config }) {
10553
10707
  placement: preloaded.config.placement,
10554
10708
  config: preloaded.config.config,
10555
10709
  primaryProductId: preloaded.config.primaryProductId,
10556
- secondaryProductId: preloaded.config.secondaryProductId
10710
+ secondaryProductId: preloaded.config.secondaryProductId,
10711
+ tertiaryProductId: preloaded.config.tertiaryProductId
10557
10712
  },
10558
10713
  preloadedProducts: preloaded.products
10559
10714
  }
@@ -10855,6 +11010,9 @@ function resolveProductsVariable(parts, context) {
10855
11010
  if (parts[1] === "secondary" && context.secondaryProductId) {
10856
11011
  return getProductVariable(context.products.get(context.secondaryProductId), parts[2]);
10857
11012
  }
11013
+ if (parts[1] === "tertiary" && context.tertiaryProductId) {
11014
+ return getProductVariable(context.products.get(context.tertiaryProductId), parts[2]);
11015
+ }
10858
11016
  if (parts[1] === "hasIntroductoryOffer") {
10859
11017
  return Array.from(context.products.values()).some((p) => p.introductoryPrice) ? "true" : "false";
10860
11018
  }