@pixels-online/pixels-client-js-sdk 1.17.0 → 1.19.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.js CHANGED
@@ -630,25 +630,33 @@ class SSEConnection {
630
630
  }
631
631
 
632
632
  class OfferStore {
633
- constructor(config) {
633
+ constructor(config, client) {
634
+ this.client = client;
634
635
  this.offers = new Map();
635
- this.player = null;
636
+ this.players = new Map();
636
637
  this.logger = createLogger(config, 'OfferStore');
637
638
  }
638
- getPlayer() {
639
- return this.player || null;
639
+ getPlayer(targetId = this.client.getSelfId()) {
640
+ if (!targetId)
641
+ return null;
642
+ return this.players.get(targetId) || null;
640
643
  }
641
644
  setPlayer(player) {
642
- this.player = player;
645
+ this.players.set(player.snapshot.playerId, player);
643
646
  this.logger.log('Updated player:', player);
644
647
  }
645
648
  /**
646
649
  * Set all offers (replaces existing)
647
650
  */
648
- setOffers(offers) {
649
- this.offers.clear();
651
+ setOffers(offers, target) {
652
+ const targetPlayer = target || this.getPlayer();
653
+ if (!targetPlayer) {
654
+ this.logger.warn('No target player to set offers for');
655
+ return;
656
+ }
657
+ this.offers.set(targetPlayer.snapshot.playerId, new Map());
650
658
  offers.forEach((offer) => {
651
- this.offers.set(offer.instanceId, offer);
659
+ this.offers.get(targetPlayer.snapshot.playerId).set(offer.instanceId, offer);
652
660
  });
653
661
  this.logger.log(`Set ${offers.length} offers`);
654
662
  }
@@ -656,16 +664,23 @@ class OfferStore {
656
664
  * Add or update a single offer
657
665
  */
658
666
  upsertOffer(offer) {
659
- const previousOffer = this.offers.get(offer.instanceId);
660
- this.offers.set(offer.instanceId, offer);
667
+ let playerOffers = this.offers.get(offer.playerId);
668
+ if (!playerOffers) {
669
+ playerOffers = new Map();
670
+ this.offers.set(offer.playerId, playerOffers);
671
+ }
672
+ const previousOffer = playerOffers.get(offer.instanceId);
673
+ playerOffers.set(offer.instanceId, offer);
661
674
  this.logger.log(`${previousOffer ? 'Updated' : 'Added'} offer:`, offer.instanceId);
662
675
  return previousOffer;
663
676
  }
664
677
  /**
665
678
  * Remove an offer
666
679
  */
667
- removeOffer(offerId) {
668
- const removed = this.offers.delete(offerId);
680
+ removeOffer(offerId, targetId = this.client.getSelfId()) {
681
+ if (!targetId)
682
+ return false;
683
+ const removed = this.offers.get(targetId)?.delete(offerId) || false;
669
684
  if (removed) {
670
685
  this.logger.log(`Removed offer:`, offerId);
671
686
  }
@@ -674,26 +689,30 @@ class OfferStore {
674
689
  /**
675
690
  * Get a single offer
676
691
  */
677
- getOffer(offerId) {
678
- return this.offers.get(offerId);
692
+ getOffer(offerId, targetId = this.client.getSelfId()) {
693
+ if (!targetId)
694
+ return undefined;
695
+ return this.offers.get(targetId)?.get(offerId);
679
696
  }
680
697
  /**
681
698
  * Get all offers
682
699
  */
683
- getAllOffers() {
684
- return Array.from(this.offers.values());
700
+ getAllOffers(targetId = this.client.getSelfId()) {
701
+ if (!targetId)
702
+ return [];
703
+ return Array.from(this.offers.get(targetId)?.values() || []);
685
704
  }
686
705
  /**
687
706
  * Get offers filtered by status
688
707
  */
689
- getOffersByStatus(status) {
690
- return this.getAllOffers().filter((offer) => offer.status === status);
708
+ getOffersByStatus(status, targetId = this.client.getSelfId()) {
709
+ return this.getAllOffers(targetId).filter((offer) => offer.status === status);
691
710
  }
692
711
  /**
693
712
  * Get active offers (not expired, not claimed)
694
713
  */
695
- getActiveOffers() {
696
- return this.getAllOffers().filter((offer) => {
714
+ getActiveOffers(targetId = this.client.getSelfId()) {
715
+ return this.getAllOffers(targetId).filter((offer) => {
697
716
  if (!offer.status)
698
717
  return false; // Must have a status
699
718
  return (offer.status === 'surfaced' ||
@@ -704,14 +723,14 @@ class OfferStore {
704
723
  /**
705
724
  * Get claimable offers
706
725
  */
707
- getClaimableOffers() {
708
- return this.getOffersByStatus('claimable');
726
+ getClaimableOffers(targetId = this.client.getSelfId()) {
727
+ return this.getOffersByStatus('claimable', targetId);
709
728
  }
710
729
  /**
711
730
  * Check if an offer has expired
712
731
  */
713
- isOfferExpired(offerId) {
714
- const offer = this.getOffer(offerId);
732
+ isOfferExpired(offerId, targetId = this.client.getSelfId()) {
733
+ const offer = this.getOffer(offerId, targetId);
715
734
  if (!offer)
716
735
  return true;
717
736
  // Check status
@@ -724,17 +743,29 @@ class OfferStore {
724
743
  return false;
725
744
  }
726
745
  /**
727
- * Clear all offers
746
+ * Clear all offers for a specific player
728
747
  */
729
- clear() {
748
+ clear(targetId = this.client.getSelfId()) {
749
+ if (!targetId)
750
+ return;
751
+ this.offers.set(targetId, new Map());
752
+ this.logger.log('Cleared all offers for player:', targetId);
753
+ }
754
+ /**
755
+ * Clear all offers for all players
756
+ */
757
+ clearAll() {
730
758
  this.offers.clear();
731
- this.logger.log('Cleared all offers');
759
+ this.logger.log('Cleared all offers for all players');
732
760
  }
733
761
  /**
734
- * Get offer count
762
+ * Get offer count (for self player)
735
763
  */
736
764
  get size() {
737
- return this.offers.size;
765
+ const selfId = this.client.getSelfId();
766
+ if (!selfId)
767
+ return 0;
768
+ return this.offers.get(selfId)?.size || 0;
738
769
  }
739
770
  }
740
771
 
@@ -837,7 +868,9 @@ class AssetHelper {
837
868
  return null;
838
869
  }
839
870
  setCurrencyAssetContents(currencies) {
840
- this.currencies = currencies;
871
+ Object.keys(currencies).forEach((key) => {
872
+ this.currencies[key] = currencies[key];
873
+ });
841
874
  }
842
875
  resolveReward(reward) {
843
876
  if (reward.kind === 'loyalty_currency' && reward.rewardId) {
@@ -893,6 +926,7 @@ class OfferwallClient {
893
926
  constructor(config) {
894
927
  this.isInitializing = false;
895
928
  this.pendingStackedToken = null;
929
+ this.selfId = null;
896
930
  this.config = {
897
931
  autoConnect: config.autoConnect ?? false,
898
932
  reconnect: config.reconnect ?? true,
@@ -905,7 +939,7 @@ class OfferwallClient {
905
939
  this.hooks = this.config.hooks || {};
906
940
  this.logger = createLogger(this.config, 'OfferwallClient');
907
941
  this.eventEmitter = new EventEmitter(this.config);
908
- this.offerStore = new OfferStore(this.config);
942
+ this.offerStore = new OfferStore(this.config, this);
909
943
  this.tokenManager = new TokenManager(this.config);
910
944
  this.assetHelper = new AssetHelper(this.config);
911
945
  this.sseConnection = new SSEConnection(this.config, this.eventEmitter, this.tokenManager);
@@ -946,6 +980,9 @@ class OfferwallClient {
946
980
  get assets() {
947
981
  return this.assetHelper;
948
982
  }
983
+ getSelfId() {
984
+ return this.selfId;
985
+ }
949
986
  /**
950
987
  * Initialize the offerwall client and connect
951
988
  */
@@ -1008,8 +1045,9 @@ class OfferwallClient {
1008
1045
  if (this.sseConnection) {
1009
1046
  this.sseConnection.disconnect();
1010
1047
  }
1011
- this.offerStore.clear();
1048
+ this.offerStore.clearAll();
1012
1049
  this.tokenManager.clearToken();
1050
+ this.selfId = null;
1013
1051
  if (this.hooks.afterDisconnect) {
1014
1052
  await this.hooks.afterDisconnect();
1015
1053
  }
@@ -1017,8 +1055,8 @@ class OfferwallClient {
1017
1055
  /**
1018
1056
  * Claim rewards for an offer
1019
1057
  */
1020
- async claimReward(instanceId) {
1021
- const offer = this.offerStore.getOffer(instanceId);
1058
+ async claimReward(instanceId, targetId = this.getSelfId()) {
1059
+ const offer = this.offerStore.getOffer(instanceId, targetId);
1022
1060
  if (!offer) {
1023
1061
  throw new Error(`Offer ${instanceId} not found`);
1024
1062
  }
@@ -1033,7 +1071,7 @@ class OfferwallClient {
1033
1071
  }
1034
1072
  }
1035
1073
  try {
1036
- const response = await this.claimOfferAPI(instanceId);
1074
+ const response = await this.claimOfferAPI(instanceId, targetId);
1037
1075
  const updatedOffer = { ...offer, status: 'claimed' };
1038
1076
  this.offerStore.upsertOffer(updatedOffer);
1039
1077
  this.eventEmitter.emit(exports.OfferEvent.OFFER_CLAIMED, {
@@ -1089,7 +1127,7 @@ class OfferwallClient {
1089
1127
  });
1090
1128
  */
1091
1129
  this.eventEmitter.on(exports.OfferEvent.OFFER_SURFACED, ({ offer }) => {
1092
- this.offerStore.upsertOffer(offer);
1130
+ this.offerStore.upsertOffer(offer); // should always be selfId
1093
1131
  this.logger.log(`Surfaced offer: ${offer.instanceId}`);
1094
1132
  });
1095
1133
  }
@@ -1119,23 +1157,27 @@ class OfferwallClient {
1119
1157
  }
1120
1158
  return response.json();
1121
1159
  }
1122
- async claimOfferAPI(instanceId) {
1160
+ async claimOfferAPI(instanceId, targetId = null) {
1123
1161
  return this.postWithAuth('/v1/client/reward/claim', {
1124
1162
  instanceId,
1125
1163
  kind: 'offer',
1164
+ targetId: targetId || undefined,
1126
1165
  });
1127
1166
  }
1128
- getPlayer() {
1129
- return this.offerStore.getPlayer();
1167
+ getPlayer(targetId = this.getSelfId()) {
1168
+ return this.offerStore.getPlayer(targetId);
1130
1169
  }
1131
- getOffers() {
1132
- return this.offerStore.getAllOffers();
1170
+ getOffers(targetId = this.getSelfId()) {
1171
+ return this.offerStore.getAllOffers(targetId);
1133
1172
  }
1134
- async refreshOffersAndPlayer() {
1173
+ async refreshOffersAndPlayer(targetId = null) {
1135
1174
  try {
1136
- const { offers, player } = await this.getOffersAndPlayer();
1137
- this.offerStore.setOffers(offers);
1175
+ const { offers, player } = await this.getOffersAndPlayer(targetId);
1176
+ if (targetId == null) {
1177
+ this.selfId = player.snapshot.playerId;
1178
+ }
1138
1179
  this.offerStore.setPlayer(player);
1180
+ this.offerStore.setOffers(offers, player);
1139
1181
  this.eventEmitter.emit(exports.OfferEvent.REFRESH, { offers, player: player });
1140
1182
  this.logger.log('Refreshed offers and player snapshot');
1141
1183
  }
@@ -1144,9 +1186,10 @@ class OfferwallClient {
1144
1186
  throw error;
1145
1187
  }
1146
1188
  }
1147
- async getOffersAndPlayer() {
1189
+ async getOffersAndPlayer(targetId = null) {
1148
1190
  const data = await this.postWithAuth('/v1/client/player/campaigns', {
1149
1191
  viewingCampaigns: true,
1192
+ targetId: targetId || undefined,
1150
1193
  });
1151
1194
  if (!data.offers || !Array.isArray(data.offers)) {
1152
1195
  throw new Error('No offers returned from offers endpoint');
@@ -1921,6 +1964,8 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1921
1964
  const claimMultiplier = shouldScale ? (playerOffer.claimedCount || 0) + 1 : 1;
1922
1965
  const conditionData = [];
1923
1966
  let isValid = true;
1967
+ let maxTotalClaimsFromScaling = Infinity;
1968
+ const updateMax = (limit) => (maxTotalClaimsFromScaling = Math.min(maxTotalClaimsFromScaling, limit));
1924
1969
  if (completionConditions?.context?.id) {
1925
1970
  const hasTrackedContext = completionTrackers?.context &&
1926
1971
  completionConditions.context.id === completionTrackers.context;
@@ -1937,17 +1982,22 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1937
1982
  }
1938
1983
  else {
1939
1984
  if (isDisqualify)
1940
- return { isValid: false };
1985
+ return { isValid: false, availableClaimsNow: 0 };
1941
1986
  }
1942
1987
  }
1943
1988
  if (conditions?.buyItem) {
1944
- const scaledAmount = (conditions.buyItem.amount || 1) * claimMultiplier;
1945
- const isDisqualify = (completionTrackers?.buyItem || 0) < scaledAmount;
1989
+ const baseAmount = conditions.buyItem.amount || 1;
1990
+ const scaledAmount = baseAmount * claimMultiplier;
1991
+ const trackerValue = completionTrackers?.buyItem || 0;
1992
+ const isDisqualify = trackerValue < scaledAmount;
1993
+ if (shouldScale && baseAmount > 0) {
1994
+ updateMax(Math.floor(trackerValue / baseAmount));
1995
+ }
1946
1996
  if (addDetails) {
1947
1997
  conditionData.push({
1948
1998
  isMet: !isDisqualify,
1949
1999
  kind: 'buyItem',
1950
- trackerAmount: completionTrackers?.buyItem || 0,
2000
+ trackerAmount: trackerValue,
1951
2001
  text: `Buy ${scaledAmount} ${conditions.buyItem.name}`,
1952
2002
  });
1953
2003
  if (isDisqualify)
@@ -1955,17 +2005,22 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1955
2005
  }
1956
2006
  else {
1957
2007
  if (isDisqualify)
1958
- return { isValid: false };
2008
+ return { isValid: false, availableClaimsNow: 0 };
1959
2009
  }
1960
2010
  }
1961
2011
  if (conditions?.spendCurrency) {
1962
- const scaledAmount = (conditions.spendCurrency.amount || 1) * claimMultiplier;
1963
- const isDisqualify = (completionTrackers?.spendCurrency || 0) < scaledAmount;
2012
+ const baseAmount = conditions.spendCurrency.amount || 1;
2013
+ const scaledAmount = baseAmount * claimMultiplier;
2014
+ const trackerValue = completionTrackers?.spendCurrency || 0;
2015
+ const isDisqualify = trackerValue < scaledAmount;
2016
+ if (shouldScale && baseAmount > 0) {
2017
+ updateMax(Math.floor(trackerValue / baseAmount));
2018
+ }
1964
2019
  if (addDetails) {
1965
2020
  conditionData.push({
1966
2021
  isMet: !isDisqualify,
1967
2022
  kind: 'spendCurrency',
1968
- trackerAmount: completionTrackers?.spendCurrency || 0,
2023
+ trackerAmount: trackerValue,
1969
2024
  text: `Spend ${scaledAmount} ${conditions.spendCurrency.name}`,
1970
2025
  });
1971
2026
  if (isDisqualify)
@@ -1973,17 +2028,22 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1973
2028
  }
1974
2029
  else {
1975
2030
  if (isDisqualify)
1976
- return { isValid: false };
2031
+ return { isValid: false, availableClaimsNow: 0 };
1977
2032
  }
1978
2033
  }
1979
2034
  if (conditions?.depositCurrency) {
1980
- const scaledAmount = (conditions.depositCurrency.amount || 1) * claimMultiplier;
1981
- const isDisqualify = (completionTrackers?.depositCurrency || 0) < scaledAmount;
2035
+ const baseAmount = conditions.depositCurrency.amount || 1;
2036
+ const scaledAmount = baseAmount * claimMultiplier;
2037
+ const trackerValue = completionTrackers?.depositCurrency || 0;
2038
+ const isDisqualify = trackerValue < scaledAmount;
2039
+ if (shouldScale && baseAmount > 0) {
2040
+ updateMax(Math.floor(trackerValue / baseAmount));
2041
+ }
1982
2042
  if (addDetails) {
1983
2043
  conditionData.push({
1984
2044
  isMet: !isDisqualify,
1985
2045
  kind: 'depositCurrency',
1986
- trackerAmount: completionTrackers?.depositCurrency || 0,
2046
+ trackerAmount: trackerValue,
1987
2047
  text: `Deposit ${scaledAmount} ${conditions.depositCurrency.name}`,
1988
2048
  });
1989
2049
  if (isDisqualify)
@@ -1991,7 +2051,7 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1991
2051
  }
1992
2052
  else {
1993
2053
  if (isDisqualify)
1994
- return { isValid: false };
2054
+ return { isValid: false, availableClaimsNow: 0 };
1995
2055
  }
1996
2056
  }
1997
2057
  if (conditions?.login) {
@@ -2008,7 +2068,7 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2008
2068
  }
2009
2069
  else {
2010
2070
  if (!isMet)
2011
- return { isValid: false };
2071
+ return { isValid: false, availableClaimsNow: 0 };
2012
2072
  }
2013
2073
  }
2014
2074
  if (conditions?.loginStreak) {
@@ -2028,7 +2088,7 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2028
2088
  }
2029
2089
  else {
2030
2090
  if (isDisqualify)
2031
- return { isValid: false };
2091
+ return { isValid: false, availableClaimsNow: 0 };
2032
2092
  }
2033
2093
  }
2034
2094
  if (conditions?.social) {
@@ -2050,6 +2110,17 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2050
2110
  if (likes < minLikes || views < minViews || comments < minComments) {
2051
2111
  isDisqualify = true;
2052
2112
  }
2113
+ if (shouldScale && mode === 'accumulate' && hasContent) {
2114
+ const baseLikes = cSocial?.minLikes || 0;
2115
+ const baseViews = cSocial?.minViews || 0;
2116
+ const baseComments = cSocial?.minComments || 0;
2117
+ if (baseLikes > 0)
2118
+ updateMax(Math.floor(likes / baseLikes));
2119
+ if (baseViews > 0)
2120
+ updateMax(Math.floor(views / baseViews));
2121
+ if (baseComments > 0)
2122
+ updateMax(Math.floor(comments / baseComments));
2123
+ }
2053
2124
  if (addDetails) {
2054
2125
  const platformMap = {
2055
2126
  tiktok: 'TikTok',
@@ -2121,14 +2192,18 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2121
2192
  }
2122
2193
  else {
2123
2194
  if (isDisqualify)
2124
- return { isValid: false };
2195
+ return { isValid: false, availableClaimsNow: 0 };
2125
2196
  }
2126
2197
  }
2127
2198
  // Linked completions - wait for N linked entities to complete
2128
2199
  if (conditions?.linkedCompletions?.min) {
2200
+ const baseMin = conditions.linkedCompletions.min;
2129
2201
  const currentCount = completionTrackers?.linkedCompletions || 0;
2130
- const scaledMin = conditions.linkedCompletions.min * claimMultiplier;
2202
+ const scaledMin = baseMin * claimMultiplier;
2131
2203
  const isDisqualify = currentCount < scaledMin;
2204
+ if (shouldScale && baseMin > 0) {
2205
+ updateMax(Math.floor(currentCount / baseMin));
2206
+ }
2132
2207
  if (addDetails) {
2133
2208
  conditionData.push({
2134
2209
  isMet: !isDisqualify,
@@ -2141,7 +2216,7 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2141
2216
  }
2142
2217
  else {
2143
2218
  if (isDisqualify)
2144
- return { isValid: false };
2219
+ return { isValid: false, availableClaimsNow: 0 };
2145
2220
  }
2146
2221
  }
2147
2222
  if (conditions?.dynamicTracker?.conditions?.length) {
@@ -2151,6 +2226,13 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2151
2226
  ...conditions.dynamicTracker,
2152
2227
  conditions: resolvedConditions,
2153
2228
  }, claimMultiplier);
2229
+ if (shouldScale) {
2230
+ const dynamicMax = getMaxClaimsForDynamicGroup(completionTrackers?.dynamicTracker || {}, {
2231
+ ...conditions.dynamicTracker,
2232
+ conditions: resolvedConditions,
2233
+ }, playerOffer.claimedCount || 0);
2234
+ updateMax(dynamicMax);
2235
+ }
2154
2236
  if (addDetails) {
2155
2237
  conditionData.push({
2156
2238
  isMet: dynamicResult,
@@ -2162,7 +2244,7 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2162
2244
  }
2163
2245
  else {
2164
2246
  if (!dynamicResult)
2165
- return { isValid: false };
2247
+ return { isValid: false, availableClaimsNow: 0 };
2166
2248
  }
2167
2249
  }
2168
2250
  const r = meetsBaseConditions({
@@ -2173,9 +2255,18 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2173
2255
  });
2174
2256
  isValid = isValid && r.isValid;
2175
2257
  conditionData.push(...(r.conditionData || []));
2176
- return { isValid, conditionData };
2177
- }
2178
- return { isValid: true, conditionData: [] };
2258
+ if (maxClaimCount && maxClaimCount > 0) {
2259
+ updateMax(maxClaimCount);
2260
+ }
2261
+ const claimedCount = playerOffer.claimedCount || 0;
2262
+ let availableClaimsNow = !isValid
2263
+ ? 0
2264
+ : maxTotalClaimsFromScaling === Infinity
2265
+ ? -1
2266
+ : Math.max(0, maxTotalClaimsFromScaling - claimedCount);
2267
+ return { isValid, conditionData, availableClaimsNow };
2268
+ }
2269
+ return { isValid: true, conditionData: [], availableClaimsNow: -1 };
2179
2270
  };
2180
2271
  /**
2181
2272
  * Checks if completion conditions were met before a specific expiry time.
@@ -2340,26 +2431,30 @@ function evaluateDynamicCondition(dynamicObj, cond, claimMultiplier = 1) {
2340
2431
  }
2341
2432
  }
2342
2433
  const compareTo = isNumber ? Number(cond.compareTo) : String(cond.compareTo);
2343
- switch (cond.operator) {
2344
- case '==':
2345
- return val === compareTo;
2346
- case '!=':
2347
- return val !== compareTo;
2348
- }
2349
2434
  if (isNumber && typeof compareTo === 'number') {
2435
+ const skipMultiplier = cond.operator === '==' || cond.operator === '!=';
2436
+ const scaledCompareTo = skipMultiplier ? compareTo : compareTo * claimMultiplier;
2350
2437
  switch (cond.operator) {
2438
+ case '==':
2439
+ return val === scaledCompareTo;
2440
+ case '!=':
2441
+ return val !== scaledCompareTo;
2351
2442
  case '>':
2352
- return val > compareTo * claimMultiplier;
2443
+ return val > scaledCompareTo;
2353
2444
  case '>=':
2354
- return val >= compareTo * claimMultiplier;
2445
+ return val >= scaledCompareTo;
2355
2446
  case '<':
2356
- return val < compareTo * claimMultiplier;
2447
+ return val < scaledCompareTo;
2357
2448
  case '<=':
2358
- return val <= compareTo * claimMultiplier;
2449
+ return val <= scaledCompareTo;
2359
2450
  }
2360
2451
  }
2361
2452
  else if (!isNumber && typeof compareTo === 'string') {
2362
2453
  switch (cond.operator) {
2454
+ case '==':
2455
+ return val === compareTo;
2456
+ case '!=':
2457
+ return val !== compareTo;
2363
2458
  case 'has':
2364
2459
  return val.includes(compareTo);
2365
2460
  case 'not_has':
@@ -2368,6 +2463,98 @@ function evaluateDynamicCondition(dynamicObj, cond, claimMultiplier = 1) {
2368
2463
  }
2369
2464
  return false;
2370
2465
  }
2466
+ /**
2467
+ * Calculates the maximum number of claims supported by a single dynamic condition.
2468
+ */
2469
+ function getMaxClaimsForDynamicCondition(dynamicObj, cond) {
2470
+ if (!dynamicObj)
2471
+ return 0;
2472
+ const val = dynamicObj[cond.key];
2473
+ if (val === undefined)
2474
+ return 0;
2475
+ if (typeof val === 'number') {
2476
+ const base = Number(cond.compareTo);
2477
+ if (isNaN(base)) {
2478
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2479
+ }
2480
+ switch (cond.operator) {
2481
+ case '>=':
2482
+ if (base === 0)
2483
+ return val >= 0 ? Infinity : 0;
2484
+ if (base < 0)
2485
+ return val >= base ? Infinity : 0;
2486
+ return Math.max(0, Math.floor(val / base));
2487
+ case '>':
2488
+ if (base === 0)
2489
+ return val > 0 ? Infinity : 0;
2490
+ if (base < 0)
2491
+ return val > base ? Infinity : 0;
2492
+ if (val <= 0)
2493
+ return 0;
2494
+ return Math.max(0, Math.ceil(val / base) - 1);
2495
+ case '==':
2496
+ return val === base ? Infinity : 0;
2497
+ case '!=':
2498
+ return val !== base ? Infinity : 0;
2499
+ case '<=':
2500
+ if (base === 0)
2501
+ return val <= 0 ? Infinity : 0;
2502
+ if (base > 0)
2503
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2504
+ if (val >= 0)
2505
+ return 0;
2506
+ return Math.max(0, Math.floor(val / base));
2507
+ case '<':
2508
+ if (base === 0)
2509
+ return val < 0 ? Infinity : 0;
2510
+ if (base > 0)
2511
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2512
+ if (val >= 0)
2513
+ return 0;
2514
+ return Math.max(0, Math.ceil(val / base) - 1);
2515
+ }
2516
+ }
2517
+ // we don't scale the rest, they are always true or always false
2518
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2519
+ }
2520
+ /**
2521
+ * Calculates the maximum number of claims supported by a group of dynamic conditions.
2522
+ */
2523
+ function getMaxClaimsForDynamicGroup(dynamicObj, dynamicGroup, currentClaimCount = 0) {
2524
+ const { conditions, links } = dynamicGroup;
2525
+ if (!conditions || conditions.length === 0)
2526
+ return Infinity;
2527
+ // AND only
2528
+ if (!links || links.length === 0 || links.every((l) => l === 'AND')) {
2529
+ let minClaims = Infinity;
2530
+ for (const cond of conditions) {
2531
+ const max = getMaxClaimsForDynamicCondition(dynamicObj, cond);
2532
+ if (max === 0)
2533
+ return 0;
2534
+ minClaims = Math.min(minClaims, max);
2535
+ }
2536
+ return minClaims;
2537
+ }
2538
+ // OR only
2539
+ if (links.every((l) => l === 'OR')) {
2540
+ let maxClaims = 0;
2541
+ for (const cond of conditions) {
2542
+ const max = getMaxClaimsForDynamicCondition(dynamicObj, cond);
2543
+ if (max === Infinity)
2544
+ return Infinity;
2545
+ maxClaims = Math.max(maxClaims, max);
2546
+ }
2547
+ return maxClaims;
2548
+ }
2549
+ // mixed:
2550
+ const maxIterations = 100;
2551
+ for (let n = currentClaimCount + 1; n <= currentClaimCount + maxIterations; n++) {
2552
+ if (!meetsDynamicConditions(dynamicObj, dynamicGroup, n)) {
2553
+ return n - 1;
2554
+ }
2555
+ }
2556
+ return currentClaimCount + maxIterations;
2557
+ }
2371
2558
  /**
2372
2559
  * Evaluates a group of dynamic conditions with logical links (AND, OR, AND NOT).
2373
2560
  * @param dynamicObj - The player's dynamic object with any key and string or number value.
@@ -2476,6 +2663,8 @@ exports.OfferStore = OfferStore;
2476
2663
  exports.OfferwallClient = OfferwallClient;
2477
2664
  exports.PlayerOfferStatuses = PlayerOfferStatuses;
2478
2665
  exports.SSEConnection = SSEConnection;
2666
+ exports.getMaxClaimsForDynamicCondition = getMaxClaimsForDynamicCondition;
2667
+ exports.getMaxClaimsForDynamicGroup = getMaxClaimsForDynamicGroup;
2479
2668
  exports.hasConditions = hasConditions;
2480
2669
  exports.meetsBaseConditions = meetsBaseConditions;
2481
2670
  exports.meetsClaimableConditions = meetsClaimableConditions;