@pixels-online/pixels-client-js-sdk 1.16.0 → 1.18.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.esm.js CHANGED
@@ -1278,19 +1278,47 @@ class OfferwallClient {
1278
1278
  }
1279
1279
 
1280
1280
  const keyPattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
1281
- // renders template by replacing {keyName} with values from dynamic
1281
+ /**
1282
+ * This replaces {keyName} keys from the template with corresponding values from the dynamic object.
1283
+ */
1282
1284
  function renderTemplate(template, dynamic) {
1283
1285
  if (!template)
1284
1286
  return '';
1285
- return template.replace(keyPattern, (match, key) => {
1287
+ return template.replace(keyPattern, (_match, key) => {
1288
+ if (dynamic && typeof dynamic[key] === 'boolean') {
1289
+ return dynamic[key] ? '✓' : '✗';
1290
+ }
1286
1291
  if (dynamic && dynamic[key] !== undefined) {
1287
1292
  return String(dynamic[key]);
1288
1293
  }
1289
1294
  return '{?}'; // indicate missing key
1290
1295
  });
1291
1296
  }
1297
+ /**
1298
+ * This replaces {{keyName}} in dynamic condition keys with corresponding values from
1299
+ * the PlayerOffer.trackers
1300
+ *
1301
+ * eg. a condition high_score_pet-{{surfacerPlayerId}} with high_score_pet-12345
1302
+ */
1303
+ function replaceDynamicConditionKey(key, trackers) {
1304
+ return key?.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (match, p1) => {
1305
+ const value = trackers[p1];
1306
+ return value !== undefined ? String(value) : match;
1307
+ });
1308
+ }
1309
+ /** this replaces all of the dynamic conditions.keys by calling replaceDynamicConditionKey */
1310
+ function replaceDynamicConditionKeys(conditions, trackers) {
1311
+ return conditions.map((condition) => ({
1312
+ ...condition,
1313
+ key: replaceDynamicConditionKey(condition.key, trackers),
1314
+ }));
1315
+ }
1292
1316
 
1293
- const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
1317
+ const meetsBaseConditions = ({ conditions, playerSnap, addDetails,
1318
+ /** this exists if calling meetsBaseConditions from meetsCompletionConditions. but surfacing
1319
+ * check doesn't use this since we don't have a playerOffer at surfacing time
1320
+ */
1321
+ playerOffer, }) => {
1294
1322
  const conditionData = [];
1295
1323
  let isValid = true;
1296
1324
  if (conditions?.minDaysInGame) {
@@ -1657,7 +1685,11 @@ const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
1657
1685
  }
1658
1686
  // Evaluate dynamic conditions
1659
1687
  if (conditions?.dynamic?.conditions?.length) {
1660
- const dynamicResult = meetsDynamicConditions(playerSnap.dynamic || {}, conditions.dynamic);
1688
+ const resolvedConditions = replaceDynamicConditionKeys(conditions.dynamic.conditions, playerOffer?.trackers || {});
1689
+ const dynamicResult = meetsDynamicConditions(playerSnap.dynamic, {
1690
+ ...conditions.dynamic,
1691
+ conditions: resolvedConditions,
1692
+ });
1661
1693
  if (addDetails) {
1662
1694
  conditionData.push({
1663
1695
  isMet: dynamicResult,
@@ -1766,6 +1798,31 @@ const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, pl
1766
1798
  return { isValid: false };
1767
1799
  }
1768
1800
  }
1801
+ if (conditions.allowedCountries?.length) {
1802
+ const playerCountry = playerSnap.ip?.countryCode;
1803
+ if (!playerCountry || !conditions.allowedCountries.includes(playerCountry)) {
1804
+ return { isValid: false };
1805
+ }
1806
+ }
1807
+ if (conditions.restrictedCountries?.length) {
1808
+ const playerCountry = playerSnap.ip?.countryCode;
1809
+ if (!playerCountry) {
1810
+ return { isValid: false };
1811
+ }
1812
+ if (conditions.restrictedCountries.includes(playerCountry)) {
1813
+ return { isValid: false };
1814
+ }
1815
+ }
1816
+ if (conditions.networkRestrictions?.length) {
1817
+ if (!playerSnap.ip) {
1818
+ return { isValid: false };
1819
+ }
1820
+ for (const restriction of conditions.networkRestrictions) {
1821
+ if (playerSnap.ip[restriction]) {
1822
+ return { isValid: false };
1823
+ }
1824
+ }
1825
+ }
1769
1826
  return meetsBaseConditions({ conditions, playerSnap });
1770
1827
  };
1771
1828
  const hasConditions = (conditions) => {
@@ -1818,6 +1875,12 @@ const hasConditions = (conditions) => {
1818
1875
  return true;
1819
1876
  if (surCond.links && Object.keys(surCond.links).length > 0)
1820
1877
  return true;
1878
+ if (surCond.allowedCountries?.length)
1879
+ return true;
1880
+ if (surCond.restrictedCountries?.length)
1881
+ return true;
1882
+ if (surCond.networkRestrictions?.length)
1883
+ return true;
1821
1884
  const compCond = conditions;
1822
1885
  if (compCond.context)
1823
1886
  return true;
@@ -1839,11 +1902,25 @@ const hasConditions = (conditions) => {
1839
1902
  return true;
1840
1903
  return false;
1841
1904
  };
1842
- const meetsCompletionConditions = ({ completionConditions, completionTrackers, playerSnap, addDetails = false, }) => {
1905
+ const offerMeetsCompletionConditions = (offer, snapshot) => {
1906
+ return meetsCompletionConditions({
1907
+ completionConditions: offer.completionConditions || {},
1908
+ completionTrackers: offer.completionTrackers,
1909
+ playerSnap: snapshot,
1910
+ playerOffer: offer,
1911
+ addDetails: true,
1912
+ });
1913
+ };
1914
+ const meetsCompletionConditions = ({ completionConditions, completionTrackers, playerSnap, playerOffer, addDetails = false, maxClaimCount, }) => {
1843
1915
  if (completionConditions) {
1844
1916
  const conditions = completionConditions;
1917
+ // For multi-claim offers, scale cumulative requirements by (claimedCount + 1)
1918
+ const shouldScale = maxClaimCount === -1 || (maxClaimCount && maxClaimCount > 1);
1919
+ const claimMultiplier = shouldScale ? (playerOffer.claimedCount || 0) + 1 : 1;
1845
1920
  const conditionData = [];
1846
1921
  let isValid = true;
1922
+ let maxTotalClaimsFromScaling = Infinity;
1923
+ const updateMax = (limit) => (maxTotalClaimsFromScaling = Math.min(maxTotalClaimsFromScaling, limit));
1847
1924
  if (completionConditions?.context?.id) {
1848
1925
  const hasTrackedContext = completionTrackers?.context &&
1849
1926
  completionConditions.context.id === completionTrackers.context;
@@ -1860,73 +1937,93 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1860
1937
  }
1861
1938
  else {
1862
1939
  if (isDisqualify)
1863
- return { isValid: false };
1940
+ return { isValid: false, availableClaimsNow: 0 };
1864
1941
  }
1865
1942
  }
1866
1943
  if (conditions?.buyItem) {
1867
- const isDisqualify = (completionTrackers?.buyItem || 0) < (conditions.buyItem.amount || 1);
1944
+ const baseAmount = conditions.buyItem.amount || 1;
1945
+ const scaledAmount = baseAmount * claimMultiplier;
1946
+ const trackerValue = completionTrackers?.buyItem || 0;
1947
+ const isDisqualify = trackerValue < scaledAmount;
1948
+ if (shouldScale && baseAmount > 0) {
1949
+ updateMax(Math.floor(trackerValue / baseAmount));
1950
+ }
1868
1951
  if (addDetails) {
1869
1952
  conditionData.push({
1870
1953
  isMet: !isDisqualify,
1871
1954
  kind: 'buyItem',
1872
- trackerAmount: completionTrackers?.buyItem || 0,
1873
- text: `Buy ${conditions.buyItem.amount || 1} ${conditions.buyItem.name}`,
1955
+ trackerAmount: trackerValue,
1956
+ text: `Buy ${scaledAmount} ${conditions.buyItem.name}`,
1874
1957
  });
1875
1958
  if (isDisqualify)
1876
1959
  isValid = false;
1877
1960
  }
1878
1961
  else {
1879
1962
  if (isDisqualify)
1880
- return { isValid: false };
1963
+ return { isValid: false, availableClaimsNow: 0 };
1881
1964
  }
1882
1965
  }
1883
1966
  if (conditions?.spendCurrency) {
1884
- const isDisqualify = (completionTrackers?.spendCurrency || 0) < (conditions.spendCurrency.amount || 1);
1967
+ const baseAmount = conditions.spendCurrency.amount || 1;
1968
+ const scaledAmount = baseAmount * claimMultiplier;
1969
+ const trackerValue = completionTrackers?.spendCurrency || 0;
1970
+ const isDisqualify = trackerValue < scaledAmount;
1971
+ if (shouldScale && baseAmount > 0) {
1972
+ updateMax(Math.floor(trackerValue / baseAmount));
1973
+ }
1885
1974
  if (addDetails) {
1886
1975
  conditionData.push({
1887
1976
  isMet: !isDisqualify,
1888
1977
  kind: 'spendCurrency',
1889
- trackerAmount: completionTrackers?.spendCurrency || 0,
1890
- text: `Spend ${conditions.spendCurrency.amount || 1} ${conditions.spendCurrency.name}`,
1978
+ trackerAmount: trackerValue,
1979
+ text: `Spend ${scaledAmount} ${conditions.spendCurrency.name}`,
1891
1980
  });
1892
1981
  if (isDisqualify)
1893
1982
  isValid = false;
1894
1983
  }
1895
1984
  else {
1896
1985
  if (isDisqualify)
1897
- return { isValid: false };
1986
+ return { isValid: false, availableClaimsNow: 0 };
1898
1987
  }
1899
1988
  }
1900
1989
  if (conditions?.depositCurrency) {
1901
- const isDisqualify = (completionTrackers?.depositCurrency || 0) <
1902
- (conditions.depositCurrency.amount || 1);
1990
+ const baseAmount = conditions.depositCurrency.amount || 1;
1991
+ const scaledAmount = baseAmount * claimMultiplier;
1992
+ const trackerValue = completionTrackers?.depositCurrency || 0;
1993
+ const isDisqualify = trackerValue < scaledAmount;
1994
+ if (shouldScale && baseAmount > 0) {
1995
+ updateMax(Math.floor(trackerValue / baseAmount));
1996
+ }
1903
1997
  if (addDetails) {
1904
1998
  conditionData.push({
1905
1999
  isMet: !isDisqualify,
1906
2000
  kind: 'depositCurrency',
1907
- trackerAmount: completionTrackers?.depositCurrency || 0,
1908
- text: `Deposit ${conditions.depositCurrency.amount || 1} ${conditions.depositCurrency.name}`,
2001
+ trackerAmount: trackerValue,
2002
+ text: `Deposit ${scaledAmount} ${conditions.depositCurrency.name}`,
1909
2003
  });
2004
+ if (isDisqualify)
2005
+ isValid = false;
1910
2006
  }
1911
2007
  else {
1912
2008
  if (isDisqualify)
1913
- return { isValid: false };
2009
+ return { isValid: false, availableClaimsNow: 0 };
1914
2010
  }
1915
2011
  }
1916
2012
  if (conditions?.login) {
1917
- const isMet = completionTrackers?.login || false;
2013
+ const isMet = new Date(playerSnap.snapshotLastUpdated || 0).getTime() >
2014
+ new Date(playerOffer.createdAt || 0).getTime();
1918
2015
  if (addDetails) {
1919
2016
  conditionData.push({
1920
2017
  isMet,
1921
2018
  kind: 'login',
1922
- trackerAmount: completionTrackers?.login ? 1 : 0,
2019
+ trackerAmount: isMet ? 1 : 0,
1923
2020
  text: `Login to the game`,
1924
2021
  });
1925
2022
  isValid = isMet;
1926
2023
  }
1927
2024
  else {
1928
2025
  if (!isMet)
1929
- return { isValid: false };
2026
+ return { isValid: false, availableClaimsNow: 0 };
1930
2027
  }
1931
2028
  }
1932
2029
  if (conditions?.loginStreak) {
@@ -1946,7 +2043,7 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1946
2043
  }
1947
2044
  else {
1948
2045
  if (isDisqualify)
1949
- return { isValid: false };
2046
+ return { isValid: false, availableClaimsNow: 0 };
1950
2047
  }
1951
2048
  }
1952
2049
  if (conditions?.social) {
@@ -1956,9 +2053,11 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1956
2053
  const hasContent = Boolean(mode === 'accumulate'
1957
2054
  ? tSocial?.mode === 'accumulate'
1958
2055
  : tSocial && tSocial.mode !== 'accumulate' && !!tSocial.videoId);
1959
- const minLikes = cSocial?.minLikes || 0;
1960
- const minViews = cSocial?.minViews || 0;
1961
- const minComments = cSocial?.minComments || 0;
2056
+ // Only scale social metrics in accumulate mode (attach mode is single content)
2057
+ const socialMultiplier = mode === 'accumulate' ? claimMultiplier : 1;
2058
+ const minLikes = (cSocial?.minLikes || 0) * socialMultiplier;
2059
+ const minViews = (cSocial?.minViews || 0) * socialMultiplier;
2060
+ const minComments = (cSocial?.minComments || 0) * socialMultiplier;
1962
2061
  const likes = tSocial?.likes || 0;
1963
2062
  const views = tSocial?.views || 0;
1964
2063
  const comments = tSocial?.comments || 0;
@@ -1966,6 +2065,17 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1966
2065
  if (likes < minLikes || views < minViews || comments < minComments) {
1967
2066
  isDisqualify = true;
1968
2067
  }
2068
+ if (shouldScale && mode === 'accumulate' && hasContent) {
2069
+ const baseLikes = cSocial?.minLikes || 0;
2070
+ const baseViews = cSocial?.minViews || 0;
2071
+ const baseComments = cSocial?.minComments || 0;
2072
+ if (baseLikes > 0)
2073
+ updateMax(Math.floor(likes / baseLikes));
2074
+ if (baseViews > 0)
2075
+ updateMax(Math.floor(views / baseViews));
2076
+ if (baseComments > 0)
2077
+ updateMax(Math.floor(comments / baseComments));
2078
+ }
1969
2079
  if (addDetails) {
1970
2080
  const platformMap = {
1971
2081
  tiktok: 'TikTok',
@@ -2037,31 +2147,47 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2037
2147
  }
2038
2148
  else {
2039
2149
  if (isDisqualify)
2040
- return { isValid: false };
2150
+ return { isValid: false, availableClaimsNow: 0 };
2041
2151
  }
2042
2152
  }
2043
2153
  // Linked completions - wait for N linked entities to complete
2044
2154
  if (conditions?.linkedCompletions?.min) {
2155
+ const baseMin = conditions.linkedCompletions.min;
2045
2156
  const currentCount = completionTrackers?.linkedCompletions || 0;
2046
- const requiredCount = conditions.linkedCompletions.min;
2047
- const isDisqualify = currentCount < requiredCount;
2157
+ const scaledMin = baseMin * claimMultiplier;
2158
+ const isDisqualify = currentCount < scaledMin;
2159
+ if (shouldScale && baseMin > 0) {
2160
+ updateMax(Math.floor(currentCount / baseMin));
2161
+ }
2048
2162
  if (addDetails) {
2049
2163
  conditionData.push({
2050
2164
  isMet: !isDisqualify,
2051
2165
  kind: 'linkedCompletions',
2052
2166
  trackerAmount: currentCount,
2053
- text: `Wait for ${requiredCount} linked ${requiredCount === 1 ? 'entity' : 'entities'} to complete`,
2167
+ text: `Wait for ${scaledMin} linked ${scaledMin === 1 ? 'entity' : 'entities'} to complete`,
2054
2168
  });
2055
2169
  if (isDisqualify)
2056
2170
  isValid = false;
2057
2171
  }
2058
2172
  else {
2059
2173
  if (isDisqualify)
2060
- return { isValid: false };
2174
+ return { isValid: false, availableClaimsNow: 0 };
2061
2175
  }
2062
2176
  }
2063
2177
  if (conditions?.dynamicTracker?.conditions?.length) {
2064
- const dynamicResult = meetsDynamicConditions(completionTrackers?.dynamicTracker || {}, conditions.dynamicTracker);
2178
+ const resolvedConditions = replaceDynamicConditionKeys(conditions.dynamicTracker.conditions, playerOffer?.trackers || {});
2179
+ // now we have the game-defined conditions with {{}} keys populated. feed these conditions into evaluator
2180
+ const dynamicResult = meetsDynamicConditions(completionTrackers?.dynamicTracker, {
2181
+ ...conditions.dynamicTracker,
2182
+ conditions: resolvedConditions,
2183
+ }, claimMultiplier);
2184
+ if (shouldScale) {
2185
+ const dynamicMax = getMaxClaimsForDynamicGroup(completionTrackers?.dynamicTracker || {}, {
2186
+ ...conditions.dynamicTracker,
2187
+ conditions: resolvedConditions,
2188
+ }, playerOffer.claimedCount || 0);
2189
+ updateMax(dynamicMax);
2190
+ }
2065
2191
  if (addDetails) {
2066
2192
  conditionData.push({
2067
2193
  isMet: dynamicResult,
@@ -2073,19 +2199,29 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2073
2199
  }
2074
2200
  else {
2075
2201
  if (!dynamicResult)
2076
- return { isValid: false };
2202
+ return { isValid: false, availableClaimsNow: 0 };
2077
2203
  }
2078
2204
  }
2079
2205
  const r = meetsBaseConditions({
2080
2206
  conditions,
2081
2207
  playerSnap,
2082
2208
  addDetails: true,
2209
+ playerOffer,
2083
2210
  });
2084
2211
  isValid = isValid && r.isValid;
2085
2212
  conditionData.push(...(r.conditionData || []));
2086
- return { isValid, conditionData };
2087
- }
2088
- return { isValid: true, conditionData: [] };
2213
+ if (maxClaimCount && maxClaimCount > 0) {
2214
+ updateMax(maxClaimCount);
2215
+ }
2216
+ const claimedCount = playerOffer.claimedCount || 0;
2217
+ let availableClaimsNow = !isValid
2218
+ ? 0
2219
+ : maxTotalClaimsFromScaling === Infinity
2220
+ ? -1
2221
+ : Math.max(0, maxTotalClaimsFromScaling - claimedCount);
2222
+ return { isValid, conditionData, availableClaimsNow };
2223
+ }
2224
+ return { isValid: true, conditionData: [], availableClaimsNow: -1 };
2089
2225
  };
2090
2226
  /**
2091
2227
  * Checks if completion conditions were met before a specific expiry time.
@@ -2094,10 +2230,9 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2094
2230
  * @param completionConditions - The completion conditions to check
2095
2231
  * @param completionTrackers - The completion trackers (for buyItem, spendCurrency, etc.)
2096
2232
  * @param playerSnap - The player snapshot with field timestamps
2097
- * @param expiryTime - The expiry timestamp in milliseconds
2098
2233
  * @returns true if all conditions were met before expiry, false otherwise
2099
2234
  */
2100
- const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completionTrackers, playerSnap, expiryTime, }) => {
2235
+ const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completionTrackers, playerSnap, playerOffer, maxClaimCount, }) => {
2101
2236
  if (!completionConditions)
2102
2237
  return false;
2103
2238
  // Check if there are actually any conditions to evaluate
@@ -2107,10 +2242,15 @@ const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completio
2107
2242
  const conditionsMet = meetsCompletionConditions({
2108
2243
  completionConditions,
2109
2244
  completionTrackers,
2245
+ playerOffer,
2110
2246
  playerSnap,
2247
+ maxClaimCount,
2111
2248
  });
2112
2249
  if (!conditionsMet.isValid)
2113
2250
  return false;
2251
+ if (!playerOffer.expiresAt)
2252
+ return true;
2253
+ const expiryTime = new Date(playerOffer.expiresAt).getTime();
2114
2254
  const lastSnapshotUpdate = new Date(playerSnap.snapshotLastUpdated).getTime();
2115
2255
  /**
2116
2256
  * Checks if a field was updated after the expiry time.
@@ -2221,37 +2361,55 @@ const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completio
2221
2361
  * Checks if a dynamic object meets a set of dynamic field conditions.
2222
2362
  * @param dynamicObj - The object with any key and string or number value.
2223
2363
  * @param conditions - Array of conditions to check.
2364
+ * @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
2224
2365
  * @returns true if all conditions are met, false otherwise.
2225
2366
  */
2226
2367
  /**
2227
2368
  * Evaluates a single dynamic condition against the dynamic object.
2228
2369
  */
2229
- function evaluateDynamicCondition(dynamicObj, cond) {
2370
+ function evaluateDynamicCondition(dynamicObj, cond, claimMultiplier = 1) {
2371
+ if (!dynamicObj)
2372
+ return false;
2230
2373
  const val = dynamicObj[cond.key];
2231
2374
  if (val === undefined)
2232
2375
  return false;
2233
2376
  const isNumber = typeof val === 'number';
2234
- const compareTo = isNumber ? Number(cond.compareTo) : String(cond.compareTo);
2235
- switch (cond.operator) {
2236
- case '==':
2237
- return val === compareTo;
2238
- case '!=':
2239
- return val !== compareTo;
2377
+ const isBoolean = typeof val === 'boolean';
2378
+ if (isBoolean) {
2379
+ switch (cond.operator) {
2380
+ case '==':
2381
+ return val === Boolean(cond.compareTo);
2382
+ case '!=':
2383
+ return val !== Boolean(cond.compareTo);
2384
+ default:
2385
+ return false;
2386
+ }
2240
2387
  }
2388
+ const compareTo = isNumber ? Number(cond.compareTo) : String(cond.compareTo);
2241
2389
  if (isNumber && typeof compareTo === 'number') {
2390
+ const skipMultiplier = cond.operator === '==' || cond.operator === '!=';
2391
+ const scaledCompareTo = skipMultiplier ? compareTo : compareTo * claimMultiplier;
2242
2392
  switch (cond.operator) {
2393
+ case '==':
2394
+ return val === scaledCompareTo;
2395
+ case '!=':
2396
+ return val !== scaledCompareTo;
2243
2397
  case '>':
2244
- return val > compareTo;
2398
+ return val > scaledCompareTo;
2245
2399
  case '>=':
2246
- return val >= compareTo;
2400
+ return val >= scaledCompareTo;
2247
2401
  case '<':
2248
- return val < compareTo;
2402
+ return val < scaledCompareTo;
2249
2403
  case '<=':
2250
- return val <= compareTo;
2404
+ return val <= scaledCompareTo;
2251
2405
  }
2252
2406
  }
2253
2407
  else if (!isNumber && typeof compareTo === 'string') {
2254
2408
  switch (cond.operator) {
2409
+ case '==':
2410
+ return val === compareTo;
2411
+ case '!=':
2412
+ return val !== compareTo;
2255
2413
  case 'has':
2256
2414
  return val.includes(compareTo);
2257
2415
  case 'not_has':
@@ -2260,24 +2418,119 @@ function evaluateDynamicCondition(dynamicObj, cond) {
2260
2418
  }
2261
2419
  return false;
2262
2420
  }
2421
+ /**
2422
+ * Calculates the maximum number of claims supported by a single dynamic condition.
2423
+ */
2424
+ function getMaxClaimsForDynamicCondition(dynamicObj, cond) {
2425
+ if (!dynamicObj)
2426
+ return 0;
2427
+ const val = dynamicObj[cond.key];
2428
+ if (val === undefined)
2429
+ return 0;
2430
+ if (typeof val === 'number') {
2431
+ const base = Number(cond.compareTo);
2432
+ if (isNaN(base)) {
2433
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2434
+ }
2435
+ switch (cond.operator) {
2436
+ case '>=':
2437
+ if (base === 0)
2438
+ return val >= 0 ? Infinity : 0;
2439
+ if (base < 0)
2440
+ return val >= base ? Infinity : 0;
2441
+ return Math.max(0, Math.floor(val / base));
2442
+ case '>':
2443
+ if (base === 0)
2444
+ return val > 0 ? Infinity : 0;
2445
+ if (base < 0)
2446
+ return val > base ? Infinity : 0;
2447
+ if (val <= 0)
2448
+ return 0;
2449
+ return Math.max(0, Math.ceil(val / base) - 1);
2450
+ case '==':
2451
+ return val === base ? Infinity : 0;
2452
+ case '!=':
2453
+ return val !== base ? Infinity : 0;
2454
+ case '<=':
2455
+ if (base === 0)
2456
+ return val <= 0 ? Infinity : 0;
2457
+ if (base > 0)
2458
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2459
+ if (val >= 0)
2460
+ return 0;
2461
+ return Math.max(0, Math.floor(val / base));
2462
+ case '<':
2463
+ if (base === 0)
2464
+ return val < 0 ? Infinity : 0;
2465
+ if (base > 0)
2466
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2467
+ if (val >= 0)
2468
+ return 0;
2469
+ return Math.max(0, Math.ceil(val / base) - 1);
2470
+ }
2471
+ }
2472
+ // we don't scale the rest, they are always true or always false
2473
+ return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
2474
+ }
2475
+ /**
2476
+ * Calculates the maximum number of claims supported by a group of dynamic conditions.
2477
+ */
2478
+ function getMaxClaimsForDynamicGroup(dynamicObj, dynamicGroup, currentClaimCount = 0) {
2479
+ const { conditions, links } = dynamicGroup;
2480
+ if (!conditions || conditions.length === 0)
2481
+ return Infinity;
2482
+ // AND only
2483
+ if (!links || links.length === 0 || links.every((l) => l === 'AND')) {
2484
+ let minClaims = Infinity;
2485
+ for (const cond of conditions) {
2486
+ const max = getMaxClaimsForDynamicCondition(dynamicObj, cond);
2487
+ if (max === 0)
2488
+ return 0;
2489
+ minClaims = Math.min(minClaims, max);
2490
+ }
2491
+ return minClaims;
2492
+ }
2493
+ // OR only
2494
+ if (links.every((l) => l === 'OR')) {
2495
+ let maxClaims = 0;
2496
+ for (const cond of conditions) {
2497
+ const max = getMaxClaimsForDynamicCondition(dynamicObj, cond);
2498
+ if (max === Infinity)
2499
+ return Infinity;
2500
+ maxClaims = Math.max(maxClaims, max);
2501
+ }
2502
+ return maxClaims;
2503
+ }
2504
+ // mixed:
2505
+ const maxIterations = 100;
2506
+ for (let n = currentClaimCount + 1; n <= currentClaimCount + maxIterations; n++) {
2507
+ if (!meetsDynamicConditions(dynamicObj, dynamicGroup, n)) {
2508
+ return n - 1;
2509
+ }
2510
+ }
2511
+ return currentClaimCount + maxIterations;
2512
+ }
2263
2513
  /**
2264
2514
  * Evaluates a group of dynamic conditions with logical links (AND, OR, AND NOT).
2265
2515
  * @param dynamicObj - The player's dynamic object with any key and string or number value.
2266
2516
  * @param dynamicGroup - The group of conditions and links to check.
2517
+ * @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
2267
2518
  * @returns true if the group evaluates to true, false otherwise.
2268
2519
  */
2269
- function meetsDynamicConditions(dynamicObj, dynamicGroup) {
2520
+ function meetsDynamicConditions(dynamicObj, dynamicGroup, claimMultiplier = 1) {
2270
2521
  const { conditions, links } = dynamicGroup;
2271
2522
  if (!conditions || conditions.length === 0)
2272
2523
  return true;
2524
+ if (!dynamicObj)
2525
+ return false;
2273
2526
  // If no links, treat as AND between all conditions
2274
2527
  if (!links || links.length === 0) {
2275
- return conditions.every((cond) => evaluateDynamicCondition(dynamicObj, cond));
2528
+ return conditions.every((cond) => evaluateDynamicCondition(dynamicObj, cond, claimMultiplier));
2276
2529
  }
2277
2530
  // Evaluate the first condition
2278
- let result = evaluateDynamicCondition(dynamicObj, conditions[0]);
2531
+ let result = evaluateDynamicCondition(dynamicObj, conditions[0], claimMultiplier);
2279
2532
  for (let i = 0; i < links.length; i++) {
2280
- const nextCond = evaluateDynamicCondition(dynamicObj, conditions[i + 1]);
2533
+ const nextCond = evaluateDynamicCondition(dynamicObj, conditions[i + 1], claimMultiplier);
2281
2534
  const link = links[i];
2282
2535
  if (link === 'AND') {
2283
2536
  result = result && nextCond;
@@ -2359,5 +2612,5 @@ const rewardSchema = {
2359
2612
  image: String,
2360
2613
  };
2361
2614
 
2362
- export { AssetHelper, ConnectionState, EventEmitter, OfferEvent, OfferStore, OfferwallClient, PlayerOfferStatuses, SSEConnection, hasConditions, meetsBaseConditions, meetsClaimableConditions, meetsCompletionConditions, meetsCompletionConditionsBeforeExpiry, meetsDynamicConditions, meetsSurfacingConditions, offerListenerEvents, rewardKinds, rewardSchema };
2615
+ export { AssetHelper, ConnectionState, EventEmitter, OfferEvent, OfferStore, OfferwallClient, PlayerOfferStatuses, SSEConnection, getMaxClaimsForDynamicCondition, getMaxClaimsForDynamicGroup, hasConditions, meetsBaseConditions, meetsClaimableConditions, meetsCompletionConditions, meetsCompletionConditionsBeforeExpiry, meetsDynamicConditions, meetsSurfacingConditions, offerListenerEvents, offerMeetsCompletionConditions, rewardKinds, rewardSchema };
2363
2616
  //# sourceMappingURL=index.esm.js.map