@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.
@@ -634,25 +634,33 @@
634
634
  }
635
635
 
636
636
  class OfferStore {
637
- constructor(config) {
637
+ constructor(config, client) {
638
+ this.client = client;
638
639
  this.offers = new Map();
639
- this.player = null;
640
+ this.players = new Map();
640
641
  this.logger = createLogger(config, 'OfferStore');
641
642
  }
642
- getPlayer() {
643
- return this.player || null;
643
+ getPlayer(targetId = this.client.getSelfId()) {
644
+ if (!targetId)
645
+ return null;
646
+ return this.players.get(targetId) || null;
644
647
  }
645
648
  setPlayer(player) {
646
- this.player = player;
649
+ this.players.set(player.snapshot.playerId, player);
647
650
  this.logger.log('Updated player:', player);
648
651
  }
649
652
  /**
650
653
  * Set all offers (replaces existing)
651
654
  */
652
- setOffers(offers) {
653
- this.offers.clear();
655
+ setOffers(offers, target) {
656
+ const targetPlayer = target || this.getPlayer();
657
+ if (!targetPlayer) {
658
+ this.logger.warn('No target player to set offers for');
659
+ return;
660
+ }
661
+ this.offers.set(targetPlayer.snapshot.playerId, new Map());
654
662
  offers.forEach((offer) => {
655
- this.offers.set(offer.instanceId, offer);
663
+ this.offers.get(targetPlayer.snapshot.playerId).set(offer.instanceId, offer);
656
664
  });
657
665
  this.logger.log(`Set ${offers.length} offers`);
658
666
  }
@@ -660,16 +668,23 @@
660
668
  * Add or update a single offer
661
669
  */
662
670
  upsertOffer(offer) {
663
- const previousOffer = this.offers.get(offer.instanceId);
664
- this.offers.set(offer.instanceId, offer);
671
+ let playerOffers = this.offers.get(offer.playerId);
672
+ if (!playerOffers) {
673
+ playerOffers = new Map();
674
+ this.offers.set(offer.playerId, playerOffers);
675
+ }
676
+ const previousOffer = playerOffers.get(offer.instanceId);
677
+ playerOffers.set(offer.instanceId, offer);
665
678
  this.logger.log(`${previousOffer ? 'Updated' : 'Added'} offer:`, offer.instanceId);
666
679
  return previousOffer;
667
680
  }
668
681
  /**
669
682
  * Remove an offer
670
683
  */
671
- removeOffer(offerId) {
672
- const removed = this.offers.delete(offerId);
684
+ removeOffer(offerId, targetId = this.client.getSelfId()) {
685
+ if (!targetId)
686
+ return false;
687
+ const removed = this.offers.get(targetId)?.delete(offerId) || false;
673
688
  if (removed) {
674
689
  this.logger.log(`Removed offer:`, offerId);
675
690
  }
@@ -678,26 +693,30 @@
678
693
  /**
679
694
  * Get a single offer
680
695
  */
681
- getOffer(offerId) {
682
- return this.offers.get(offerId);
696
+ getOffer(offerId, targetId = this.client.getSelfId()) {
697
+ if (!targetId)
698
+ return undefined;
699
+ return this.offers.get(targetId)?.get(offerId);
683
700
  }
684
701
  /**
685
702
  * Get all offers
686
703
  */
687
- getAllOffers() {
688
- return Array.from(this.offers.values());
704
+ getAllOffers(targetId = this.client.getSelfId()) {
705
+ if (!targetId)
706
+ return [];
707
+ return Array.from(this.offers.get(targetId)?.values() || []);
689
708
  }
690
709
  /**
691
710
  * Get offers filtered by status
692
711
  */
693
- getOffersByStatus(status) {
694
- return this.getAllOffers().filter((offer) => offer.status === status);
712
+ getOffersByStatus(status, targetId = this.client.getSelfId()) {
713
+ return this.getAllOffers(targetId).filter((offer) => offer.status === status);
695
714
  }
696
715
  /**
697
716
  * Get active offers (not expired, not claimed)
698
717
  */
699
- getActiveOffers() {
700
- return this.getAllOffers().filter((offer) => {
718
+ getActiveOffers(targetId = this.client.getSelfId()) {
719
+ return this.getAllOffers(targetId).filter((offer) => {
701
720
  if (!offer.status)
702
721
  return false; // Must have a status
703
722
  return (offer.status === 'surfaced' ||
@@ -708,14 +727,14 @@
708
727
  /**
709
728
  * Get claimable offers
710
729
  */
711
- getClaimableOffers() {
712
- return this.getOffersByStatus('claimable');
730
+ getClaimableOffers(targetId = this.client.getSelfId()) {
731
+ return this.getOffersByStatus('claimable', targetId);
713
732
  }
714
733
  /**
715
734
  * Check if an offer has expired
716
735
  */
717
- isOfferExpired(offerId) {
718
- const offer = this.getOffer(offerId);
736
+ isOfferExpired(offerId, targetId = this.client.getSelfId()) {
737
+ const offer = this.getOffer(offerId, targetId);
719
738
  if (!offer)
720
739
  return true;
721
740
  // Check status
@@ -728,17 +747,29 @@
728
747
  return false;
729
748
  }
730
749
  /**
731
- * Clear all offers
750
+ * Clear all offers for a specific player
732
751
  */
733
- clear() {
752
+ clear(targetId = this.client.getSelfId()) {
753
+ if (!targetId)
754
+ return;
755
+ this.offers.set(targetId, new Map());
756
+ this.logger.log('Cleared all offers for player:', targetId);
757
+ }
758
+ /**
759
+ * Clear all offers for all players
760
+ */
761
+ clearAll() {
734
762
  this.offers.clear();
735
- this.logger.log('Cleared all offers');
763
+ this.logger.log('Cleared all offers for all players');
736
764
  }
737
765
  /**
738
- * Get offer count
766
+ * Get offer count (for self player)
739
767
  */
740
768
  get size() {
741
- return this.offers.size;
769
+ const selfId = this.client.getSelfId();
770
+ if (!selfId)
771
+ return 0;
772
+ return this.offers.get(selfId)?.size || 0;
742
773
  }
743
774
  }
744
775
 
@@ -841,7 +872,9 @@
841
872
  return null;
842
873
  }
843
874
  setCurrencyAssetContents(currencies) {
844
- this.currencies = currencies;
875
+ Object.keys(currencies).forEach((key) => {
876
+ this.currencies[key] = currencies[key];
877
+ });
845
878
  }
846
879
  resolveReward(reward) {
847
880
  if (reward.kind === 'loyalty_currency' && reward.rewardId) {
@@ -897,6 +930,7 @@
897
930
  constructor(config) {
898
931
  this.isInitializing = false;
899
932
  this.pendingStackedToken = null;
933
+ this.selfId = null;
900
934
  this.config = {
901
935
  autoConnect: config.autoConnect ?? false,
902
936
  reconnect: config.reconnect ?? true,
@@ -909,7 +943,7 @@
909
943
  this.hooks = this.config.hooks || {};
910
944
  this.logger = createLogger(this.config, 'OfferwallClient');
911
945
  this.eventEmitter = new EventEmitter(this.config);
912
- this.offerStore = new OfferStore(this.config);
946
+ this.offerStore = new OfferStore(this.config, this);
913
947
  this.tokenManager = new TokenManager(this.config);
914
948
  this.assetHelper = new AssetHelper(this.config);
915
949
  this.sseConnection = new SSEConnection(this.config, this.eventEmitter, this.tokenManager);
@@ -950,6 +984,9 @@
950
984
  get assets() {
951
985
  return this.assetHelper;
952
986
  }
987
+ getSelfId() {
988
+ return this.selfId;
989
+ }
953
990
  /**
954
991
  * Initialize the offerwall client and connect
955
992
  */
@@ -1012,8 +1049,9 @@
1012
1049
  if (this.sseConnection) {
1013
1050
  this.sseConnection.disconnect();
1014
1051
  }
1015
- this.offerStore.clear();
1052
+ this.offerStore.clearAll();
1016
1053
  this.tokenManager.clearToken();
1054
+ this.selfId = null;
1017
1055
  if (this.hooks.afterDisconnect) {
1018
1056
  await this.hooks.afterDisconnect();
1019
1057
  }
@@ -1021,8 +1059,8 @@
1021
1059
  /**
1022
1060
  * Claim rewards for an offer
1023
1061
  */
1024
- async claimReward(instanceId) {
1025
- const offer = this.offerStore.getOffer(instanceId);
1062
+ async claimReward(instanceId, targetId = this.getSelfId()) {
1063
+ const offer = this.offerStore.getOffer(instanceId, targetId);
1026
1064
  if (!offer) {
1027
1065
  throw new Error(`Offer ${instanceId} not found`);
1028
1066
  }
@@ -1037,7 +1075,7 @@
1037
1075
  }
1038
1076
  }
1039
1077
  try {
1040
- const response = await this.claimOfferAPI(instanceId);
1078
+ const response = await this.claimOfferAPI(instanceId, targetId);
1041
1079
  const updatedOffer = { ...offer, status: 'claimed' };
1042
1080
  this.offerStore.upsertOffer(updatedOffer);
1043
1081
  this.eventEmitter.emit(exports.OfferEvent.OFFER_CLAIMED, {
@@ -1093,7 +1131,7 @@
1093
1131
  });
1094
1132
  */
1095
1133
  this.eventEmitter.on(exports.OfferEvent.OFFER_SURFACED, ({ offer }) => {
1096
- this.offerStore.upsertOffer(offer);
1134
+ this.offerStore.upsertOffer(offer); // should always be selfId
1097
1135
  this.logger.log(`Surfaced offer: ${offer.instanceId}`);
1098
1136
  });
1099
1137
  }
@@ -1123,23 +1161,27 @@
1123
1161
  }
1124
1162
  return response.json();
1125
1163
  }
1126
- async claimOfferAPI(instanceId) {
1164
+ async claimOfferAPI(instanceId, targetId = null) {
1127
1165
  return this.postWithAuth('/v1/client/reward/claim', {
1128
1166
  instanceId,
1129
1167
  kind: 'offer',
1168
+ targetId: targetId || undefined,
1130
1169
  });
1131
1170
  }
1132
- getPlayer() {
1133
- return this.offerStore.getPlayer();
1171
+ getPlayer(targetId = this.getSelfId()) {
1172
+ return this.offerStore.getPlayer(targetId);
1134
1173
  }
1135
- getOffers() {
1136
- return this.offerStore.getAllOffers();
1174
+ getOffers(targetId = this.getSelfId()) {
1175
+ return this.offerStore.getAllOffers(targetId);
1137
1176
  }
1138
- async refreshOffersAndPlayer() {
1177
+ async refreshOffersAndPlayer(targetId = null) {
1139
1178
  try {
1140
- const { offers, player } = await this.getOffersAndPlayer();
1141
- this.offerStore.setOffers(offers);
1179
+ const { offers, player } = await this.getOffersAndPlayer(targetId);
1180
+ if (targetId == null) {
1181
+ this.selfId = player.snapshot.playerId;
1182
+ }
1142
1183
  this.offerStore.setPlayer(player);
1184
+ this.offerStore.setOffers(offers, player);
1143
1185
  this.eventEmitter.emit(exports.OfferEvent.REFRESH, { offers, player: player });
1144
1186
  this.logger.log('Refreshed offers and player snapshot');
1145
1187
  }
@@ -1148,9 +1190,10 @@
1148
1190
  throw error;
1149
1191
  }
1150
1192
  }
1151
- async getOffersAndPlayer() {
1193
+ async getOffersAndPlayer(targetId = null) {
1152
1194
  const data = await this.postWithAuth('/v1/client/player/campaigns', {
1153
1195
  viewingCampaigns: true,
1196
+ targetId: targetId || undefined,
1154
1197
  });
1155
1198
  if (!data.offers || !Array.isArray(data.offers)) {
1156
1199
  throw new Error('No offers returned from offers endpoint');
@@ -1925,6 +1968,8 @@
1925
1968
  const claimMultiplier = shouldScale ? (playerOffer.claimedCount || 0) + 1 : 1;
1926
1969
  const conditionData = [];
1927
1970
  let isValid = true;
1971
+ let maxTotalClaimsFromScaling = Infinity;
1972
+ const updateMax = (limit) => (maxTotalClaimsFromScaling = Math.min(maxTotalClaimsFromScaling, limit));
1928
1973
  if (completionConditions?.context?.id) {
1929
1974
  const hasTrackedContext = completionTrackers?.context &&
1930
1975
  completionConditions.context.id === completionTrackers.context;
@@ -1941,17 +1986,22 @@
1941
1986
  }
1942
1987
  else {
1943
1988
  if (isDisqualify)
1944
- return { isValid: false };
1989
+ return { isValid: false, availableClaimsNow: 0 };
1945
1990
  }
1946
1991
  }
1947
1992
  if (conditions?.buyItem) {
1948
- const scaledAmount = (conditions.buyItem.amount || 1) * claimMultiplier;
1949
- const isDisqualify = (completionTrackers?.buyItem || 0) < scaledAmount;
1993
+ const baseAmount = conditions.buyItem.amount || 1;
1994
+ const scaledAmount = baseAmount * claimMultiplier;
1995
+ const trackerValue = completionTrackers?.buyItem || 0;
1996
+ const isDisqualify = trackerValue < scaledAmount;
1997
+ if (shouldScale && baseAmount > 0) {
1998
+ updateMax(Math.floor(trackerValue / baseAmount));
1999
+ }
1950
2000
  if (addDetails) {
1951
2001
  conditionData.push({
1952
2002
  isMet: !isDisqualify,
1953
2003
  kind: 'buyItem',
1954
- trackerAmount: completionTrackers?.buyItem || 0,
2004
+ trackerAmount: trackerValue,
1955
2005
  text: `Buy ${scaledAmount} ${conditions.buyItem.name}`,
1956
2006
  });
1957
2007
  if (isDisqualify)
@@ -1959,17 +2009,22 @@
1959
2009
  }
1960
2010
  else {
1961
2011
  if (isDisqualify)
1962
- return { isValid: false };
2012
+ return { isValid: false, availableClaimsNow: 0 };
1963
2013
  }
1964
2014
  }
1965
2015
  if (conditions?.spendCurrency) {
1966
- const scaledAmount = (conditions.spendCurrency.amount || 1) * claimMultiplier;
1967
- const isDisqualify = (completionTrackers?.spendCurrency || 0) < scaledAmount;
2016
+ const baseAmount = conditions.spendCurrency.amount || 1;
2017
+ const scaledAmount = baseAmount * claimMultiplier;
2018
+ const trackerValue = completionTrackers?.spendCurrency || 0;
2019
+ const isDisqualify = trackerValue < scaledAmount;
2020
+ if (shouldScale && baseAmount > 0) {
2021
+ updateMax(Math.floor(trackerValue / baseAmount));
2022
+ }
1968
2023
  if (addDetails) {
1969
2024
  conditionData.push({
1970
2025
  isMet: !isDisqualify,
1971
2026
  kind: 'spendCurrency',
1972
- trackerAmount: completionTrackers?.spendCurrency || 0,
2027
+ trackerAmount: trackerValue,
1973
2028
  text: `Spend ${scaledAmount} ${conditions.spendCurrency.name}`,
1974
2029
  });
1975
2030
  if (isDisqualify)
@@ -1977,17 +2032,22 @@
1977
2032
  }
1978
2033
  else {
1979
2034
  if (isDisqualify)
1980
- return { isValid: false };
2035
+ return { isValid: false, availableClaimsNow: 0 };
1981
2036
  }
1982
2037
  }
1983
2038
  if (conditions?.depositCurrency) {
1984
- const scaledAmount = (conditions.depositCurrency.amount || 1) * claimMultiplier;
1985
- const isDisqualify = (completionTrackers?.depositCurrency || 0) < scaledAmount;
2039
+ const baseAmount = conditions.depositCurrency.amount || 1;
2040
+ const scaledAmount = baseAmount * claimMultiplier;
2041
+ const trackerValue = completionTrackers?.depositCurrency || 0;
2042
+ const isDisqualify = trackerValue < scaledAmount;
2043
+ if (shouldScale && baseAmount > 0) {
2044
+ updateMax(Math.floor(trackerValue / baseAmount));
2045
+ }
1986
2046
  if (addDetails) {
1987
2047
  conditionData.push({
1988
2048
  isMet: !isDisqualify,
1989
2049
  kind: 'depositCurrency',
1990
- trackerAmount: completionTrackers?.depositCurrency || 0,
2050
+ trackerAmount: trackerValue,
1991
2051
  text: `Deposit ${scaledAmount} ${conditions.depositCurrency.name}`,
1992
2052
  });
1993
2053
  if (isDisqualify)
@@ -1995,7 +2055,7 @@
1995
2055
  }
1996
2056
  else {
1997
2057
  if (isDisqualify)
1998
- return { isValid: false };
2058
+ return { isValid: false, availableClaimsNow: 0 };
1999
2059
  }
2000
2060
  }
2001
2061
  if (conditions?.login) {
@@ -2012,7 +2072,7 @@
2012
2072
  }
2013
2073
  else {
2014
2074
  if (!isMet)
2015
- return { isValid: false };
2075
+ return { isValid: false, availableClaimsNow: 0 };
2016
2076
  }
2017
2077
  }
2018
2078
  if (conditions?.loginStreak) {
@@ -2032,7 +2092,7 @@
2032
2092
  }
2033
2093
  else {
2034
2094
  if (isDisqualify)
2035
- return { isValid: false };
2095
+ return { isValid: false, availableClaimsNow: 0 };
2036
2096
  }
2037
2097
  }
2038
2098
  if (conditions?.social) {
@@ -2054,6 +2114,17 @@
2054
2114
  if (likes < minLikes || views < minViews || comments < minComments) {
2055
2115
  isDisqualify = true;
2056
2116
  }
2117
+ if (shouldScale && mode === 'accumulate' && hasContent) {
2118
+ const baseLikes = cSocial?.minLikes || 0;
2119
+ const baseViews = cSocial?.minViews || 0;
2120
+ const baseComments = cSocial?.minComments || 0;
2121
+ if (baseLikes > 0)
2122
+ updateMax(Math.floor(likes / baseLikes));
2123
+ if (baseViews > 0)
2124
+ updateMax(Math.floor(views / baseViews));
2125
+ if (baseComments > 0)
2126
+ updateMax(Math.floor(comments / baseComments));
2127
+ }
2057
2128
  if (addDetails) {
2058
2129
  const platformMap = {
2059
2130
  tiktok: 'TikTok',
@@ -2125,14 +2196,18 @@
2125
2196
  }
2126
2197
  else {
2127
2198
  if (isDisqualify)
2128
- return { isValid: false };
2199
+ return { isValid: false, availableClaimsNow: 0 };
2129
2200
  }
2130
2201
  }
2131
2202
  // Linked completions - wait for N linked entities to complete
2132
2203
  if (conditions?.linkedCompletions?.min) {
2204
+ const baseMin = conditions.linkedCompletions.min;
2133
2205
  const currentCount = completionTrackers?.linkedCompletions || 0;
2134
- const scaledMin = conditions.linkedCompletions.min * claimMultiplier;
2206
+ const scaledMin = baseMin * claimMultiplier;
2135
2207
  const isDisqualify = currentCount < scaledMin;
2208
+ if (shouldScale && baseMin > 0) {
2209
+ updateMax(Math.floor(currentCount / baseMin));
2210
+ }
2136
2211
  if (addDetails) {
2137
2212
  conditionData.push({
2138
2213
  isMet: !isDisqualify,
@@ -2145,7 +2220,7 @@
2145
2220
  }
2146
2221
  else {
2147
2222
  if (isDisqualify)
2148
- return { isValid: false };
2223
+ return { isValid: false, availableClaimsNow: 0 };
2149
2224
  }
2150
2225
  }
2151
2226
  if (conditions?.dynamicTracker?.conditions?.length) {
@@ -2155,6 +2230,13 @@
2155
2230
  ...conditions.dynamicTracker,
2156
2231
  conditions: resolvedConditions,
2157
2232
  }, claimMultiplier);
2233
+ if (shouldScale) {
2234
+ const dynamicMax = getMaxClaimsForDynamicGroup(completionTrackers?.dynamicTracker || {}, {
2235
+ ...conditions.dynamicTracker,
2236
+ conditions: resolvedConditions,
2237
+ }, playerOffer.claimedCount || 0);
2238
+ updateMax(dynamicMax);
2239
+ }
2158
2240
  if (addDetails) {
2159
2241
  conditionData.push({
2160
2242
  isMet: dynamicResult,
@@ -2166,7 +2248,7 @@
2166
2248
  }
2167
2249
  else {
2168
2250
  if (!dynamicResult)
2169
- return { isValid: false };
2251
+ return { isValid: false, availableClaimsNow: 0 };
2170
2252
  }
2171
2253
  }
2172
2254
  const r = meetsBaseConditions({
@@ -2177,9 +2259,18 @@
2177
2259
  });
2178
2260
  isValid = isValid && r.isValid;
2179
2261
  conditionData.push(...(r.conditionData || []));
2180
- return { isValid, conditionData };
2181
- }
2182
- return { isValid: true, conditionData: [] };
2262
+ if (maxClaimCount && maxClaimCount > 0) {
2263
+ updateMax(maxClaimCount);
2264
+ }
2265
+ const claimedCount = playerOffer.claimedCount || 0;
2266
+ let availableClaimsNow = !isValid
2267
+ ? 0
2268
+ : maxTotalClaimsFromScaling === Infinity
2269
+ ? -1
2270
+ : Math.max(0, maxTotalClaimsFromScaling - claimedCount);
2271
+ return { isValid, conditionData, availableClaimsNow };
2272
+ }
2273
+ return { isValid: true, conditionData: [], availableClaimsNow: -1 };
2183
2274
  };
2184
2275
  /**
2185
2276
  * Checks if completion conditions were met before a specific expiry time.
@@ -2344,26 +2435,30 @@
2344
2435
  }
2345
2436
  }
2346
2437
  const compareTo = isNumber ? Number(cond.compareTo) : String(cond.compareTo);
2347
- switch (cond.operator) {
2348
- case '==':
2349
- return val === compareTo;
2350
- case '!=':
2351
- return val !== compareTo;
2352
- }
2353
2438
  if (isNumber && typeof compareTo === 'number') {
2439
+ const skipMultiplier = cond.operator === '==' || cond.operator === '!=';
2440
+ const scaledCompareTo = skipMultiplier ? compareTo : compareTo * claimMultiplier;
2354
2441
  switch (cond.operator) {
2442
+ case '==':
2443
+ return val === scaledCompareTo;
2444
+ case '!=':
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
  case '<':
2360
- return val < compareTo * claimMultiplier;
2451
+ return val < scaledCompareTo;
2361
2452
  case '<=':
2362
- return val <= compareTo * claimMultiplier;
2453
+ return val <= scaledCompareTo;
2363
2454
  }
2364
2455
  }
2365
2456
  else if (!isNumber && typeof compareTo === 'string') {
2366
2457
  switch (cond.operator) {
2458
+ case '==':
2459
+ return val === compareTo;
2460
+ case '!=':
2461
+ return val !== compareTo;
2367
2462
  case 'has':
2368
2463
  return val.includes(compareTo);
2369
2464
  case 'not_has':
@@ -2372,6 +2467,98 @@
2372
2467
  }
2373
2468
  return false;
2374
2469
  }
2470
+ /**
2471
+ * Calculates the maximum number of claims supported by a single dynamic condition.
2472
+ */
2473
+ function getMaxClaimsForDynamicCondition(dynamicObj, cond) {
2474
+ if (!dynamicObj)
2475
+ return 0;
2476
+ const val = dynamicObj[cond.key];
2477
+ if (val === undefined)
2478
+ return 0;
2479
+ if (typeof val === 'number') {
2480
+ const base = Number(cond.compareTo);
2481
+ if (isNaN(base)) {
2482
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2483
+ }
2484
+ switch (cond.operator) {
2485
+ case '>=':
2486
+ if (base === 0)
2487
+ return val >= 0 ? Infinity : 0;
2488
+ if (base < 0)
2489
+ return val >= base ? Infinity : 0;
2490
+ return Math.max(0, Math.floor(val / base));
2491
+ case '>':
2492
+ if (base === 0)
2493
+ return val > 0 ? Infinity : 0;
2494
+ if (base < 0)
2495
+ return val > base ? Infinity : 0;
2496
+ if (val <= 0)
2497
+ return 0;
2498
+ return Math.max(0, Math.ceil(val / base) - 1);
2499
+ case '==':
2500
+ return val === base ? Infinity : 0;
2501
+ case '!=':
2502
+ return val !== base ? Infinity : 0;
2503
+ case '<=':
2504
+ if (base === 0)
2505
+ return val <= 0 ? Infinity : 0;
2506
+ if (base > 0)
2507
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2508
+ if (val >= 0)
2509
+ return 0;
2510
+ return Math.max(0, Math.floor(val / base));
2511
+ case '<':
2512
+ if (base === 0)
2513
+ return val < 0 ? Infinity : 0;
2514
+ if (base > 0)
2515
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2516
+ if (val >= 0)
2517
+ return 0;
2518
+ return Math.max(0, Math.ceil(val / base) - 1);
2519
+ }
2520
+ }
2521
+ // we don't scale the rest, they are always true or always false
2522
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2523
+ }
2524
+ /**
2525
+ * Calculates the maximum number of claims supported by a group of dynamic conditions.
2526
+ */
2527
+ function getMaxClaimsForDynamicGroup(dynamicObj, dynamicGroup, currentClaimCount = 0) {
2528
+ const { conditions, links } = dynamicGroup;
2529
+ if (!conditions || conditions.length === 0)
2530
+ return Infinity;
2531
+ // AND only
2532
+ if (!links || links.length === 0 || links.every((l) => l === 'AND')) {
2533
+ let minClaims = Infinity;
2534
+ for (const cond of conditions) {
2535
+ const max = getMaxClaimsForDynamicCondition(dynamicObj, cond);
2536
+ if (max === 0)
2537
+ return 0;
2538
+ minClaims = Math.min(minClaims, max);
2539
+ }
2540
+ return minClaims;
2541
+ }
2542
+ // OR only
2543
+ if (links.every((l) => l === 'OR')) {
2544
+ let maxClaims = 0;
2545
+ for (const cond of conditions) {
2546
+ const max = getMaxClaimsForDynamicCondition(dynamicObj, cond);
2547
+ if (max === Infinity)
2548
+ return Infinity;
2549
+ maxClaims = Math.max(maxClaims, max);
2550
+ }
2551
+ return maxClaims;
2552
+ }
2553
+ // mixed:
2554
+ const maxIterations = 100;
2555
+ for (let n = currentClaimCount + 1; n <= currentClaimCount + maxIterations; n++) {
2556
+ if (!meetsDynamicConditions(dynamicObj, dynamicGroup, n)) {
2557
+ return n - 1;
2558
+ }
2559
+ }
2560
+ return currentClaimCount + maxIterations;
2561
+ }
2375
2562
  /**
2376
2563
  * Evaluates a group of dynamic conditions with logical links (AND, OR, AND NOT).
2377
2564
  * @param dynamicObj - The player's dynamic object with any key and string or number value.
@@ -2480,6 +2667,8 @@
2480
2667
  exports.OfferwallClient = OfferwallClient;
2481
2668
  exports.PlayerOfferStatuses = PlayerOfferStatuses;
2482
2669
  exports.SSEConnection = SSEConnection;
2670
+ exports.getMaxClaimsForDynamicCondition = getMaxClaimsForDynamicCondition;
2671
+ exports.getMaxClaimsForDynamicGroup = getMaxClaimsForDynamicGroup;
2483
2672
  exports.hasConditions = hasConditions;
2484
2673
  exports.meetsBaseConditions = meetsBaseConditions;
2485
2674
  exports.meetsClaimableConditions = meetsClaimableConditions;