@pixels-online/pixels-client-js-sdk 1.16.0 → 1.17.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
@@ -1280,19 +1280,47 @@ class OfferwallClient {
1280
1280
  }
1281
1281
 
1282
1282
  const keyPattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
1283
- // renders template by replacing {keyName} with values from dynamic
1283
+ /**
1284
+ * This replaces {keyName} keys from the template with corresponding values from the dynamic object.
1285
+ */
1284
1286
  function renderTemplate(template, dynamic) {
1285
1287
  if (!template)
1286
1288
  return '';
1287
- return template.replace(keyPattern, (match, key) => {
1289
+ return template.replace(keyPattern, (_match, key) => {
1290
+ if (dynamic && typeof dynamic[key] === 'boolean') {
1291
+ return dynamic[key] ? '✓' : '✗';
1292
+ }
1288
1293
  if (dynamic && dynamic[key] !== undefined) {
1289
1294
  return String(dynamic[key]);
1290
1295
  }
1291
1296
  return '{?}'; // indicate missing key
1292
1297
  });
1293
1298
  }
1299
+ /**
1300
+ * This replaces {{keyName}} in dynamic condition keys with corresponding values from
1301
+ * the PlayerOffer.trackers
1302
+ *
1303
+ * eg. a condition high_score_pet-{{surfacerPlayerId}} with high_score_pet-12345
1304
+ */
1305
+ function replaceDynamicConditionKey(key, trackers) {
1306
+ return key?.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (match, p1) => {
1307
+ const value = trackers[p1];
1308
+ return value !== undefined ? String(value) : match;
1309
+ });
1310
+ }
1311
+ /** this replaces all of the dynamic conditions.keys by calling replaceDynamicConditionKey */
1312
+ function replaceDynamicConditionKeys(conditions, trackers) {
1313
+ return conditions.map((condition) => ({
1314
+ ...condition,
1315
+ key: replaceDynamicConditionKey(condition.key, trackers),
1316
+ }));
1317
+ }
1294
1318
 
1295
- const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
1319
+ const meetsBaseConditions = ({ conditions, playerSnap, addDetails,
1320
+ /** this exists if calling meetsBaseConditions from meetsCompletionConditions. but surfacing
1321
+ * check doesn't use this since we don't have a playerOffer at surfacing time
1322
+ */
1323
+ playerOffer, }) => {
1296
1324
  const conditionData = [];
1297
1325
  let isValid = true;
1298
1326
  if (conditions?.minDaysInGame) {
@@ -1659,7 +1687,11 @@ const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
1659
1687
  }
1660
1688
  // Evaluate dynamic conditions
1661
1689
  if (conditions?.dynamic?.conditions?.length) {
1662
- const dynamicResult = meetsDynamicConditions(playerSnap.dynamic || {}, conditions.dynamic);
1690
+ const resolvedConditions = replaceDynamicConditionKeys(conditions.dynamic.conditions, playerOffer?.trackers || {});
1691
+ const dynamicResult = meetsDynamicConditions(playerSnap.dynamic, {
1692
+ ...conditions.dynamic,
1693
+ conditions: resolvedConditions,
1694
+ });
1663
1695
  if (addDetails) {
1664
1696
  conditionData.push({
1665
1697
  isMet: dynamicResult,
@@ -1768,6 +1800,31 @@ const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, pl
1768
1800
  return { isValid: false };
1769
1801
  }
1770
1802
  }
1803
+ if (conditions.allowedCountries?.length) {
1804
+ const playerCountry = playerSnap.ip?.countryCode;
1805
+ if (!playerCountry || !conditions.allowedCountries.includes(playerCountry)) {
1806
+ return { isValid: false };
1807
+ }
1808
+ }
1809
+ if (conditions.restrictedCountries?.length) {
1810
+ const playerCountry = playerSnap.ip?.countryCode;
1811
+ if (!playerCountry) {
1812
+ return { isValid: false };
1813
+ }
1814
+ if (conditions.restrictedCountries.includes(playerCountry)) {
1815
+ return { isValid: false };
1816
+ }
1817
+ }
1818
+ if (conditions.networkRestrictions?.length) {
1819
+ if (!playerSnap.ip) {
1820
+ return { isValid: false };
1821
+ }
1822
+ for (const restriction of conditions.networkRestrictions) {
1823
+ if (playerSnap.ip[restriction]) {
1824
+ return { isValid: false };
1825
+ }
1826
+ }
1827
+ }
1771
1828
  return meetsBaseConditions({ conditions, playerSnap });
1772
1829
  };
1773
1830
  const hasConditions = (conditions) => {
@@ -1820,6 +1877,12 @@ const hasConditions = (conditions) => {
1820
1877
  return true;
1821
1878
  if (surCond.links && Object.keys(surCond.links).length > 0)
1822
1879
  return true;
1880
+ if (surCond.allowedCountries?.length)
1881
+ return true;
1882
+ if (surCond.restrictedCountries?.length)
1883
+ return true;
1884
+ if (surCond.networkRestrictions?.length)
1885
+ return true;
1823
1886
  const compCond = conditions;
1824
1887
  if (compCond.context)
1825
1888
  return true;
@@ -1841,9 +1904,21 @@ const hasConditions = (conditions) => {
1841
1904
  return true;
1842
1905
  return false;
1843
1906
  };
1844
- const meetsCompletionConditions = ({ completionConditions, completionTrackers, playerSnap, addDetails = false, }) => {
1907
+ const offerMeetsCompletionConditions = (offer, snapshot) => {
1908
+ return meetsCompletionConditions({
1909
+ completionConditions: offer.completionConditions || {},
1910
+ completionTrackers: offer.completionTrackers,
1911
+ playerSnap: snapshot,
1912
+ playerOffer: offer,
1913
+ addDetails: true,
1914
+ });
1915
+ };
1916
+ const meetsCompletionConditions = ({ completionConditions, completionTrackers, playerSnap, playerOffer, addDetails = false, maxClaimCount, }) => {
1845
1917
  if (completionConditions) {
1846
1918
  const conditions = completionConditions;
1919
+ // For multi-claim offers, scale cumulative requirements by (claimedCount + 1)
1920
+ const shouldScale = maxClaimCount === -1 || (maxClaimCount && maxClaimCount > 1);
1921
+ const claimMultiplier = shouldScale ? (playerOffer.claimedCount || 0) + 1 : 1;
1847
1922
  const conditionData = [];
1848
1923
  let isValid = true;
1849
1924
  if (completionConditions?.context?.id) {
@@ -1866,13 +1941,14 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1866
1941
  }
1867
1942
  }
1868
1943
  if (conditions?.buyItem) {
1869
- const isDisqualify = (completionTrackers?.buyItem || 0) < (conditions.buyItem.amount || 1);
1944
+ const scaledAmount = (conditions.buyItem.amount || 1) * claimMultiplier;
1945
+ const isDisqualify = (completionTrackers?.buyItem || 0) < scaledAmount;
1870
1946
  if (addDetails) {
1871
1947
  conditionData.push({
1872
1948
  isMet: !isDisqualify,
1873
1949
  kind: 'buyItem',
1874
1950
  trackerAmount: completionTrackers?.buyItem || 0,
1875
- text: `Buy ${conditions.buyItem.amount || 1} ${conditions.buyItem.name}`,
1951
+ text: `Buy ${scaledAmount} ${conditions.buyItem.name}`,
1876
1952
  });
1877
1953
  if (isDisqualify)
1878
1954
  isValid = false;
@@ -1883,13 +1959,14 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1883
1959
  }
1884
1960
  }
1885
1961
  if (conditions?.spendCurrency) {
1886
- const isDisqualify = (completionTrackers?.spendCurrency || 0) < (conditions.spendCurrency.amount || 1);
1962
+ const scaledAmount = (conditions.spendCurrency.amount || 1) * claimMultiplier;
1963
+ const isDisqualify = (completionTrackers?.spendCurrency || 0) < scaledAmount;
1887
1964
  if (addDetails) {
1888
1965
  conditionData.push({
1889
1966
  isMet: !isDisqualify,
1890
1967
  kind: 'spendCurrency',
1891
1968
  trackerAmount: completionTrackers?.spendCurrency || 0,
1892
- text: `Spend ${conditions.spendCurrency.amount || 1} ${conditions.spendCurrency.name}`,
1969
+ text: `Spend ${scaledAmount} ${conditions.spendCurrency.name}`,
1893
1970
  });
1894
1971
  if (isDisqualify)
1895
1972
  isValid = false;
@@ -1900,15 +1977,17 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1900
1977
  }
1901
1978
  }
1902
1979
  if (conditions?.depositCurrency) {
1903
- const isDisqualify = (completionTrackers?.depositCurrency || 0) <
1904
- (conditions.depositCurrency.amount || 1);
1980
+ const scaledAmount = (conditions.depositCurrency.amount || 1) * claimMultiplier;
1981
+ const isDisqualify = (completionTrackers?.depositCurrency || 0) < scaledAmount;
1905
1982
  if (addDetails) {
1906
1983
  conditionData.push({
1907
1984
  isMet: !isDisqualify,
1908
1985
  kind: 'depositCurrency',
1909
1986
  trackerAmount: completionTrackers?.depositCurrency || 0,
1910
- text: `Deposit ${conditions.depositCurrency.amount || 1} ${conditions.depositCurrency.name}`,
1987
+ text: `Deposit ${scaledAmount} ${conditions.depositCurrency.name}`,
1911
1988
  });
1989
+ if (isDisqualify)
1990
+ isValid = false;
1912
1991
  }
1913
1992
  else {
1914
1993
  if (isDisqualify)
@@ -1916,12 +1995,13 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1916
1995
  }
1917
1996
  }
1918
1997
  if (conditions?.login) {
1919
- const isMet = completionTrackers?.login || false;
1998
+ const isMet = new Date(playerSnap.snapshotLastUpdated || 0).getTime() >
1999
+ new Date(playerOffer.createdAt || 0).getTime();
1920
2000
  if (addDetails) {
1921
2001
  conditionData.push({
1922
2002
  isMet,
1923
2003
  kind: 'login',
1924
- trackerAmount: completionTrackers?.login ? 1 : 0,
2004
+ trackerAmount: isMet ? 1 : 0,
1925
2005
  text: `Login to the game`,
1926
2006
  });
1927
2007
  isValid = isMet;
@@ -1958,9 +2038,11 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1958
2038
  const hasContent = Boolean(mode === 'accumulate'
1959
2039
  ? tSocial?.mode === 'accumulate'
1960
2040
  : tSocial && tSocial.mode !== 'accumulate' && !!tSocial.videoId);
1961
- const minLikes = cSocial?.minLikes || 0;
1962
- const minViews = cSocial?.minViews || 0;
1963
- const minComments = cSocial?.minComments || 0;
2041
+ // Only scale social metrics in accumulate mode (attach mode is single content)
2042
+ const socialMultiplier = mode === 'accumulate' ? claimMultiplier : 1;
2043
+ const minLikes = (cSocial?.minLikes || 0) * socialMultiplier;
2044
+ const minViews = (cSocial?.minViews || 0) * socialMultiplier;
2045
+ const minComments = (cSocial?.minComments || 0) * socialMultiplier;
1964
2046
  const likes = tSocial?.likes || 0;
1965
2047
  const views = tSocial?.views || 0;
1966
2048
  const comments = tSocial?.comments || 0;
@@ -2045,14 +2127,14 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2045
2127
  // Linked completions - wait for N linked entities to complete
2046
2128
  if (conditions?.linkedCompletions?.min) {
2047
2129
  const currentCount = completionTrackers?.linkedCompletions || 0;
2048
- const requiredCount = conditions.linkedCompletions.min;
2049
- const isDisqualify = currentCount < requiredCount;
2130
+ const scaledMin = conditions.linkedCompletions.min * claimMultiplier;
2131
+ const isDisqualify = currentCount < scaledMin;
2050
2132
  if (addDetails) {
2051
2133
  conditionData.push({
2052
2134
  isMet: !isDisqualify,
2053
2135
  kind: 'linkedCompletions',
2054
2136
  trackerAmount: currentCount,
2055
- text: `Wait for ${requiredCount} linked ${requiredCount === 1 ? 'entity' : 'entities'} to complete`,
2137
+ text: `Wait for ${scaledMin} linked ${scaledMin === 1 ? 'entity' : 'entities'} to complete`,
2056
2138
  });
2057
2139
  if (isDisqualify)
2058
2140
  isValid = false;
@@ -2063,7 +2145,12 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2063
2145
  }
2064
2146
  }
2065
2147
  if (conditions?.dynamicTracker?.conditions?.length) {
2066
- const dynamicResult = meetsDynamicConditions(completionTrackers?.dynamicTracker || {}, conditions.dynamicTracker);
2148
+ const resolvedConditions = replaceDynamicConditionKeys(conditions.dynamicTracker.conditions, playerOffer?.trackers || {});
2149
+ // now we have the game-defined conditions with {{}} keys populated. feed these conditions into evaluator
2150
+ const dynamicResult = meetsDynamicConditions(completionTrackers?.dynamicTracker, {
2151
+ ...conditions.dynamicTracker,
2152
+ conditions: resolvedConditions,
2153
+ }, claimMultiplier);
2067
2154
  if (addDetails) {
2068
2155
  conditionData.push({
2069
2156
  isMet: dynamicResult,
@@ -2082,6 +2169,7 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2082
2169
  conditions,
2083
2170
  playerSnap,
2084
2171
  addDetails: true,
2172
+ playerOffer,
2085
2173
  });
2086
2174
  isValid = isValid && r.isValid;
2087
2175
  conditionData.push(...(r.conditionData || []));
@@ -2096,10 +2184,9 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2096
2184
  * @param completionConditions - The completion conditions to check
2097
2185
  * @param completionTrackers - The completion trackers (for buyItem, spendCurrency, etc.)
2098
2186
  * @param playerSnap - The player snapshot with field timestamps
2099
- * @param expiryTime - The expiry timestamp in milliseconds
2100
2187
  * @returns true if all conditions were met before expiry, false otherwise
2101
2188
  */
2102
- const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completionTrackers, playerSnap, expiryTime, }) => {
2189
+ const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completionTrackers, playerSnap, playerOffer, maxClaimCount, }) => {
2103
2190
  if (!completionConditions)
2104
2191
  return false;
2105
2192
  // Check if there are actually any conditions to evaluate
@@ -2109,10 +2196,15 @@ const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completio
2109
2196
  const conditionsMet = meetsCompletionConditions({
2110
2197
  completionConditions,
2111
2198
  completionTrackers,
2199
+ playerOffer,
2112
2200
  playerSnap,
2201
+ maxClaimCount,
2113
2202
  });
2114
2203
  if (!conditionsMet.isValid)
2115
2204
  return false;
2205
+ if (!playerOffer.expiresAt)
2206
+ return true;
2207
+ const expiryTime = new Date(playerOffer.expiresAt).getTime();
2116
2208
  const lastSnapshotUpdate = new Date(playerSnap.snapshotLastUpdated).getTime();
2117
2209
  /**
2118
2210
  * Checks if a field was updated after the expiry time.
@@ -2223,16 +2315,30 @@ const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completio
2223
2315
  * Checks if a dynamic object meets a set of dynamic field conditions.
2224
2316
  * @param dynamicObj - The object with any key and string or number value.
2225
2317
  * @param conditions - Array of conditions to check.
2318
+ * @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
2226
2319
  * @returns true if all conditions are met, false otherwise.
2227
2320
  */
2228
2321
  /**
2229
2322
  * Evaluates a single dynamic condition against the dynamic object.
2230
2323
  */
2231
- function evaluateDynamicCondition(dynamicObj, cond) {
2324
+ function evaluateDynamicCondition(dynamicObj, cond, claimMultiplier = 1) {
2325
+ if (!dynamicObj)
2326
+ return false;
2232
2327
  const val = dynamicObj[cond.key];
2233
2328
  if (val === undefined)
2234
2329
  return false;
2235
2330
  const isNumber = typeof val === 'number';
2331
+ const isBoolean = typeof val === 'boolean';
2332
+ if (isBoolean) {
2333
+ switch (cond.operator) {
2334
+ case '==':
2335
+ return val === Boolean(cond.compareTo);
2336
+ case '!=':
2337
+ return val !== Boolean(cond.compareTo);
2338
+ default:
2339
+ return false;
2340
+ }
2341
+ }
2236
2342
  const compareTo = isNumber ? Number(cond.compareTo) : String(cond.compareTo);
2237
2343
  switch (cond.operator) {
2238
2344
  case '==':
@@ -2243,13 +2349,13 @@ function evaluateDynamicCondition(dynamicObj, cond) {
2243
2349
  if (isNumber && typeof compareTo === 'number') {
2244
2350
  switch (cond.operator) {
2245
2351
  case '>':
2246
- return val > compareTo;
2352
+ return val > compareTo * claimMultiplier;
2247
2353
  case '>=':
2248
- return val >= compareTo;
2354
+ return val >= compareTo * claimMultiplier;
2249
2355
  case '<':
2250
- return val < compareTo;
2356
+ return val < compareTo * claimMultiplier;
2251
2357
  case '<=':
2252
- return val <= compareTo;
2358
+ return val <= compareTo * claimMultiplier;
2253
2359
  }
2254
2360
  }
2255
2361
  else if (!isNumber && typeof compareTo === 'string') {
@@ -2266,20 +2372,23 @@ function evaluateDynamicCondition(dynamicObj, cond) {
2266
2372
  * Evaluates a group of dynamic conditions with logical links (AND, OR, AND NOT).
2267
2373
  * @param dynamicObj - The player's dynamic object with any key and string or number value.
2268
2374
  * @param dynamicGroup - The group of conditions and links to check.
2375
+ * @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
2269
2376
  * @returns true if the group evaluates to true, false otherwise.
2270
2377
  */
2271
- function meetsDynamicConditions(dynamicObj, dynamicGroup) {
2378
+ function meetsDynamicConditions(dynamicObj, dynamicGroup, claimMultiplier = 1) {
2272
2379
  const { conditions, links } = dynamicGroup;
2273
2380
  if (!conditions || conditions.length === 0)
2274
2381
  return true;
2382
+ if (!dynamicObj)
2383
+ return false;
2275
2384
  // If no links, treat as AND between all conditions
2276
2385
  if (!links || links.length === 0) {
2277
- return conditions.every((cond) => evaluateDynamicCondition(dynamicObj, cond));
2386
+ return conditions.every((cond) => evaluateDynamicCondition(dynamicObj, cond, claimMultiplier));
2278
2387
  }
2279
2388
  // Evaluate the first condition
2280
- let result = evaluateDynamicCondition(dynamicObj, conditions[0]);
2389
+ let result = evaluateDynamicCondition(dynamicObj, conditions[0], claimMultiplier);
2281
2390
  for (let i = 0; i < links.length; i++) {
2282
- const nextCond = evaluateDynamicCondition(dynamicObj, conditions[i + 1]);
2391
+ const nextCond = evaluateDynamicCondition(dynamicObj, conditions[i + 1], claimMultiplier);
2283
2392
  const link = links[i];
2284
2393
  if (link === 'AND') {
2285
2394
  result = result && nextCond;
@@ -2375,6 +2484,7 @@ exports.meetsCompletionConditionsBeforeExpiry = meetsCompletionConditionsBeforeE
2375
2484
  exports.meetsDynamicConditions = meetsDynamicConditions;
2376
2485
  exports.meetsSurfacingConditions = meetsSurfacingConditions;
2377
2486
  exports.offerListenerEvents = offerListenerEvents;
2487
+ exports.offerMeetsCompletionConditions = offerMeetsCompletionConditions;
2378
2488
  exports.rewardKinds = rewardKinds;
2379
2489
  exports.rewardSchema = rewardSchema;
2380
2490
  //# sourceMappingURL=index.js.map