@pixels-online/pixels-client-js-sdk 1.15.0 → 1.16.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 +177 -32
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +177 -31
- package/dist/index.js.map +1 -1
- package/dist/offerwall-sdk.umd.js +177 -31
- package/dist/offerwall-sdk.umd.js.map +1 -1
- package/dist/types/blockchain/user_wallet.d.ts +20 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/linking.d.ts +17 -0
- package/dist/types/offer.d.ts +47 -11
- package/dist/types/player.d.ts +13 -5
- package/dist/utils/conditions.d.ts +15 -3
- 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) {
|
|
@@ -1508,7 +1616,7 @@ const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
|
|
|
1508
1616
|
}
|
|
1509
1617
|
}
|
|
1510
1618
|
// Validate link count conditions
|
|
1511
|
-
if (conditions?.links) {
|
|
1619
|
+
if (conditions?.links && 'entityLinks' in playerSnap) {
|
|
1512
1620
|
for (const [linkType, constraint] of Object.entries(conditions.links)) {
|
|
1513
1621
|
const linkCount = playerSnap.entityLinks?.filter((link) => link.kind === linkType).length || 0;
|
|
1514
1622
|
if (constraint.min !== undefined) {
|
|
@@ -1565,7 +1673,7 @@ const meetsBaseConditions = ({ conditions, playerSnap, addDetails, }) => {
|
|
|
1565
1673
|
return { isValid: false };
|
|
1566
1674
|
}
|
|
1567
1675
|
}
|
|
1568
|
-
if (conditions?.identifiers?.platforms?.length) {
|
|
1676
|
+
if (conditions?.identifiers?.platforms?.length && 'identifiers' in playerSnap) {
|
|
1569
1677
|
const playerPlatforms = new Set(playerSnap.identifiers?.map((i) => i.platform.toLowerCase()) || []);
|
|
1570
1678
|
const isAndBehaviour = conditions.identifiers.behaviour === 'AND';
|
|
1571
1679
|
const platformsToCheck = conditions.identifiers.platforms;
|
|
@@ -1602,7 +1710,7 @@ const meetsSurfacingConditions = ({ surfacingConditions, playerSnap, context, pl
|
|
|
1602
1710
|
return { isValid: false };
|
|
1603
1711
|
}
|
|
1604
1712
|
if (surfacingConditions?.targetEntityTypes?.length) {
|
|
1605
|
-
const playerTarget = playerSnap.
|
|
1713
|
+
const playerTarget = playerSnap.entityKind || 'default';
|
|
1606
1714
|
// check if entity type is allowed
|
|
1607
1715
|
if (!surfacingConditions.targetEntityTypes.includes(playerTarget)) {
|
|
1608
1716
|
return { isValid: false };
|
|
@@ -1727,6 +1835,8 @@ const hasConditions = (conditions) => {
|
|
|
1727
1835
|
return true;
|
|
1728
1836
|
if (compCond.linkedCompletions)
|
|
1729
1837
|
return true;
|
|
1838
|
+
if (compCond.dynamicTracker?.conditions?.length)
|
|
1839
|
+
return true;
|
|
1730
1840
|
return false;
|
|
1731
1841
|
};
|
|
1732
1842
|
const meetsCompletionConditions = ({ completionConditions, completionTrackers, playerSnap, addDetails = false, }) => {
|
|
@@ -1950,6 +2060,22 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
|
|
|
1950
2060
|
return { isValid: false };
|
|
1951
2061
|
}
|
|
1952
2062
|
}
|
|
2063
|
+
if (conditions?.dynamicTracker?.conditions?.length) {
|
|
2064
|
+
const dynamicResult = meetsDynamicConditions(completionTrackers?.dynamicTracker || {}, conditions.dynamicTracker);
|
|
2065
|
+
if (addDetails) {
|
|
2066
|
+
conditionData.push({
|
|
2067
|
+
isMet: dynamicResult,
|
|
2068
|
+
kind: 'dynamic',
|
|
2069
|
+
text: renderTemplate(conditions.dynamicTracker.template, completionTrackers?.dynamicTracker) || 'Dynamic conditions',
|
|
2070
|
+
});
|
|
2071
|
+
if (!dynamicResult)
|
|
2072
|
+
isValid = false;
|
|
2073
|
+
}
|
|
2074
|
+
else {
|
|
2075
|
+
if (!dynamicResult)
|
|
2076
|
+
return { isValid: false };
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
1953
2079
|
const r = meetsBaseConditions({
|
|
1954
2080
|
conditions,
|
|
1955
2081
|
playerSnap,
|
|
@@ -2104,38 +2230,35 @@ function evaluateDynamicCondition(dynamicObj, cond) {
|
|
|
2104
2230
|
const val = dynamicObj[cond.key];
|
|
2105
2231
|
if (val === undefined)
|
|
2106
2232
|
return false;
|
|
2233
|
+
const isNumber = typeof val === 'number';
|
|
2234
|
+
const compareTo = isNumber ? Number(cond.compareTo) : String(cond.compareTo);
|
|
2107
2235
|
switch (cond.operator) {
|
|
2108
2236
|
case '==':
|
|
2109
|
-
return val ===
|
|
2237
|
+
return val === compareTo;
|
|
2110
2238
|
case '!=':
|
|
2111
|
-
return val !==
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
case 'not_has':
|
|
2133
|
-
return (typeof val === 'string' &&
|
|
2134
|
-
typeof cond.compareTo === 'string' &&
|
|
2135
|
-
!val.includes(cond.compareTo));
|
|
2136
|
-
default:
|
|
2137
|
-
return false;
|
|
2239
|
+
return val !== compareTo;
|
|
2240
|
+
}
|
|
2241
|
+
if (isNumber && typeof compareTo === 'number') {
|
|
2242
|
+
switch (cond.operator) {
|
|
2243
|
+
case '>':
|
|
2244
|
+
return val > compareTo;
|
|
2245
|
+
case '>=':
|
|
2246
|
+
return val >= compareTo;
|
|
2247
|
+
case '<':
|
|
2248
|
+
return val < compareTo;
|
|
2249
|
+
case '<=':
|
|
2250
|
+
return val <= compareTo;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
else if (!isNumber && typeof compareTo === 'string') {
|
|
2254
|
+
switch (cond.operator) {
|
|
2255
|
+
case 'has':
|
|
2256
|
+
return val.includes(compareTo);
|
|
2257
|
+
case 'not_has':
|
|
2258
|
+
return !val.includes(compareTo);
|
|
2259
|
+
}
|
|
2138
2260
|
}
|
|
2261
|
+
return false;
|
|
2139
2262
|
}
|
|
2140
2263
|
/**
|
|
2141
2264
|
* Evaluates a group of dynamic conditions with logical links (AND, OR, AND NOT).
|
|
@@ -2168,12 +2291,34 @@ function meetsDynamicConditions(dynamicObj, dynamicGroup) {
|
|
|
2168
2291
|
}
|
|
2169
2292
|
return result;
|
|
2170
2293
|
}
|
|
2294
|
+
/**
|
|
2295
|
+
* Checks if a PlayerOffer meets its claimable conditions (completed -> claimable transition).
|
|
2296
|
+
* @param claimableConditions - The offer's claimableConditions (from IOffer)
|
|
2297
|
+
* @param claimableTrackers - The player offer's claimableTrackers
|
|
2298
|
+
*/
|
|
2299
|
+
function meetsClaimableConditions({ claimableConditions, playerOfferTrackers, claimableTrackers, }) {
|
|
2300
|
+
if (!claimableConditions) {
|
|
2301
|
+
return { isValid: true };
|
|
2302
|
+
}
|
|
2303
|
+
if (claimableConditions.siblingCompletions) {
|
|
2304
|
+
const siblingCount = playerOfferTrackers?.siblingPlayerOffer_ids?.length ?? 0;
|
|
2305
|
+
let completedCount = claimableTrackers?.siblingCompletions ?? 0;
|
|
2306
|
+
if (completedCount == -1)
|
|
2307
|
+
completedCount = siblingCount; // treat -1 as all completed
|
|
2308
|
+
// if siblings exist but not all are completed, return false
|
|
2309
|
+
if (siblingCount > 0 && completedCount < siblingCount) {
|
|
2310
|
+
return { isValid: false };
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
return { isValid: true };
|
|
2314
|
+
}
|
|
2171
2315
|
|
|
2172
2316
|
const offerListenerEvents = ['claim_offer'];
|
|
2173
2317
|
const PlayerOfferStatuses = [
|
|
2174
2318
|
// 'inQueue', // fuck this shit. just don't surface offers if their offer plate is full.
|
|
2175
2319
|
'surfaced',
|
|
2176
2320
|
'viewed',
|
|
2321
|
+
'completed', // Individual completionConditions met, waiting for claimableConditions (e.g., siblings)
|
|
2177
2322
|
'claimable',
|
|
2178
2323
|
'claimed',
|
|
2179
2324
|
'expired',
|
|
@@ -2214,5 +2359,5 @@ const rewardSchema = {
|
|
|
2214
2359
|
image: String,
|
|
2215
2360
|
};
|
|
2216
2361
|
|
|
2217
|
-
export { AssetHelper, ConnectionState, EventEmitter, OfferEvent, OfferStore, OfferwallClient, PlayerOfferStatuses, SSEConnection, hasConditions, meetsBaseConditions, meetsCompletionConditions, meetsCompletionConditionsBeforeExpiry, meetsDynamicConditions, meetsSurfacingConditions, offerListenerEvents, rewardKinds, rewardSchema };
|
|
2362
|
+
export { AssetHelper, ConnectionState, EventEmitter, OfferEvent, OfferStore, OfferwallClient, PlayerOfferStatuses, SSEConnection, hasConditions, meetsBaseConditions, meetsClaimableConditions, meetsCompletionConditions, meetsCompletionConditionsBeforeExpiry, meetsDynamicConditions, meetsSurfacingConditions, offerListenerEvents, rewardKinds, rewardSchema };
|
|
2218
2363
|
//# sourceMappingURL=index.esm.js.map
|