@pixels-online/pixels-client-js-sdk 1.15.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/core/OfferwallClient.d.ts +28 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +313 -59
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +314 -58
- package/dist/index.js.map +1 -1
- package/dist/offerwall-sdk.umd.js +314 -58
- package/dist/offerwall-sdk.umd.js.map +1 -1
- package/dist/types/blockchain/user_wallet.d.ts +19 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/linking.d.ts +17 -0
- package/dist/types/offer.d.ts +65 -17
- package/dist/types/player.d.ts +25 -11
- package/dist/utils/conditions.d.ts +50 -9
- package/dist/utils/template.d.ts +18 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -892,6 +892,7 @@ const mapEnvToOfferClientUrl = (env) => {
|
|
|
892
892
|
class OfferwallClient {
|
|
893
893
|
constructor(config) {
|
|
894
894
|
this.isInitializing = false;
|
|
895
|
+
this.pendingStackedToken = null;
|
|
895
896
|
this.config = {
|
|
896
897
|
autoConnect: config.autoConnect ?? false,
|
|
897
898
|
reconnect: config.reconnect ?? true,
|
|
@@ -909,12 +910,24 @@ class OfferwallClient {
|
|
|
909
910
|
this.assetHelper = new AssetHelper(this.config);
|
|
910
911
|
this.sseConnection = new SSEConnection(this.config, this.eventEmitter, this.tokenManager);
|
|
911
912
|
this.setupInternalListeners();
|
|
913
|
+
this.setupStackedLinkDetection();
|
|
912
914
|
if (this.config.autoConnect) {
|
|
913
915
|
this.initialize().catch((err) => {
|
|
914
916
|
this.logger.error('Auto-initialization failed:', err);
|
|
915
917
|
});
|
|
916
918
|
}
|
|
917
919
|
}
|
|
920
|
+
setupStackedLinkDetection() {
|
|
921
|
+
if (!this.config.stackedLink?.autoConsume) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
const token = this.detectStackedLinkToken();
|
|
925
|
+
if (token) {
|
|
926
|
+
this.logger.log('Detected stackedToken in URL');
|
|
927
|
+
this.pendingStackedToken = token;
|
|
928
|
+
this.clearStackedTokenFromUrl();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
918
931
|
/**
|
|
919
932
|
* Get the offer store instance
|
|
920
933
|
*/
|
|
@@ -945,9 +958,19 @@ class OfferwallClient {
|
|
|
945
958
|
this.isInitializing = true;
|
|
946
959
|
await this.refreshOffersAndPlayer();
|
|
947
960
|
await this.connect();
|
|
961
|
+
// Process pending Stacked link token if exists and autoConsume is enabled
|
|
962
|
+
if (this.pendingStackedToken && this.config.stackedLink?.autoConsume) {
|
|
963
|
+
this.logger.log('Processing pending Stacked link token');
|
|
964
|
+
const result = await this.consumeStackedLinkToken(this.pendingStackedToken);
|
|
965
|
+
this.pendingStackedToken = null;
|
|
966
|
+
if (this.config.stackedLink?.onComplete) {
|
|
967
|
+
this.config.stackedLink.onComplete(result);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
948
970
|
this.isInitializing = false;
|
|
949
971
|
}
|
|
950
972
|
catch (error) {
|
|
973
|
+
this.isInitializing = false;
|
|
951
974
|
this.handleError(error, 'initialize');
|
|
952
975
|
throw error;
|
|
953
976
|
}
|
|
@@ -1163,6 +1186,91 @@ class OfferwallClient {
|
|
|
1163
1186
|
return null;
|
|
1164
1187
|
return `${dashboardBaseUrl}/auth/enter?token=${token}&gameId=${gameId}`;
|
|
1165
1188
|
}
|
|
1189
|
+
// ==================== Stacked Link Methods ====================
|
|
1190
|
+
/**
|
|
1191
|
+
* Detect if there's a Stacked link token in the current URL
|
|
1192
|
+
* @returns The token string if found, null otherwise
|
|
1193
|
+
*/
|
|
1194
|
+
detectStackedLinkToken() {
|
|
1195
|
+
if (typeof window === 'undefined')
|
|
1196
|
+
return null;
|
|
1197
|
+
try {
|
|
1198
|
+
const params = new URLSearchParams(window.location.search);
|
|
1199
|
+
const token = params.get('stackedToken');
|
|
1200
|
+
if (token && token.length > 0) {
|
|
1201
|
+
return token;
|
|
1202
|
+
}
|
|
1203
|
+
// for SPA routers that use hash routing
|
|
1204
|
+
if (window.location.hash) {
|
|
1205
|
+
const hashQuery = window.location.hash.split('?')[1];
|
|
1206
|
+
if (hashQuery) {
|
|
1207
|
+
const hashParams = new URLSearchParams(hashQuery);
|
|
1208
|
+
const hashToken = hashParams.get('stackedToken');
|
|
1209
|
+
if (hashToken && hashToken.length > 0) {
|
|
1210
|
+
return hashToken;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
catch (error) {
|
|
1217
|
+
this.logger.error('Error detecting stacked token:', error);
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Clear the stackedToken from the URL without page reload
|
|
1223
|
+
*/
|
|
1224
|
+
clearStackedTokenFromUrl() {
|
|
1225
|
+
if (typeof window === 'undefined')
|
|
1226
|
+
return;
|
|
1227
|
+
try {
|
|
1228
|
+
const url = new URL(window.location.href);
|
|
1229
|
+
url.searchParams.delete('stackedToken');
|
|
1230
|
+
window.history.replaceState({}, '', url.toString());
|
|
1231
|
+
this.logger.log('Cleared stackedToken from URL');
|
|
1232
|
+
}
|
|
1233
|
+
catch (error) {
|
|
1234
|
+
this.logger.error('Error clearing stacked token from URL:', error);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Consume a Stacked link token to link the current game player
|
|
1239
|
+
* to a Stacked unified user account.
|
|
1240
|
+
*
|
|
1241
|
+
* IMPORTANT: The player must be authenticated (have a valid JWT from tokenProvider)
|
|
1242
|
+
* before calling this method.
|
|
1243
|
+
*
|
|
1244
|
+
* @param token The Stacked link token from the URL
|
|
1245
|
+
* @returns Promise resolving to the link result
|
|
1246
|
+
*/
|
|
1247
|
+
async consumeStackedLinkToken(token) {
|
|
1248
|
+
try {
|
|
1249
|
+
const data = await this.postWithAuth('/v1/auth/stacked_link/exchange', { stackedToken: token });
|
|
1250
|
+
return {
|
|
1251
|
+
linked: data.linked,
|
|
1252
|
+
alreadyLinked: data.alreadyLinked,
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
catch (error) {
|
|
1256
|
+
this.logger.error('Error consuming stacked link token:', error);
|
|
1257
|
+
return {
|
|
1258
|
+
linked: false,
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Detects the token from URL, consumes it if player is authenticated.
|
|
1264
|
+
* @returns Promise resolving to the link result, or null if no token found
|
|
1265
|
+
*/
|
|
1266
|
+
async processStackedLinkToken() {
|
|
1267
|
+
const token = this.detectStackedLinkToken();
|
|
1268
|
+
if (!token) {
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
this.clearStackedTokenFromUrl();
|
|
1272
|
+
return this.consumeStackedLinkToken(token);
|
|
1273
|
+
}
|
|
1166
1274
|
handleError(error, context) {
|
|
1167
1275
|
this.logger.error(`Error in ${context}:`, error);
|
|
1168
1276
|
if (this.hooks.onError) {
|
|
@@ -1172,19 +1280,47 @@ class OfferwallClient {
|
|
|
1172
1280
|
}
|
|
1173
1281
|
|
|
1174
1282
|
const keyPattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
1175
|
-
|
|
1283
|
+
/**
|
|
1284
|
+
* This replaces {keyName} keys from the template with corresponding values from the dynamic object.
|
|
1285
|
+
*/
|
|
1176
1286
|
function renderTemplate(template, dynamic) {
|
|
1177
1287
|
if (!template)
|
|
1178
1288
|
return '';
|
|
1179
|
-
return template.replace(keyPattern, (
|
|
1289
|
+
return template.replace(keyPattern, (_match, key) => {
|
|
1290
|
+
if (dynamic && typeof dynamic[key] === 'boolean') {
|
|
1291
|
+
return dynamic[key] ? '✓' : '✗';
|
|
1292
|
+
}
|
|
1180
1293
|
if (dynamic && dynamic[key] !== undefined) {
|
|
1181
1294
|
return String(dynamic[key]);
|
|
1182
1295
|
}
|
|
1183
1296
|
return '{?}'; // indicate missing key
|
|
1184
1297
|
});
|
|
1185
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
|
+
}
|
|
1186
1318
|
|
|
1187
|
-
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, }) => {
|
|
1188
1324
|
const conditionData = [];
|
|
1189
1325
|
let isValid = true;
|
|
1190
1326
|
if (conditions?.minDaysInGame) {
|
|
@@ -1510,7 +1646,7 @@ const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
|
|
|
1510
1646
|
}
|
|
1511
1647
|
}
|
|
1512
1648
|
// Validate link count conditions
|
|
1513
|
-
if (conditions?.links) {
|
|
1649
|
+
if (conditions?.links && 'entityLinks' in playerSnap) {
|
|
1514
1650
|
for (const [linkType, constraint] of Object.entries(conditions.links)) {
|
|
1515
1651
|
const linkCount = playerSnap.entityLinks?.filter((link) => link.kind === linkType).length || 0;
|
|
1516
1652
|
if (constraint.min !== undefined) {
|
|
@@ -1551,7 +1687,11 @@ const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
|
|
|
1551
1687
|
}
|
|
1552
1688
|
// Evaluate dynamic conditions
|
|
1553
1689
|
if (conditions?.dynamic?.conditions?.length) {
|
|
1554
|
-
const
|
|
1690
|
+
const resolvedConditions = replaceDynamicConditionKeys(conditions.dynamic.conditions, playerOffer?.trackers || {});
|
|
1691
|
+
const dynamicResult = meetsDynamicConditions(playerSnap.dynamic, {
|
|
1692
|
+
...conditions.dynamic,
|
|
1693
|
+
conditions: resolvedConditions,
|
|
1694
|
+
});
|
|
1555
1695
|
if (addDetails) {
|
|
1556
1696
|
conditionData.push({
|
|
1557
1697
|
isMet: dynamicResult,
|
|
@@ -1567,7 +1707,7 @@ const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
|
|
|
1567
1707
|
return { isValid: false };
|
|
1568
1708
|
}
|
|
1569
1709
|
}
|
|
1570
|
-
if (conditions?.identifiers?.platforms?.length) {
|
|
1710
|
+
if (conditions?.identifiers?.platforms?.length && 'identifiers' in playerSnap) {
|
|
1571
1711
|
const playerPlatforms = new Set(playerSnap.identifiers?.map((i) => i.platform.toLowerCase()) || []);
|
|
1572
1712
|
const isAndBehaviour = conditions.identifiers.behaviour === 'AND';
|
|
1573
1713
|
const platformsToCheck = conditions.identifiers.platforms;
|
|
@@ -1604,7 +1744,7 @@ const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, pl
|
|
|
1604
1744
|
return { isValid: false };
|
|
1605
1745
|
}
|
|
1606
1746
|
if (surfacingConditions?.targetEntityTypes?.length) {
|
|
1607
|
-
const playerTarget = playerSnap.
|
|
1747
|
+
const playerTarget = playerSnap.entityKind || 'default';
|
|
1608
1748
|
// check if entity type is allowed
|
|
1609
1749
|
if (!surfacingConditions.targetEntityTypes.includes(playerTarget)) {
|
|
1610
1750
|
return { isValid: false };
|
|
@@ -1660,6 +1800,31 @@ const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, pl
|
|
|
1660
1800
|
return { isValid: false };
|
|
1661
1801
|
}
|
|
1662
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
|
+
}
|
|
1663
1828
|
return meetsBaseConditions({ conditions, playerSnap });
|
|
1664
1829
|
};
|
|
1665
1830
|
const hasConditions = (conditions) => {
|
|
@@ -1712,6 +1877,12 @@ const hasConditions = (conditions) => {
|
|
|
1712
1877
|
return true;
|
|
1713
1878
|
if (surCond.links && Object.keys(surCond.links).length > 0)
|
|
1714
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;
|
|
1715
1886
|
const compCond = conditions;
|
|
1716
1887
|
if (compCond.context)
|
|
1717
1888
|
return true;
|
|
@@ -1729,11 +1900,25 @@ const hasConditions = (conditions) => {
|
|
|
1729
1900
|
return true;
|
|
1730
1901
|
if (compCond.linkedCompletions)
|
|
1731
1902
|
return true;
|
|
1903
|
+
if (compCond.dynamicTracker?.conditions?.length)
|
|
1904
|
+
return true;
|
|
1732
1905
|
return false;
|
|
1733
1906
|
};
|
|
1734
|
-
const
|
|
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, }) => {
|
|
1735
1917
|
if (completionConditions) {
|
|
1736
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;
|
|
1737
1922
|
const conditionData = [];
|
|
1738
1923
|
let isValid = true;
|
|
1739
1924
|
if (completionConditions?.context?.id) {
|
|
@@ -1756,13 +1941,14 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1756
1941
|
}
|
|
1757
1942
|
}
|
|
1758
1943
|
if (conditions?.buyItem) {
|
|
1759
|
-
const
|
|
1944
|
+
const scaledAmount = (conditions.buyItem.amount || 1) * claimMultiplier;
|
|
1945
|
+
const isDisqualify = (completionTrackers?.buyItem || 0) < scaledAmount;
|
|
1760
1946
|
if (addDetails) {
|
|
1761
1947
|
conditionData.push({
|
|
1762
1948
|
isMet: !isDisqualify,
|
|
1763
1949
|
kind: 'buyItem',
|
|
1764
1950
|
trackerAmount: completionTrackers?.buyItem || 0,
|
|
1765
|
-
text: `Buy ${
|
|
1951
|
+
text: `Buy ${scaledAmount} ${conditions.buyItem.name}`,
|
|
1766
1952
|
});
|
|
1767
1953
|
if (isDisqualify)
|
|
1768
1954
|
isValid = false;
|
|
@@ -1773,13 +1959,14 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1773
1959
|
}
|
|
1774
1960
|
}
|
|
1775
1961
|
if (conditions?.spendCurrency) {
|
|
1776
|
-
const
|
|
1962
|
+
const scaledAmount = (conditions.spendCurrency.amount || 1) * claimMultiplier;
|
|
1963
|
+
const isDisqualify = (completionTrackers?.spendCurrency || 0) < scaledAmount;
|
|
1777
1964
|
if (addDetails) {
|
|
1778
1965
|
conditionData.push({
|
|
1779
1966
|
isMet: !isDisqualify,
|
|
1780
1967
|
kind: 'spendCurrency',
|
|
1781
1968
|
trackerAmount: completionTrackers?.spendCurrency || 0,
|
|
1782
|
-
text: `Spend ${
|
|
1969
|
+
text: `Spend ${scaledAmount} ${conditions.spendCurrency.name}`,
|
|
1783
1970
|
});
|
|
1784
1971
|
if (isDisqualify)
|
|
1785
1972
|
isValid = false;
|
|
@@ -1790,15 +1977,17 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1790
1977
|
}
|
|
1791
1978
|
}
|
|
1792
1979
|
if (conditions?.depositCurrency) {
|
|
1793
|
-
const
|
|
1794
|
-
|
|
1980
|
+
const scaledAmount = (conditions.depositCurrency.amount || 1) * claimMultiplier;
|
|
1981
|
+
const isDisqualify = (completionTrackers?.depositCurrency || 0) < scaledAmount;
|
|
1795
1982
|
if (addDetails) {
|
|
1796
1983
|
conditionData.push({
|
|
1797
1984
|
isMet: !isDisqualify,
|
|
1798
1985
|
kind: 'depositCurrency',
|
|
1799
1986
|
trackerAmount: completionTrackers?.depositCurrency || 0,
|
|
1800
|
-
text: `Deposit ${
|
|
1987
|
+
text: `Deposit ${scaledAmount} ${conditions.depositCurrency.name}`,
|
|
1801
1988
|
});
|
|
1989
|
+
if (isDisqualify)
|
|
1990
|
+
isValid = false;
|
|
1802
1991
|
}
|
|
1803
1992
|
else {
|
|
1804
1993
|
if (isDisqualify)
|
|
@@ -1806,12 +1995,13 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1806
1995
|
}
|
|
1807
1996
|
}
|
|
1808
1997
|
if (conditions?.login) {
|
|
1809
|
-
const isMet =
|
|
1998
|
+
const isMet = new Date(playerSnap.snapshotLastUpdated || 0).getTime() >
|
|
1999
|
+
new Date(playerOffer.createdAt || 0).getTime();
|
|
1810
2000
|
if (addDetails) {
|
|
1811
2001
|
conditionData.push({
|
|
1812
2002
|
isMet,
|
|
1813
2003
|
kind: 'login',
|
|
1814
|
-
trackerAmount:
|
|
2004
|
+
trackerAmount: isMet ? 1 : 0,
|
|
1815
2005
|
text: `Login to the game`,
|
|
1816
2006
|
});
|
|
1817
2007
|
isValid = isMet;
|
|
@@ -1848,9 +2038,11 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1848
2038
|
const hasContent = Boolean(mode === 'accumulate'
|
|
1849
2039
|
? tSocial?.mode === 'accumulate'
|
|
1850
2040
|
: tSocial && tSocial.mode !== 'accumulate' && !!tSocial.videoId);
|
|
1851
|
-
|
|
1852
|
-
const
|
|
1853
|
-
const
|
|
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;
|
|
1854
2046
|
const likes = tSocial?.likes || 0;
|
|
1855
2047
|
const views = tSocial?.views || 0;
|
|
1856
2048
|
const comments = tSocial?.comments || 0;
|
|
@@ -1935,14 +2127,14 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1935
2127
|
// Linked completions - wait for N linked entities to complete
|
|
1936
2128
|
if (conditions?.linkedCompletions?.min) {
|
|
1937
2129
|
const currentCount = completionTrackers?.linkedCompletions || 0;
|
|
1938
|
-
const
|
|
1939
|
-
const isDisqualify = currentCount <
|
|
2130
|
+
const scaledMin = conditions.linkedCompletions.min * claimMultiplier;
|
|
2131
|
+
const isDisqualify = currentCount < scaledMin;
|
|
1940
2132
|
if (addDetails) {
|
|
1941
2133
|
conditionData.push({
|
|
1942
2134
|
isMet: !isDisqualify,
|
|
1943
2135
|
kind: 'linkedCompletions',
|
|
1944
2136
|
trackerAmount: currentCount,
|
|
1945
|
-
text: `Wait for ${
|
|
2137
|
+
text: `Wait for ${scaledMin} linked ${scaledMin === 1 ? 'entity' : 'entities'} to complete`,
|
|
1946
2138
|
});
|
|
1947
2139
|
if (isDisqualify)
|
|
1948
2140
|
isValid = false;
|
|
@@ -1952,10 +2144,32 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1952
2144
|
return { isValid: false };
|
|
1953
2145
|
}
|
|
1954
2146
|
}
|
|
2147
|
+
if (conditions?.dynamicTracker?.conditions?.length) {
|
|
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);
|
|
2154
|
+
if (addDetails) {
|
|
2155
|
+
conditionData.push({
|
|
2156
|
+
isMet: dynamicResult,
|
|
2157
|
+
kind: 'dynamic',
|
|
2158
|
+
text: renderTemplate(conditions.dynamicTracker.template, completionTrackers?.dynamicTracker) || 'Dynamic conditions',
|
|
2159
|
+
});
|
|
2160
|
+
if (!dynamicResult)
|
|
2161
|
+
isValid = false;
|
|
2162
|
+
}
|
|
2163
|
+
else {
|
|
2164
|
+
if (!dynamicResult)
|
|
2165
|
+
return { isValid: false };
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
1955
2168
|
const r = meetsBaseConditions({
|
|
1956
2169
|
conditions,
|
|
1957
2170
|
playerSnap,
|
|
1958
2171
|
addDetails: true,
|
|
2172
|
+
playerOffer,
|
|
1959
2173
|
});
|
|
1960
2174
|
isValid = isValid && r.isValid;
|
|
1961
2175
|
conditionData.push(...(r.conditionData || []));
|
|
@@ -1970,10 +2184,9 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1970
2184
|
* @param completionConditions - The completion conditions to check
|
|
1971
2185
|
* @param completionTrackers - The completion trackers (for buyItem, spendCurrency, etc.)
|
|
1972
2186
|
* @param playerSnap - The player snapshot with field timestamps
|
|
1973
|
-
* @param expiryTime - The expiry timestamp in milliseconds
|
|
1974
2187
|
* @returns true if all conditions were met before expiry, false otherwise
|
|
1975
2188
|
*/
|
|
1976
|
-
const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completionTrackers, playerSnap,
|
|
2189
|
+
const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completionTrackers, playerSnap, playerOffer, maxClaimCount, }) => {
|
|
1977
2190
|
if (!completionConditions)
|
|
1978
2191
|
return false;
|
|
1979
2192
|
// Check if there are actually any conditions to evaluate
|
|
@@ -1983,10 +2196,15 @@ const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completio
|
|
|
1983
2196
|
const conditionsMet = meetsCompletionConditions({
|
|
1984
2197
|
completionConditions,
|
|
1985
2198
|
completionTrackers,
|
|
2199
|
+
playerOffer,
|
|
1986
2200
|
playerSnap,
|
|
2201
|
+
maxClaimCount,
|
|
1987
2202
|
});
|
|
1988
2203
|
if (!conditionsMet.isValid)
|
|
1989
2204
|
return false;
|
|
2205
|
+
if (!playerOffer.expiresAt)
|
|
2206
|
+
return true;
|
|
2207
|
+
const expiryTime = new Date(playerOffer.expiresAt).getTime();
|
|
1990
2208
|
const lastSnapshotUpdate = new Date(playerSnap.snapshotLastUpdated).getTime();
|
|
1991
2209
|
/**
|
|
1992
2210
|
* Checks if a field was updated after the expiry time.
|
|
@@ -2097,66 +2315,80 @@ const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completio
|
|
|
2097
2315
|
* Checks if a dynamic object meets a set of dynamic field conditions.
|
|
2098
2316
|
* @param dynamicObj - The object with any key and string or number value.
|
|
2099
2317
|
* @param conditions - Array of conditions to check.
|
|
2318
|
+
* @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
|
|
2100
2319
|
* @returns true if all conditions are met, false otherwise.
|
|
2101
2320
|
*/
|
|
2102
2321
|
/**
|
|
2103
2322
|
* Evaluates a single dynamic condition against the dynamic object.
|
|
2104
2323
|
*/
|
|
2105
|
-
function evaluateDynamicCondition(dynamicObj, cond) {
|
|
2324
|
+
function evaluateDynamicCondition(dynamicObj, cond, claimMultiplier = 1) {
|
|
2325
|
+
if (!dynamicObj)
|
|
2326
|
+
return false;
|
|
2106
2327
|
const val = dynamicObj[cond.key];
|
|
2107
2328
|
if (val === undefined)
|
|
2108
2329
|
return false;
|
|
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
|
+
}
|
|
2342
|
+
const compareTo = isNumber ? Number(cond.compareTo) : String(cond.compareTo);
|
|
2109
2343
|
switch (cond.operator) {
|
|
2110
2344
|
case '==':
|
|
2111
|
-
return val ===
|
|
2345
|
+
return val === compareTo;
|
|
2112
2346
|
case '!=':
|
|
2113
|
-
return val !==
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
case '<=':
|
|
2127
|
-
return (typeof val === 'number' &&
|
|
2128
|
-
typeof cond.compareTo === 'number' &&
|
|
2129
|
-
val <= cond.compareTo);
|
|
2130
|
-
case 'has':
|
|
2131
|
-
return (typeof val === 'string' &&
|
|
2132
|
-
typeof cond.compareTo === 'string' &&
|
|
2133
|
-
val.includes(cond.compareTo));
|
|
2134
|
-
case 'not_has':
|
|
2135
|
-
return (typeof val === 'string' &&
|
|
2136
|
-
typeof cond.compareTo === 'string' &&
|
|
2137
|
-
!val.includes(cond.compareTo));
|
|
2138
|
-
default:
|
|
2139
|
-
return false;
|
|
2347
|
+
return val !== compareTo;
|
|
2348
|
+
}
|
|
2349
|
+
if (isNumber && typeof compareTo === 'number') {
|
|
2350
|
+
switch (cond.operator) {
|
|
2351
|
+
case '>':
|
|
2352
|
+
return val > compareTo * claimMultiplier;
|
|
2353
|
+
case '>=':
|
|
2354
|
+
return val >= compareTo * claimMultiplier;
|
|
2355
|
+
case '<':
|
|
2356
|
+
return val < compareTo * claimMultiplier;
|
|
2357
|
+
case '<=':
|
|
2358
|
+
return val <= compareTo * claimMultiplier;
|
|
2359
|
+
}
|
|
2140
2360
|
}
|
|
2361
|
+
else if (!isNumber && typeof compareTo === 'string') {
|
|
2362
|
+
switch (cond.operator) {
|
|
2363
|
+
case 'has':
|
|
2364
|
+
return val.includes(compareTo);
|
|
2365
|
+
case 'not_has':
|
|
2366
|
+
return !val.includes(compareTo);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
return false;
|
|
2141
2370
|
}
|
|
2142
2371
|
/**
|
|
2143
2372
|
* Evaluates a group of dynamic conditions with logical links (AND, OR, AND NOT).
|
|
2144
2373
|
* @param dynamicObj - The player's dynamic object with any key and string or number value.
|
|
2145
2374
|
* @param dynamicGroup - The group of conditions and links to check.
|
|
2375
|
+
* @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
|
|
2146
2376
|
* @returns true if the group evaluates to true, false otherwise.
|
|
2147
2377
|
*/
|
|
2148
|
-
function meetsDynamicConditions(dynamicObj, dynamicGroup) {
|
|
2378
|
+
function meetsDynamicConditions(dynamicObj, dynamicGroup, claimMultiplier = 1) {
|
|
2149
2379
|
const { conditions, links } = dynamicGroup;
|
|
2150
2380
|
if (!conditions || conditions.length === 0)
|
|
2151
2381
|
return true;
|
|
2382
|
+
if (!dynamicObj)
|
|
2383
|
+
return false;
|
|
2152
2384
|
// If no links, treat as AND between all conditions
|
|
2153
2385
|
if (!links || links.length === 0) {
|
|
2154
|
-
return conditions.every((cond) => evaluateDynamicCondition(dynamicObj, cond));
|
|
2386
|
+
return conditions.every((cond) => evaluateDynamicCondition(dynamicObj, cond, claimMultiplier));
|
|
2155
2387
|
}
|
|
2156
2388
|
// Evaluate the first condition
|
|
2157
|
-
let result = evaluateDynamicCondition(dynamicObj, conditions[0]);
|
|
2389
|
+
let result = evaluateDynamicCondition(dynamicObj, conditions[0], claimMultiplier);
|
|
2158
2390
|
for (let i = 0; i < links.length; i++) {
|
|
2159
|
-
const nextCond = evaluateDynamicCondition(dynamicObj, conditions[i + 1]);
|
|
2391
|
+
const nextCond = evaluateDynamicCondition(dynamicObj, conditions[i + 1], claimMultiplier);
|
|
2160
2392
|
const link = links[i];
|
|
2161
2393
|
if (link === 'AND') {
|
|
2162
2394
|
result = result && nextCond;
|
|
@@ -2170,12 +2402,34 @@ function meetsDynamicConditions(dynamicObj, dynamicGroup) {
|
|
|
2170
2402
|
}
|
|
2171
2403
|
return result;
|
|
2172
2404
|
}
|
|
2405
|
+
/**
|
|
2406
|
+
* Checks if a PlayerOffer meets its claimable conditions (completed -> claimable transition).
|
|
2407
|
+
* @param claimableConditions - The offer's claimableConditions (from IOffer)
|
|
2408
|
+
* @param claimableTrackers - The player offer's claimableTrackers
|
|
2409
|
+
*/
|
|
2410
|
+
function meetsClaimableConditions({ claimableConditions, playerOfferTrackers, claimableTrackers, }) {
|
|
2411
|
+
if (!claimableConditions) {
|
|
2412
|
+
return { isValid: true };
|
|
2413
|
+
}
|
|
2414
|
+
if (claimableConditions.siblingCompletions) {
|
|
2415
|
+
const siblingCount = playerOfferTrackers?.siblingPlayerOffer_ids?.length ?? 0;
|
|
2416
|
+
let completedCount = claimableTrackers?.siblingCompletions ?? 0;
|
|
2417
|
+
if (completedCount == -1)
|
|
2418
|
+
completedCount = siblingCount; // treat -1 as all completed
|
|
2419
|
+
// if siblings exist but not all are completed, return false
|
|
2420
|
+
if (siblingCount > 0 && completedCount < siblingCount) {
|
|
2421
|
+
return { isValid: false };
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
return { isValid: true };
|
|
2425
|
+
}
|
|
2173
2426
|
|
|
2174
2427
|
const offerListenerEvents = ['claim_offer'];
|
|
2175
2428
|
const PlayerOfferStatuses = [
|
|
2176
2429
|
// 'inQueue', // fuck this shit. just don't surface offers if their offer plate is full.
|
|
2177
2430
|
'surfaced',
|
|
2178
2431
|
'viewed',
|
|
2432
|
+
'completed', // Individual completionConditions met, waiting for claimableConditions (e.g., siblings)
|
|
2179
2433
|
'claimable',
|
|
2180
2434
|
'claimed',
|
|
2181
2435
|
'expired',
|
|
@@ -2224,11 +2478,13 @@ exports.PlayerOfferStatuses = PlayerOfferStatuses;
|
|
|
2224
2478
|
exports.SSEConnection = SSEConnection;
|
|
2225
2479
|
exports.hasConditions = hasConditions;
|
|
2226
2480
|
exports.meetsBaseConditions = meetsBaseConditions;
|
|
2481
|
+
exports.meetsClaimableConditions = meetsClaimableConditions;
|
|
2227
2482
|
exports.meetsCompletionConditions = meetsCompletionConditions;
|
|
2228
2483
|
exports.meetsCompletionConditionsBeforeExpiry = meetsCompletionConditionsBeforeExpiry;
|
|
2229
2484
|
exports.meetsDynamicConditions = meetsDynamicConditions;
|
|
2230
2485
|
exports.meetsSurfacingConditions = meetsSurfacingConditions;
|
|
2231
2486
|
exports.offerListenerEvents = offerListenerEvents;
|
|
2487
|
+
exports.offerMeetsCompletionConditions = offerMeetsCompletionConditions;
|
|
2232
2488
|
exports.rewardKinds = rewardKinds;
|
|
2233
2489
|
exports.rewardSchema = rewardSchema;
|
|
2234
2490
|
//# sourceMappingURL=index.js.map
|