@pixels-online/pixels-client-js-sdk 1.19.0 → 1.20.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.
@@ -67,7 +67,7 @@ export declare class OfferwallClient {
67
67
  private claimOfferAPI;
68
68
  getPlayer(targetId?: string | null): IClientPlayer | null;
69
69
  getOffers(targetId?: string | null): IClientOffer[];
70
- refreshOffersAndPlayer(targetId?: string | null): Promise<void>;
70
+ refreshOffersAndPlayer(targetId?: string | null): Promise<IClientOffer[]>;
71
71
  private getOffersAndPlayer;
72
72
  getAuthLinkToken(): Promise<string>;
73
73
  getGameId(): string | null;
package/dist/index.esm.js CHANGED
@@ -640,7 +640,7 @@ class OfferStore {
640
640
  return this.players.get(targetId) || null;
641
641
  }
642
642
  setPlayer(player) {
643
- this.players.set(player.snapshot.playerId, player);
643
+ this.players.set(player.gameData.playerId, player);
644
644
  this.logger.log('Updated player:', player);
645
645
  }
646
646
  /**
@@ -652,9 +652,9 @@ class OfferStore {
652
652
  this.logger.warn('No target player to set offers for');
653
653
  return;
654
654
  }
655
- this.offers.set(targetPlayer.snapshot.playerId, new Map());
655
+ this.offers.set(targetPlayer.gameData.playerId, new Map());
656
656
  offers.forEach((offer) => {
657
- this.offers.get(targetPlayer.snapshot.playerId).set(offer.instanceId, offer);
657
+ this.offers.get(targetPlayer.gameData.playerId).set(offer.instanceId, offer);
658
658
  });
659
659
  this.logger.log(`Set ${offers.length} offers`);
660
660
  }
@@ -1172,12 +1172,13 @@ class OfferwallClient {
1172
1172
  try {
1173
1173
  const { offers, player } = await this.getOffersAndPlayer(targetId);
1174
1174
  if (targetId == null) {
1175
- this.selfId = player.snapshot.playerId;
1175
+ this.selfId = player.gameData.playerId;
1176
1176
  }
1177
1177
  this.offerStore.setPlayer(player);
1178
1178
  this.offerStore.setOffers(offers, player);
1179
1179
  this.eventEmitter.emit(OfferEvent.REFRESH, { offers, player: player });
1180
1180
  this.logger.log('Refreshed offers and player snapshot');
1181
+ return offers;
1181
1182
  }
1182
1183
  catch (error) {
1183
1184
  this.handleError(error, 'refreshOffersAndPlayer');
@@ -1320,6 +1321,8 @@ class OfferwallClient {
1320
1321
  }
1321
1322
  }
1322
1323
 
1324
+ const DEFAULT_ENTITY_KIND = '_default';
1325
+
1323
1326
  const keyPattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
1324
1327
  /**
1325
1328
  * This replaces {keyName} keys from the template with corresponding values from the dynamic object.
@@ -1357,11 +1360,25 @@ function replaceDynamicConditionKeys(conditions, trackers) {
1357
1360
  }));
1358
1361
  }
1359
1362
 
1363
+ const dynamicTrackerToPrimitive = (dynaTrack) => {
1364
+ const primitive = {};
1365
+ for (const key in dynaTrack) {
1366
+ primitive[key] = dynaTrack[key].value || 0;
1367
+ }
1368
+ return primitive;
1369
+ };
1370
+
1371
+ const addressNetworkId = (contractAddress, network) => {
1372
+ return `${contractAddress.toLowerCase()}:${network.toUpperCase()}`;
1373
+ };
1374
+
1360
1375
  const meetsBaseConditions = ({ conditions, playerSnap, addDetails,
1361
1376
  /** this exists if calling meetsBaseConditions from meetsCompletionConditions. but surfacing
1362
1377
  * check doesn't use this since we don't have a playerOffer at surfacing time
1363
1378
  */
1364
- playerOffer, }) => {
1379
+ playerOffer,
1380
+ /** Additional data like fetched token balances that isn't part of playerSnap */
1381
+ additionalData, }) => {
1365
1382
  const conditionData = [];
1366
1383
  let isValid = true;
1367
1384
  if (conditions?.minDaysInGame) {
@@ -1689,7 +1706,8 @@ playerOffer, }) => {
1689
1706
  // Validate link count conditions
1690
1707
  if (conditions?.links && 'entityLinks' in playerSnap) {
1691
1708
  for (const [linkType, constraint] of Object.entries(conditions.links)) {
1692
- const linkCount = playerSnap.entityLinks?.filter((link) => link.kind === linkType).length || 0;
1709
+ // linkType should always exist. and be default is none was specified
1710
+ const linkCount = playerSnap.entityLinks?.filter((link) => (link.kind || DEFAULT_ENTITY_KIND) === linkType).length || 0;
1693
1711
  if (constraint.min !== undefined) {
1694
1712
  const isDisqualify = linkCount < constraint.min;
1695
1713
  if (addDetails) {
@@ -1776,16 +1794,60 @@ playerOffer, }) => {
1776
1794
  return { isValid: false };
1777
1795
  }
1778
1796
  }
1797
+ // Evaluate token balance conditions
1798
+ for (const tokenCond of conditions?.tokenBalances || []) {
1799
+ const contracts = tokenCond.contracts || [];
1800
+ let totalBalance = 0;
1801
+ const fetchedBalances = aggregateTokenBalances(additionalData);
1802
+ for (const contract of contracts) {
1803
+ const balanceKey = addressNetworkId(contract.contractAddress, contract.network);
1804
+ totalBalance += fetchedBalances[balanceKey] || 0;
1805
+ }
1806
+ if (tokenCond.min !== undefined) {
1807
+ const isDisqualify = totalBalance < tokenCond.min;
1808
+ if (addDetails) {
1809
+ conditionData.push({
1810
+ isMet: !isDisqualify,
1811
+ kind: 'tokenBalances',
1812
+ trackerAmount: totalBalance,
1813
+ text: `Have at least ${tokenCond.min} ${tokenCond.name || 'tokens'}`,
1814
+ });
1815
+ if (isDisqualify)
1816
+ isValid = false;
1817
+ }
1818
+ else {
1819
+ if (isDisqualify)
1820
+ return { isValid: false };
1821
+ }
1822
+ }
1823
+ if (tokenCond.max !== undefined) {
1824
+ const isDisqualify = totalBalance > tokenCond.max;
1825
+ if (addDetails) {
1826
+ conditionData.push({
1827
+ isMet: !isDisqualify,
1828
+ kind: 'tokenBalances',
1829
+ trackerAmount: totalBalance,
1830
+ text: `Have at most ${tokenCond.max} ${tokenCond.name || 'tokens'}`,
1831
+ });
1832
+ if (isDisqualify)
1833
+ isValid = false;
1834
+ }
1835
+ else {
1836
+ if (isDisqualify)
1837
+ return { isValid: false };
1838
+ }
1839
+ }
1840
+ }
1779
1841
  return { isValid, conditionData: addDetails ? conditionData : undefined };
1780
1842
  };
1781
- const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, playerOffers, }) => {
1843
+ const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, playerOffers, additionalData, }) => {
1782
1844
  if (surfacingConditions?.contexts?.length &&
1783
1845
  !surfacingConditions.contexts?.includes(context || '')) {
1784
1846
  // context is not in the list of surfacing contexts, so we don't want to surface this offer
1785
1847
  return { isValid: false };
1786
1848
  }
1787
1849
  if (surfacingConditions?.targetEntityTypes?.length) {
1788
- const playerTarget = playerSnap.entityKind || 'default';
1850
+ const playerTarget = playerSnap.entityKind || DEFAULT_ENTITY_KIND;
1789
1851
  // check if entity type is allowed
1790
1852
  if (!surfacingConditions.targetEntityTypes.includes(playerTarget)) {
1791
1853
  return { isValid: false };
@@ -1866,7 +1928,7 @@ const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, pl
1866
1928
  }
1867
1929
  }
1868
1930
  }
