@clocktone/game-sdk 1.0.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/README.md +402 -0
- package/android/build.gradle +69 -0
- package/android/src/main/kotlin/com/playloop/plugins/applovinmax/PlayLoopAppLovinMaxPlugin.kt +316 -0
- package/android/src/main/kotlin/com/playloop/plugins/deviceid/PlayLoopDeviceIdPlugin.kt +30 -0
- package/dist/loader/cjs/index.js +72 -0
- package/dist/loader/esm/index.js +69 -0
- package/dist/types/CashableSDK.d.ts +46 -0
- package/dist/types/adapters/CapacitorAdsAdapter.d.ts +25 -0
- package/dist/types/adapters/WebViewAdsAdapter.d.ts +13 -0
- package/dist/types/babylon/BabylonPlugin.d.ts +17 -0
- package/dist/types/babylon/index.d.ts +17 -0
- package/dist/types/index.d.ts +481 -0
- package/dist/types/loader/index.d.ts +300 -0
- package/dist/types/modules/AnalyticsModule.d.ts +17 -0
- package/dist/types/modules/EmbeddedAdsModule.d.ts +30 -0
- package/dist/types/modules/FeatureFlagModule.d.ts +36 -0
- package/dist/types/modules/RewardsModule.d.ts +19 -0
- package/dist/types/modules/SessionModule.d.ts +16 -0
- package/dist/types/modules/StandaloneAdsModule.d.ts +51 -0
- package/dist/types/transport/HttpTransport.d.ts +19 -0
- package/dist/types/transport/WebViewTransport.d.ts +17 -0
- package/dist/types/types.d.ts +147 -0
- package/dist/types/ui/UIModule.d.ts +64 -0
- package/docs/ads.md +210 -0
- package/docs/analytics.md +45 -0
- package/docs/babylon.md +88 -0
- package/docs/feature-flags.md +109 -0
- package/docs/game-integration-guide.md +449 -0
- package/docs/loader.md +113 -0
- package/docs/rewards.md +57 -0
- package/docs/session.md +43 -0
- package/docs/ui.md +248 -0
- package/docs/wire-protocol.md +194 -0
- package/package.json +81 -0
package/docs/ui.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# UI Module
|
|
2
|
+
|
|
3
|
+
Standalone-mode UI overlay: top bar, sponsored interstitials, bonus/booster offers, tier celebrations, and coin toasts.
|
|
4
|
+
|
|
5
|
+
**Accessor**: `cashable.ui` (nullable — `null` in embedded mode or when the user is not authenticated)
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The UI module renders a DOM overlay on top of the game canvas in standalone mode. It provides:
|
|
10
|
+
|
|
11
|
+
- **PlayLoopButton** — floating button that opens a panel with three content areas:
|
|
12
|
+
- **Rewards tab (main screen)** — coins pill, tier card with progress bar, "Convert coins to real money" CTA button, partners section
|
|
13
|
+
- **Cashout screen (sub-screen)** — countdown timer, earnings display, cashout button (with wait modal), partners. Accessed via the CTA button; back button returns to main screen
|
|
14
|
+
- **Games tab** — lists available games
|
|
15
|
+
- **SponsoredModal** — animated countdown before interstitial ads
|
|
16
|
+
- **BonusOfferModal** — periodic bonus coin offers (watch ad to claim)
|
|
17
|
+
- **BoostOfferModal** — 2x coin multiplier activation (watch ad to activate)
|
|
18
|
+
- **TierCelebrationModal** — tier-up celebrations with optional ad-doubled rewards
|
|
19
|
+
- **CoinToast** — floating "+N coins" notifications
|
|
20
|
+
|
|
21
|
+
The module is initialized automatically during `cashable.init()` when running in standalone mode with an authenticated user. The `showPlayLoopButton` config option (default: `false`) controls whether the floating button is rendered.
|
|
22
|
+
|
|
23
|
+
### PlayLoopButton Config
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
ui: {
|
|
27
|
+
showPlayLoopButton: true,
|
|
28
|
+
cashableButton: {
|
|
29
|
+
position: 'top-left', // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
|
30
|
+
autoPeek: false, // Auto-open panel briefly on first wallet data (default: true)
|
|
31
|
+
},
|
|
32
|
+
onClose: () => { /* handle close */ },
|
|
33
|
+
onMenuOpen: () => { /* handle menu */ },
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Methods
|
|
38
|
+
|
|
39
|
+
### `showSponsoredInterstitial(): Promise<boolean>`
|
|
40
|
+
|
|
41
|
+
Shows a sponsored interstitial ad flow:
|
|
42
|
+
|
|
43
|
+
1. Reserves a reward amount from the backend (`rewards.reserveSponsoredVideo`)
|
|
44
|
+
2. Shows a 3-second animated countdown modal
|
|
45
|
+
3. Plays the interstitial ad
|
|
46
|
+
4. On success: claims the reward (`rewards.claimSponsoredVideo`), updates coin balance, shows toast
|
|
47
|
+
5. On failure: shows "Video unavailable" state
|
|
48
|
+
|
|
49
|
+
Returns `true` if the ad was shown and the reward was claimed. Returns `false` if the interstitial is on cooldown, the reservation fails, or the ad fails.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const success = await cashable.ui?.showSponsoredInterstitial();
|
|
53
|
+
if (success) {
|
|
54
|
+
console.log('User earned coins from sponsored video');
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `showCoinAward(newTotalCoins): void`
|
|
59
|
+
|
|
60
|
+
Calculates the delta from the current balance, updates the displayed coin count, and shows a coin toast for the difference.
|
|
61
|
+
|
|
62
|
+
- `newTotalCoins: number` — the user's new total coin balance
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
cashable.ui?.showCoinAward(1500); // If user had 1400, shows "+100 coins" toast
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `showCoinToast(amount, note?): void`
|
|
69
|
+
|
|
70
|
+
Shows a floating coin toast notification. Automatically appends "Includes 2x boost" when a boost is active and no custom note is provided.
|
|
71
|
+
|
|
72
|
+
- `amount: number` — coins earned
|
|
73
|
+
- `note?: string` — optional note text
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
cashable.ui?.showCoinToast(50, 'Bonus reward!');
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `updateCoins(coins): void`
|
|
80
|
+
|
|
81
|
+
Updates the displayed coin count in the panel and triggers a tier advancement check.
|
|
82
|
+
|
|
83
|
+
- `coins: number` — new total coin balance
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
cashable.ui?.updateCoins(2000);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `updateThemeColor(color): void`
|
|
90
|
+
|
|
91
|
+
Changes the panel background color. The panel automatically adjusts text/icon colors for contrast.
|
|
92
|
+
|
|
93
|
+
- `color: string` — CSS color value (e.g. `'#1a1a2e'`)
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
cashable.ui?.updateThemeColor('#1a1a2e');
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `refreshWallet(): Promise<void>`
|
|
100
|
+
|
|
101
|
+
Re-fetches wallet data from the backend (`session.getWallet`) and updates all UI elements (coins, tier progress).
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
await cashable.ui?.refreshWallet();
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `getTopBarHeight(): number`
|
|
108
|
+
|
|
109
|
+
Returns the current pixel height of the panel trigger row. Useful for positioning game content below the button.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const offset = cashable.ui?.getTopBarHeight() ?? 0;
|
|
113
|
+
gameCanvas.style.marginTop = `${offset}px`;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Also available as a top-level convenience method: `cashable.getTopBarHeight()`.
|
|
117
|
+
|
|
118
|
+
### `setVisible(visible): void`
|
|
119
|
+
|
|
120
|
+
Shows or hides the UI overlay. Called automatically on `lifecycle.freeze` (hide) and `lifecycle.resume` (show). Also pauses/resumes the offer scheduler.
|
|
121
|
+
|
|
122
|
+
- `visible: boolean`
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
cashable.ui?.setVisible(false); // Hide during cutscene
|
|
126
|
+
cashable.ui?.setVisible(true); // Show again
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `triggerBonusOffer(): Promise<void>`
|
|
130
|
+
|
|
131
|
+
**Debug method.** Immediately triggers a bonus offer modal by fetching one from the backend, bypassing the normal offer scheduler cadence.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
await cashable.ui?.triggerBonusOffer();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `triggerBoosterOffer(): Promise<void>`
|
|
138
|
+
|
|
139
|
+
**Debug method.** Immediately triggers a booster offer modal, bypassing the normal offer scheduler cadence.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
await cashable.ui?.triggerBoosterOffer();
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `dispose(): void`
|
|
146
|
+
|
|
147
|
+
Removes all DOM elements, clears timers, and disposes child components. Called automatically by `cashable.dispose()`.
|
|
148
|
+
|
|
149
|
+
## Components
|
|
150
|
+
|
|
151
|
+
### PlayLoopButton (Panel)
|
|
152
|
+
|
|
153
|
+
Floating button that opens an expandable panel with tabbed navigation:
|
|
154
|
+
|
|
155
|
+
- **Trigger row** — coin balance display with earn animation, inline countdown timer, offer countdown badge
|
|
156
|
+
- **Rewards tab (main screen):**
|
|
157
|
+
- Coins pill with "Your Coins" header
|
|
158
|
+
- Tier card with progress bar, tier name, and "Fill the bar to maximize your earnings" hint
|
|
159
|
+
- "Convert coins to real money" CTA button (navigates to cashout screen)
|
|
160
|
+
- Partners section ("Get paid through" with payment logos)
|
|
161
|
+
- **Cashout screen (sub-screen):**
|
|
162
|
+
- Back button to return to main screen
|
|
163
|
+
- Title: "Coins → Real Money" (no earnings) or "Money Ready!" (has earnings)
|
|
164
|
+
- Subtitle with context-sensitive messaging
|
|
165
|
+
- Countdown timer (hidden when earnings ready)
|
|
166
|
+
- Earnings display (shown when earnings ready)
|
|
167
|
+
- Cashout CTA button with wait modal logic
|
|
168
|
+
- Partners section
|
|
169
|
+
- **Games tab** — lists available games with play buttons
|
|
170
|
+
- **Close button** — calls `config.onClose` callback
|
|
171
|
+
|
|
172
|
+
The panel auto-resets to the rewards main screen when closed.
|
|
173
|
+
|
|
174
|
+
### SponsoredModal
|
|
175
|
+
|
|
176
|
+
Fullscreen modal with phases:
|
|
177
|
+
|
|
178
|
+
1. **Countdown** (3 seconds) — animated SVG ring, reward badge shows "+{amount} coins"
|
|
179
|
+
2. **Playing** — "Loading video..." while the ad shows
|
|
180
|
+
3. **Success** — green checkmark + "Coins Earned!" (auto-dismiss after 2s)
|
|
181
|
+
4. **Failed** — "Video unavailable" (auto-dismiss after 2s)
|
|
182
|
+
|
|
183
|
+
### BonusOfferModal
|
|
184
|
+
|
|
185
|
+
Modal showing a periodic bonus coin offer. Displays the reward amount (doubled if boost is active). User taps "Claim" to watch a rewarded ad, then coins are awarded via `rewards.awardBonus`.
|
|
186
|
+
|
|
187
|
+
### BoostOfferModal
|
|
188
|
+
|
|
189
|
+
Modal offering a 2x coin multiplier for 3 minutes. User taps "Activate" to watch a rewarded ad, then the boost is activated.
|
|
190
|
+
|
|
191
|
+
### TierCelebrationModal
|
|
192
|
+
|
|
193
|
+
Shown when the user advances to a new tier. Offers two claim options:
|
|
194
|
+
|
|
195
|
+
- **Claim** — claim the tier reward directly (`tier.claimCelebration` with `doubled: false`)
|
|
196
|
+
- **Watch Ad & Double** — watch a rewarded ad, then claim doubled reward (`tier.claimCelebration` with `doubled: true`)
|
|
197
|
+
|
|
198
|
+
### CoinToast
|
|
199
|
+
|
|
200
|
+
Floating toast notification showing "+N coins" with an optional note line.
|
|
201
|
+
|
|
202
|
+
## Types
|
|
203
|
+
|
|
204
|
+
### `ActiveOffer`
|
|
205
|
+
|
|
206
|
+
Represents a bonus or booster offer in progress:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
interface ActiveOffer {
|
|
210
|
+
offerId: string;
|
|
211
|
+
amount?: number; // Coin reward amount (bonus offers only)
|
|
212
|
+
type: 'bonus' | 'booster';
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Tier System
|
|
217
|
+
|
|
218
|
+
Tier progress is tracked via `WalletData.tier`:
|
|
219
|
+
|
|
220
|
+
- `currentTier` — current tier number (0-based)
|
|
221
|
+
- `currentTierName` — display name (e.g. "Silver")
|
|
222
|
+
- `nextTier` — next tier info with `name` and `target` (coins needed)
|
|
223
|
+
|
|
224
|
+
When `updateCoins()` is called, the module fetches fresh wallet data and compares the new tier against the last known tier. If the tier increased, a `TierCelebrationModal` is shown.
|
|
225
|
+
|
|
226
|
+
## Offer System
|
|
227
|
+
|
|
228
|
+
The `OfferScheduler` alternates between bonus and booster offers on configurable cadences:
|
|
229
|
+
|
|
230
|
+
- **Bonus offer cadence**: `adConfig.gamesBonusOfferCadenceMs` (default: 60s)
|
|
231
|
+
- **Booster offer cadence**: `adConfig.gamesCoinBoosterCadenceMs` (default: 60s)
|
|
232
|
+
|
|
233
|
+
When an offer triggers:
|
|
234
|
+
1. The scheduler fetches a bonus offer from the backend (for bonus type)
|
|
235
|
+
2. A countdown badge appears on the panel trigger (20 seconds)
|
|
236
|
+
3. If the user taps within the countdown, the corresponding modal opens
|
|
237
|
+
4. If the countdown expires, the offer is dismissed
|
|
238
|
+
|
|
239
|
+
The scheduler pauses when the UI is hidden and resumes when visible.
|
|
240
|
+
|
|
241
|
+
## Boost Mechanics
|
|
242
|
+
|
|
243
|
+
- **Duration**: 3 minutes (180,000ms)
|
|
244
|
+
- **Multiplier**: 2x on all coin awards
|
|
245
|
+
- **Persistence**: `localStorage` key `cashable:boost:endsAt` stores the expiry timestamp
|
|
246
|
+
- **Survival**: Boost survives page reloads within the 3-minute window
|
|
247
|
+
- **Countdown**: Panel trigger shows remaining time (updates every second)
|
|
248
|
+
- **Integration**: The `RewardsModule` reads boost state from localStorage to apply the multiplier
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Wire Protocol
|
|
2
|
+
|
|
3
|
+
Reference for host app implementors (React Native WebView bridge). Game developers using the SDK don't need to know about this.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Communication between the SDK (running inside a WebView) and the host PlayLoop app uses `postMessage` with JSON payloads. There are four message types:
|
|
8
|
+
|
|
9
|
+
| Type | Direction | Purpose |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `cashable:request` | SDK -> Host | SDK requests an action |
|
|
12
|
+
| `cashable:response` | Host -> SDK | Host responds to a request |
|
|
13
|
+
| `cashable:event` | Host -> SDK | Host pushes an event |
|
|
14
|
+
| `cashable:ready` | SDK -> Host | SDK signals initialization complete |
|
|
15
|
+
|
|
16
|
+
## Message Formats
|
|
17
|
+
|
|
18
|
+
### Request (SDK -> Host)
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"type": "cashable:request",
|
|
23
|
+
"action": "ads.showRewarded",
|
|
24
|
+
"requestId": "req_1707000000000_1",
|
|
25
|
+
"payload": {}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Response (Host -> SDK)
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"type": "cashable:response",
|
|
34
|
+
"requestId": "req_1707000000000_1",
|
|
35
|
+
"data": { "success": true }
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Error response:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"type": "cashable:response",
|
|
44
|
+
"requestId": "req_1707000000000_1",
|
|
45
|
+
"error": {
|
|
46
|
+
"code": "AD_NOT_AVAILABLE",
|
|
47
|
+
"message": "No ad fill available"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Event (Host -> SDK)
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"type": "cashable:event",
|
|
57
|
+
"event": "lifecycle.freeze",
|
|
58
|
+
"data": null
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Ready (SDK -> Host)
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"type": "cashable:ready",
|
|
67
|
+
"version": "0.6.0"
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Actions
|
|
72
|
+
|
|
73
|
+
Actions are sent from the SDK to the host via `cashable:request` messages.
|
|
74
|
+
|
|
75
|
+
| Action | Payload | Expected Response |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `ads.showRewarded` | `{}` | `{ success: boolean }` |
|
|
78
|
+
| `ads.showInterstitial` | `{}` | `{ success: boolean }` |
|
|
79
|
+
| `ads.showBanner` | `{ position?: 'top' \| 'bottom' }` | `{ success: boolean }` |
|
|
80
|
+
| `ads.hideBanner` | `{}` | `{}` (ack) |
|
|
81
|
+
| `ads.destroyBanner` | `{}` | `{}` (ack) |
|
|
82
|
+
| `rewards.awardCoins` | `{ multiplier?: number }` | `{ coins: number }` |
|
|
83
|
+
| `rewards.bonusOffer` | `{}` | `{ offered: boolean }` |
|
|
84
|
+
| `rewards.awardBonus` | `{}` | `{ coins: number }` |
|
|
85
|
+
| `rewards.reserveSponsoredVideo` | `{}` | `{ offerId: string, amount: number }` |
|
|
86
|
+
| `rewards.claimSponsoredVideo` | `{ offerId: string, amount: number }` | `{ coinsAwarded: number, totalCoins: number }` |
|
|
87
|
+
| `session.getBalance` | - | `{ coins: number }` |
|
|
88
|
+
| `session.getWallet` | - | `WalletData` (see [Types](#types)) |
|
|
89
|
+
| `games.init` | `{ gameId: string, mode: string }` | `InitResponse` (see [Types](#types)) |
|
|
90
|
+
| `analytics.playTick` | `{ seconds: number }` | `{}` (ack) |
|
|
91
|
+
| `analytics.themeColor` | `{ color: string }` | `{}` (ack) |
|
|
92
|
+
| `lifecycle.ready` | `{ version: string }` | `{}` (ack) |
|
|
93
|
+
| `lifecycle.progress` | `{ percent: number }` | `{}` (ack) |
|
|
94
|
+
| `tier.claimCelebration` | `{ tierNumber: number, doubled: boolean }` | `{ success: boolean, coinsAwarded: number, totalCoins: number }` |
|
|
95
|
+
|
|
96
|
+
## Events
|
|
97
|
+
|
|
98
|
+
Events are pushed from the host to the SDK via `cashable:event` messages.
|
|
99
|
+
|
|
100
|
+
| Event | Data | Description |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| `lifecycle.freeze` | - | Host is putting the game in the background |
|
|
103
|
+
| `lifecycle.resume` | - | Host is bringing the game back to the foreground |
|
|
104
|
+
| `rewards.coinsAwarded` | `{ coins: number }` | Host awarded coins (e.g., bonus) |
|
|
105
|
+
| `ui.topBarHeight` | `{ height: number }` | Host reports top bar pixel height for game layout offset |
|
|
106
|
+
|
|
107
|
+
## Error Codes
|
|
108
|
+
|
|
109
|
+
| Code | Meaning |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `TIMEOUT` | Request timed out (default: 10s) |
|
|
112
|
+
| `TRANSPORT_ERROR` | Communication failure |
|
|
113
|
+
| `AD_NOT_AVAILABLE` | No ad fill |
|
|
114
|
+
| `NETWORK_ERROR` | HTTP request failed |
|
|
115
|
+
| `USER_NOT_LINKED` | User not found in backend |
|
|
116
|
+
|
|
117
|
+
## Host Implementation
|
|
118
|
+
|
|
119
|
+
The host (React Native) should:
|
|
120
|
+
|
|
121
|
+
1. Listen for `onMessage` events from the WebView
|
|
122
|
+
2. Parse JSON, check for `type: 'cashable:request'`
|
|
123
|
+
3. Dispatch by `action`
|
|
124
|
+
4. Send responses back via `webViewRef.current.injectJavaScript()`:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
// In React Native
|
|
128
|
+
const sendResponse = (requestId, data, error) => {
|
|
129
|
+
const message = JSON.stringify({
|
|
130
|
+
type: 'cashable:response',
|
|
131
|
+
requestId,
|
|
132
|
+
data: error ? undefined : data,
|
|
133
|
+
error: error ? { code: error.code, message: error.message } : undefined,
|
|
134
|
+
});
|
|
135
|
+
webViewRef.current?.injectJavaScript(
|
|
136
|
+
`window.dispatchEvent(new MessageEvent('message', { data: '${message}' }));true;`
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const sendEvent = (event, data) => {
|
|
141
|
+
const message = JSON.stringify({
|
|
142
|
+
type: 'cashable:event',
|
|
143
|
+
event,
|
|
144
|
+
data,
|
|
145
|
+
});
|
|
146
|
+
webViewRef.current?.injectJavaScript(
|
|
147
|
+
`window.dispatchEvent(new MessageEvent('message', { data: '${message}' }));true;`
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
See `frontend/src/screens/PlayGamesScreen/hooks/usePlayLoopGameBridge.ts` for the full reference implementation.
|
|
153
|
+
|
|
154
|
+
## Types
|
|
155
|
+
|
|
156
|
+
### `WalletData`
|
|
157
|
+
|
|
158
|
+
Returned by `session.getWallet`:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
interface WalletData {
|
|
162
|
+
coins: number;
|
|
163
|
+
tier: {
|
|
164
|
+
currentTier: number;
|
|
165
|
+
currentTierName: string;
|
|
166
|
+
nextTier?: { name: string; target: number };
|
|
167
|
+
};
|
|
168
|
+
tierDefinitions: TierInfo[];
|
|
169
|
+
coinGoal: number;
|
|
170
|
+
coinsEarnedThisPeriod: number;
|
|
171
|
+
earnings: number;
|
|
172
|
+
cashoutPeriodEndsAt: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
interface TierInfo {
|
|
176
|
+
tier: number;
|
|
177
|
+
name: string;
|
|
178
|
+
target: number;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `InitResponse`
|
|
183
|
+
|
|
184
|
+
Returned by `games.init`:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
interface InitResponse {
|
|
188
|
+
userId: string;
|
|
189
|
+
featureFlags?: {
|
|
190
|
+
clientKey: string;
|
|
191
|
+
bootstrapValues?: string; // JSON-serialized Statsig bootstrap
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clocktone/game-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Cashable Unified Game SDK for embedded (WebView) and standalone (Capacitor) games",
|
|
5
|
+
"main": "dist/loader/cjs/index.js",
|
|
6
|
+
"module": "dist/loader/esm/index.js",
|
|
7
|
+
"types": "dist/types/loader/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/types/index.d.ts",
|
|
11
|
+
"import": "./dist/loader/esm/index.js",
|
|
12
|
+
"require": "./dist/loader/cjs/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./loader": {
|
|
15
|
+
"types": "./dist/types/loader/index.d.ts",
|
|
16
|
+
"import": "./dist/loader/esm/index.js",
|
|
17
|
+
"require": "./dist/loader/cjs/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist/loader",
|
|
25
|
+
"dist/types",
|
|
26
|
+
"android/src",
|
|
27
|
+
"android/build.gradle",
|
|
28
|
+
"docs"
|
|
29
|
+
],
|
|
30
|
+
"capacitor": {
|
|
31
|
+
"android": {
|
|
32
|
+
"src": "android"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "npm run build:loader && npm run build:cdn",
|
|
37
|
+
"build:loader": "rollup -c rollup.loader.config.mjs",
|
|
38
|
+
"build:cdn": "rollup -c rollup.cdn.config.mjs",
|
|
39
|
+
"dev": "rollup -c rollup.cdn.config.mjs -w",
|
|
40
|
+
"preview": "vite --config preview/vite.config.ts",
|
|
41
|
+
"lint": "tsc --noEmit",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:watch": "vitest"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@babylonjs/core": ">=6.0.0",
|
|
47
|
+
"@capacitor/core": ">=5.0.0",
|
|
48
|
+
"@statsig/js-client": ">=3.0.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"@babylonjs/core": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"@capacitor/core": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@statsig/js-client": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@capacitor/core": "^8.1.0",
|
|
63
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
64
|
+
"@rollup/plugin-image": "^3.0.3",
|
|
65
|
+
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
66
|
+
"@rollup/plugin-replace": "^6.0.0",
|
|
67
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
68
|
+
"@rollup/plugin-typescript": "^12.1.0",
|
|
69
|
+
"@statsig/js-client": "^3.31.2",
|
|
70
|
+
"@vitest/runner": "^4.1.5",
|
|
71
|
+
"happy-dom": "^20.9.0",
|
|
72
|
+
"jsdom": "^27.0.1",
|
|
73
|
+
"rollup": "^4.9.0",
|
|
74
|
+
"rollup-plugin-dts": "^6.1.0",
|
|
75
|
+
"tslib": "^2.8.0",
|
|
76
|
+
"typescript": "~5.8.0",
|
|
77
|
+
"vite": "^7.3.1",
|
|
78
|
+
"vitest": "^3.2.0"
|
|
79
|
+
},
|
|
80
|
+
"license": "UNLICENSED"
|
|
81
|
+
}
|