@stackedapp/types 0.26.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/index.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ // Re-export all public types
18
+ __exportStar(require("./stacked_offer"), exports);
19
+ __exportStar(require("./stacked_reward"), exports);
20
+ __exportStar(require("./stacked_snapshot"), exports);
21
+ __exportStar(require("./stacked_user"), exports);
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,788 @@
1
+ interface Stringable {
2
+ toString(): string;
3
+ }
4
+ type IEnv = 'dev' | 'preview' | 'staging' | 'production' | 'sandbox' | 'local';
5
+
6
+ /**
7
+ * TypeScript interface for dynamicGroupSchema
8
+ */
9
+ type DynamicConditionOperator = '==' | '!=' | '>' | '>=' | '<' | '<=' | 'has' | 'not_has';
10
+ interface IDynamicCondition {
11
+ key: string;
12
+ compareTo: string | number | boolean;
13
+ operator: DynamicConditionOperator;
14
+ /** If you only want to update this tracker key every X hrs (like once per day), set here */
15
+ intervalHr?: number;
16
+ }
17
+ /**
18
+ * links[i] is the link between conditions[i] and conditions[i+1]. Can be 'AND', 'OR', or 'AND NOT'.
19
+ */
20
+ type DynamicConditionLink = 'AND' | 'OR' | 'AND NOT';
21
+ interface IDynamicGroup {
22
+ conditions: Array<IDynamicCondition>;
23
+ links?: Array<DynamicConditionLink>;
24
+ /** Display template using {keyName} syntax for referenced dynamic keys */
25
+ template?: string;
26
+ }
27
+ interface IBaseCondition {
28
+ /** how many days worth of consecutive logins are required to surface? Or complete this? If checking completion conditions, we need to subtract
29
+ * this number from IPlayerOffer.completionTrackers.currentLoginStreak to see how many consecutive logins the player has had since surfacing this offer.
30
+ */
31
+ loginStreak?: number;
32
+ levels?: Record<string, {
33
+ /** public facing display name */
34
+ name: string;
35
+ /** min level required */
36
+ min?: number;
37
+ /** max level for offer */
38
+ max?: number;
39
+ }>;
40
+ stakedTokens?: Record<string, {
41
+ /** public facing display name */
42
+ name: string;
43
+ /** min amount of tokens that need to be staked? */
44
+ min?: number;
45
+ }>;
46
+ /** if the condition revolves around having a specific amount of currency or currencies. Key is the currencyId
47
+ * and the value is the min or max amount of that currency the player must have to be eligible for this offer
48
+ */
49
+ currencies?: Record<string, {
50
+ /** public facing display name */
51
+ name: string;
52
+ /** if the player has this currency, how much do they need to have? */
53
+ min?: number;
54
+ /** if the player has this currency, how much do they need to have? */
55
+ max?: number;
56
+ /** required deposits required for this currency */
57
+ in?: number;
58
+ /** required withdrawals required for this currency */
59
+ out?: number;
60
+ }>;
61
+ /** for high-rep kind, how much rep does a player need to qualify? */
62
+ minTrustScore?: number;
63
+ maxTrustScore?: number;
64
+ /** number of days that the player has logged in-game. Not the absolute amount of time playing the game. But number of days they have logged in for.
65
+ * Can obtain this on a DAILY join_room player metric if it is set up.
66
+ */
67
+ minDaysInGame?: number;
68
+ /** achievements that this player has */
69
+ achievements?: Record<string, {
70
+ name: string;
71
+ minCount?: number;
72
+ }>;
73
+ memberships?: Record<string, {
74
+ /** public facing display name */
75
+ name: string;
76
+ /** if the player has this currency, how much do they need to have? */
77
+ minCount?: number;
78
+ /** if the player has this currency, how much do they need to have? */
79
+ maxCount?: number;
80
+ /** how long does a player have to have this membership? */
81
+ minMs?: number;
82
+ /** max duration a player can have this membership for */
83
+ maxMs?: number;
84
+ }>;
85
+ /** player must have completed these quests for this offer to surface */
86
+ quests?: Record<string, {
87
+ /** public facing display name */
88
+ name: string;
89
+ /** if a min number of quest completions is required */
90
+ completions?: number;
91
+ }>;
92
+ /**
93
+ * For player snapshot entities that are linked to other player snap entities,
94
+ * what are the min and max number of links of a specific link kind?
95
+ * Keys are link kinds (e.g., "pet", "guild", "npc", etc.)
96
+ */
97
+ links?: Record<string, {
98
+ /** minimum number of links of this type required */
99
+ min?: number;
100
+ /** maximum number of links of this type allowed */
101
+ max?: number;
102
+ }>;
103
+ /** dynamic field conditions */
104
+ dynamic?: IDynamicGroup;
105
+ /** Auth platform identifiers condition */
106
+ identifiers?: {
107
+ /** List of auth platforms to check (e.g., 'tiktok', 'google', 'email') */
108
+ platforms: string[];
109
+ /** 'AND' = player must have ALL platforms, 'OR' = player must have ANY platform */
110
+ behaviour: 'AND' | 'OR';
111
+ };
112
+ /**
113
+ * On-chain token balance conditions - check wallet balances across specified contracts/networks.
114
+ * Balances are summed across all specified contracts and all player wallets.
115
+ */
116
+ tokenBalances?: Array<{
117
+ /** Display name for this condition (e.g., "PIXEL Token") */
118
+ name: string;
119
+ /** List of contract+network pairs to check. Balances summed across all pairs. */
120
+ contracts: Array<{
121
+ /** Token contract address (lowercase) */
122
+ contractAddress: string;
123
+ /** Network identifier (e.g., 'ronin-mainnet', 'ethereum-mainnet') */
124
+ network: string;
125
+ }>;
126
+ /** Minimum total balance required */
127
+ min?: number;
128
+ /** Maximum total balance allowed */
129
+ max?: number;
130
+ }>;
131
+ }
132
+
133
+ /** conditions that must be met for an already surfaced offer to be claimable */
134
+ interface ICompletionCondition extends IBaseCondition {
135
+ /** itemId that must be bought to complete */
136
+ buyItem?: {
137
+ id: string;
138
+ name: string;
139
+ /** amount of item bought or currency spent/deposited to fufil condition */
140
+ amount?: number;
141
+ };
142
+ /** currencyId that must be spent to complete */
143
+ spendCurrency?: {
144
+ id: string;
145
+ name: string;
146
+ /** amount of item bought or currency spent/deposited to fufil condition */
147
+ amount?: number;
148
+ };
149
+ /** currencyId that must be deposited to complete */
150
+ depositCurrency?: {
151
+ id: string;
152
+ name: string;
153
+ /** amount of item bought or currency spent/deposited to fufil condition */
154
+ amount?: number;
155
+ };
156
+ /** social media content condition - player must attach content meeting requirements */
157
+ social?: {
158
+ /** 'attach' (default) = user attaches single content, 'accumulate' = auto-sum all matching content */
159
+ mode?: 'attach' | 'accumulate';
160
+ /** platforms accepted (OR logic) - e.g., ['tiktok', 'instagram', 'youtube'] */
161
+ platforms: string[];
162
+ /** words that must ALL appear in video title or description (case-insensitive). Hashtags must match exactly including # */
163
+ requiredWords: string[];
164
+ /** minimum view count required on the video (0 = no requirement) */
165
+ minViews: number;
166
+ /** minimum like count required on the video (0 = no requirement) */
167
+ minLikes: number;
168
+ /** minimum comment count required on the video (0 = no requirement) */
169
+ minComments: number;
170
+ /** content must be posted after this timestamp in milliseconds (defaults to time of offer creation) */
171
+ postedAfterMs: number;
172
+ };
173
+ login?: boolean;
174
+ /** for contexts that require completion conditions. We have a name field so that we can display a user-friendly name
175
+ * for the completion condition when it is rendered to the player. */
176
+ context?: {
177
+ id: string;
178
+ name: string;
179
+ };
180
+ /**
181
+ * Linked completions - wait for N linked entities to complete their offers
182
+ */
183
+ linkedCompletions?: {
184
+ /** Number of linked entity completions required */
185
+ min: number;
186
+ };
187
+ /**
188
+ * Dynamic field tracker - tracks changes to dynamic fields AFTER offer surfacing.
189
+ * Unlike the base `dynamic` condition which checks snapshot totals,
190
+ * this tracks per-offer values since surfacing.
191
+ */
192
+ dynamicTracker?: IDynamicGroup;
193
+ /**
194
+ * Contract interaction tracker - tracks blockchain events for currencies, NFTs, and custom events.
195
+ * Keyed by condition ID for multiple trackers.
196
+ */
197
+ contractInteractions?: Record<string, {
198
+ /** Display name for this condition (e.g., "Spend PIXEL", "Earn NFT") */
199
+ name: string;
200
+ /**
201
+ * Event type to track:
202
+ * - 'spend': Currency transferred OUT (onchain_currency_tx kind=spend)
203
+ * - 'earn': Currency transferred IN (onchain_currency_tx kind=earn)
204
+ * - 'lose': NFT transferred OUT (onchain_item_tx kind=lose)
205
+ * - 'gain': NFT transferred IN (onchain_item_tx kind=gain)
206
+ * - 'onchain_X': Custom event (e.g., 'onchain_staked')
207
+ */
208
+ event: 'spend' | 'earn' | 'lose' | 'gain' | `onchain_${string}`;
209
+ /** List of contract+network pairs to track. Events from any of these count. */
210
+ contracts: Array<{
211
+ /** Contract address (lowercase) */
212
+ contractAddress: string;
213
+ /** Network identifier (e.g., 'ronin-mainnet', 'ethereum-mainnet') */
214
+ network: string;
215
+ }>;
216
+ /** Optional: Only count transfers TO these addresses (for spend/lose). Empty = all destinations. */
217
+ destinations?: string[];
218
+ /** Optional: Only count transfers FROM these addresses (for earn/gain). Empty = all sources. */
219
+ sources?: string[];
220
+ /**
221
+ * Target amount/count:
222
+ * - For currencies (spend/earn): token amount
223
+ * - For NFTs (gain/lose): NFT count
224
+ * - For custom events: event occurrence count
225
+ */
226
+ amount: number;
227
+ }>;
228
+ }
229
+ interface ICompletionDynamicTracker {
230
+ value: string | number | boolean;
231
+ lastUpdated?: number;
232
+ }
233
+ /** tracking the player's status for completing the conditions to claim the offer, if
234
+ * required.
235
+ */
236
+ interface ICompletionTrackers {
237
+ /** how many counts of the required item has been bought. itemId and amount required defined in completionConditions */
238
+ buyItem?: number;
239
+ /** how many counts of the required currency has been spent. currencyId and amount required defined in completionConditions */
240
+ spendCurrency?: number;
241
+ /** how many counts of the required currency has been deposited. currencyId and amount required defined in completionConditions */
242
+ depositCurrency?: number;
243
+ /** the number of days that have been consecutively logged in at the time of surfacing this offer */
244
+ currentLoginStreak?: number;
245
+ /** tracks attached social media content and cached validation state */
246
+ social?: ISocialTrackerAttach | ISocialTrackerAccumulate;
247
+ /** completed context for this player */
248
+ context?: string;
249
+ /**
250
+ * Count of linked entities that have completed their offers.
251
+ * For example a referrer refers 5 other referees that have all
252
+ * completed the referral criteria. this would then be a value of 5.
253
+ */
254
+ linkedCompletions?: number;
255
+ /**
256
+ * Tracked dynamic field values per key.
257
+ * Key = dynamic field key, Value = tracked value since offer surfaced
258
+ */
259
+ dynamicTracker?: Record<string, ICompletionDynamicTracker>;
260
+ /**
261
+ * Tracked contract interaction values per condition ID.
262
+ * Key = condition ID, Value = accumulated amount/count since offer surfaced
263
+ * - For spend/earn: token amount
264
+ * - For gain/lose: NFT count
265
+ * - For custom onchain events: event occurrence count
266
+ */
267
+ contractInteractions?: Record<string, number>;
268
+ }
269
+ /** Social tracker for attach mode - user manually attaches a single piece of content */
270
+ interface ISocialTrackerAttach {
271
+ /** platform name - tiktok, instagram, or youtube */
272
+ platform: string;
273
+ /** video ID on the platform */
274
+ videoId: string;
275
+ /** platform specific user ID */
276
+ userId: string;
277
+ /** cached video title for display */
278
+ title?: string;
279
+ /** cached view count */
280
+ views: number;
281
+ /** cached like count */
282
+ likes?: number;
283
+ /** cached comment count */
284
+ comments?: number;
285
+ /** timestamp of last validation check (for 5-minute cache) */
286
+ lastChecked: Date;
287
+ }
288
+ /** Social tracker for accumulate mode - auto-sum all matching content */
289
+ interface ISocialTrackerAccumulate {
290
+ /** sum of view counts across all matching content */
291
+ views: number;
292
+ /** sum of like counts across all matching content */
293
+ likes?: number;
294
+ /** sum of comment counts across all matching content */
295
+ comments?: number;
296
+ /** timestamp of last validation check (for 5-minute cache) */
297
+ lastChecked: Date;
298
+ /** number of matching content items */
299
+ matchCount: number;
300
+ }
301
+
302
+ interface ISurfacingCondition extends IBaseCondition {
303
+ /** number of days that the player has logged in-game. Not the absolute amount of time playing the game. But number of days they have logged in for.
304
+ * Can obtain this on a DAILY join_room player metric if it is set up.
305
+ */
306
+ maxDaysInGame?: number;
307
+ /** player must have all tags specified to be eligible */
308
+ andTags?: Array<string>;
309
+ /** player can have any of these tags to be eligible */
310
+ orTags?: Array<string>;
311
+ /** player cannot have any of these tags */
312
+ notTags?: Array<string>;
313
+ /** minimum signup date (timestamp) - player must have signed up after this date */
314
+ minDateSignedUp?: number;
315
+ /** maximum signup date (timestamp) - player must have signed up before this date */
316
+ maxDateSignedUp?: number;
317
+ /** player must have completed ALL of these offers (AND logic) - array of offer IDs */
318
+ completedOffers?: Array<string>;
319
+ /** surfacing contexts */
320
+ contexts?: Array<string>;
321
+ /** if true, offer can only be surfaced programmatically (e.g., via spawnLinkedOffer) - never through normal auto-surfacing */
322
+ programmatic?: boolean;
323
+ /** entity types that this offer can surface to - undefined or empty means only regular players. */
324
+ targetEntityTypes?: string[];
325
+ /** List of allowed country codes (whitelist mode) */
326
+ allowedCountries?: Array<string>;
327
+ /** List of restricted country codes (blacklist mode) - mutually exclusive with allowedCountries */
328
+ restrictedCountries?: Array<string>;
329
+ /** List of blocked network types - VPN/TOR/PROXY/RELAY */
330
+ networkRestrictions?: Array<'vpn' | 'tor' | 'proxy' | 'relay'>;
331
+ /** Check if any linked entity (by kind) has a specific offer in surfaced/viewed status. */
332
+ linkedEntityOffers?: {
333
+ /** The entityLink kind to filter (e.g., "parent", "guild", "pet") */
334
+ kind?: string;
335
+ /** The offer_id to check for on linked entities */
336
+ offer_id?: string;
337
+ conditions?: ISurfacingCondition;
338
+ };
339
+ }
340
+
341
+ interface IReward {
342
+ /** reward kind */
343
+ kind: RewardKind;
344
+ /** if the trigger is type daily-login, which day is this reward for? */
345
+ /** reward id for rewards of kind item, coins, exp or loyalty currency */
346
+ rewardId?: string;
347
+ /** amount of reward to give. If kind is discount, then this refers to the discount amount, as a fraction */
348
+ amount: number;
349
+ /** public facing name for this reward */
350
+ name: string;
351
+ /** unique image url for this reward */
352
+ image?: string;
353
+ }
354
+ type RewardKind = 'item' | 'coins' | 'exp' | 'trust_points' | 'loyalty_currency' | 'discount';
355
+
356
+ /** onSurface and onComplete hooks for offers. Should support:
357
+ * - Spawning linked offers for referral/referee type offers
358
+ * - Creating cross-game offers
359
+ * - Surfacing offers to other linked player snapshots
360
+ *
361
+ * When an offer is SURFACED to a player, we are able to:
362
+ * - surface a linked offer in same game to self
363
+ * - surface a linked offer in same game to other players
364
+ * - surface a linked offer in different game to other players
365
+ *
366
+ * When an offer is COMPLETED by a player, we are able to:
367
+ * - increment linked tracker of any player offer
368
+ * - surface a linked offer in same game to self
369
+ * - surface a linked offer in same game to other players
370
+ * - surface a linked offer in different game to other players
371
+ */
372
+ interface IOfferHookEvent {
373
+ _id?: undefined | false | null;
374
+ kind: 'spawn_linked_offer' | 'increment_linked_tracker' | 'increment_completed_siblings' | 'attach_player_entity_link_offers';
375
+ /** player snapshot entity kind that we are spawning the linked offer for */
376
+ target?: 'self' | string;
377
+ }
378
+ type OfferListenerEvent = 'claim_offer';
379
+ interface IOfferListener extends IOfferHookEvent {
380
+ event: OfferListenerEvent;
381
+ }
382
+ interface IOffer {
383
+ _id: Stringable;
384
+ /** for grouping offers together if they are assocaited with each othert for easy rendering in front-end */
385
+ groupId?: string;
386
+ name: string;
387
+ /** how high of a priority is this offer in comparison to other offers? 1 is highest priority */
388
+ priority?: number;
389
+ description: string;
390
+ createdAt?: Date;
391
+ /** notes about this offer */
392
+ notes?: string;
393
+ /** if this should be an offer that gets surfaced in real time */
394
+ realTime?: string;
395
+ /** if this offer is tied to other offers for a/b testing, this is the ID that ties
396
+ * all of the offers together
397
+ */
398
+ campaign?: string;
399
+ image?: string;
400
+ /** game that this offer corresponds to */
401
+ gameId: string;
402
+ /** start date of surfacing this offer to players */
403
+ startDate?: Date;
404
+ /** end date of surfacing this offer to players */
405
+ endDate?: Date;
406
+ /** if fully archived, the offer will be deleted from memory and not be surfaced anymore to ANYONE. Current players with the offer will no longer see it */
407
+ archived?: boolean;
408
+ /** is this offer disabled right now? */
409
+ disabled?: boolean;
410
+ /** labels for UI stuff */
411
+ labels?: Array<string>;
412
+ /** how long after completing this offer can a player get the same offer again? If undefined, this offer only shows up once EVER for the player */
413
+ cooldownMs?: number;
414
+ /** how long after a player receives this offer is it available for, before ing? */
415
+ expiryMs?: number;
416
+ /** @description DO NOT USE! */
417
+ surfacingContexts?: string[];
418
+ /** conditions that all must be fulfilled if this offer is to be surfaced to a player */
419
+ surfacingConditions: ISurfacingCondition;
420
+ /** triggers that allow this offer to be claimable. Leave empty if merely surfacing the offer should allow claiming. */
421
+ completionConditions?: ICompletionCondition;
422
+ /**
423
+ * Conditions for transitioning from 'completed' to 'claimable' status.
424
+ * If absent, offers go directly to claimable when completionConditions are met.
425
+ */
426
+ claimableConditions?: {
427
+ /** number of sibling offers that must be completed to make this offer claimable (-1 means all siblings) */
428
+ siblingCompletions?: number;
429
+ };
430
+ /** what does completing this offer reward the player? */
431
+ rewards: Array<IReward>;
432
+ /** if you can claim rewards multiple times, what is the max claim count you can claim? */
433
+ maxClaimCount?: number;
434
+ /**
435
+ * Linked offer configuration - specifies which offer to spawn when this offer surfaces or completes
436
+ */
437
+ linkedOffer?: {
438
+ /** Specific offer _id to spawn */
439
+ _id?: Stringable;
440
+ /** OR use campaign for A/B test selection via GrowthBook.
441
+ *
442
+ * !!!THIS IS NOT SUPPORTED FOR CROSS-GAME OFFERS!!!
443
+ */
444
+ campaign?: string;
445
+ };
446
+ /**
447
+ * Actions to perform when this offer is surfaced to a player/entity
448
+ */
449
+ onSurface?: Array<IOfferHookEvent>;
450
+ /**
451
+ * Actions to perform when ANY instance of this offer is completed
452
+ * Gets copied to PlayerOffer and populated with specific IDs
453
+ */
454
+ onComplete?: Array<IOfferHookEvent>;
455
+ /** */
456
+ eventListeners?: Array<IOfferListener>;
457
+ invitedGameId?: string;
458
+ }
459
+
460
+ type PlayerOfferStatus = 'surfaced' | 'viewed' | 'completed' | 'claimable' | 'claimed' | 'expired';
461
+ interface IPlayerOfferTrackers {
462
+ /********************** These are not really completion trackers, but just regular trackers **********************/
463
+ /** player snapshot _id of the person who surfaced this offer */
464
+ surfacer_id?: Stringable;
465
+ /** playerId of the person who surfaced this player offer */
466
+ surfacerPlayerId?: string;
467
+ /** gameId of the game who surfaced this player offer */
468
+ surfacerGameId?: string;
469
+ /** the referral code used to activate the .linkedOffer from the offer. */
470
+ referralCode?: string;
471
+ /** number of linked player offers spawned off this one. */
472
+ linkedCount?: number;
473
+ /** if this playerOffer was created due to someone else's offer hook event, what was their player offer _id? */
474
+ linkedPlayerOffer_ids?: Array<Stringable>;
475
+ /**
476
+ * IDs of sibling PlayerOffers created together (e.g., both parents from a pet offer).
477
+ * Used to check if all siblings completed before any can claim.
478
+ * If any sibling expires, all siblings should expire (strict co-op).
479
+ */
480
+ siblingPlayerOffer_ids?: Array<Stringable>;
481
+ /**
482
+ * Tracks how many times a linked offer has been surfaced (for cross-game offers)
483
+ * Used to prevent duplicate surfacing when games are linked/unlinked/relinked
484
+ */
485
+ surfacedCount?: number;
486
+ claimedCount?: number;
487
+ }
488
+ interface IPlayerOffer {
489
+ _id: Stringable;
490
+ /** starts at 1. multiples of the cooldown timeframe if there is a cooldown. there is a unique index on this and
491
+ * it ensures that we do not get multiple offers created for the same offer in the same cooldown timeframe if someone
492
+ * tries to abuse and slam the fetchPlayerOffers endpoint
493
+ */
494
+ cooldownTimeframeMultiple?: number;
495
+ /** offer id */
496
+ offer_id: Stringable | IOffer;
497
+ /** is this an offer that should be surfaced AND PUSHED in real-time to a player? */
498
+ realTime?: boolean;
499
+ claimedAt?: Date;
500
+ rewards: Array<IReward>;
501
+ /** tracking the player's status for completing the conditions to claim the offer, if
502
+ * required.
503
+ */
504
+ completionTrackers?: ICompletionTrackers;
505
+ trackers?: IPlayerOfferTrackers;
506
+ /**
507
+ * Trackers for claimableConditions (completed → claimable transition).
508
+ */
509
+ claimableTrackers?: {
510
+ /** Counter incremented when a sibling offer completes */
511
+ siblingCompletions?: number;
512
+ };
513
+ /** status of the offer
514
+ * - surfaced: offer is now surfaced to the player, but not viewed by the player yet.
515
+ * - viewed: offer has been viewed by the player, but not yet completed. The expiration time only starts when the player views the offer
516
+ * - completed: individual completionConditions met, waiting for claimableConditions (e.g., all siblings must complete)
517
+ * - claimable: offer has been completed and is now claimable by the player
518
+ * - claimed: offer has been completed and the player has claimed the rewards.
519
+ * - expired: offer has expired and is no longer available to the player
520
+ */
521
+ status: PlayerOfferStatus;
522
+ /** player _id */
523
+ playerId: string;
524
+ /** game _id */
525
+ gameId: string;
526
+ /** created at date */
527
+ createdAt: Date;
528
+ /** expiry date */
529
+ expiresAt?: Date;
530
+ /**
531
+ * Actions to perform when this PlayerOffer is completed
532
+ */
533
+ onComplete?: Array<IOfferHookEvent>;
534
+ }
535
+
536
+ type CoinSyncStatusType = "pending" | "success" | "error" | "exclusive" | "synced" | null;
537
+ interface IBlockchainSyncStatus {
538
+ ts: number | null;
539
+ status: CoinSyncStatusType;
540
+ waiting?: boolean;
541
+ error?: string;
542
+ }
543
+
544
+ interface AppConnection {
545
+ gameId: string;
546
+ playerId: string;
547
+ linkedAt: Date;
548
+ }
549
+ type Restriction = {
550
+ kind?: 'block' | 'restrict';
551
+ start?: number;
552
+ until?: number;
553
+ eventCount: number;
554
+ message?: string;
555
+ };
556
+ interface IUser {
557
+ _id: Stringable;
558
+ deleted?: boolean;
559
+ authId: string;
560
+ lastLoggedIn?: number;
561
+ appConnections?: Array<AppConnection>;
562
+ notifications?: {
563
+ /** last push notification sent */
564
+ lastPushed?: number;
565
+ /** last push notification that was deep linked into a specific offer or campaign */
566
+ lastPushedOffer?: number;
567
+ /** last push generic notification to come back into the app */
568
+ lastPushedGeneric?: number;
569
+ };
570
+ cryptoWallets?: Array<{
571
+ address: string;
572
+ createdAt: number;
573
+ }>;
574
+ rewardWallets?: Array<{
575
+ address: string;
576
+ createdAt: number;
577
+ networks: Array<string>;
578
+ }>;
579
+ providers?: Array<{
580
+ provider: string;
581
+ accessToken: string;
582
+ refreshToken?: string;
583
+ expiresAt?: Date;
584
+ refreshTokenExpiresAt?: Date;
585
+ scope?: string;
586
+ tokenType?: string;
587
+ providerUserId?: string;
588
+ updatedAt: Date;
589
+ displayName?: string;
590
+ username?: string;
591
+ }>;
592
+ restriction?: Restriction;
593
+ }
594
+
595
+ interface IPlayerSnapshot {
596
+ _id: string;
597
+ gameId: string;
598
+ playerId: string;
599
+ /** when this was last linked to a unified user */
600
+ unifiedUserLinkedAt?: number;
601
+ /** when this was last unlinked to a unified user */
602
+ unifiedUserUnlinkedAt?: number;
603
+ /** player that referred this player to this game */
604
+ referredById?: string;
605
+ username?: string;
606
+ snapshotLastUpdated: number;
607
+ offersLastChecked?: number;
608
+ daysInGame: number;
609
+ loginStreak: number;
610
+ daysInGameLastUpdated: number;
611
+ /** timestamp when player first signed up */
612
+ dateSignedUp?: number;
613
+ /** dynamic key value pairs that external apps can set to anything */
614
+ dynamic?: Record<string, string | number | boolean>;
615
+ /** staked tokens data for this player */
616
+ stakedTokens?: Record<
617
+ /** the key is the token name. Eg. PIXEL or RON */
618
+ string, {
619
+ balance?: number;
620
+ totalClaimed?: number;
621
+ totalStaked?: number;
622
+ lastStake?: number;
623
+ lastUnstake?: number;
624
+ }>;
625
+ currencies?: Record<string, {
626
+ balance?: number;
627
+ in?: number;
628
+ out?: number;
629
+ lastUpdated?: number;
630
+ }>;
631
+ levels?: Record<string, {
632
+ level?: number;
633
+ lastUpdated?: number;
634
+ }>;
635
+ quests?: Record<string, {
636
+ completions?: number;
637
+ lastUpdated?: number;
638
+ }>;
639
+ trustScore?: number;
640
+ trustLastUpdated?: number;
641
+ achievements?: Record<string, {
642
+ count?: number;
643
+ lastUpdated: number;
644
+ }>;
645
+ memberships?: Record<string, {
646
+ count?: number;
647
+ expiresAt?: number;
648
+ lastUpdated?: number;
649
+ }>;
650
+ /** auth identifiers. like social tiktok or email or sms or whatever */
651
+ identifiers?: Array<{
652
+ /** id */
653
+ identifier: string;
654
+ /** sms/email/tiktok etc */
655
+ platform: string;
656
+ lastUpdated: number;
657
+ }>;
658
+ cryptoWallets?: Array<{
659
+ address: string;
660
+ lastUpdated?: number;
661
+ }>;
662
+ tags?: Array<string>;
663
+ tagsLastUpdated?: number;
664
+ entityKind?: string;
665
+ /**
666
+ * Links to other entities (players, pets, guilds, etc.)
667
+ */
668
+ entityLinks?: Array<{
669
+ /** if undefined, it means same game */
670
+ gameId?: string;
671
+ playerId: string;
672
+ kind?: string;
673
+ }>;
674
+ /**
675
+ * collection of information about IP address player connected from
676
+ */
677
+ ip?: IPlayerIpInfo;
678
+ }
679
+ interface IPlayerDataCurrency {
680
+ /**
681
+ * balance held
682
+ */
683
+ balance: number;
684
+ /**
685
+ * total amount deposited
686
+ */
687
+ in?: number;
688
+ /**
689
+ * total amount withdrawn
690
+ */
691
+ out?: number;
692
+ /**
693
+ * last updated timestamp
694
+ * if not present, means never updated
695
+ */
696
+ lastUpdated?: number;
697
+ }
698
+ interface IPlayerData {
699
+ _id?: Stringable;
700
+ gameId: string;
701
+ playerId: string;
702
+ currencies?: Record<string, IPlayerDataCurrency>;
703
+ blockchainSync?: Record<string, IBlockchainSyncStatus>;
704
+ restriction?: Restriction;
705
+ }
706
+ interface IPlayerIpInfo {
707
+ lastUpdated: Date;
708
+ ipAddress: string;
709
+ vpn?: boolean;
710
+ proxy?: boolean;
711
+ tor?: boolean;
712
+ relay?: boolean;
713
+ timeZone: string;
714
+ regionCode?: string;
715
+ countryCode?: string;
716
+ continentCode?: string;
717
+ }
718
+
719
+ /** this is the Stacked platforms' copy of a user/entity's snapshot for the current app/game */
720
+ type StackedSnapshot = Pick<IPlayerSnapshot, '_id' | 'gameId' | 'playerId' | 'unifiedUserLinkedAt' | 'unifiedUserUnlinkedAt' | 'referredById' | 'username' | 'snapshotLastUpdated' | 'offersLastChecked' | 'daysInGame' | 'loginStreak' | 'daysInGameLastUpdated' | 'dateSignedUp' | 'dynamic' | 'stakedTokens' | 'currencies' | 'levels' | 'quests' | 'trustScore' | 'trustLastUpdated' | 'achievements' | 'memberships' | 'entityKind' | 'entityLinks'>;
721
+ /** represents a stacked snapshot's data within a specific app/game. This is scoped to the app/game! */
722
+ type StackedSnapshotData = Pick<IPlayerData, 'gameId' | 'playerId' | 'currencies'>;
723
+
724
+ type StackedCompletionConditions = ICompletionCondition;
725
+ /** Stripped sibling offer data for client rendering */
726
+ interface StackedSnapshotProgress {
727
+ status: UserOfferStatus;
728
+ completionTrackers?: ICompletionTrackers;
729
+ completionConditions?: ICompletionCondition;
730
+ snapshot?: StackedSnapshot;
731
+ }
732
+ /** offer fields that define this offer's properties */
733
+ interface StackedBaseOffer extends Pick<IOffer, OfferPickFields> {
734
+ /** Siblings */
735
+ siblings?: StackedSnapshotProgress[];
736
+ /** Progress data for linked entity's offer (e.g., pet's progress on their task) */
737
+ linked?: StackedSnapshotProgress[];
738
+ completionConditions: StackedCompletionConditions;
739
+ }
740
+ type OfferPickFields = 'groupId' | 'name' | 'description' | 'image' | 'gameId' | 'archived' | 'disabled' | 'labels' | 'claimableConditions' | 'rewards' | 'maxClaimCount';
741
+ /** user-specific offer fields for this specific user's offer progress */
742
+ interface StackedUserOffer extends Pick<IPlayerOffer, UserOfferPickFields> {
743
+ }
744
+ type UserOfferPickFields = 'claimedAt' | 'rewards' | 'completionTrackers' | 'trackers' | 'claimableTrackers' | 'status' | 'playerId' | 'gameId' | 'createdAt' | 'expiresAt' | 'onComplete';
745
+ type UserOfferStatus = PlayerOfferStatus;
746
+ /** complete offer object that defines the offer and user's progress within the offer. The
747
+ * rewards field is taken from the UserOffer rather than the BaseOffer
748
+ */
749
+ type StackedOffer = StackedBaseOffer & StackedUserOffer & {
750
+ /** offer id */
751
+ offerId: string;
752
+ /** user offer id */
753
+ instanceId: string;
754
+ };
755
+
756
+ type StackedRewardKind = RewardKind;
757
+ interface StackedReward extends IReward {
758
+ image?: string;
759
+ }
760
+
761
+ /** represents a stacked consumer from the web/mobile app */
762
+ type StackedBaseUser = Pick<IUser, 'appConnections' | 'providers'>;
763
+ /** extra data populated into a stacked web/mobile app user */
764
+ interface StackedBaseUserExtra {
765
+ cryptoWallets?: Array<{
766
+ address: string;
767
+ balances: Record<string, number>;
768
+ }>;
769
+ }
770
+ type StackedUser = StackedBaseUser & StackedBaseUserExtra & {
771
+ apps: {
772
+ [appId: string]: StackedSnapshotData;
773
+ };
774
+ };
775
+ /** @deprecated - old way of getting a specific game player's currencies in stacked */
776
+ type IClientPlayerData = StackedSnapshotData;
777
+ type StackedUserResponse = {
778
+ /** @deprecated use stacked instead */
779
+ data?: IClientPlayerData | null;
780
+ /** this is the current user's snapshot within this app/game */
781
+ snapshot: StackedSnapshot;
782
+ /** this current user's stacked web/mobile app profile */
783
+ user: StackedUser;
784
+ };
785
+
786
+ type StackedEnv = IEnv;
787
+
788
+ export type { IClientPlayerData, StackedBaseOffer, StackedBaseUser, StackedBaseUserExtra, StackedCompletionConditions, StackedEnv, StackedOffer, StackedReward, StackedRewardKind, StackedSnapshot, StackedSnapshotData, StackedSnapshotProgress, StackedUser, StackedUserOffer, StackedUserResponse, UserOfferStatus };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@stackedapp/types",
3
+ "version": "0.26.0",
4
+ "description": "Public types for Stacked platform SDK",
5
+ "main": "dist/index.js",
6
+ "types": "dist/stacked-types.d.ts",
7
+ "files": [
8
+ "dist/index.js",
9
+ "dist/stacked-types.d.ts"
10
+ ],
11
+ "private": false,
12
+ "scripts": {
13
+ "build": "tsc && api-extractor run -l && rollup -c rollup.config.mjs",
14
+ "build:tsc": "tsc",
15
+ "build:bundle-types": "rollup -c rollup.config.mjs",
16
+ "clean": "rimraf dist"
17
+ },
18
+ "devDependencies": {
19
+ "@stackedplatform/types-internal": "*",
20
+ "@microsoft/api-extractor": "^7.55.2",
21
+ "typescript": "^5.8.2"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "license": "ISC"
27
+ }