1869
- return meetsBaseConditions({ conditions, playerSnap });
1931
+ return meetsBaseConditions({ conditions, playerSnap, additionalData });
1870
1932
  };
1871
1933
  const hasConditions = (conditions) => {
1872
1934
  if (!conditions)
@@ -1924,6 +1986,8 @@ const hasConditions = (conditions) => {
1924
1986
  return true;
1925
1987
  if (surCond.networkRestrictions?.length)
1926
1988
  return true;
1989
+ if (surCond.linkedEntityOffers?.offer_id)
1990
+ return true;
1927
1991
  const compCond = conditions;
1928
1992
  if (compCond.context)
1929
1993
  return true;
@@ -1943,23 +2007,47 @@ const hasConditions = (conditions) => {
1943
2007
  return true;
1944
2008
  if (compCond.dynamicTracker?.conditions?.length)
1945
2009
  return true;
2010
+ if (conditions.tokenBalances?.length)
2011
+ return true;
2012
+ if (Object.keys(compCond.contractInteractions || {}).length > 0)
2013
+ return true;
1946
2014
  return false;
1947
2015
  };
1948
- const offerMeetsCompletionConditions = (offer, snapshot) => {
2016
+ const meetsLinkedEntityOffersCondition = ({ linkedEntityOffers, matchingLinks, linkedPOfferMap, }) => {
2017
+ if (!linkedPOfferMap)
2018
+ return { isValid: false };
2019
+ const linkedPlayerOffer_ids = [];
2020
+ for (const link of matchingLinks) {
2021
+ const key = `${link.playerId}:${linkedEntityOffers.offer_id}`;
2022
+ const po = linkedPOfferMap.get(key);
2023
+ if (po) {
2024
+ linkedPlayerOffer_ids.push(po._id.toString());
2025
+ }
2026
+ }
2027
+ if (linkedPlayerOffer_ids.length > 0) {
2028
+ return { isValid: true, linkedPlayerOffer_ids };
2029
+ }
2030
+ return { isValid: false };
2031
+ };
2032
+ const offerMeetsCompletionConditions = (offer, snapshot, additionalData) => {
1949
2033
  return meetsCompletionConditions({
1950
2034
  completionConditions: offer.completionConditions || {},
1951
2035
  completionTrackers: offer.completionTrackers,
1952
2036
  playerSnap: snapshot,
1953
2037
  playerOffer: offer,
1954
2038
  addDetails: true,
2039
+ maxClaimCount: offer.maxClaimCount,
2040
+ additionalData,
1955
2041
  });
1956
2042
  };
1957
- const meetsCompletionConditions = ({ completionConditions, completionTrackers, playerSnap, playerOffer, addDetails = false, maxClaimCount, }) => {
2043
+ const meetsCompletionConditions = ({ completionConditions, completionTrackers, playerSnap, playerOffer, addDetails = false, maxClaimCount, additionalData, }) => {
1958
2044
  if (completionConditions) {
1959
2045
  const conditions = completionConditions;
1960
2046
  // For multi-claim offers, scale cumulative requirements by (claimedCount + 1)
1961
2047
  const shouldScale = maxClaimCount === -1 || (maxClaimCount && maxClaimCount > 1);
1962
- const claimMultiplier = shouldScale ? (playerOffer.claimedCount || 0) + 1 : 1;
2048
+ const claimMultiplier = shouldScale
2049
+ ? (playerOffer.trackers?.claimedCount || 0) + 1
2050
+ : 1;
1963
2051
  const conditionData = [];
1964
2052
  let isValid = true;
1965
2053
  let maxTotalClaimsFromScaling = Infinity;
@@ -2090,12 +2178,14 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2090
2178
  }
2091
2179
  }
2092
2180
  if (conditions?.social) {
2093
- const tSocial = completionTrackers?.social;
2181
+ const tSocialAccumulate = completionTrackers?.social;
2182
+ const tSocialAttach = completionTrackers?.social;
2094
2183
  const cSocial = completionConditions.social;
2095
2184
  const mode = cSocial?.mode || 'attach';
2185
+ const tSocial = mode === 'accumulate' ? tSocialAccumulate : tSocialAttach;
2096
2186
  const hasContent = Boolean(mode === 'accumulate'
2097
- ? tSocial?.mode === 'accumulate'
2098
- : tSocial && tSocial.mode !== 'accumulate' && !!tSocial.videoId);
2187
+ ? tSocialAccumulate?.matchCount > 0
2188
+ : tSocialAttach?.videoId);
2099
2189
  // Only scale social metrics in accumulate mode (attach mode is single content)
2100
2190
  const socialMultiplier = mode === 'accumulate' ? claimMultiplier : 1;
2101
2191
  const minLikes = (cSocial?.minLikes || 0) * socialMultiplier;
@@ -2130,7 +2220,7 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2130
2220
  .join(' | ');
2131
2221
  const requiredWords = cSocial?.requiredWords ?? [];
2132
2222
  if (mode === 'accumulate') {
2133
- const matchCount = (tSocial?.mode === 'accumulate' && tSocial.matchCount) || 0;
2223
+ const matchCount = tSocialAccumulate?.matchCount || 0;
2134
2224
  conditionData.push({
2135
2225
  isMet: hasContent,
2136
2226
  kind: 'social',
@@ -2143,7 +2233,7 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2143
2233
  });
2144
2234
  }
2145
2235
  else {
2146
- const title = (tSocial && tSocial.mode !== 'accumulate' && tSocial.title) || undefined;
2236
+ const title = tSocialAttach?.title;
2147
2237
  conditionData.push({
2148
2238
  isMet: hasContent,
2149
2239
  kind: 'social',
@@ -2220,22 +2310,22 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2220
2310
  if (conditions?.dynamicTracker?.conditions?.length) {
2221
2311
  const resolvedConditions = replaceDynamicConditionKeys(conditions.dynamicTracker.conditions, playerOffer?.trackers || {});
2222
2312
  // now we have the game-defined conditions with {{}} keys populated. feed these conditions into evaluator
2223
- const dynamicResult = meetsDynamicConditions(completionTrackers?.dynamicTracker, {
2313
+ const dynamicResult = meetsDynamicConditions(dynamicTrackerToPrimitive(completionTrackers?.dynamicTracker || {}), {
2224
2314
  ...conditions.dynamicTracker,
2225
2315
  conditions: resolvedConditions,
2226
2316
  }, claimMultiplier);
2227
2317
  if (shouldScale) {
2228
- const dynamicMax = getMaxClaimsForDynamicGroup(completionTrackers?.dynamicTracker || {}, {
2318
+ const dynamicMax = getMaxClaimsForDynamicGroup(dynamicTrackerToPrimitive(completionTrackers?.dynamicTracker || {}), {
2229
2319
  ...conditions.dynamicTracker,
2230
2320
  conditions: resolvedConditions,
2231
- }, playerOffer.claimedCount || 0);
2321
+ }, playerOffer?.trackers?.claimedCount || 0);
2232
2322
  updateMax(dynamicMax);
2233
2323
  }
2234
2324
  if (addDetails) {
2235
2325
  conditionData.push({
2236
2326
  isMet: dynamicResult,
2237
- kind: 'dynamic',
2238
- text: renderTemplate(conditions.dynamicTracker.template, completionTrackers?.dynamicTracker) || 'Dynamic conditions',
2327
+ kind: 'dynamicTracker',
2328
+ text: renderTemplate(conditions.dynamicTracker.template, dynamicTrackerToPrimitive(completionTrackers?.dynamicTracker || {})) || 'Dynamic conditions',
2239
2329
  });
2240
2330
  if (!dynamicResult)
2241
2331
  isValid = false;
@@ -2245,19 +2335,64 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
2245
2335
  return { isValid: false, availableClaimsNow: 0 };
2246
2336
  }
2247
2337
  }
2338
+ // Evaluate contractInteractions completion trackers
2339
+ if (conditions?.contractInteractions) {
2340
+ for (const [conditionId, condition] of Object.entries(conditions.contractInteractions)) {
2341
+ const baseAmount = condition.amount || 0;
2342
+ const scaledAmount = baseAmount * claimMultiplier;
2343
+ const trackerValue = completionTrackers?.contractInteractions?.[conditionId] || 0;
2344
+ const isDisqualify = trackerValue < scaledAmount;
2345
+ if (shouldScale && baseAmount > 0) {
2346
+ updateMax(Math.floor(trackerValue / baseAmount));
2347
+ }
2348
+ if (addDetails) {
2349
+ let displayText;
2350
+ const eventType = condition.event;
2351
+ const name = condition.name || 'tokens';
2352
+ if (eventType === 'spend') {
2353
+ displayText = `Spend ${scaledAmount} ${name}`;
2354
+ }
2355
+ else if (eventType === 'earn') {
2356
+ displayText = `Earn ${scaledAmount} ${name}`;
2357
+ }
2358
+ else if (eventType === 'gain') {
2359
+ displayText = `Gain ${scaledAmount} ${name}`;
2360
+ }
2361
+ else if (eventType === 'lose') {
2362
+ displayText = `Lose ${scaledAmount} ${name}`;
2363
+ }
2364
+ else {
2365
+ displayText = `${name}: ${scaledAmount}`;
2366
+ }
2367
+ conditionData.push({
2368
+ isMet: !isDisqualify,
2369
+ kind: 'contractInteractions',
2370
+ trackerAmount: trackerValue,
2371
+ text: displayText,
2372
+ });
2373
+ if (isDisqualify)
2374
+ isValid = false;
2375
+ }
2376
+ else {
2377
+ if (isDisqualify)
2378
+ return { isValid: false, availableClaimsNow: 0 };
2379
+ }
2380
+ }
2381
+ }
2248
2382
  const r = meetsBaseConditions({
2249
2383
  conditions,
2250
2384
  playerSnap,
2251
2385
  addDetails: true,
2252
2386
  playerOffer,
2387
+ additionalData,
2253
2388
  });
2254
2389
  isValid = isValid && r.isValid;
2255
2390
  conditionData.push(...(r.conditionData || []));
2256
2391
  if (maxClaimCount && maxClaimCount > 0) {
2257
2392
  updateMax(maxClaimCount);
2258
2393
  }
2259
- const claimedCount = playerOffer.claimedCount || 0;
2260
- let availableClaimsNow = !isValid
2394
+ const claimedCount = playerOffer?.trackers?.claimedCount || 0;
2395
+ const availableClaimsNow = !isValid
2261
2396
  ? 0
2262
2397
  : maxTotalClaimsFromScaling === Infinity
2263
2398
  ? -1
@@ -2608,6 +2743,19 @@ function meetsClaimableConditions({ claimableConditions, playerOfferTrackers, cl
2608
2743
  }
2609
2744
  return { isValid: true };
2610
2745
  }
2746
+ // returns contractAddress:network -> balance
2747
+ function aggregateTokenBalances(data) {
2748
+ const aggregatedBalances = {};
2749
+ for (const { balances } of data?.cryptoWallets || []) {
2750
+ for (const [key, balance] of Object.entries(balances)) {
2751
+ if (!aggregatedBalances[key]) {
2752
+ aggregatedBalances[key] = 0;
2753
+ }
2754
+ aggregatedBalances[key] += balance;
2755
+ }
2756
+ }
2757
+ return aggregatedBalances;
2758
+ }
2611
2759
 
2612
2760
  const offerListenerEvents = ['claim_offer'];
2613
2761
  const PlayerOfferStatuses = [
@@ -2655,5 +2803,5 @@ const rewardSchema = {
2655
2803
  image: String,
2656
2804
  };
2657
2805
 
2658
- export { AssetHelper, ConnectionState, EventEmitter, OfferEvent, OfferStore, OfferwallClient, PlayerOfferStatuses, SSEConnection, getMaxClaimsForDynamicCondition, getMaxClaimsForDynamicGroup, hasConditions, meetsBaseConditions, meetsClaimableConditions, meetsCompletionConditions, meetsCompletionConditionsBeforeExpiry, meetsDynamicConditions, meetsSurfacingConditions, offerListenerEvents, offerMeetsCompletionConditions, rewardKinds, rewardSchema };
2806
+ export { AssetHelper, ConnectionState, DEFAULT_ENTITY_KIND, EventEmitter, OfferEvent, OfferStore, OfferwallClient, PlayerOfferStatuses, SSEConnection, aggregateTokenBalances, getMaxClaimsForDynamicCondition, getMaxClaimsForDynamicGroup, hasConditions, meetsBaseConditions, meetsClaimableConditions, meetsCompletionConditions, meetsCompletionConditionsBeforeExpiry, meetsDynamicConditions, meetsLinkedEntityOffersCondition, meetsSurfacingConditions, offerListenerEvents, offerMeetsCompletionConditions, rewardKinds, rewardSchema };
2659
2807
  //# sourceMappingURL=index.esm.js.map