@pixels-online/pixels-client-js-sdk 1.21.0 → 2.1.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/core/OfferStore.d.ts +14 -11
- package/dist/core/OfferwallClient.d.ts +5 -6
- package/dist/index.d.ts +0 -9
- package/dist/index.esm.js +26 -1525
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +52 -1569
- package/dist/index.js.map +1 -1
- package/dist/offerwall-sdk.umd.js +52 -1569
- package/dist/offerwall-sdk.umd.js.map +1 -1
- package/dist/types/events.d.ts +4 -5
- package/dist/types/hooks.d.ts +4 -5
- package/dist/types/index.d.ts +3 -6
- package/dist/utils/assets.d.ts +5 -4
- package/package.json +5 -1
- package/dist/types/blockchain/user_wallet.d.ts +0 -19
- package/dist/types/offer.d.ts +0 -274
- package/dist/types/player.d.ts +0 -485
- package/dist/types/reward.d.ts +0 -39
- package/dist/types/user.d.ts +0 -62
- package/dist/types/user_wallet.d.ts +0 -36
- package/dist/utils/blockchain_utils.d.ts +0 -5
- package/dist/utils/conditions.d.ts +0 -153
- package/dist/utils/dynamic.d.ts +0 -2
- package/dist/utils/template.d.ts +0 -20
package/dist/index.esm.js
CHANGED
|
@@ -641,7 +641,12 @@ class OfferStore {
|
|
|
641
641
|
return this.players.get(targetId) || null;
|
|
642
642
|
}
|
|
643
643
|
setPlayer(player) {
|
|
644
|
-
|
|
644
|
+
const playerId = player.snapshot?.playerId;
|
|
645
|
+
if (!playerId) {
|
|
646
|
+
this.logger.warn('No playerId in player snapshot');
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
this.players.set(playerId, player);
|
|
645
650
|
this.logger.log('Updated player:', player);
|
|
646
651
|
}
|
|
647
652
|
/**
|
|
@@ -649,13 +654,14 @@ class OfferStore {
|
|
|
649
654
|
*/
|
|
650
655
|
setOffers(offers, target) {
|
|
651
656
|
const targetPlayer = target || this.getPlayer();
|
|
652
|
-
if (!targetPlayer) {
|
|
657
|
+
if (!targetPlayer?.snapshot?.playerId) {
|
|
653
658
|
this.logger.warn('No target player to set offers for');
|
|
654
659
|
return;
|
|
655
660
|
}
|
|
656
|
-
|
|
661
|
+
const playerId = targetPlayer.snapshot.playerId;
|
|
662
|
+
this.offers.set(playerId, new Map());
|
|
657
663
|
offers.forEach((offer) => {
|
|
658
|
-
this.offers.get(
|
|
664
|
+
this.offers.get(playerId).set(offer.instanceId, offer);
|
|
659
665
|
});
|
|
660
666
|
this.logger.log(`Set ${offers.length} offers`);
|
|
661
667
|
}
|
|
@@ -693,6 +699,17 @@ class OfferStore {
|
|
|
693
699
|
return undefined;
|
|
694
700
|
return this.offers.get(targetId)?.get(offerId);
|
|
695
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* Find an offer by instanceId across all players in the store
|
|
704
|
+
*/
|
|
705
|
+
findOfferById(instanceId) {
|
|
706
|
+
for (const playerOffers of this.offers.values()) {
|
|
707
|
+
const offer = playerOffers.get(instanceId);
|
|
708
|
+
if (offer)
|
|
709
|
+
return offer;
|
|
710
|
+
}
|
|
711
|
+
return undefined;
|
|
712
|
+
}
|
|
696
713
|
/**
|
|
697
714
|
* Get all offers
|
|
698
715
|
*/
|
|
@@ -1053,8 +1070,8 @@ class OfferwallClient {
|
|
|
1053
1070
|
/**
|
|
1054
1071
|
* Claim rewards for an offer
|
|
1055
1072
|
*/
|
|
1056
|
-
async claimReward(instanceId
|
|
1057
|
-
const offer = this.offerStore.
|
|
1073
|
+
async claimReward(instanceId) {
|
|
1074
|
+
const offer = this.offerStore.findOfferById(instanceId);
|
|
1058
1075
|
if (!offer) {
|
|
1059
1076
|
throw new Error(`Offer ${instanceId} not found`);
|
|
1060
1077
|
}
|
|
@@ -1069,7 +1086,7 @@ class OfferwallClient {
|
|
|
1069
1086
|
}
|
|
1070
1087
|
}
|
|
1071
1088
|
try {
|
|
1072
|
-
const response = await this.claimOfferAPI(instanceId,
|
|
1089
|
+
const response = await this.claimOfferAPI(instanceId, offer.playerId);
|
|
1073
1090
|
const updatedOffer = { ...offer, status: 'claimed' };
|
|
1074
1091
|
this.offerStore.upsertOffer(updatedOffer);
|
|
1075
1092
|
this.eventEmitter.emit(OfferEvent.OFFER_CLAIMED, {
|
|
@@ -1172,7 +1189,7 @@ class OfferwallClient {
|
|
|
1172
1189
|
try {
|
|
1173
1190
|
const { offers, player } = await this.getOffersAndPlayer(targetId);
|
|
1174
1191
|
if (targetId == null) {
|
|
1175
|
-
this.selfId = player.
|
|
1192
|
+
this.selfId = player.snapshot?.playerId ?? null;
|
|
1176
1193
|
}
|
|
1177
1194
|
this.offerStore.setPlayer(player);
|
|
1178
1195
|
this.offerStore.setOffers(offers, player);
|
|
@@ -1321,1521 +1338,5 @@ class OfferwallClient {
|
|
|
1321
1338
|
}
|
|
1322
1339
|
}
|
|
1323
1340
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
const keyPattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
1327
|
-
/**
|
|
1328
|
-
* This replaces {keyName} keys from the template with corresponding values from the dynamic object.
|
|
1329
|
-
*/
|
|
1330
|
-
function renderTemplate(template, dynamic) {
|
|
1331
|
-
if (!template)
|
|
1332
|
-
return '';
|
|
1333
|
-
return template.replace(keyPattern, (_match, key) => {
|
|
1334
|
-
if (dynamic && typeof dynamic[key] === 'boolean') {
|
|
1335
|
-
return dynamic[key] ? '✓' : '✗';
|
|
1336
|
-
}
|
|
1337
|
-
if (dynamic && dynamic[key] !== undefined) {
|
|
1338
|
-
return String(dynamic[key]);
|
|
1339
|
-
}
|
|
1340
|
-
return '{?}'; // indicate missing key
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
/**
|
|
1344
|
-
* This replaces {{keyName}} in dynamic condition keys with corresponding values from
|
|
1345
|
-
* the PlayerOffer.trackers
|
|
1346
|
-
*
|
|
1347
|
-
* eg. a condition high_score_pet-{{surfacerPlayerId}} with high_score_pet-12345
|
|
1348
|
-
*/
|
|
1349
|
-
function replaceDynamicConditionKey(key, trackers) {
|
|
1350
|
-
return key?.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (match, p1) => {
|
|
1351
|
-
const value = trackers[p1];
|
|
1352
|
-
return value !== undefined ? String(value) : match;
|
|
1353
|
-
});
|
|
1354
|
-
}
|
|
1355
|
-
/** this replaces all of the dynamic conditions.keys by calling replaceDynamicConditionKey */
|
|
1356
|
-
function replaceDynamicConditionKeys(conditions, trackers) {
|
|
1357
|
-
return conditions.map((condition) => ({
|
|
1358
|
-
...condition,
|
|
1359
|
-
key: replaceDynamicConditionKey(condition.key, trackers),
|
|
1360
|
-
}));
|
|
1361
|
-
}
|
|
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
|
-
|
|
1375
|
-
const meetsBaseConditions = ({ conditions, playerSnap, addDetails,
|
|
1376
|
-
/** this exists if calling meetsBaseConditions from meetsCompletionConditions. but surfacing
|
|
1377
|
-
* check doesn't use this since we don't have a playerOffer at surfacing time
|
|
1378
|
-
*/
|
|
1379
|
-
playerOffer,
|
|
1380
|
-
/** Additional data like fetched token balances that isn't part of playerSnap */
|
|
1381
|
-
additionalData, }) => {
|
|
1382
|
-
const conditionData = [];
|
|
1383
|
-
let isValid = true;
|
|
1384
|
-
if (conditions?.minDaysInGame) {
|
|
1385
|
-
const isDisqualify = (playerSnap.daysInGame || 0) < conditions.minDaysInGame;
|
|
1386
|
-
if (addDetails) {
|
|
1387
|
-
conditionData.push({
|
|
1388
|
-
isMet: !isDisqualify,
|
|
1389
|
-
kind: 'minDaysInGame',
|
|
1390
|
-
trackerAmount: playerSnap.daysInGame || 0,
|
|
1391
|
-
trackerGoal: conditions.minDaysInGame,
|
|
1392
|
-
text: `More than ${conditions.minDaysInGame} Days in Game`,
|
|
1393
|
-
});
|
|
1394
|
-
if (isDisqualify)
|
|
1395
|
-
isValid = false;
|
|
1396
|
-
}
|
|
1397
|
-
else {
|
|
1398
|
-
if (isDisqualify)
|
|
1399
|
-
return { isValid: false };
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
if (conditions?.minTrustScore) {
|
|
1403
|
-
const isDisqualify = (playerSnap.trustScore || 0) < conditions.minTrustScore;
|
|
1404
|
-
if (addDetails) {
|
|
1405
|
-
conditionData.push({
|
|
1406
|
-
isMet: !isDisqualify,
|
|
1407
|
-
kind: 'minTrustScore',
|
|
1408
|
-
trackerAmount: playerSnap.trustScore || 0,
|
|
1409
|
-
trackerGoal: conditions.minTrustScore,
|
|
1410
|
-
text: `More than ${conditions.minTrustScore} Rep`,
|
|
1411
|
-
});
|
|
1412
|
-
if (isDisqualify)
|
|
1413
|
-
isValid = false;
|
|
1414
|
-
}
|
|
1415
|
-
else {
|
|
1416
|
-
if (isDisqualify)
|
|
1417
|
-
return { isValid: false };
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
if (conditions?.maxTrustScore) {
|
|
1421
|
-
const isDisqualify = (playerSnap.trustScore || 0) > conditions.maxTrustScore;
|
|
1422
|
-
if (addDetails) {
|
|
1423
|
-
conditionData.push({
|
|
1424
|
-
isMet: !isDisqualify,
|
|
1425
|
-
kind: 'maxTrustScore',
|
|
1426
|
-
trackerAmount: playerSnap.trustScore || 0,
|
|
1427
|
-
text: `Less than ${conditions.maxTrustScore} Rep`,
|
|
1428
|
-
});
|
|
1429
|
-
if (isDisqualify)
|
|
1430
|
-
isValid = false;
|
|
1431
|
-
}
|
|
1432
|
-
else {
|
|
1433
|
-
if (isDisqualify)
|
|
1434
|
-
return { isValid: false };
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
for (const key in conditions?.achievements) {
|
|
1438
|
-
const a = conditions.achievements[key];
|
|
1439
|
-
const playerAchData = playerSnap.achievements?.[key];
|
|
1440
|
-
if (!playerAchData) {
|
|
1441
|
-
if (addDetails) {
|
|
1442
|
-
conditionData.push({
|
|
1443
|
-
isMet: false,
|
|
1444
|
-
kind: 'achievements',
|
|
1445
|
-
trackerAmount: 0,
|
|
1446
|
-
trackerGoal: 1,
|
|
1447
|
-
text: `Have the achievement ${a.name}`,
|
|
1448
|
-
});
|
|
1449
|
-
isValid = false;
|
|
1450
|
-
}
|
|
1451
|
-
else {
|
|
1452
|
-
return { isValid: false };
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
if (a.minCount) {
|
|
1456
|
-
const isDisqualify = (playerAchData?.count || 0) < a.minCount;
|
|
1457
|
-
if (addDetails) {
|
|
1458
|
-
conditionData.push({
|
|
1459
|
-
isMet: !isDisqualify,
|
|
1460
|
-
kind: 'achievements',
|
|
1461
|
-
trackerAmount: playerAchData?.count || 0,
|
|
1462
|
-
trackerGoal: a.minCount,
|
|
1463
|
-
text: `Have the achievement ${a.name} more than ${a.minCount} times`,
|
|
1464
|
-
});
|
|
1465
|
-
if (isDisqualify)
|
|
1466
|
-
isValid = false;
|
|
1467
|
-
}
|
|
1468
|
-
else {
|
|
1469
|
-
if (isDisqualify)
|
|
1470
|
-
return { isValid: false };
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
for (const key in conditions?.currencies) {
|
|
1475
|
-
const c = conditions.currencies[key];
|
|
1476
|
-
const playerCurrencyData = playerSnap.currencies?.[key];
|
|
1477
|
-
if (c.min) {
|
|
1478
|
-
const isDisqualify = (playerCurrencyData?.balance || 0) < c.min;
|
|
1479
|
-
if (addDetails) {
|
|
1480
|
-
conditionData.push({
|
|
1481
|
-
isMet: !isDisqualify,
|
|
1482
|
-
kind: 'currencies',
|
|
1483
|
-
trackerAmount: playerCurrencyData?.balance || 0,
|
|
1484
|
-
trackerGoal: c.min,
|
|
1485
|
-
text: `Have more than ${c.min} ${c.name}`,
|
|
1486
|
-
});
|
|
1487
|
-
if (isDisqualify)
|
|
1488
|
-
isValid = false;
|
|
1489
|
-
}
|
|
1490
|
-
else {
|
|
1491
|
-
if (isDisqualify)
|
|
1492
|
-
return { isValid: false };
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
if (c.max) {
|
|
1496
|
-
const isDisqualify = (playerCurrencyData?.balance || 0) > c.max;
|
|
1497
|
-
if (addDetails) {
|
|
1498
|
-
conditionData.push({
|
|
1499
|
-
isMet: !isDisqualify,
|
|
1500
|
-
kind: 'currencies',
|
|
1501
|
-
trackerAmount: playerCurrencyData?.balance || 0,
|
|
1502
|
-
text: `Have less than ${c.max} ${c.name}`,
|
|
1503
|
-
});
|
|
1504
|
-
if (isDisqualify)
|
|
1505
|
-
isValid = false;
|
|
1506
|
-
}
|
|
1507
|
-
else {
|
|
1508
|
-
if (isDisqualify)
|
|
1509
|
-
return { isValid: false };
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
if (c.in) {
|
|
1513
|
-
const isDisqualify = (playerCurrencyData?.in || 0) < c.in;
|
|
1514
|
-
if (addDetails) {
|
|
1515
|
-
conditionData.push({
|
|
1516
|
-
isMet: !isDisqualify,
|
|
1517
|
-
kind: 'currencies',
|
|
1518
|
-
trackerAmount: playerCurrencyData?.in || 0,
|
|
1519
|
-
trackerGoal: c.in,
|
|
1520
|
-
text: `Deposit at least ${c.in} ${c.name}`,
|
|
1521
|
-
});
|
|
1522
|
-
if (isDisqualify)
|
|
1523
|
-
isValid = false;
|
|
1524
|
-
}
|
|
1525
|
-
else {
|
|
1526
|
-
if (isDisqualify)
|
|
1527
|
-
return { isValid: false };
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
if (c.out) {
|
|
1531
|
-
const isDisqualify = (playerCurrencyData?.out || 0) < c.out;
|
|
1532
|
-
if (addDetails) {
|
|
1533
|
-
conditionData.push({
|
|
1534
|
-
isMet: !isDisqualify,
|
|
1535
|
-
kind: 'currencies',
|
|
1536
|
-
trackerAmount: playerCurrencyData?.out || 0,
|
|
1537
|
-
trackerGoal: c.out,
|
|
1538
|
-
text: `Withdraw at least ${c.out} ${c.name}`,
|
|
1539
|
-
});
|
|
1540
|
-
if (isDisqualify)
|
|
1541
|
-
isValid = false;
|
|
1542
|
-
}
|
|
1543
|
-
else {
|
|
1544
|
-
if (isDisqualify)
|
|
1545
|
-
return { isValid: false };
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
for (const key in conditions?.levels) {
|
|
1550
|
-
const l = conditions.levels[key];
|
|
1551
|
-
const playerLevelData = playerSnap.levels?.[key];
|
|
1552
|
-
if (l.min) {
|
|
1553
|
-
const isDisqualify = (playerLevelData?.level || 0) < l.min;
|
|
1554
|
-
if (addDetails) {
|
|
1555
|
-
conditionData.push({
|
|
1556
|
-
isMet: !isDisqualify,
|
|
1557
|
-
kind: 'levels',
|
|
1558
|
-
trackerAmount: playerLevelData?.level || 0,
|
|
1559
|
-
trackerGoal: l.min,
|
|
1560
|
-
text: `Be above level ${l.min} ${l.name}`,
|
|
1561
|
-
});
|
|
1562
|
-
if (isDisqualify)
|
|
1563
|
-
isValid = false;
|
|
1564
|
-
}
|
|
1565
|
-
else {
|
|
1566
|
-
if (isDisqualify)
|
|
1567
|
-
return { isValid: false };
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
if (l.max) {
|
|
1571
|
-
const isDisqualify = (playerLevelData?.level || 0) > l.max;
|
|
1572
|
-
if (addDetails) {
|
|
1573
|
-
conditionData.push({
|
|
1574
|
-
isMet: !isDisqualify,
|
|
1575
|
-
kind: 'levels',
|
|
1576
|
-
trackerAmount: playerLevelData?.level || 0,
|
|
1577
|
-
text: `Be under level ${l.max} ${l.name}`,
|
|
1578
|
-
});
|
|
1579
|
-
if (isDisqualify)
|
|
1580
|
-
isValid = false;
|
|
1581
|
-
}
|
|
1582
|
-
else {
|
|
1583
|
-
if (isDisqualify)
|
|
1584
|
-
return { isValid: false };
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
if (conditions?.quests) {
|
|
1589
|
-
for (const questId in conditions.quests) {
|
|
1590
|
-
const quest = conditions.quests[questId];
|
|
1591
|
-
const playerQuestData = playerSnap.quests?.[questId];
|
|
1592
|
-
const isDisqualify = playerQuestData
|
|
1593
|
-
? (playerQuestData?.completions || 0) < (quest.completions || 0)
|
|
1594
|
-
: true; // if player has no data for this quest, they haven't completed it
|
|
1595
|
-
if (addDetails) {
|
|
1596
|
-
conditionData.push({
|
|
1597
|
-
isMet: !isDisqualify,
|
|
1598
|
-
kind: 'quests',
|
|
1599
|
-
trackerAmount: playerQuestData?.completions || 0,
|
|
1600
|
-
trackerGoal: quest.completions || 1,
|
|
1601
|
-
text: quest.completions === 1
|
|
1602
|
-
? `Complete the quest ${quest.name}`
|
|
1603
|
-
: (quest.completions || 0) < 1
|
|
1604
|
-
? `Start the quest ${quest.name}`
|
|
1605
|
-
: `Complete the quest ${quest.name} ${quest.completions} times`,
|
|
1606
|
-
});
|
|
1607
|
-
if (isDisqualify)
|
|
1608
|
-
isValid = false;
|
|
1609
|
-
}
|
|
1610
|
-
else {
|
|
1611
|
-
if (isDisqualify)
|
|
1612
|
-
return { isValid: false };
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
for (const key in conditions?.memberships) {
|
|
1617
|
-
const m = conditions.memberships[key];
|
|
1618
|
-
const playerMembershipsData = playerSnap.memberships?.[key];
|
|
1619
|
-
if (m.minCount) {
|
|
1620
|
-
const isDisqualify = (playerMembershipsData?.count || 0) < m.minCount;
|
|
1621
|
-
if (addDetails) {
|
|
1622
|
-
conditionData.push({
|
|
1623
|
-
isMet: !isDisqualify,
|
|
1624
|
-
kind: 'memberships',
|
|
1625
|
-
trackerAmount: playerMembershipsData?.count || 0,
|
|
1626
|
-
trackerGoal: m.minCount,
|
|
1627
|
-
text: m.minCount > 1
|
|
1628
|
-
? `Have at least ${m.minCount} ${m.name} memberships`
|
|
1629
|
-
: `Have a ${m.name} membership`,
|
|
1630
|
-
});
|
|
1631
|
-
if (isDisqualify)
|
|
1632
|
-
isValid = false;
|
|
1633
|
-
}
|
|
1634
|
-
else {
|
|
1635
|
-
if (isDisqualify)
|
|
1636
|
-
return { isValid: false };
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
if (m.maxCount) {
|
|
1640
|
-
const isDisqualify = (playerMembershipsData?.count || 0) > m.maxCount;
|
|
1641
|
-
if (addDetails) {
|
|
1642
|
-
conditionData.push({
|
|
1643
|
-
isMet: !isDisqualify,
|
|
1644
|
-
kind: 'memberships',
|
|
1645
|
-
trackerAmount: playerMembershipsData?.count || 0,
|
|
1646
|
-
text: `Have less than ${m.maxCount} ${m.name} memberships`,
|
|
1647
|
-
});
|
|
1648
|
-
if (isDisqualify)
|
|
1649
|
-
isValid = false;
|
|
1650
|
-
}
|
|
1651
|
-
else {
|
|
1652
|
-
if (isDisqualify)
|
|
1653
|
-
return { isValid: false };
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
const timeOwned = (playerMembershipsData?.expiresAt || 0) - Date.now();
|
|
1657
|
-
if (m.minMs) {
|
|
1658
|
-
const isDisqualify = timeOwned < m.minMs;
|
|
1659
|
-
if (addDetails) {
|
|
1660
|
-
conditionData.push({
|
|
1661
|
-
isMet: !isDisqualify,
|
|
1662
|
-
kind: 'memberships',
|
|
1663
|
-
trackerAmount: Number((timeOwned / (1000 * 60 * 60 * 24)).toFixed(1)),
|
|
1664
|
-
trackerGoal: Number((m.minMs / (1000 * 60 * 60 * 24)).toFixed(1)),
|
|
1665
|
-
text: `Own ${m.name} membership for at least ${(m.minMs /
|
|
1666
|
-
(1000 * 60 * 60 * 24)).toFixed(1)} days`,
|
|
1667
|
-
});
|
|
1668
|
-
if (isDisqualify)
|
|
1669
|
-
isValid = false;
|
|
1670
|
-
}
|
|
1671
|
-
else {
|
|
1672
|
-
if (isDisqualify)
|
|
1673
|
-
return { isValid: false };
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
if (m.maxMs) {
|
|
1677
|
-
const isDisqualify = timeOwned > m.maxMs;
|
|
1678
|
-
if (addDetails) {
|
|
1679
|
-
conditionData.push({
|
|
1680
|
-
isMet: !isDisqualify,
|
|
1681
|
-
kind: 'memberships',
|
|
1682
|
-
trackerAmount: Number((timeOwned / (1000 * 60 * 60 * 24)).toFixed(1)),
|
|
1683
|
-
text: `Own ${m.name} membership for less than ${(m.maxMs /
|
|
1684
|
-
(1000 * 60 * 60 * 24)).toFixed(1)} days`,
|
|
1685
|
-
});
|
|
1686
|
-
if (isDisqualify)
|
|
1687
|
-
isValid = false;
|
|
1688
|
-
}
|
|
1689
|
-
else {
|
|
1690
|
-
if (isDisqualify)
|
|
1691
|
-
return { isValid: false };
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
for (const key in conditions?.stakedTokens) {
|
|
1696
|
-
const s = conditions.stakedTokens[key];
|
|
1697
|
-
const playerStakedData = playerSnap.stakedTokens?.[key];
|
|
1698
|
-
if (s.min) {
|
|
1699
|
-
const isDisqualify = (playerStakedData?.balance || 0) < s.min;
|
|
1700
|
-
if (addDetails) {
|
|
1701
|
-
conditionData.push({
|
|
1702
|
-
isMet: !isDisqualify,
|
|
1703
|
-
kind: 'stakedTokens',
|
|
1704
|
-
trackerAmount: playerStakedData?.balance || 0,
|
|
1705
|
-
trackerGoal: s.min,
|
|
1706
|
-
text: `Have at least ${s.min} ${s.name} staked`,
|
|
1707
|
-
});
|
|
1708
|
-
if (isDisqualify)
|
|
1709
|
-
isValid = false;
|
|
1710
|
-
}
|
|
1711
|
-
else {
|
|
1712
|
-
if (isDisqualify)
|
|
1713
|
-
return { isValid: false };
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
// Validate link count conditions
|
|
1718
|
-
if (conditions?.links && 'entityLinks' in playerSnap) {
|
|
1719
|
-
for (const [linkType, constraint] of Object.entries(conditions.links)) {
|
|
1720
|
-
// linkType should always exist. and be default is none was specified
|
|
1721
|
-
const linkCount = playerSnap.entityLinks?.filter((link) => (link.kind || DEFAULT_ENTITY_KIND) === linkType).length || 0;
|
|
1722
|
-
if (constraint.min !== undefined) {
|
|
1723
|
-
const isDisqualify = linkCount < constraint.min;
|
|
1724
|
-
if (addDetails) {
|
|
1725
|
-
conditionData.push({
|
|
1726
|
-
isMet: !isDisqualify,
|
|
1727
|
-
kind: 'links',
|
|
1728
|
-
trackerAmount: linkCount,
|
|
1729
|
-
trackerGoal: constraint.min,
|
|
1730
|
-
text: constraint.template
|
|
1731
|
-
? renderTemplate(constraint.template, {
|
|
1732
|
-
current: linkCount,
|
|
1733
|
-
min: constraint.min ?? 0,
|
|
1734
|
-
max: constraint.max ?? 0,
|
|
1735
|
-
type: linkType,
|
|
1736
|
-
})
|
|
1737
|
-
: `At least ${constraint.min} ${linkType} link(s)`,
|
|
1738
|
-
});
|
|
1739
|
-
if (isDisqualify)
|
|
1740
|
-
isValid = false;
|
|
1741
|
-
}
|
|
1742
|
-
else {
|
|
1743
|
-
if (isDisqualify)
|
|
1744
|
-
return { isValid: false };
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
if (constraint.max !== undefined) {
|
|
1748
|
-
const isDisqualify = linkCount > constraint.max;
|
|
1749
|
-
if (addDetails) {
|
|
1750
|
-
conditionData.push({
|
|
1751
|
-
isMet: !isDisqualify,
|
|
1752
|
-
kind: 'links',
|
|
1753
|
-
trackerAmount: linkCount,
|
|
1754
|
-
text: constraint.template
|
|
1755
|
-
? renderTemplate(constraint.template, {
|
|
1756
|
-
current: linkCount,
|
|
1757
|
-
min: constraint.min ?? 0,
|
|
1758
|
-
max: constraint.max ?? 0,
|
|
1759
|
-
type: linkType,
|
|
1760
|
-
})
|
|
1761
|
-
: `At most ${constraint.max} ${linkType} link(s)`,
|
|
1762
|
-
});
|
|
1763
|
-
if (isDisqualify)
|
|
1764
|
-
isValid = false;
|
|
1765
|
-
}
|
|
1766
|
-
else {
|
|
1767
|
-
if (isDisqualify)
|
|
1768
|
-
return { isValid: false };
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
// Evaluate dynamic conditions
|
|
1774
|
-
if (conditions?.dynamic?.conditions?.length) {
|
|
1775
|
-
const resolvedConditions = replaceDynamicConditionKeys(conditions.dynamic.conditions, playerOffer?.trackers || {});
|
|
1776
|
-
const dynamicResult = meetsDynamicConditions(playerSnap.dynamic, {
|
|
1777
|
-
...conditions.dynamic,
|
|
1778
|
-
conditions: resolvedConditions,
|
|
1779
|
-
});
|
|
1780
|
-
if (addDetails) {
|
|
1781
|
-
conditionData.push({
|
|
1782
|
-
isMet: dynamicResult,
|
|
1783
|
-
kind: 'dynamic',
|
|
1784
|
-
text: renderTemplate(conditions.dynamic.template, playerSnap.dynamic) ||
|
|
1785
|
-
'Dynamic conditions',
|
|
1786
|
-
});
|
|
1787
|
-
if (!dynamicResult)
|
|
1788
|
-
isValid = false;
|
|
1789
|
-
}
|
|
1790
|
-
else {
|
|
1791
|
-
if (!dynamicResult)
|
|
1792
|
-
return { isValid: false };
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
if (conditions?.identifiers?.platforms?.length && 'identifiers' in playerSnap) {
|
|
1796
|
-
const playerPlatforms = new Set(playerSnap.identifiers?.map((i) => i.platform.toLowerCase()) || []);
|
|
1797
|
-
const isAndBehaviour = conditions.identifiers.behaviour === 'AND';
|
|
1798
|
-
const platformsToCheck = conditions.identifiers.platforms;
|
|
1799
|
-
let isMet;
|
|
1800
|
-
let displayText;
|
|
1801
|
-
if (isAndBehaviour) {
|
|
1802
|
-
isMet = platformsToCheck.every((platform) => playerPlatforms.has(platform.toLowerCase()));
|
|
1803
|
-
displayText = `Link all: ${platformsToCheck.join(', ')}`;
|
|
1804
|
-
}
|
|
1805
|
-
else {
|
|
1806
|
-
isMet = platformsToCheck.some((platform) => playerPlatforms.has(platform.toLowerCase()));
|
|
1807
|
-
displayText = `Link any: ${platformsToCheck.join(', ')}`;
|
|
1808
|
-
}
|
|
1809
|
-
if (addDetails) {
|
|
1810
|
-
conditionData.push({
|
|
1811
|
-
isMet,
|
|
1812
|
-
kind: 'identifiers',
|
|
1813
|
-
trackerAmount: isMet ? 1 : 0,
|
|
1814
|
-
trackerGoal: 1,
|
|
1815
|
-
text: displayText,
|
|
1816
|
-
});
|
|
1817
|
-
if (!isMet)
|
|
1818
|
-
isValid = false;
|
|
1819
|
-
}
|
|
1820
|
-
else {
|
|
1821
|
-
if (!isMet)
|
|
1822
|
-
return { isValid: false };
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
// Evaluate token balance conditions
|
|
1826
|
-
for (const tokenCond of conditions?.tokenBalances || []) {
|
|
1827
|
-
const contracts = tokenCond.contracts || [];
|
|
1828
|
-
let totalBalance = 0;
|
|
1829
|
-
const fetchedBalances = aggregateTokenBalances(additionalData);
|
|
1830
|
-
for (const contract of contracts) {
|
|
1831
|
-
const balanceKey = addressNetworkId(contract.contractAddress, contract.network);
|
|
1832
|
-
totalBalance += fetchedBalances[balanceKey] || 0;
|
|
1833
|
-
}
|
|
1834
|
-
if (tokenCond.min !== undefined) {
|
|
1835
|
-
const isDisqualify = totalBalance < tokenCond.min;
|
|
1836
|
-
if (addDetails) {
|
|
1837
|
-
conditionData.push({
|
|
1838
|
-
isMet: !isDisqualify,
|
|
1839
|
-
kind: 'tokenBalances',
|
|
1840
|
-
trackerAmount: totalBalance,
|
|
1841
|
-
trackerGoal: tokenCond.min,
|
|
1842
|
-
text: `Have at least ${tokenCond.min} ${tokenCond.name || 'tokens'}`,
|
|
1843
|
-
});
|
|
1844
|
-
if (isDisqualify)
|
|
1845
|
-
isValid = false;
|
|
1846
|
-
}
|
|
1847
|
-
else {
|
|
1848
|
-
if (isDisqualify)
|
|
1849
|
-
return { isValid: false };
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
if (tokenCond.max !== undefined) {
|
|
1853
|
-
const isDisqualify = totalBalance > tokenCond.max;
|
|
1854
|
-
if (addDetails) {
|
|
1855
|
-
conditionData.push({
|
|
1856
|
-
isMet: !isDisqualify,
|
|
1857
|
-
kind: 'tokenBalances',
|
|
1858
|
-
trackerAmount: totalBalance,
|
|
1859
|
-
text: `Have at most ${tokenCond.max} ${tokenCond.name || 'tokens'}`,
|
|
1860
|
-
});
|
|
1861
|
-
if (isDisqualify)
|
|
1862
|
-
isValid = false;
|
|
1863
|
-
}
|
|
1864
|
-
else {
|
|
1865
|
-
if (isDisqualify)
|
|
1866
|
-
return { isValid: false };
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
return { isValid, conditionData: addDetails ? conditionData : undefined };
|
|
1871
|
-
};
|
|
1872
|
-
const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, playerOffers, ignoreRequiredCompletions, additionalData, }) => {
|
|
1873
|
-
if (surfacingConditions?.contexts?.length &&
|
|
1874
|
-
!surfacingConditions.contexts?.includes(context || '')) {
|
|
1875
|
-
// context is not in the list of surfacing contexts, so we don't want to surface this offer
|
|
1876
|
-
return { isValid: false };
|
|
1877
|
-
}
|
|
1878
|
-
if (surfacingConditions?.targetEntityTypes?.length) {
|
|
1879
|
-
const playerTarget = playerSnap.entityKind || DEFAULT_ENTITY_KIND;
|
|
1880
|
-
// check if entity type is allowed
|
|
1881
|
-
if (!surfacingConditions.targetEntityTypes.includes(playerTarget)) {
|
|
1882
|
-
return { isValid: false };
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
const conditions = surfacingConditions;
|
|
1886
|
-
if (conditions?.andTags?.length) {
|
|
1887
|
-
// check if player has all of the tags
|
|
1888
|
-
const hasAllTags = conditions.andTags.every((tag) => playerSnap.tags?.includes(tag));
|
|
1889
|
-
if (!hasAllTags) {
|
|
1890
|
-
return { isValid: false };
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
if (conditions?.orTags?.length) {
|
|
1894
|
-
// check if player has any of the tags
|
|
1895
|
-
const hasAnyTags = conditions.orTags.some((tag) => playerSnap.tags?.includes(tag));
|
|
1896
|
-
if (!hasAnyTags) {
|
|
1897
|
-
return { isValid: false };
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
if (conditions?.notTags?.length) {
|
|
1901
|
-
// check if player has any of the tags
|
|
1902
|
-
const hasAnyTags = conditions.notTags.some((tag) => playerSnap.tags?.includes(tag));
|
|
1903
|
-
if (hasAnyTags) {
|
|
1904
|
-
return { isValid: false };
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
if (conditions?.maxDaysInGame &&
|
|
1908
|
-
(playerSnap.daysInGame ?? 0) > conditions.maxDaysInGame) {
|
|
1909
|
-
return { isValid: false };
|
|
1910
|
-
}
|
|
1911
|
-
if (conditions.loginStreak && (playerSnap.loginStreak || 0) < conditions.loginStreak) {
|
|
1912
|
-
return { isValid: false };
|
|
1913
|
-
}
|
|
1914
|
-
// Check dateSignedUp conditions
|
|
1915
|
-
if (conditions.minDateSignedUp &&
|
|
1916
|
-
(!playerSnap.dateSignedUp || playerSnap.dateSignedUp < conditions.minDateSignedUp)) {
|
|
1917
|
-
return { isValid: false };
|
|
1918
|
-
}
|
|
1919
|
-
if (conditions.maxDateSignedUp &&
|
|
1920
|
-
(!playerSnap.dateSignedUp || playerSnap.dateSignedUp > conditions.maxDateSignedUp)) {
|
|
1921
|
-
return { isValid: false };
|
|
1922
|
-
}
|
|
1923
|
-
const completedOfferIds = new Set();
|
|
1924
|
-
for (const pOffer of playerOffers?.values() || []) {
|
|
1925
|
-
if (pOffer.status === 'claimed' || pOffer.status === 'claimable') {
|
|
1926
|
-
completedOfferIds.add(pOffer.offer_id.toString());
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
if (conditions.completedOffers?.length) {
|
|
1930
|
-
const hasCompletedAllOffers = conditions.completedOffers.every((offerId) => completedOfferIds.has(offerId));
|
|
1931
|
-
if (!hasCompletedAllOffers && !ignoreRequiredCompletions) {
|
|
1932
|
-
return { isValid: false };
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
if (conditions.allowedCountries?.length) {
|
|
1936
|
-
const playerCountry = playerSnap.ip?.countryCode;
|
|
1937
|
-
if (!playerCountry || !conditions.allowedCountries.includes(playerCountry)) {
|
|
1938
|
-
return { isValid: false };
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
if (conditions.restrictedCountries?.length) {
|
|
1942
|
-
const playerCountry = playerSnap.ip?.countryCode;
|
|
1943
|
-
if (!playerCountry) {
|
|
1944
|
-
return { isValid: false };
|
|
1945
|
-
}
|
|
1946
|
-
if (conditions.restrictedCountries.includes(playerCountry)) {
|
|
1947
|
-
return { isValid: false };
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
if (conditions.networkRestrictions?.length) {
|
|
1951
|
-
if (!playerSnap.ip) {
|
|
1952
|
-
return { isValid: false };
|
|
1953
|
-
}
|
|
1954
|
-
for (const restriction of conditions.networkRestrictions) {
|
|
1955
|
-
if (playerSnap.ip[restriction]) {
|
|
1956
|
-
return { isValid: false };
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
return meetsBaseConditions({ conditions, playerSnap, additionalData });
|
|
1961
|
-
};
|
|
1962
|
-
const hasConditions = (conditions) => {
|
|
1963
|
-
if (!conditions)
|
|
1964
|
-
return false;
|
|
1965
|
-
if (Object.keys(conditions.currencies || {}).length > 0)
|
|
1966
|
-
return true;
|
|
1967
|
-
if (Object.keys(conditions.levels || {}).length > 0)
|
|
1968
|
-
return true;
|
|
1969
|
-
if (Object.keys(conditions.stakedTokens || {}).length > 0)
|
|
1970
|
-
return true;
|
|
1971
|
-
if (Object.keys(conditions.memberships || {}).length > 0)
|
|
1972
|
-
return true;
|
|
1973
|
-
if (Object.keys(conditions.quests || {}).length > 0)
|
|
1974
|
-
return true;
|
|
1975
|
-
if (conditions.minTrustScore)
|
|
1976
|
-
return true;
|
|
1977
|
-
if (conditions.maxTrustScore)
|
|
1978
|
-
return true;
|
|
1979
|
-
if (conditions.achievements)
|
|
1980
|
-
return true;
|
|
1981
|
-
if (conditions.minDaysInGame)
|
|
1982
|
-
return true;
|
|
1983
|
-
if (conditions.dynamic?.conditions?.length)
|
|
1984
|
-
return true;
|
|
1985
|
-
if (conditions.identifiers?.platforms?.length)
|
|
1986
|
-
return true;
|
|
1987
|
-
const surCond = conditions;
|
|
1988
|
-
if (surCond.contexts?.length)
|
|
1989
|
-
return true;
|
|
1990
|
-
if (surCond.andTags?.length)
|
|
1991
|
-
return true;
|
|
1992
|
-
if (surCond.orTags?.length)
|
|
1993
|
-
return true;
|
|
1994
|
-
if (surCond.notTags?.length)
|
|
1995
|
-
return true;
|
|
1996
|
-
if (surCond.maxDaysInGame)
|
|
1997
|
-
return true;
|
|
1998
|
-
if (surCond.loginStreak)
|
|
1999
|
-
return true;
|
|
2000
|
-
if (surCond.minDateSignedUp)
|
|
2001
|
-
return true;
|
|
2002
|
-
if (surCond.maxDateSignedUp)
|
|
2003
|
-
return true;
|
|
2004
|
-
if (surCond.completedOffers?.length)
|
|
2005
|
-
return true;
|
|
2006
|
-
if (surCond.programmatic)
|
|
2007
|
-
return true;
|
|
2008
|
-
if (surCond.targetEntityTypes?.length)
|
|
2009
|
-
return true;
|
|
2010
|
-
if (surCond.links && Object.keys(surCond.links).length > 0)
|
|
2011
|
-
return true;
|
|
2012
|
-
if (surCond.allowedCountries?.length)
|
|
2013
|
-
return true;
|
|
2014
|
-
if (surCond.restrictedCountries?.length)
|
|
2015
|
-
return true;
|
|
2016
|
-
if (surCond.networkRestrictions?.length)
|
|
2017
|
-
return true;
|
|
2018
|
-
if (surCond.linkedEntityOffers?.offer_id)
|
|
2019
|
-
return true;
|
|
2020
|
-
const compCond = conditions;
|
|
2021
|
-
if (compCond.context)
|
|
2022
|
-
return true;
|
|
2023
|
-
if (compCond.buyItem)
|
|
2024
|
-
return true;
|
|
2025
|
-
if (compCond.spendCurrency)
|
|
2026
|
-
return true;
|
|
2027
|
-
if (compCond.depositCurrency)
|
|
2028
|
-
return true;
|
|
2029
|
-
if (compCond.social)
|
|
2030
|
-
return true;
|
|
2031
|
-
if (compCond.login)
|
|
2032
|
-
return true;
|
|
2033
|
-
if (compCond.loginStreak)
|
|
2034
|
-
return true;
|
|
2035
|
-
if (compCond.linkedCompletions)
|
|
2036
|
-
return true;
|
|
2037
|
-
if (compCond.dynamicTracker?.conditions?.length)
|
|
2038
|
-
return true;
|
|
2039
|
-
if (conditions.tokenBalances?.length)
|
|
2040
|
-
return true;
|
|
2041
|
-
if (Object.keys(compCond.contractInteractions || {}).length > 0)
|
|
2042
|
-
return true;
|
|
2043
|
-
return false;
|
|
2044
|
-
};
|
|
2045
|
-
const meetsLinkedEntityOffersCondition = ({ linkedEntityOffers, matchingLinks, linkedPOfferMap, }) => {
|
|
2046
|
-
if (!linkedPOfferMap)
|
|
2047
|
-
return { isValid: false };
|
|
2048
|
-
const linkedPlayerOffer_ids = [];
|
|
2049
|
-
for (const link of matchingLinks) {
|
|
2050
|
-
const key = `${link.playerId}:${linkedEntityOffers.offer_id}`;
|
|
2051
|
-
const po = linkedPOfferMap.get(key);
|
|
2052
|
-
if (po) {
|
|
2053
|
-
linkedPlayerOffer_ids.push(po._id.toString());
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
if (linkedPlayerOffer_ids.length > 0) {
|
|
2057
|
-
return { isValid: true, linkedPlayerOffer_ids };
|
|
2058
|
-
}
|
|
2059
|
-
return { isValid: false };
|
|
2060
|
-
};
|
|
2061
|
-
const offerMeetsCompletionConditions = (offer, snapshot, additionalData) => {
|
|
2062
|
-
return meetsCompletionConditions({
|
|
2063
|
-
completionConditions: offer.completionConditions || {},
|
|
2064
|
-
completionTrackers: offer.completionTrackers,
|
|
2065
|
-
playerSnap: snapshot,
|
|
2066
|
-
playerOffer: offer,
|
|
2067
|
-
addDetails: true,
|
|
2068
|
-
maxClaimCount: offer.maxClaimCount,
|
|
2069
|
-
additionalData,
|
|
2070
|
-
});
|
|
2071
|
-
};
|
|
2072
|
-
const meetsCompletionConditions = ({ completionConditions, completionTrackers, playerSnap, playerOffer, addDetails = false, maxClaimCount, additionalData, }) => {
|
|
2073
|
-
if (completionConditions) {
|
|
2074
|
-
const conditions = completionConditions;
|
|
2075
|
-
// For multi-claim offers, scale cumulative requirements by (claimedCount + 1)
|
|
2076
|
-
const shouldScale = maxClaimCount === -1 || (maxClaimCount && maxClaimCount > 1);
|
|
2077
|
-
const claimMultiplier = shouldScale
|
|
2078
|
-
? (playerOffer.trackers?.claimedCount || 0) + 1
|
|
2079
|
-
: 1;
|
|
2080
|
-
const conditionData = [];
|
|
2081
|
-
let isValid = true;
|
|
2082
|
-
let maxTotalClaimsFromScaling = Infinity;
|
|
2083
|
-
const updateMax = (limit) => (maxTotalClaimsFromScaling = Math.min(maxTotalClaimsFromScaling, limit));
|
|
2084
|
-
if (completionConditions?.context?.id) {
|
|
2085
|
-
const hasTrackedContext = completionTrackers?.context &&
|
|
2086
|
-
completionConditions.context.id === completionTrackers.context;
|
|
2087
|
-
const isDisqualify = !hasTrackedContext;
|
|
2088
|
-
if (addDetails) {
|
|
2089
|
-
conditionData.push({
|
|
2090
|
-
isMet: !isDisqualify,
|
|
2091
|
-
kind: 'context',
|
|
2092
|
-
trackerAmount: hasTrackedContext ? 1 : 0,
|
|
2093
|
-
trackerGoal: 1,
|
|
2094
|
-
text: completionConditions.context.name,
|
|
2095
|
-
});
|
|
2096
|
-
if (isDisqualify)
|
|
2097
|
-
isValid = false;
|
|
2098
|
-
}
|
|
2099
|
-
else {
|
|
2100
|
-
if (isDisqualify)
|
|
2101
|
-
return { isValid: false, availableClaimsNow: 0 };
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
if (conditions?.buyItem) {
|
|
2105
|
-
const baseAmount = conditions.buyItem.amount || 1;
|
|
2106
|
-
const scaledAmount = baseAmount * claimMultiplier;
|
|
2107
|
-
const trackerValue = completionTrackers?.buyItem || 0;
|
|
2108
|
-
const isDisqualify = trackerValue < scaledAmount;
|
|
2109
|
-
if (shouldScale && baseAmount > 0) {
|
|
2110
|
-
updateMax(Math.floor(trackerValue / baseAmount));
|
|
2111
|
-
}
|
|
2112
|
-
if (addDetails) {
|
|
2113
|
-
conditionData.push({
|
|
2114
|
-
isMet: !isDisqualify,
|
|
2115
|
-
kind: 'buyItem',
|
|
2116
|
-
trackerAmount: trackerValue,
|
|
2117
|
-
trackerGoal: scaledAmount,
|
|
2118
|
-
text: `Buy ${scaledAmount} ${conditions.buyItem.name}`,
|
|
2119
|
-
});
|
|
2120
|
-
if (isDisqualify)
|
|
2121
|
-
isValid = false;
|
|
2122
|
-
}
|
|
2123
|
-
else {
|
|
2124
|
-
if (isDisqualify)
|
|
2125
|
-
return { isValid: false, availableClaimsNow: 0 };
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
if (conditions?.spendCurrency) {
|
|
2129
|
-
const baseAmount = conditions.spendCurrency.amount || 1;
|
|
2130
|
-
const scaledAmount = baseAmount * claimMultiplier;
|
|
2131
|
-
const trackerValue = completionTrackers?.spendCurrency || 0;
|
|
2132
|
-
const isDisqualify = trackerValue < scaledAmount;
|
|
2133
|
-
if (shouldScale && baseAmount > 0) {
|
|
2134
|
-
updateMax(Math.floor(trackerValue / baseAmount));
|
|
2135
|
-
}
|
|
2136
|
-
if (addDetails) {
|
|
2137
|
-
conditionData.push({
|
|
2138
|
-
isMet: !isDisqualify,
|
|
2139
|
-
kind: 'spendCurrency',
|
|
2140
|
-
trackerAmount: trackerValue,
|
|
2141
|
-
trackerGoal: scaledAmount,
|
|
2142
|
-
text: `Spend ${scaledAmount} ${conditions.spendCurrency.name}`,
|
|
2143
|
-
});
|
|
2144
|
-
if (isDisqualify)
|
|
2145
|
-
isValid = false;
|
|
2146
|
-
}
|
|
2147
|
-
else {
|
|
2148
|
-
if (isDisqualify)
|
|
2149
|
-
return { isValid: false, availableClaimsNow: 0 };
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
if (conditions?.depositCurrency) {
|
|
2153
|
-
const baseAmount = conditions.depositCurrency.amount || 1;
|
|
2154
|
-
const scaledAmount = baseAmount * claimMultiplier;
|
|
2155
|
-
const trackerValue = completionTrackers?.depositCurrency || 0;
|
|
2156
|
-
const isDisqualify = trackerValue < scaledAmount;
|
|
2157
|
-
if (shouldScale && baseAmount > 0) {
|
|
2158
|
-
updateMax(Math.floor(trackerValue / baseAmount));
|
|
2159
|
-
}
|
|
2160
|
-
if (addDetails) {
|
|
2161
|
-
conditionData.push({
|
|
2162
|
-
isMet: !isDisqualify,
|
|
2163
|
-
kind: 'depositCurrency',
|
|
2164
|
-
trackerAmount: trackerValue,
|
|
2165
|
-
trackerGoal: scaledAmount,
|
|
2166
|
-
text: `Deposit ${scaledAmount} ${conditions.depositCurrency.name}`,
|
|
2167
|
-
});
|
|
2168
|
-
if (isDisqualify)
|
|
2169
|
-
isValid = false;
|
|
2170
|
-
}
|
|
2171
|
-
else {
|
|
2172
|
-
if (isDisqualify)
|
|
2173
|
-
return { isValid: false, availableClaimsNow: 0 };
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
if (conditions?.login) {
|
|
2177
|
-
const isMet = new Date(playerSnap.snapshotLastUpdated || 0).getTime() >
|
|
2178
|
-
new Date(playerOffer.createdAt || 0).getTime();
|
|
2179
|
-
if (addDetails) {
|
|
2180
|
-
conditionData.push({
|
|
2181
|
-
isMet,
|
|
2182
|
-
kind: 'login',
|
|
2183
|
-
trackerAmount: isMet ? 1 : 0,
|
|
2184
|
-
trackerGoal: 1,
|
|
2185
|
-
text: `Login to the game`,
|
|
2186
|
-
});
|
|
2187
|
-
if (!isMet)
|
|
2188
|
-
isValid = false;
|
|
2189
|
-
}
|
|
2190
|
-
else {
|
|
2191
|
-
if (!isMet)
|
|
2192
|
-
return { isValid: false, availableClaimsNow: 0 };
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
if (conditions?.loginStreak) {
|
|
2196
|
-
// player's login streak snapshot right now - their login streak when offer was surfaced = their login streak since the offer was surfaced
|
|
2197
|
-
// if their login streak since the offer was surfaced is less than the required login streak, then they are not yet eligible for the offer
|
|
2198
|
-
const streakSinceOffer = (playerSnap.loginStreak || 0) - (completionTrackers?.currentLoginStreak || 0);
|
|
2199
|
-
const isDisqualify = streakSinceOffer + 1 < conditions.loginStreak;
|
|
2200
|
-
if (addDetails) {
|
|
2201
|
-
conditionData.push({
|
|
2202
|
-
isMet: !isDisqualify,
|
|
2203
|
-
kind: 'loginStreak',
|
|
2204
|
-
trackerAmount: streakSinceOffer + 1,
|
|
2205
|
-
trackerGoal: conditions.loginStreak,
|
|
2206
|
-
text: `Login streak of ${conditions.loginStreak || 0} days`,
|
|
2207
|
-
});
|
|
2208
|
-
if (isDisqualify)
|
|
2209
|
-
isValid = false;
|
|
2210
|
-
}
|
|
2211
|
-
else {
|
|
2212
|
-
if (isDisqualify)
|
|
2213
|
-
return { isValid: false, availableClaimsNow: 0 };
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
if (conditions?.social) {
|
|
2217
|
-
const tSocialAccumulate = completionTrackers?.social;
|
|
2218
|
-
const tSocialAttach = completionTrackers?.social;
|
|
2219
|
-
const cSocial = completionConditions.social;
|
|
2220
|
-
const mode = cSocial?.mode || 'attach';
|
|
2221
|
-
const tSocial = mode === 'accumulate' ? tSocialAccumulate : tSocialAttach;
|
|
2222
|
-
const hasContent = Boolean(mode === 'accumulate'
|
|
2223
|
-
? tSocialAccumulate?.matchCount > 0
|
|
2224
|
-
: tSocialAttach?.videoId);
|
|
2225
|
-
// Only scale social metrics in accumulate mode (attach mode is single content)
|
|
2226
|
-
const socialMultiplier = mode === 'accumulate' ? claimMultiplier : 1;
|
|
2227
|
-
const minLikes = (cSocial?.minLikes || 0) * socialMultiplier;
|
|
2228
|
-
const minViews = (cSocial?.minViews || 0) * socialMultiplier;
|
|
2229
|
-
const minComments = (cSocial?.minComments || 0) * socialMultiplier;
|
|
2230
|
-
const likes = tSocial?.likes || 0;
|
|
2231
|
-
const views = tSocial?.views || 0;
|
|
2232
|
-
const comments = tSocial?.comments || 0;
|
|
2233
|
-
let isDisqualify = !hasContent;
|
|
2234
|
-
if (likes < minLikes || views < minViews || comments < minComments) {
|
|
2235
|
-
isDisqualify = true;
|
|
2236
|
-
}
|
|
2237
|
-
if (shouldScale && mode === 'accumulate' && hasContent) {
|
|
2238
|
-
const baseLikes = cSocial?.minLikes || 0;
|
|
2239
|
-
const baseViews = cSocial?.minViews || 0;
|
|
2240
|
-
const baseComments = cSocial?.minComments || 0;
|
|
2241
|
-
if (baseLikes > 0)
|
|
2242
|
-
updateMax(Math.floor(likes / baseLikes));
|
|
2243
|
-
if (baseViews > 0)
|
|
2244
|
-
updateMax(Math.floor(views / baseViews));
|
|
2245
|
-
if (baseComments > 0)
|
|
2246
|
-
updateMax(Math.floor(comments / baseComments));
|
|
2247
|
-
}
|
|
2248
|
-
if (addDetails) {
|
|
2249
|
-
const platformMap = {
|
|
2250
|
-
tiktok: 'TikTok',
|
|
2251
|
-
instagram: 'Instagram',
|
|
2252
|
-
youtube: 'YouTube',
|
|
2253
|
-
};
|
|
2254
|
-
const platformText = conditions.social.platforms
|
|
2255
|
-
.map((platform) => platformMap[platform])
|
|
2256
|
-
.join(' | ');
|
|
2257
|
-
const requiredWords = cSocial?.requiredWords ?? [];
|
|
2258
|
-
if (mode === 'accumulate') {
|
|
2259
|
-
const matchCount = tSocialAccumulate?.matchCount || 0;
|
|
2260
|
-
conditionData.push({
|
|
2261
|
-
isMet: hasContent,
|
|
2262
|
-
kind: 'social',
|
|
2263
|
-
trackerAmount: matchCount,
|
|
2264
|
-
trackerGoal: 1,
|
|
2265
|
-
text: hasContent
|
|
2266
|
-
? `Found ${matchCount} matching ${platformText} post${matchCount !== 1 ? 's' : ''}`
|
|
2267
|
-
: requiredWords.length > 0
|
|
2268
|
-
? `Post ${platformText} content with ${requiredWords.map((w) => `"${w}"`).join(', ')}`
|
|
2269
|
-
: `Post ${platformText} content`,
|
|
2270
|
-
});
|
|
2271
|
-
}
|
|
2272
|
-
else {
|
|
2273
|
-
const title = tSocialAttach?.title;
|
|
2274
|
-
conditionData.push({
|
|
2275
|
-
isMet: hasContent,
|
|
2276
|
-
kind: 'social',
|
|
2277
|
-
trackerAmount: hasContent ? 1 : 0,
|
|
2278
|
-
trackerGoal: 1,
|
|
2279
|
-
text: !hasContent
|
|
2280
|
-
? requiredWords.length > 0
|
|
2281
|
-
? `Attach a ${platformText} post with ${requiredWords.map((w) => `"${w}"`).join(', ')} in the title`
|
|
2282
|
-
: `Attach a ${platformText} post`
|
|
2283
|
-
: `Attached: ${title}`,
|
|
2284
|
-
});
|
|
2285
|
-
}
|
|
2286
|
-
if (minLikes > 0) {
|
|
2287
|
-
conditionData.push({
|
|
2288
|
-
isMet: hasContent && likes >= minLikes,
|
|
2289
|
-
kind: 'social',
|
|
2290
|
-
trackerAmount: likes,
|
|
2291
|
-
trackerGoal: minLikes,
|
|
2292
|
-
text: mode === 'accumulate'
|
|
2293
|
-
? `Combined ${minLikes} Likes`
|
|
2294
|
-
: `Reach ${minLikes} Likes`,
|
|
2295
|
-
});
|
|
2296
|
-
}
|
|
2297
|
-
if (minViews > 0) {
|
|
2298
|
-
conditionData.push({
|
|
2299
|
-
isMet: hasContent && views >= minViews,
|
|
2300
|
-
kind: 'social',
|
|
2301
|
-
trackerAmount: views,
|
|
2302
|
-
trackerGoal: minViews,
|
|
2303
|
-
text: mode === 'accumulate'
|
|
2304
|
-
? `Combined ${minViews} Views`
|
|
2305
|
-
: `Reach ${minViews} Views`,
|
|
2306
|
-
});
|
|
2307
|
-
}
|
|
2308
|
-
if (minComments > 0) {
|
|
2309
|
-
conditionData.push({
|
|
2310
|
-
isMet: hasContent && comments >= minComments,
|
|
2311
|
-
kind: 'social',
|
|
2312
|
-
trackerAmount: comments,
|
|
2313
|
-
trackerGoal: minComments,
|
|
2314
|
-
text: mode === 'accumulate'
|
|
2315
|
-
? `Combined ${minComments} Comments`
|
|
2316
|
-
: `Reach ${minComments} Comments`,
|
|
2317
|
-
});
|
|
2318
|
-
}
|
|
2319
|
-
if (isDisqualify)
|
|
2320
|
-
isValid = false;
|
|
2321
|
-
}
|
|
2322
|
-
else {
|
|
2323
|
-
if (isDisqualify)
|
|
2324
|
-
return { isValid: false, availableClaimsNow: 0 };
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
// Linked completions - wait for N linked entities to complete
|
|
2328
|
-
if (conditions?.linkedCompletions?.min) {
|
|
2329
|
-
const baseMin = conditions.linkedCompletions.min;
|
|
2330
|
-
const currentCount = completionTrackers?.linkedCompletions || 0;
|
|
2331
|
-
const scaledMin = baseMin * claimMultiplier;
|
|
2332
|
-
const isDisqualify = currentCount < scaledMin;
|
|
2333
|
-
if (shouldScale && baseMin > 0) {
|
|
2334
|
-
updateMax(Math.floor(currentCount / baseMin));
|
|
2335
|
-
}
|
|
2336
|
-
if (addDetails) {
|
|
2337
|
-
conditionData.push({
|
|
2338
|
-
isMet: !isDisqualify,
|
|
2339
|
-
kind: 'linkedCompletions',
|
|
2340
|
-
trackerAmount: currentCount,
|
|
2341
|
-
trackerGoal: scaledMin,
|
|
2342
|
-
text: conditions.linkedCompletions.template
|
|
2343
|
-
? renderTemplate(conditions.linkedCompletions.template, {
|
|
2344
|
-
current: currentCount,
|
|
2345
|
-
required: scaledMin,
|
|
2346
|
-
})
|
|
2347
|
-
: `Wait for ${scaledMin} linked ${scaledMin === 1 ? 'entity' : 'entities'} to complete`,
|
|
2348
|
-
});
|
|
2349
|
-
if (isDisqualify)
|
|
2350
|
-
isValid = false;
|
|
2351
|
-
}
|
|
2352
|
-
else {
|
|
2353
|
-
if (isDisqualify)
|
|
2354
|
-
return { isValid: false, availableClaimsNow: 0 };
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
if (conditions?.dynamicTracker?.conditions?.length) {
|
|
2358
|
-
const resolvedConditions = replaceDynamicConditionKeys(conditions.dynamicTracker.conditions, playerOffer?.trackers || {});
|
|
2359
|
-
// now we have the game-defined conditions with {{}} keys populated. feed these conditions into evaluator
|
|
2360
|
-
const dynamicResult = meetsDynamicConditions(dynamicTrackerToPrimitive(completionTrackers?.dynamicTracker || {}), {
|
|
2361
|
-
...conditions.dynamicTracker,
|
|
2362
|
-
conditions: resolvedConditions,
|
|
2363
|
-
}, claimMultiplier);
|
|
2364
|
-
if (shouldScale) {
|
|
2365
|
-
const dynamicMax = getMaxClaimsForDynamicGroup(dynamicTrackerToPrimitive(completionTrackers?.dynamicTracker || {}), {
|
|
2366
|
-
...conditions.dynamicTracker,
|
|
2367
|
-
conditions: resolvedConditions,
|
|
2368
|
-
}, playerOffer?.trackers?.claimedCount || 0);
|
|
2369
|
-
updateMax(dynamicMax);
|
|
2370
|
-
}
|
|
2371
|
-
if (addDetails) {
|
|
2372
|
-
conditionData.push({
|
|
2373
|
-
isMet: dynamicResult,
|
|
2374
|
-
kind: 'dynamicTracker',
|
|
2375
|
-
text: renderTemplate(conditions.dynamicTracker.template, dynamicTrackerToPrimitive(completionTrackers?.dynamicTracker || {})) || 'Dynamic conditions',
|
|
2376
|
-
});
|
|
2377
|
-
if (!dynamicResult)
|
|
2378
|
-
isValid = false;
|
|
2379
|
-
}
|
|
2380
|
-
else {
|
|
2381
|
-
if (!dynamicResult)
|
|
2382
|
-
return { isValid: false, availableClaimsNow: 0 };
|
|
2383
|
-
}
|
|
2384
|
-
}
|
|
2385
|
-
// Evaluate contractInteractions completion trackers
|
|
2386
|
-
if (conditions?.contractInteractions) {
|
|
2387
|
-
for (const [conditionId, condition] of Object.entries(conditions.contractInteractions)) {
|
|
2388
|
-
const baseAmount = condition.amount || 0;
|
|
2389
|
-
const scaledAmount = baseAmount * claimMultiplier;
|
|
2390
|
-
const trackerValue = completionTrackers?.contractInteractions?.[conditionId] || 0;
|
|
2391
|
-
const isDisqualify = trackerValue < scaledAmount;
|
|
2392
|
-
if (shouldScale && baseAmount > 0) {
|
|
2393
|
-
updateMax(Math.floor(trackerValue / baseAmount));
|
|
2394
|
-
}
|
|
2395
|
-
if (addDetails) {
|
|
2396
|
-
const displayText = renderTemplate(condition.template, {
|
|
2397
|
-
current: trackerValue,
|
|
2398
|
-
amount: scaledAmount,
|
|
2399
|
-
});
|
|
2400
|
-
conditionData.push({
|
|
2401
|
-
isMet: !isDisqualify,
|
|
2402
|
-
kind: 'contractInteractions',
|
|
2403
|
-
trackerAmount: trackerValue,
|
|
2404
|
-
trackerGoal: scaledAmount,
|
|
2405
|
-
text: displayText,
|
|
2406
|
-
});
|
|
2407
|
-
if (isDisqualify)
|
|
2408
|
-
isValid = false;
|
|
2409
|
-
}
|
|
2410
|
-
else {
|
|
2411
|
-
if (isDisqualify)
|
|
2412
|
-
return { isValid: false, availableClaimsNow: 0 };
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
|
-
const r = meetsBaseConditions({
|
|
2417
|
-
conditions,
|
|
2418
|
-
playerSnap,
|
|
2419
|
-
addDetails: true,
|
|
2420
|
-
playerOffer,
|
|
2421
|
-
additionalData,
|
|
2422
|
-
});
|
|
2423
|
-
isValid = isValid && r.isValid;
|
|
2424
|
-
conditionData.push(...(r.conditionData || []));
|
|
2425
|
-
if (maxClaimCount && maxClaimCount > 0) {
|
|
2426
|
-
updateMax(maxClaimCount);
|
|
2427
|
-
}
|
|
2428
|
-
const claimedCount = playerOffer?.trackers?.claimedCount || 0;
|
|
2429
|
-
const availableClaimsNow = !isValid
|
|
2430
|
-
? 0
|
|
2431
|
-
: maxTotalClaimsFromScaling === Infinity
|
|
2432
|
-
? -1
|
|
2433
|
-
: Math.max(0, maxTotalClaimsFromScaling - claimedCount);
|
|
2434
|
-
return { isValid, conditionData, availableClaimsNow };
|
|
2435
|
-
}
|
|
2436
|
-
return { isValid: true, conditionData: [], availableClaimsNow: -1 };
|
|
2437
|
-
};
|
|
2438
|
-
/**
|
|
2439
|
-
* Checks if completion conditions were met before a specific expiry time.
|
|
2440
|
-
* Returns true if all relevant condition fields were updated before expiryTime.
|
|
2441
|
-
*
|
|
2442
|
-
* @param completionConditions - The completion conditions to check
|
|
2443
|
-
* @param completionTrackers - The completion trackers (for buyItem, spendCurrency, etc.)
|
|
2444
|
-
* @param playerSnap - The player snapshot with field timestamps
|
|
2445
|
-
* @returns true if all conditions were met before expiry, false otherwise
|
|
2446
|
-
*/
|
|
2447
|
-
const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completionTrackers, playerSnap, playerOffer, maxClaimCount, }) => {
|
|
2448
|
-
if (!completionConditions)
|
|
2449
|
-
return false;
|
|
2450
|
-
// Check if there are actually any conditions to evaluate
|
|
2451
|
-
if (!hasConditions(completionConditions))
|
|
2452
|
-
return false;
|
|
2453
|
-
// First check if conditions are actually met
|
|
2454
|
-
const conditionsMet = meetsCompletionConditions({
|
|
2455
|
-
completionConditions,
|
|
2456
|
-
completionTrackers,
|
|
2457
|
-
playerOffer,
|
|
2458
|
-
playerSnap,
|
|
2459
|
-
maxClaimCount,
|
|
2460
|
-
});
|
|
2461
|
-
if (!conditionsMet.isValid)
|
|
2462
|
-
return false;
|
|
2463
|
-
if (!playerOffer.expiresAt)
|
|
2464
|
-
return true;
|
|
2465
|
-
const expiryTime = new Date(playerOffer.expiresAt).getTime();
|
|
2466
|
-
const lastSnapshotUpdate = new Date(playerSnap.snapshotLastUpdated).getTime();
|
|
2467
|
-
/**
|
|
2468
|
-
* Checks if a field was updated after the expiry time.
|
|
2469
|
-
* Returns true if updated AFTER or AT expiry (violates grace period).
|
|
2470
|
-
* Returns false if updated BEFORE expiry (allows grace period).
|
|
2471
|
-
*/
|
|
2472
|
-
function wasUpdatedAfterExpiry(data) {
|
|
2473
|
-
let lastUpdated;
|
|
2474
|
-
if (typeof data === 'object' && data !== null && !(data instanceof Date)) {
|
|
2475
|
-
// Object with optional lastUpdated field
|
|
2476
|
-
lastUpdated = data.lastUpdated
|
|
2477
|
-
? new Date(data.lastUpdated).getTime()
|
|
2478
|
-
: lastSnapshotUpdate;
|
|
2479
|
-
}
|
|
2480
|
-
else if (data instanceof Date) {
|
|
2481
|
-
lastUpdated = data.getTime();
|
|
2482
|
-
}
|
|
2483
|
-
else if (typeof data === 'string' || typeof data === 'number') {
|
|
2484
|
-
lastUpdated = new Date(data).getTime();
|
|
2485
|
-
}
|
|
2486
|
-
else {
|
|
2487
|
-
// No data provided, use snapshot timestamp
|
|
2488
|
-
lastUpdated = lastSnapshotUpdate;
|
|
2489
|
-
}
|
|
2490
|
-
return lastUpdated >= expiryTime;
|
|
2491
|
-
}
|
|
2492
|
-
if (completionConditions.currencies) {
|
|
2493
|
-
for (const currencyId in completionConditions.currencies) {
|
|
2494
|
-
const currency = playerSnap.currencies?.[currencyId];
|
|
2495
|
-
if (!currency)
|
|
2496
|
-
continue;
|
|
2497
|
-
if (wasUpdatedAfterExpiry(currency))
|
|
2498
|
-
return false;
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
if (completionConditions.levels) {
|
|
2502
|
-
for (const skillId in completionConditions.levels) {
|
|
2503
|
-
const level = playerSnap.levels?.[skillId];
|
|
2504
|
-
if (!level)
|
|
2505
|
-
continue;
|
|
2506
|
-
if (wasUpdatedAfterExpiry(level))
|
|
2507
|
-
return false;
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
if (completionConditions.quests) {
|
|
2511
|
-
for (const questId in completionConditions.quests) {
|
|
2512
|
-
const quest = playerSnap.quests?.[questId];
|
|
2513
|
-
if (!quest)
|
|
2514
|
-
continue;
|
|
2515
|
-
if (wasUpdatedAfterExpiry(quest))
|
|
2516
|
-
return false;
|
|
2517
|
-
}
|
|
2518
|
-
}
|
|
2519
|
-
if (completionConditions.memberships) {
|
|
2520
|
-
for (const membershipId in completionConditions.memberships) {
|
|
2521
|
-
const membership = playerSnap.memberships?.[membershipId];
|
|
2522
|
-
if (!membership)
|
|
2523
|
-
continue;
|
|
2524
|
-
if (wasUpdatedAfterExpiry(membership))
|
|
2525
|
-
return false;
|
|
2526
|
-
}
|
|
2527
|
-
}
|
|
2528
|
-
if (completionConditions.achievements) {
|
|
2529
|
-
for (const achievementId in completionConditions.achievements) {
|
|
2530
|
-
const achievement = playerSnap.achievements?.[achievementId];
|
|
2531
|
-
if (!achievement)
|
|
2532
|
-
continue;
|
|
2533
|
-
if (wasUpdatedAfterExpiry(achievement))
|
|
2534
|
-
return false;
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
if (completionConditions.stakedTokens) {
|
|
2538
|
-
for (const tokenId in completionConditions.stakedTokens) {
|
|
2539
|
-
const stakedToken = playerSnap.stakedTokens?.[tokenId];
|
|
2540
|
-
if (!stakedToken)
|
|
2541
|
-
continue;
|
|
2542
|
-
const lastStakeTime = new Date(stakedToken.lastStake ?? 0).getTime();
|
|
2543
|
-
const lastUnstakeTime = new Date(stakedToken.lastUnstake ?? 0).getTime();
|
|
2544
|
-
const lastUpdated = Math.max(lastStakeTime, lastUnstakeTime);
|
|
2545
|
-
if (lastUpdated >= expiryTime)
|
|
2546
|
-
return false;
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
if (completionConditions.minTrustScore !== undefined ||
|
|
2550
|
-
completionConditions.maxTrustScore !== undefined) {
|
|
2551
|
-
if (wasUpdatedAfterExpiry(playerSnap.trustLastUpdated))
|
|
2552
|
-
return false;
|
|
2553
|
-
}
|
|
2554
|
-
if (completionConditions.minDaysInGame !== undefined) {
|
|
2555
|
-
if (wasUpdatedAfterExpiry(playerSnap.daysInGameLastUpdated))
|
|
2556
|
-
return false;
|
|
2557
|
-
}
|
|
2558
|
-
if (completionConditions.login || completionConditions.loginStreak) {
|
|
2559
|
-
if (wasUpdatedAfterExpiry())
|
|
2560
|
-
return false;
|
|
2561
|
-
}
|
|
2562
|
-
if (completionConditions.social) {
|
|
2563
|
-
// Check if social content was attached/validated after expiry
|
|
2564
|
-
if (completionTrackers?.social?.lastChecked) {
|
|
2565
|
-
if (wasUpdatedAfterExpiry(completionTrackers.social.lastChecked))
|
|
2566
|
-
return false;
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
// All conditions were met before expiry
|
|
2570
|
-
return true;
|
|
2571
|
-
};
|
|
2572
|
-
/**
|
|
2573
|
-
* Checks if a dynamic object meets a set of dynamic field conditions.
|
|
2574
|
-
* @param dynamicObj - The object with any key and string or number value.
|
|
2575
|
-
* @param conditions - Array of conditions to check.
|
|
2576
|
-
* @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
|
|
2577
|
-
* @returns true if all conditions are met, false otherwise.
|
|
2578
|
-
*/
|
|
2579
|
-
/**
|
|
2580
|
-
* Evaluates a single dynamic condition against the dynamic object.
|
|
2581
|
-
*/
|
|
2582
|
-
function evaluateDynamicCondition(dynamicObj, cond, claimMultiplier = 1) {
|
|
2583
|
-
if (!dynamicObj)
|
|
2584
|
-
return false;
|
|
2585
|
-
const val = dynamicObj[cond.key];
|
|
2586
|
-
if (val == undefined)
|
|
2587
|
-
return false;
|
|
2588
|
-
const isNumber = typeof val === 'number';
|
|
2589
|
-
const isBoolean = typeof val === 'boolean';
|
|
2590
|
-
if (isBoolean) {
|
|
2591
|
-
switch (cond.operator) {
|
|
2592
|
-
case '==':
|
|
2593
|
-
return val === Boolean(cond.compareTo);
|
|
2594
|
-
case '!=':
|
|
2595
|
-
return val !== Boolean(cond.compareTo);
|
|
2596
|
-
default:
|
|
2597
|
-
return false;
|
|
2598
|
-
}
|
|
2599
|
-
}
|
|
2600
|
-
const compareTo = isNumber ? Number(cond.compareTo) : String(cond.compareTo);
|
|
2601
|
-
if (isNumber && typeof compareTo === 'number') {
|
|
2602
|
-
const skipMultiplier = cond.operator === '==' || cond.operator === '!=';
|
|
2603
|
-
const scaledCompareTo = skipMultiplier ? compareTo : compareTo * claimMultiplier;
|
|
2604
|
-
switch (cond.operator) {
|
|
2605
|
-
case '==':
|
|
2606
|
-
return val === scaledCompareTo;
|
|
2607
|
-
case '!=':
|
|
2608
|
-
return val !== scaledCompareTo;
|
|
2609
|
-
case '>':
|
|
2610
|
-
return val > scaledCompareTo;
|
|
2611
|
-
case '>=':
|
|
2612
|
-
return val >= scaledCompareTo;
|
|
2613
|
-
case '<':
|
|
2614
|
-
return val < scaledCompareTo;
|
|
2615
|
-
case '<=':
|
|
2616
|
-
return val <= scaledCompareTo;
|
|
2617
|
-
}
|
|
2618
|
-
}
|
|
2619
|
-
else if (!isNumber && typeof compareTo === 'string') {
|
|
2620
|
-
switch (cond.operator) {
|
|
2621
|
-
case '==':
|
|
2622
|
-
return val === compareTo;
|
|
2623
|
-
case '!=':
|
|
2624
|
-
return val !== compareTo;
|
|
2625
|
-
case 'has':
|
|
2626
|
-
return val.includes(compareTo);
|
|
2627
|
-
case 'not_has':
|
|
2628
|
-
return !val.includes(compareTo);
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
return false;
|
|
2632
|
-
}
|
|
2633
|
-
/**
|
|
2634
|
-
* Calculates the maximum number of claims supported by a single dynamic condition.
|
|
2635
|
-
*/
|
|
2636
|
-
function getMaxClaimsForDynamicCondition(dynamicObj, cond) {
|
|
2637
|
-
if (!dynamicObj)
|
|
2638
|
-
return 0;
|
|
2639
|
-
const val = dynamicObj[cond.key];
|
|
2640
|
-
if (val === undefined)
|
|
2641
|
-
return 0;
|
|
2642
|
-
if (typeof val === 'number') {
|
|
2643
|
-
const base = Number(cond.compareTo);
|
|
2644
|
-
if (isNaN(base)) {
|
|
2645
|
-
return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
|
|
2646
|
-
}
|
|
2647
|
-
switch (cond.operator) {
|
|
2648
|
-
case '>=':
|
|
2649
|
-
if (base === 0)
|
|
2650
|
-
return val >= 0 ? Infinity : 0;
|
|
2651
|
-
if (base < 0)
|
|
2652
|
-
return val >= base ? Infinity : 0;
|
|
2653
|
-
return Math.max(0, Math.floor(val / base));
|
|
2654
|
-
case '>':
|
|
2655
|
-
if (base === 0)
|
|
2656
|
-
return val > 0 ? Infinity : 0;
|
|
2657
|
-
if (base < 0)
|
|
2658
|
-
return val > base ? Infinity : 0;
|
|
2659
|
-
if (val <= 0)
|
|
2660
|
-
return 0;
|
|
2661
|
-
return Math.max(0, Math.ceil(val / base) - 1);
|
|
2662
|
-
case '==':
|
|
2663
|
-
return val === base ? Infinity : 0;
|
|
2664
|
-
case '!=':
|
|
2665
|
-
return val !== base ? Infinity : 0;
|
|
2666
|
-
case '<=':
|
|
2667
|
-
if (base === 0)
|
|
2668
|
-
return val <= 0 ? Infinity : 0;
|
|
2669
|
-
if (base > 0)
|
|
2670
|
-
return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
|
|
2671
|
-
if (val >= 0)
|
|
2672
|
-
return 0;
|
|
2673
|
-
return Math.max(0, Math.floor(val / base));
|
|
2674
|
-
case '<':
|
|
2675
|
-
if (base === 0)
|
|
2676
|
-
return val < 0 ? Infinity : 0;
|
|
2677
|
-
if (base > 0)
|
|
2678
|
-
return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
|
|
2679
|
-
if (val >= 0)
|
|
2680
|
-
return 0;
|
|
2681
|
-
return Math.max(0, Math.ceil(val / base) - 1);
|
|
2682
|
-
}
|
|
2683
|
-
}
|
|
2684
|
-
// we don't scale the rest, they are always true or always false
|
|
2685
|
-
return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
|
|
2686
|
-
}
|
|
2687
|
-
/**
|
|
2688
|
-
* Calculates the maximum number of claims supported by a group of dynamic conditions.
|
|
2689
|
-
*/
|
|
2690
|
-
function getMaxClaimsForDynamicGroup(dynamicObj, dynamicGroup, currentClaimCount = 0) {
|
|
2691
|
-
const { conditions, links } = dynamicGroup;
|
|
2692
|
-
if (!conditions || conditions.length === 0)
|
|
2693
|
-
return Infinity;
|
|
2694
|
-
// AND only
|
|
2695
|
-
if (!links || links.length === 0 || links.every((l) => l === 'AND')) {
|
|
2696
|
-
let minClaims = Infinity;
|
|
2697
|
-
for (const cond of conditions) {
|
|
2698
|
-
const max = getMaxClaimsForDynamicCondition(dynamicObj, cond);
|
|
2699
|
-
if (max === 0)
|
|
2700
|
-
return 0;
|
|
2701
|
-
minClaims = Math.min(minClaims, max);
|
|
2702
|
-
}
|
|
2703
|
-
return minClaims;
|
|
2704
|
-
}
|
|
2705
|
-
// OR only
|
|
2706
|
-
if (links.every((l) => l === 'OR')) {
|
|
2707
|
-
let maxClaims = 0;
|
|
2708
|
-
for (const cond of conditions) {
|
|
2709
|
-
const max = getMaxClaimsForDynamicCondition(dynamicObj, cond);
|
|
2710
|
-
if (max === Infinity)
|
|
2711
|
-
return Infinity;
|
|
2712
|
-
maxClaims = Math.max(maxClaims, max);
|
|
2713
|
-
}
|
|
2714
|
-
return maxClaims;
|
|
2715
|
-
}
|
|
2716
|
-
// mixed:
|
|
2717
|
-
const maxIterations = 100;
|
|
2718
|
-
for (let n = currentClaimCount + 1; n <= currentClaimCount + maxIterations; n++) {
|
|
2719
|
-
if (!meetsDynamicConditions(dynamicObj, dynamicGroup, n)) {
|
|
2720
|
-
return n - 1;
|
|
2721
|
-
}
|
|
2722
|
-
}
|
|
2723
|
-
return currentClaimCount + maxIterations;
|
|
2724
|
-
}
|
|
2725
|
-
/**
|
|
2726
|
-
* Evaluates a group of dynamic conditions with logical links (AND, OR, AND NOT).
|
|
2727
|
-
* @param dynamicObj - The player's dynamic object with any key and string or number value.
|
|
2728
|
-
* @param dynamicGroup - The group of conditions and links to check.
|
|
2729
|
-
* @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
|
|
2730
|
-
* @returns true if the group evaluates to true, false otherwise.
|
|
2731
|
-
*/
|
|
2732
|
-
function meetsDynamicConditions(dynamicObj, dynamicGroup, claimMultiplier = 1) {
|
|
2733
|
-
const { conditions, links } = dynamicGroup;
|
|
2734
|
-
if (!conditions || conditions.length === 0)
|
|
2735
|
-
return true;
|
|
2736
|
-
if (!dynamicObj)
|
|
2737
|
-
return false;
|
|
2738
|
-
// If no links, treat as AND between all conditions
|
|
2739
|
-
if (!links || links.length === 0) {
|
|
2740
|
-
return conditions.every((cond) => evaluateDynamicCondition(dynamicObj, cond, claimMultiplier));
|
|
2741
|
-
}
|
|
2742
|
-
// Evaluate the first condition
|
|
2743
|
-
let result = evaluateDynamicCondition(dynamicObj, conditions[0], claimMultiplier);
|
|
2744
|
-
for (let i = 0; i < links.length; i++) {
|
|
2745
|
-
const nextCond = evaluateDynamicCondition(dynamicObj, conditions[i + 1], claimMultiplier);
|
|
2746
|
-
const link = links[i];
|
|
2747
|
-
if (link === 'AND') {
|
|
2748
|
-
result = result && nextCond;
|
|
2749
|
-
}
|
|
2750
|
-
else if (link === 'OR') {
|
|
2751
|
-
result = result || nextCond;
|
|
2752
|
-
}
|
|
2753
|
-
else if (link === 'AND NOT') {
|
|
2754
|
-
result = result && !nextCond;
|
|
2755
|
-
}
|
|
2756
|
-
}
|
|
2757
|
-
return result;
|
|
2758
|
-
}
|
|
2759
|
-
/**
|
|
2760
|
-
* Checks if a PlayerOffer meets its claimable conditions (completed -> claimable transition).
|
|
2761
|
-
* @param claimableConditions - The offer's claimableConditions (from IOffer)
|
|
2762
|
-
* @param claimableTrackers - The player offer's claimableTrackers
|
|
2763
|
-
*/
|
|
2764
|
-
function meetsClaimableConditions({ claimableConditions, playerOfferTrackers, claimableTrackers, }) {
|
|
2765
|
-
if (!claimableConditions) {
|
|
2766
|
-
return { isValid: true };
|
|
2767
|
-
}
|
|
2768
|
-
if (claimableConditions.siblingCompletions) {
|
|
2769
|
-
const siblingCount = playerOfferTrackers?.siblingPlayerOffer_ids?.length ?? 0;
|
|
2770
|
-
let completedCount = claimableTrackers?.siblingCompletions ?? 0;
|
|
2771
|
-
if (completedCount == -1)
|
|
2772
|
-
completedCount = siblingCount; // treat -1 as all completed
|
|
2773
|
-
// if siblings exist but not all are completed, return false
|
|
2774
|
-
if (siblingCount > 0 && completedCount < siblingCount) {
|
|
2775
|
-
return { isValid: false };
|
|
2776
|
-
}
|
|
2777
|
-
}
|
|
2778
|
-
return { isValid: true };
|
|
2779
|
-
}
|
|
2780
|
-
// returns contractAddress:network -> balance
|
|
2781
|
-
function aggregateTokenBalances(data) {
|
|
2782
|
-
const aggregatedBalances = {};
|
|
2783
|
-
for (const { balances } of data?.cryptoWallets || []) {
|
|
2784
|
-
for (const [key, balance] of Object.entries(balances)) {
|
|
2785
|
-
if (!aggregatedBalances[key]) {
|
|
2786
|
-
aggregatedBalances[key] = 0;
|
|
2787
|
-
}
|
|
2788
|
-
aggregatedBalances[key] += balance;
|
|
2789
|
-
}
|
|
2790
|
-
}
|
|
2791
|
-
return aggregatedBalances;
|
|
2792
|
-
}
|
|
2793
|
-
|
|
2794
|
-
const offerListenerEvents = ['claim_offer'];
|
|
2795
|
-
const PlayerOfferStatuses = [
|
|
2796
|
-
// 'inQueue', // fuck this shit. just don't surface offers if their offer plate is full.
|
|
2797
|
-
'surfaced',
|
|
2798
|
-
'viewed',
|
|
2799
|
-
'completed', // Individual completionConditions met, waiting for claimableConditions (e.g., siblings)
|
|
2800
|
-
'claimable',
|
|
2801
|
-
'claimed',
|
|
2802
|
-
'expired',
|
|
2803
|
-
];
|
|
2804
|
-
|
|
2805
|
-
// Use a const assertion for the array and infer the union type directly
|
|
2806
|
-
const rewardKinds = [
|
|
2807
|
-
'item',
|
|
2808
|
-
'coins',
|
|
2809
|
-
'exp',
|
|
2810
|
-
'trust_points',
|
|
2811
|
-
'loyalty_currency', // loyalty currency that the player can exchange for rewards like on-chain via withdraw, etc.
|
|
2812
|
-
'discount', // handled by the external dev, using the rewardId to identify what it is for in their system
|
|
2813
|
-
/** on-chain rewards require the builder to send funds to a custodial wallet that we use to send to player wallets*/
|
|
2814
|
-
];
|
|
2815
|
-
const rewardSchema = {
|
|
2816
|
-
_id: false,
|
|
2817
|
-
kind: { type: String, enum: rewardKinds },
|
|
2818
|
-
rewardId: {
|
|
2819
|
-
type: String,
|
|
2820
|
-
validate: {
|
|
2821
|
-
validator: function (value) {
|
|
2822
|
-
// Require rewardId for item, coins, loyalty_currency, exp, and discount kinds
|
|
2823
|
-
const requiresRewardId = ['item', 'coins', 'loyalty_currency', 'exp', 'discount'].includes(this.kind);
|
|
2824
|
-
if (requiresRewardId) {
|
|
2825
|
-
return !!value;
|
|
2826
|
-
}
|
|
2827
|
-
return true;
|
|
2828
|
-
},
|
|
2829
|
-
message: 'rewardId is required for reward kinds: item, coins, loyalty_currency, exp, and discount',
|
|
2830
|
-
},
|
|
2831
|
-
},
|
|
2832
|
-
skillId: String,
|
|
2833
|
-
currencyId: String, // could be a loyalty currency
|
|
2834
|
-
itemId: String,
|
|
2835
|
-
amount: Number,
|
|
2836
|
-
name: String,
|
|
2837
|
-
image: String,
|
|
2838
|
-
};
|
|
2839
|
-
|
|
2840
|
-
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 };
|
|
1341
|
+
export { EventEmitter, OfferStore, OfferwallClient, SSEConnection };
|
|
2841
1342
|
//# sourceMappingURL=index.esm.js.map
|