@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
|
@@ -5,6 +5,7 @@ import { OfferwallConfig } from '../types/index';
|
|
|
5
5
|
import { IClientOffer } from '../types/offer';
|
|
6
6
|
import { ConnectionState } from '../types/connection';
|
|
7
7
|
import { IClientPlayer } from '../types/player';
|
|
8
|
+
import { StackedLinkResult } from '../types/linking';
|
|
8
9
|
export declare const mapEnvToOfferClientUrl: (env: "test" | "live" | (string & {})) => "https://offers.pixels.xyz" | "https://offers.sandbox.pixels.xyz" | "https://offers.staging.pixels.xyz" | "https://offers.preview.pixels.xyz" | "https://offers.dev.pixels.xyz";
|
|
9
10
|
export declare class OfferwallClient {
|
|
10
11
|
private config;
|
|
@@ -17,7 +18,9 @@ export declare class OfferwallClient {
|
|
|
17
18
|
private hooks;
|
|
18
19
|
private logger;
|
|
19
20
|
private isInitializing;
|
|
21
|
+
private pendingStackedToken;
|
|
20
22
|
constructor(config: OfferwallConfig);
|
|
23
|
+
private setupStackedLinkDetection;
|
|
21
24
|
/**
|
|
22
25
|
* Get the offer store instance
|
|
23
26
|
*/
|
|
@@ -67,5 +70,30 @@ export declare class OfferwallClient {
|
|
|
67
70
|
getAuthLinkToken(): Promise<string>;
|
|
68
71
|
getGameId(): string | null;
|
|
69
72
|
getDashboardRedirectUrl(): Promise<string | null>;
|
|
73
|
+
/**
|
|
74
|
+
* Detect if there's a Stacked link token in the current URL
|
|
75
|
+
* @returns The token string if found, null otherwise
|
|
76
|
+
*/
|
|
77
|
+
detectStackedLinkToken(): string | null;
|
|
78
|
+
/**
|
|
79
|
+
* Clear the stackedToken from the URL without page reload
|
|
80
|
+
*/
|
|
81
|
+
private clearStackedTokenFromUrl;
|
|
82
|
+
/**
|
|
83
|
+
* Consume a Stacked link token to link the current game player
|
|
84
|
+
* to a Stacked unified user account.
|
|
85
|
+
*
|
|
86
|
+
* IMPORTANT: The player must be authenticated (have a valid JWT from tokenProvider)
|
|
87
|
+
* before calling this method.
|
|
88
|
+
*
|
|
89
|
+
* @param token The Stacked link token from the URL
|
|
90
|
+
* @returns Promise resolving to the link result
|
|
91
|
+
*/
|
|
92
|
+
consumeStackedLinkToken(token: string): Promise<StackedLinkResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Detects the token from URL, consumes it if player is authenticated.
|
|
95
|
+
* @returns Promise resolving to the link result, or null if no token found
|
|
96
|
+
*/
|
|
97
|
+
processStackedLinkToken(): Promise<StackedLinkResult | null>;
|
|
70
98
|
private handleError;
|
|
71
99
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.esm.js
CHANGED
|
@@ -890,6 +890,7 @@ const mapEnvToOfferClientUrl = (env) => {
|
|
|
890
890
|
class OfferwallClient {
|
|
891
891
|
constructor(config) {
|
|
892
892
|
this.isInitializing = false;
|
|
893
|
+
this.pendingStackedToken = null;
|
|
893
894
|
this.config = {
|
|
894
895
|
autoConnect: config.autoConnect ?? false,
|
|
895
896
|
reconnect: config.reconnect ?? true,
|
|
@@ -907,12 +908,24 @@ class OfferwallClient {
|
|
|
907
908
|
this.assetHelper = new AssetHelper(this.config);
|
|
908
909
|
this.sseConnection = new SSEConnection(this.config, this.eventEmitter, this.tokenManager);
|
|
909
910
|
this.setupInternalListeners();
|
|
911
|
+
this.setupStackedLinkDetection();
|
|
910
912
|
if (this.config.autoConnect) {
|
|
911
913
|
this.initialize().catch((err) => {
|
|
912
914
|
this.logger.error('Auto-initialization failed:', err);
|
|
913
915
|
});
|
|
914
916
|
}
|
|
915
917
|
}
|
|
918
|
+
setupStackedLinkDetection() {
|
|
919
|
+
if (!this.config.stackedLink?.autoConsume) {
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const token = this.detectStackedLinkToken();
|
|
923
|
+
if (token) {
|
|
924
|
+
this.logger.log('Detected stackedToken in URL');
|
|
925
|
+
this.pendingStackedToken = token;
|
|
926
|
+
this.clearStackedTokenFromUrl();
|
|
927
|
+
}
|
|
928
|
+
}
|
|
916
929
|
/**
|
|
917
930
|
* Get the offer store instance
|
|
918
931
|
*/
|
|
@@ -943,9 +956,19 @@ class OfferwallClient {
|
|
|
943
956
|
this.isInitializing = true;
|
|
944
957
|
await this.refreshOffersAndPlayer();
|
|
945
958
|
await this.connect();
|
|
959
|
+
// Process pending Stacked link token if exists and autoConsume is enabled
|
|
960
|
+
if (this.pendingStackedToken && this.config.stackedLink?.autoConsume) {
|
|
961
|
+
this.logger.log('Processing pending Stacked link token');
|
|
962
|
+
const result = await this.consumeStackedLinkToken(this.pendingStackedToken);
|
|
963
|
+
this.pendingStackedToken = null;
|
|
964
|
+
if (this.config.stackedLink?.onComplete) {
|
|
965
|
+
this.config.stackedLink.onComplete(result);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
946
968
|
this.isInitializing = false;
|
|
947
969
|
}
|
|
948
970
|
catch (error) {
|
|
971
|
+
this.isInitializing = false;
|
|
949
972
|
this.handleError(error, 'initialize');
|
|
950
973
|
throw error;
|
|
951
974
|
}
|
|
@@ -1161,6 +1184,91 @@ class OfferwallClient {
|
|
|
1161
1184
|
return null;
|
|
1162
1185
|
return `${dashboardBaseUrl}/auth/enter?token=${token}&gameId=${gameId}`;
|
|
1163
1186
|
}
|
|
1187
|
+
// ==================== Stacked Link Methods ====================
|
|
1188
|
+
/**
|
|
1189
|
+
* Detect if there's a Stacked link token in the current URL
|
|
1190
|
+
* @returns The token string if found, null otherwise
|
|
1191
|
+
*/
|
|
1192
|
+
detectStackedLinkToken() {
|
|
1193
|
+
if (typeof window === 'undefined')
|
|
1194
|
+
return null;
|
|
1195
|
+
try {
|
|
1196
|
+
const params = new URLSearchParams(window.location.search);
|
|
1197
|
+
const token = params.get('stackedToken');
|
|
1198
|
+
if (token && token.length > 0) {
|
|
1199
|
+
return token;
|
|
1200
|
+
}
|
|
1201
|
+
// for SPA routers that use hash routing
|
|
1202
|
+
if (window.location.hash) {
|
|
1203
|
+
const hashQuery = window.location.hash.split('?')[1];
|
|
1204
|
+
if (hashQuery) {
|
|
1205
|
+
const hashParams = new URLSearchParams(hashQuery);
|
|
1206
|
+
const hashToken = hashParams.get('stackedToken');
|
|
1207
|
+
if (hashToken && hashToken.length > 0) {
|
|
1208
|
+
return hashToken;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
catch (error) {
|
|
1215
|
+
this.logger.error('Error detecting stacked token:', error);
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Clear the stackedToken from the URL without page reload
|
|
1221
|
+
*/
|
|
1222
|
+
clearStackedTokenFromUrl() {
|
|
1223
|
+
if (typeof window === 'undefined')
|
|
1224
|
+
return;
|
|
1225
|
+
try {
|
|
1226
|
+
const url = new URL(window.location.href);
|
|
1227
|
+
url.searchParams.delete('stackedToken');
|
|
1228
|
+
window.history.replaceState({}, '', url.toString());
|
|
1229
|
+
this.logger.log('Cleared stackedToken from URL');
|
|
1230
|
+
}
|
|
1231
|
+
catch (error) {
|
|
1232
|
+
this.logger.error('Error clearing stacked token from URL:', error);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Consume a Stacked link token to link the current game player
|
|
1237
|
+
* to a Stacked unified user account.
|
|
1238
|
+
*
|
|
1239
|
+
* IMPORTANT: The player must be authenticated (have a valid JWT from tokenProvider)
|
|
1240
|
+
* before calling this method.
|
|
1241
|
+
*
|
|
1242
|
+
* @param token The Stacked link token from the URL
|
|
1243
|
+
* @returns Promise resolving to the link result
|
|
1244
|
+
*/
|
|
1245
|
+
async consumeStackedLinkToken(token) {
|
|
1246
|
+
try {
|
|
1247
|
+
const data = await this.postWithAuth('/v1/auth/stacked_link/exchange', { stackedToken: token });
|
|
1248
|
+
return {
|
|
1249
|
+
linked: data.linked,
|
|
1250
|
+
alreadyLinked: data.alreadyLinked,
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
catch (error) {
|
|
1254
|
+
this.logger.error('Error consuming stacked link token:', error);
|
|
1255
|
+
return {
|
|
1256
|
+
linked: false,
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Detects the token from URL, consumes it if player is authenticated.
|
|
1262
|
+
* @returns Promise resolving to the link result, or null if no token found
|
|
1263
|
+
*/
|
|
1264
|
+
async processStackedLinkToken() {
|
|
1265
|
+
const token = this.detectStackedLinkToken();
|
|
1266
|
+
if (!token) {
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
this.clearStackedTokenFromUrl();
|
|
1270
|
+
return this.consumeStackedLinkToken(token);
|
|
1271
|
+
}
|
|
1164
1272
|
handleError(error, context) {
|
|
1165
1273
|
this.logger.error(`Error in ${context}:`, error);
|
|
1166
1274
|
if (this.hooks.onError) {
|
|
@@ -1170,19 +1278,47 @@ class OfferwallClient {
|
|
|
1170
1278
|
}
|
|
1171
1279
|
|
|
1172
1280
|
const keyPattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
1173
|
-
|
|
1281
|
+
/**
|
|
1282
|
+
* This replaces {keyName} keys from the template with corresponding values from the dynamic object.
|
|
1283
|
+
*/
|
|
1174
1284
|
function renderTemplate(template, dynamic) {
|
|
1175
1285
|
if (!template)
|
|
1176
1286
|
return '';
|
|
1177
|
-
return template.replace(keyPattern, (
|
|
1287
|
+
return template.replace(keyPattern, (_match, key) => {
|
|
1288
|
+
if (dynamic && typeof dynamic[key] === 'boolean') {
|
|
1289
|
+
return dynamic[key] ? '✓' : '✗';
|
|
1290
|
+
}
|
|
1178
1291
|
if (dynamic && dynamic[key] !== undefined) {
|
|
1179
1292
|
return String(dynamic[key]);
|
|
1180
1293
|
}
|
|
1181
1294
|
return '{?}'; // indicate missing key
|
|
1182
1295
|
});
|
|
1183
1296
|
}
|
|
1297
|
+
/**
|
|
1298
|
+
* This replaces {{keyName}} in dynamic condition keys with corresponding values from
|
|
1299
|
+
* the PlayerOffer.trackers
|
|
1300
|
+
*
|
|
1301
|
+
* eg. a condition high_score_pet-{{surfacerPlayerId}} with high_score_pet-12345
|
|
1302
|
+
*/
|
|
1303
|
+
function replaceDynamicConditionKey(key, trackers) {
|
|
1304
|
+
return key?.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (match, p1) => {
|
|
1305
|
+
const value = trackers[p1];
|
|
1306
|
+
return value !== undefined ? String(value) : match;
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
/** this replaces all of the dynamic conditions.keys by calling replaceDynamicConditionKey */
|
|
1310
|
+
function replaceDynamicConditionKeys(conditions, trackers) {
|
|
1311
|
+
return conditions.map((condition) => ({
|
|
1312
|
+
...condition,
|
|
1313
|
+
key: replaceDynamicConditionKey(condition.key, trackers),
|
|
1314
|
+
}));
|
|
1315
|
+
}
|
|
1184
1316
|
|
|
1185
|
-
const meetsBaseConditions = ({ conditions, playerSnap, addDetails,
|
|
1317
|
+
const meetsBaseConditions = ({ conditions, playerSnap, addDetails,
|
|
1318
|
+
/** this exists if calling meetsBaseConditions from meetsCompletionConditions. but surfacing
|
|
1319
|
+
* check doesn't use this since we don't have a playerOffer at surfacing time
|
|
1320
|
+
*/
|
|
1321
|
+
playerOffer, }) => {
|
|
1186
1322
|
const conditionData = [];
|
|
1187
1323
|
let isValid = true;
|
|
1188
1324
|
if (conditions?.minDaysInGame) {
|
|
@@ -1508,7 +1644,7 @@ const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
|
|
|
1508
1644
|
}
|
|
1509
1645
|
}
|
|
1510
1646
|
// Validate link count conditions
|
|
1511
|
-
if (conditions?.links) {
|
|
1647
|
+
if (conditions?.links && 'entityLinks' in playerSnap) {
|
|
1512
1648
|
for (const [linkType, constraint] of Object.entries(conditions.links)) {
|
|
1513
1649
|
const linkCount = playerSnap.entityLinks?.filter((link) => link.kind === linkType).length || 0;
|
|
1514
1650
|
if (constraint.min !== undefined) {
|
|
@@ -1549,7 +1685,11 @@ const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
|
|
|
1549
1685
|
}
|
|
1550
1686
|
// Evaluate dynamic conditions
|
|
1551
1687
|
if (conditions?.dynamic?.conditions?.length) {
|
|
1552
|
-
const
|
|
1688
|
+
const resolvedConditions = replaceDynamicConditionKeys(conditions.dynamic.conditions, playerOffer?.trackers || {});
|
|
1689
|
+
const dynamicResult = meetsDynamicConditions(playerSnap.dynamic, {
|
|
1690
|
+
...conditions.dynamic,
|
|
1691
|
+
conditions: resolvedConditions,
|
|
1692
|
+
});
|
|
1553
1693
|
if (addDetails) {
|
|
1554
1694
|
conditionData.push({
|
|
1555
1695
|
isMet: dynamicResult,
|
|
@@ -1565,7 +1705,7 @@ const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
|
|
|
1565
1705
|
return { isValid: false };
|
|
1566
1706
|
}
|
|
1567
1707
|
}
|
|
1568
|
-
if (conditions?.identifiers?.platforms?.length) {
|
|
1708
|
+
if (conditions?.identifiers?.platforms?.length && 'identifiers' in playerSnap) {
|
|
1569
1709
|
const playerPlatforms = new Set(playerSnap.identifiers?.map((i) => i.platform.toLowerCase()) || []);
|
|
1570
1710
|
const isAndBehaviour = conditions.identifiers.behaviour === 'AND';
|
|
1571
1711
|
const platformsToCheck = conditions.identifiers.platforms;
|
|
@@ -1602,7 +1742,7 @@ const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, pl
|
|
|
1602
1742
|
return { isValid: false };
|
|
1603
1743
|
}
|
|
1604
1744
|
if (surfacingConditions?.targetEntityTypes?.length) {
|
|
1605
|
-
const playerTarget = playerSnap.
|
|
1745
|
+
const playerTarget = playerSnap.entityKind || 'default';
|
|
1606
1746
|
// check if entity type is allowed
|
|
1607
1747
|
if (!surfacingConditions.targetEntityTypes.includes(playerTarget)) {
|
|
1608
1748
|
return { isValid: false };
|
|
@@ -1658,6 +1798,31 @@ const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, pl
|
|
|
1658
1798
|
return { isValid: false };
|
|
1659
1799
|
}
|
|
1660
1800
|
}
|
|
1801
|
+
if (conditions.allowedCountries?.length) {
|
|
1802
|
+
const playerCountry = playerSnap.ip?.countryCode;
|
|
1803
|
+
if (!playerCountry || !conditions.allowedCountries.includes(playerCountry)) {
|
|
1804
|
+
return { isValid: false };
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
if (conditions.restrictedCountries?.length) {
|
|
1808
|
+
const playerCountry = playerSnap.ip?.countryCode;
|
|
1809
|
+
if (!playerCountry) {
|
|
1810
|
+
return { isValid: false };
|
|
1811
|
+
}
|
|
1812
|
+
if (conditions.restrictedCountries.includes(playerCountry)) {
|
|
1813
|
+
return { isValid: false };
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
if (conditions.networkRestrictions?.length) {
|
|
1817
|
+
if (!playerSnap.ip) {
|
|
1818
|
+
return { isValid: false };
|
|
1819
|
+
}
|
|
1820
|
+
for (const restriction of conditions.networkRestrictions) {
|
|
1821
|
+
if (playerSnap.ip[restriction]) {
|
|
1822
|
+
return { isValid: false };
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1661
1826
|
return meetsBaseConditions({ conditions, playerSnap });
|
|
1662
1827
|
};
|
|
1663
1828
|
const hasConditions = (conditions) => {
|
|
@@ -1710,6 +1875,12 @@ const hasConditions = (conditions) => {
|
|
|
1710
1875
|
return true;
|
|
1711
1876
|
if (surCond.links && Object.keys(surCond.links).length > 0)
|
|
1712
1877
|
return true;
|
|
1878
|
+
if (surCond.allowedCountries?.length)
|
|
1879
|
+
return true;
|
|
1880
|
+
if (surCond.restrictedCountries?.length)
|
|
1881
|
+
return true;
|
|
1882
|
+
if (surCond.networkRestrictions?.length)
|
|
1883
|
+
return true;
|
|
1713
1884
|
const compCond = conditions;
|
|
1714
1885
|
if (compCond.context)
|
|
1715
1886
|
return true;
|
|
@@ -1727,11 +1898,25 @@ const hasConditions = (conditions) => {
|
|
|
1727
1898
|
return true;
|
|
1728
1899
|
if (compCond.linkedCompletions)
|
|
1729
1900
|
return true;
|
|
1901
|
+
if (compCond.dynamicTracker?.conditions?.length)
|
|
1902
|
+
return true;
|
|
1730
1903
|
return false;
|
|
1731
1904
|
};
|
|
1732
|
-
const
|
|
1905
|
+
const offerMeetsCompletionConditions = (offer, snapshot) => {
|
|
1906
|
+
return meetsCompletionConditions({
|
|
1907
|
+
completionConditions: offer.completionConditions || {},
|
|
1908
|
+
completionTrackers: offer.completionTrackers,
|
|
1909
|
+
playerSnap: snapshot,
|
|
1910
|
+
playerOffer: offer,
|
|
1911
|
+
addDetails: true,
|
|
1912
|
+
});
|
|
1913
|
+
};
|
|
1914
|
+
const meetsCompletionConditions = ({ completionConditions, completionTrackers, playerSnap, playerOffer, addDetails = false, maxClaimCount, }) => {
|
|
1733
1915
|
if (completionConditions) {
|
|
1734
1916
|
const conditions = completionConditions;
|
|
1917
|
+
// For multi-claim offers, scale cumulative requirements by (claimedCount + 1)
|
|
1918
|
+
const shouldScale = maxClaimCount === -1 || (maxClaimCount && maxClaimCount > 1);
|
|
1919
|
+
const claimMultiplier = shouldScale ? (playerOffer.claimedCount || 0) + 1 : 1;
|
|
1735
1920
|
const conditionData = [];
|
|
1736
1921
|
let isValid = true;
|
|
1737
1922
|
if (completionConditions?.context?.id) {
|
|
@@ -1754,13 +1939,14 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1754
1939
|
}
|
|
1755
1940
|
}
|
|
1756
1941
|
if (conditions?.buyItem) {
|
|
1757
|
-
const
|
|
1942
|
+
const scaledAmount = (conditions.buyItem.amount || 1) * claimMultiplier;
|
|
1943
|
+
const isDisqualify = (completionTrackers?.buyItem || 0) < scaledAmount;
|
|
1758
1944
|
if (addDetails) {
|
|
1759
1945
|
conditionData.push({
|
|
1760
1946
|
isMet: !isDisqualify,
|
|
1761
1947
|
kind: 'buyItem',
|
|
1762
1948
|
trackerAmount: completionTrackers?.buyItem || 0,
|
|
1763
|
-
text: `Buy ${
|
|
1949
|
+
text: `Buy ${scaledAmount} ${conditions.buyItem.name}`,
|
|
1764
1950
|
});
|
|
1765
1951
|
if (isDisqualify)
|
|
1766
1952
|
isValid = false;
|
|
@@ -1771,13 +1957,14 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1771
1957
|
}
|
|
1772
1958
|
}
|
|
1773
1959
|
if (conditions?.spendCurrency) {
|
|
1774
|
-
const
|
|
1960
|
+
const scaledAmount = (conditions.spendCurrency.amount || 1) * claimMultiplier;
|
|
1961
|
+
const isDisqualify = (completionTrackers?.spendCurrency || 0) < scaledAmount;
|
|
1775
1962
|
if (addDetails) {
|
|
1776
1963
|
conditionData.push({
|
|
1777
1964
|
isMet: !isDisqualify,
|
|
1778
1965
|
kind: 'spendCurrency',
|
|
1779
1966
|
trackerAmount: completionTrackers?.spendCurrency || 0,
|
|
1780
|
-
text: `Spend ${
|
|
1967
|
+
text: `Spend ${scaledAmount} ${conditions.spendCurrency.name}`,
|
|
1781
1968
|
});
|
|
1782
1969
|
if (isDisqualify)
|
|
1783
1970
|
isValid = false;
|
|
@@ -1788,15 +1975,17 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1788
1975
|
}
|
|
1789
1976
|
}
|
|
1790
1977
|
if (conditions?.depositCurrency) {
|
|
1791
|
-
const
|
|
1792
|
-
|
|
1978
|
+
const scaledAmount = (conditions.depositCurrency.amount || 1) * claimMultiplier;
|
|
1979
|
+
const isDisqualify = (completionTrackers?.depositCurrency || 0) < scaledAmount;
|
|
1793
1980
|
if (addDetails) {
|
|
1794
1981
|
conditionData.push({
|
|
1795
1982
|
isMet: !isDisqualify,
|
|
1796
1983
|
kind: 'depositCurrency',
|
|
1797
1984
|
trackerAmount: completionTrackers?.depositCurrency || 0,
|
|
1798
|
-
text: `Deposit ${
|
|
1985
|
+
text: `Deposit ${scaledAmount} ${conditions.depositCurrency.name}`,
|
|
1799
1986
|
});
|
|
1987
|
+
if (isDisqualify)
|
|
1988
|
+
isValid = false;
|
|
1800
1989
|
}
|
|
1801
1990
|
else {
|
|
1802
1991
|
if (isDisqualify)
|
|
@@ -1804,12 +1993,13 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1804
1993
|
}
|
|
1805
1994
|
}
|
|
1806
1995
|
if (conditions?.login) {
|
|
1807
|
-
const isMet =
|
|
1996
|
+
const isMet = new Date(playerSnap.snapshotLastUpdated || 0).getTime() >
|
|
1997
|
+
new Date(playerOffer.createdAt || 0).getTime();
|
|
1808
1998
|
if (addDetails) {
|
|
1809
1999
|
conditionData.push({
|
|
1810
2000
|
isMet,
|
|
1811
2001
|
kind: 'login',
|
|
1812
|
-
trackerAmount:
|
|
2002
|
+
trackerAmount: isMet ? 1 : 0,
|
|
1813
2003
|
text: `Login to the game`,
|
|
1814
2004
|
});
|
|
1815
2005
|
isValid = isMet;
|
|
@@ -1846,9 +2036,11 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1846
2036
|
const hasContent = Boolean(mode === 'accumulate'
|
|
1847
2037
|
? tSocial?.mode === 'accumulate'
|
|
1848
2038
|
: tSocial && tSocial.mode !== 'accumulate' && !!tSocial.videoId);
|
|
1849
|
-
|
|
1850
|
-
const
|
|
1851
|
-
const
|
|
2039
|
+
// Only scale social metrics in accumulate mode (attach mode is single content)
|
|
2040
|
+
const socialMultiplier = mode === 'accumulate' ? claimMultiplier : 1;
|
|
2041
|
+
const minLikes = (cSocial?.minLikes || 0) * socialMultiplier;
|
|
2042
|
+
const minViews = (cSocial?.minViews || 0) * socialMultiplier;
|
|
2043
|
+
const minComments = (cSocial?.minComments || 0) * socialMultiplier;
|
|
1852
2044
|
const likes = tSocial?.likes || 0;
|
|
1853
2045
|
const views = tSocial?.views || 0;
|
|
1854
2046
|
const comments = tSocial?.comments || 0;
|
|
@@ -1933,14 +2125,14 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1933
2125
|
// Linked completions - wait for N linked entities to complete
|
|
1934
2126
|
if (conditions?.linkedCompletions?.min) {
|
|
1935
2127
|
const currentCount = completionTrackers?.linkedCompletions || 0;
|
|
1936
|
-
const
|
|
1937
|
-
const isDisqualify = currentCount <
|
|
2128
|
+
const scaledMin = conditions.linkedCompletions.min * claimMultiplier;
|
|
2129
|
+
const isDisqualify = currentCount < scaledMin;
|
|
1938
2130
|
if (addDetails) {
|
|
1939
2131
|
conditionData.push({
|
|
1940
2132
|
isMet: !isDisqualify,
|
|
1941
2133
|
kind: 'linkedCompletions',
|
|
1942
2134
|
trackerAmount: currentCount,
|
|
1943
|
-
text: `Wait for ${
|
|
2135
|
+
text: `Wait for ${scaledMin} linked ${scaledMin === 1 ? 'entity' : 'entities'} to complete`,
|
|
1944
2136
|
});
|
|
1945
2137
|
if (isDisqualify)
|
|
1946
2138
|
isValid = false;
|
|
@@ -1950,10 +2142,32 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1950
2142
|
return { isValid: false };
|
|
1951
2143
|
}
|
|
1952
2144
|
}
|
|
2145
|
+
if (conditions?.dynamicTracker?.conditions?.length) {
|
|
2146
|
+
const resolvedConditions = replaceDynamicConditionKeys(conditions.dynamicTracker.conditions, playerOffer?.trackers || {});
|
|
2147
|
+
// now we have the game-defined conditions with {{}} keys populated. feed these conditions into evaluator
|
|
2148
|
+
const dynamicResult = meetsDynamicConditions(completionTrackers?.dynamicTracker, {
|
|
2149
|
+
...conditions.dynamicTracker,
|
|
2150
|
+
conditions: resolvedConditions,
|
|
2151
|
+
}, claimMultiplier);
|
|
2152
|
+
if (addDetails) {
|
|
2153
|
+
conditionData.push({
|
|
2154
|
+
isMet: dynamicResult,
|
|
2155
|
+
kind: 'dynamic',
|
|
2156
|
+
text: renderTemplate(conditions.dynamicTracker.template, completionTrackers?.dynamicTracker) || 'Dynamic conditions',
|
|
2157
|
+
});
|
|
2158
|
+
if (!dynamicResult)
|
|
2159
|
+
isValid = false;
|
|
2160
|
+
}
|
|
2161
|
+
else {
|
|
2162
|
+
if (!dynamicResult)
|
|
2163
|
+
return { isValid: false };
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
1953
2166
|
const r = meetsBaseConditions({
|
|
1954
2167
|
conditions,
|
|
1955
2168
|
playerSnap,
|
|
1956
2169
|
addDetails: true,
|
|
2170
|
+
playerOffer,
|
|
1957
2171
|
});
|
|
1958
2172
|
isValid = isValid && r.isValid;
|
|
1959
2173
|
conditionData.push(...(r.conditionData || []));
|
|
@@ -1968,10 +2182,9 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1968
2182
|
* @param completionConditions - The completion conditions to check
|
|
1969
2183
|
* @param completionTrackers - The completion trackers (for buyItem, spendCurrency, etc.)
|
|
1970
2184
|
* @param playerSnap - The player snapshot with field timestamps
|
|
1971
|
-
* @param expiryTime - The expiry timestamp in milliseconds
|
|
1972
2185
|
* @returns true if all conditions were met before expiry, false otherwise
|
|
1973
2186
|
*/
|
|
1974
|
-
const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completionTrackers, playerSnap,
|
|
2187
|
+
const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completionTrackers, playerSnap, playerOffer, maxClaimCount, }) => {
|
|
1975
2188
|
if (!completionConditions)
|
|
1976
2189
|
return false;
|
|
1977
2190
|
// Check if there are actually any conditions to evaluate
|
|
@@ -1981,10 +2194,15 @@ const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completio
|
|
|
1981
2194
|
const conditionsMet = meetsCompletionConditions({
|
|
1982
2195
|
completionConditions,
|
|
1983
2196
|
completionTrackers,
|
|
2197
|
+
playerOffer,
|
|
1984
2198
|
playerSnap,
|
|
2199
|
+
maxClaimCount,
|
|
1985
2200
|
});
|
|
1986
2201
|
if (!conditionsMet.isValid)
|
|
1987
2202
|
return false;
|
|
2203
|
+
if (!playerOffer.expiresAt)
|
|
2204
|
+
return true;
|
|
2205
|
+
const expiryTime = new Date(playerOffer.expiresAt).getTime();
|
|
1988
2206
|
const lastSnapshotUpdate = new Date(playerSnap.snapshotLastUpdated).getTime();
|
|
1989
2207
|
/**
|
|
1990
2208
|
* Checks if a field was updated after the expiry time.
|
|
@@ -2095,66 +2313,80 @@ const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completio
|
|
|
2095
2313
|
* Checks if a dynamic object meets a set of dynamic field conditions.
|
|
2096
2314
|
* @param dynamicObj - The object with any key and string or number value.
|
|
2097
2315
|
* @param conditions - Array of conditions to check.
|
|
2316
|
+
* @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
|
|
2098
2317
|
* @returns true if all conditions are met, false otherwise.
|
|
2099
2318
|
*/
|
|
2100
2319
|
/**
|
|
2101
2320
|
* Evaluates a single dynamic condition against the dynamic object.
|
|
2102
2321
|
*/
|
|
2103
|
-
function evaluateDynamicCondition(dynamicObj, cond) {
|
|
2322
|
+
function evaluateDynamicCondition(dynamicObj, cond, claimMultiplier = 1) {
|
|
2323
|
+
if (!dynamicObj)
|
|
2324
|
+
return false;
|
|
2104
2325
|
const val = dynamicObj[cond.key];
|
|
2105
2326
|
if (val === undefined)
|
|
2106
2327
|
return false;
|
|
2328
|
+
const isNumber = typeof val === 'number';
|
|
2329
|
+
const isBoolean = typeof val === 'boolean';
|
|
2330
|
+
if (isBoolean) {
|
|
2331
|
+
switch (cond.operator) {
|
|
2332
|
+
case '==':
|
|
2333
|
+
return val === Boolean(cond.compareTo);
|
|
2334
|
+
case '!=':
|
|
2335
|
+
return val !== Boolean(cond.compareTo);
|
|
2336
|
+
default:
|
|
2337
|
+
return false;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
const compareTo = isNumber ? Number(cond.compareTo) : String(cond.compareTo);
|
|
2107
2341
|
switch (cond.operator) {
|
|
2108
2342
|
case '==':
|
|
2109
|
-
return val ===
|
|
2343
|
+
return val === compareTo;
|
|
2110
2344
|
case '!=':
|
|
2111
|
-
return val !==
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
case '<=':
|
|
2125
|
-
return (typeof val === 'number' &&
|
|
2126
|
-
typeof cond.compareTo === 'number' &&
|
|
2127
|
-
val <= cond.compareTo);
|
|
2128
|
-
case 'has':
|
|
2129
|
-
return (typeof val === 'string' &&
|
|
2130
|
-
typeof cond.compareTo === 'string' &&
|
|
2131
|
-
val.includes(cond.compareTo));
|
|
2132
|
-
case 'not_has':
|
|
2133
|
-
return (typeof val === 'string' &&
|
|
2134
|
-
typeof cond.compareTo === 'string' &&
|
|
2135
|
-
!val.includes(cond.compareTo));
|
|
2136
|
-
default:
|
|
2137
|
-
return false;
|
|
2345
|
+
return val !== compareTo;
|
|
2346
|
+
}
|
|
2347
|
+
if (isNumber && typeof compareTo === 'number') {
|
|
2348
|
+
switch (cond.operator) {
|
|
2349
|
+
case '>':
|
|
2350
|
+
return val > compareTo * claimMultiplier;
|
|
2351
|
+
case '>=':
|
|
2352
|
+
return val >= compareTo * claimMultiplier;
|
|
2353
|
+
case '<':
|
|
2354
|
+
return val < compareTo * claimMultiplier;
|
|
2355
|
+
case '<=':
|
|
2356
|
+
return val <= compareTo * claimMultiplier;
|
|
2357
|
+
}
|
|
2138
2358
|
}
|
|
2359
|
+
else if (!isNumber && typeof compareTo === 'string') {
|
|
2360
|
+
switch (cond.operator) {
|
|
2361
|
+
case 'has':
|
|
2362
|
+
return val.includes(compareTo);
|
|
2363
|
+
case 'not_has':
|
|
2364
|
+
return !val.includes(compareTo);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
return false;
|
|
2139
2368
|
}
|
|
2140
2369
|
/**
|
|
2141
2370
|
* Evaluates a group of dynamic conditions with logical links (AND, OR, AND NOT).
|
|
2142
2371
|
* @param dynamicObj - The player's dynamic object with any key and string or number value.
|
|
2143
2372
|
* @param dynamicGroup - The group of conditions and links to check.
|
|
2373
|
+
* @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
|
|
2144
2374
|
* @returns true if the group evaluates to true, false otherwise.
|
|
2145
2375
|
*/
|
|
2146
|
-
function meetsDynamicConditions(dynamicObj, dynamicGroup) {
|
|
2376
|
+
function meetsDynamicConditions(dynamicObj, dynamicGroup, claimMultiplier = 1) {
|
|
2147
2377
|
const { conditions, links } = dynamicGroup;
|
|
2148
2378
|
if (!conditions || conditions.length === 0)
|
|
2149
2379
|
return true;
|
|
2380
|
+
if (!dynamicObj)
|
|
2381
|
+
return false;
|
|
2150
2382
|
// If no links, treat as AND between all conditions
|
|
2151
2383
|
if (!links || links.length === 0) {
|
|
2152
|
-
return conditions.every((cond) => evaluateDynamicCondition(dynamicObj, cond));
|
|
2384
|
+
return conditions.every((cond) => evaluateDynamicCondition(dynamicObj, cond, claimMultiplier));
|
|
2153
2385
|
}
|
|
2154
2386
|
// Evaluate the first condition
|
|
2155
|
-
let result = evaluateDynamicCondition(dynamicObj, conditions[0]);
|
|
2387
|
+
let result = evaluateDynamicCondition(dynamicObj, conditions[0], claimMultiplier);
|
|
2156
2388
|
for (let i = 0; i < links.length; i++) {
|
|
2157
|
-
const nextCond = evaluateDynamicCondition(dynamicObj, conditions[i + 1]);
|
|
2389
|
+
const nextCond = evaluateDynamicCondition(dynamicObj, conditions[i + 1], claimMultiplier);
|
|
2158
2390
|
const link = links[i];
|
|
2159
2391
|
if (link === 'AND') {
|
|
2160
2392
|
result = result && nextCond;
|
|
@@ -2168,12 +2400,34 @@ function meetsDynamicConditions(dynamicObj, dynamicGroup) {
|
|
|
2168
2400
|
}
|
|
2169
2401
|
return result;
|
|
2170
2402
|
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Checks if a PlayerOffer meets its claimable conditions (completed -> claimable transition).
|
|
2405
|
+
* @param claimableConditions - The offer's claimableConditions (from IOffer)
|
|
2406
|
+
* @param claimableTrackers - The player offer's claimableTrackers
|
|
2407
|
+
*/
|
|
2408
|
+
function meetsClaimableConditions({ claimableConditions, playerOfferTrackers, claimableTrackers, }) {
|
|
2409
|
+
if (!claimableConditions) {
|
|
2410
|
+
return { isValid: true };
|
|
2411
|
+
}
|
|
2412
|
+
if (claimableConditions.siblingCompletions) {
|
|
2413
|
+
const siblingCount = playerOfferTrackers?.siblingPlayerOffer_ids?.length ?? 0;
|
|
2414
|
+
let completedCount = claimableTrackers?.siblingCompletions ?? 0;
|
|
2415
|
+
if (completedCount == -1)
|
|
2416
|
+
completedCount = siblingCount; // treat -1 as all completed
|
|
2417
|
+
// if siblings exist but not all are completed, return false
|
|
2418
|
+
if (siblingCount > 0 && completedCount < siblingCount) {
|
|
2419
|
+
return { isValid: false };
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
return { isValid: true };
|
|
2423
|
+
}
|
|
2171
2424
|
|
|
2172
2425
|
const offerListenerEvents = ['claim_offer'];
|
|
2173
2426
|
const PlayerOfferStatuses = [
|
|
2174
2427
|
// 'inQueue', // fuck this shit. just don't surface offers if their offer plate is full.
|
|
2175
2428
|
'surfaced',
|
|
2176
2429
|
'viewed',
|
|
2430
|
+
'completed', // Individual completionConditions met, waiting for claimableConditions (e.g., siblings)
|
|
2177
2431
|
'claimable',
|
|
2178
2432
|
'claimed',
|
|
2179
2433
|
'expired',
|
|
@@ -2214,5 +2468,5 @@ const rewardSchema = {
|
|
|
2214
2468
|
image: String,
|
|
2215
2469
|
};
|
|
2216
2470
|
|
|
2217
|
-
export { AssetHelper, ConnectionState, EventEmitter, OfferEvent, OfferStore, OfferwallClient, PlayerOfferStatuses, SSEConnection, hasConditions, meetsBaseConditions, meetsCompletionConditions, meetsCompletionConditionsBeforeExpiry, meetsDynamicConditions, meetsSurfacingConditions, offerListenerEvents, rewardKinds, rewardSchema };
|
|
2471
|
+
export { AssetHelper, ConnectionState, EventEmitter, OfferEvent, OfferStore, OfferwallClient, PlayerOfferStatuses, SSEConnection, hasConditions, meetsBaseConditions, meetsClaimableConditions, meetsCompletionConditions, meetsCompletionConditionsBeforeExpiry, meetsDynamicConditions, meetsSurfacingConditions, offerListenerEvents, offerMeetsCompletionConditions, rewardKinds, rewardSchema };
|
|
2218
2472
|
//# sourceMappingURL=index.esm.js.map
|