@series-inc/venus-sdk 3.0.6 → 3.1.0-beta.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 +1180 -14
- package/dist/{AdsApi-CIXV8I_p.d.mts → AdsApi-meVfUcZy.d.mts} +164 -355
- package/dist/{AdsApi-CIXV8I_p.d.ts → AdsApi-meVfUcZy.d.ts} +164 -355
- package/dist/chunk-2PDL7CQK.mjs +26 -0
- package/dist/chunk-2PDL7CQK.mjs.map +1 -0
- package/dist/{chunk-LBJFUHOH.mjs → chunk-EMVTVSGL.mjs} +1471 -737
- package/dist/chunk-EMVTVSGL.mjs.map +1 -0
- package/dist/chunk-IZLOB7DV.mjs +343 -0
- package/dist/chunk-IZLOB7DV.mjs.map +1 -0
- package/dist/{chunk-MWUS3A7C.mjs → chunk-QABXMFND.mjs} +3 -7
- package/dist/chunk-QABXMFND.mjs.map +1 -0
- package/dist/core-5JLON75E.mjs +4 -0
- package/dist/{core-RDMPQV6U.mjs.map → core-5JLON75E.mjs.map} +1 -1
- package/dist/index.cjs +1883 -778
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +113 -61
- package/dist/index.d.ts +113 -61
- package/dist/index.mjs +8 -2
- package/dist/index.mjs.map +1 -1
- package/dist/venus-api/index.cjs +1806 -748
- package/dist/venus-api/index.cjs.map +1 -1
- package/dist/venus-api/index.d.mts +2 -2
- package/dist/venus-api/index.d.ts +2 -2
- package/dist/venus-api/index.mjs +311 -3
- package/dist/venus-api/index.mjs.map +1 -1
- package/dist/vite/index.cjs +534 -0
- package/dist/vite/index.cjs.map +1 -0
- package/dist/vite/index.mjs +527 -0
- package/dist/vite/index.mjs.map +1 -0
- package/dist/webview/index.cjs +346 -0
- package/dist/webview/index.cjs.map +1 -0
- package/dist/webview/index.d.mts +17 -0
- package/dist/webview/index.d.ts +17 -0
- package/dist/webview/index.mjs +4 -0
- package/dist/webview/index.mjs.map +1 -0
- package/package.json +19 -1
- package/dist/chunk-LBJFUHOH.mjs.map +0 -1
- package/dist/chunk-MWUS3A7C.mjs.map +0 -1
- package/dist/core-RDMPQV6U.mjs +0 -3
package/README.md
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# Venus SDK API
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The core API package for the Venus SDK, providing comprehensive, type-safe interfaces for building H5 games and applications on the Venus platform.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install venus-sdk
|
|
9
|
+
```
|
|
9
10
|
|
|
10
|
-
##
|
|
11
|
+
## Architecture
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
The Venus SDK is built on a client-server RPC architecture:
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
- **Host Interface** - Main entry point grouping all APIs
|
|
16
|
+
- **RPC Transport** - Message-based communication with Venus platform
|
|
17
|
+
- **Mock Implementations** - Complete mock APIs for local development
|
|
18
|
+
- **Venus API** - Low-level platform interface
|
|
17
19
|
|
|
18
|
-
###
|
|
20
|
+
### Usage
|
|
19
21
|
|
|
20
22
|
```typescript
|
|
21
23
|
import { default as VenusAPI } from '@series-inc/venus-sdk/api'
|
|
@@ -24,10 +26,1174 @@ import { default as VenusAPI } from '@series-inc/venus-sdk/api'
|
|
|
24
26
|
await VenusAPI.initializeAsync()
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
##
|
|
29
|
+
## API Overview
|
|
30
|
+
|
|
31
|
+
### Profile API
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Get current user profile (synchronous)
|
|
35
|
+
const profile = VenusAPI.profile.getCurrentProfile()
|
|
36
|
+
console.log(profile.name, profile.username)
|
|
37
|
+
```
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### Safe Area & HUD Insets
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// Static safe area from initialization (baseline padding)
|
|
44
|
+
const safeArea = VenusAPI.config.ui.safeArea
|
|
45
|
+
layout.style.paddingTop = `${safeArea.top}px`
|
|
46
|
+
layout.style.paddingBottom = `${safeArea.bottom}px`
|
|
47
|
+
|
|
48
|
+
// Dynamic HUD insets arrive with lifecycle events
|
|
49
|
+
VenusAPI.lifecycle.onShow(({ hudInsets }) => {
|
|
50
|
+
applyInsets(hudInsets, 'preview')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
VenusAPI.lifecycle.onPlay(({ hudInsets }) => {
|
|
54
|
+
applyInsets(hudInsets, 'fullscreen')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
function applyInsets(insets, mode) {
|
|
58
|
+
// Use whichever inset is larger to avoid overlap with host UI
|
|
59
|
+
const top = Math.max(VenusAPI.config.ui.safeArea.top, insets.top)
|
|
60
|
+
canvas.style.paddingTop = `${top}px`
|
|
61
|
+
canvas.dataset.mode = mode
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`safeArea` provides a static baseline defined at initialization. `hudInsets` reflect the live host UI chrome and differ between preview (`onShow`) and fullscreen (`onPlay`) contexts.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### Ads API
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Check if rewarded ad is ready
|
|
73
|
+
const rewardedReady = await VenusAPI.ads.isRewardedAdReadyAsync()
|
|
74
|
+
|
|
75
|
+
// Show interstitial ad
|
|
76
|
+
const interstitialShown = await VenusAPI.ads.showInterstitialAd()
|
|
77
|
+
if (interstitialShown) {
|
|
78
|
+
// Interstitial ad was displayed
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Show rewarded video ad
|
|
82
|
+
const rewardEarned = await VenusAPI.ads.showRewardedAdAsync()
|
|
83
|
+
if (rewardEarned) {
|
|
84
|
+
// User watched the full video and earned reward
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### Haptics API
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// Trigger haptic feedback
|
|
93
|
+
await VenusAPI.haptics.triggerHapticAsync('success')
|
|
94
|
+
await VenusAPI.haptics.triggerHapticAsync('warning')
|
|
95
|
+
await VenusAPI.haptics.triggerHapticAsync('error')
|
|
96
|
+
await VenusAPI.haptics.triggerHapticAsync('light')
|
|
97
|
+
await VenusAPI.haptics.triggerHapticAsync('medium')
|
|
98
|
+
await VenusAPI.haptics.triggerHapticAsync('heavy')
|
|
99
|
+
```
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### Local Notifications API
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Schedule a delayed notification (minimal - required params only)
|
|
106
|
+
const id = await VenusApi.notifications.scheduleAsync(
|
|
107
|
+
'Notification Title',
|
|
108
|
+
'Notification Body',
|
|
109
|
+
60 // Delay in seconds
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// Schedule with optional notification ID
|
|
113
|
+
const id = await VenusApi.notifications.scheduleAsync(
|
|
114
|
+
'Notification Title',
|
|
115
|
+
'Notification Body',
|
|
116
|
+
60,
|
|
117
|
+
'custom-notification-id'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
// Schedule with all optional settings
|
|
121
|
+
const id = await VenusApi.notifications.scheduleAsync(
|
|
122
|
+
'Notification Title',
|
|
123
|
+
'Notification Body',
|
|
124
|
+
60,
|
|
125
|
+
'custom-notification-id',
|
|
126
|
+
{
|
|
127
|
+
priority: 100, // 0-100, default 50
|
|
128
|
+
groupId: 'reminders', // Group related notifications
|
|
129
|
+
payload: { key: 'value' } // Custom data
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
// Cancel a notification
|
|
134
|
+
await VenusApi.notifications.cancelNotification(notificationId)
|
|
135
|
+
|
|
136
|
+
// Get all scheduled notifications
|
|
137
|
+
await VenusApi.notifications.getAllScheduledLocalNotifications()
|
|
138
|
+
```
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### Loader API
|
|
142
|
+
|
|
143
|
+
The preloader is opt-in by default. In order to opt in, you must add
|
|
144
|
+
```typescript
|
|
145
|
+
await VenusAPI.initializeAsync({ usePreloader: true })
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// Activating and dismissing the preloader
|
|
150
|
+
await VenusApi.preloader.showLoadScreen()
|
|
151
|
+
await VenusApi.preloader.hideLoadScreen()
|
|
152
|
+
```
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### Custom Funnel Events API
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// Record a custom analytics event
|
|
159
|
+
await VenusApi.analytics.recordCustomEvent('level_completed', {
|
|
160
|
+
level: 5,
|
|
161
|
+
score: 1250,
|
|
162
|
+
time: 45.2
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Track funnel step with optional funnel name
|
|
166
|
+
await VenusApi.analytics.trackFunnelStep(1, 'tutorial_started', 'onboarding')
|
|
167
|
+
await VenusApi.analytics.trackFunnelStep(2, 'tutorial_completed', 'onboarding')
|
|
168
|
+
```
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### IAP API
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// get VBucks/Hard Currency balance
|
|
175
|
+
await VenusApi.iap.getHardCurrencyBalance()
|
|
176
|
+
// Spend VBucks/Hard Currency
|
|
177
|
+
await VenusApi.iap.spendCurrency('yourProductID', 3)
|
|
178
|
+
// Open Venus Store
|
|
179
|
+
await VenusApi.iap.openStore()
|
|
180
|
+
// Get Currency Icon
|
|
181
|
+
await VenusApi.iap.getCurrencyIcon()
|
|
182
|
+
```
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### Saves API
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// Get an item from storage
|
|
189
|
+
await VenusApi.storage.getItem('playerData')
|
|
190
|
+
// Set an item in storage
|
|
191
|
+
await VenusApi.storage.setItem('playerData', JSON.stringify({ level: 10 }))
|
|
192
|
+
// Remove an item from storage
|
|
193
|
+
await VenusApi.storage.removeItem('playerData')
|
|
194
|
+
// Get storage length
|
|
195
|
+
await VenusApi.storage.length()
|
|
196
|
+
// Get key at specific index
|
|
197
|
+
await VenusApi.storage.key(0)
|
|
198
|
+
// Clear all items from storage
|
|
199
|
+
await VenusApi.storage.clear()
|
|
200
|
+
// Set multiple items at once
|
|
201
|
+
await VenusApi.storage.setMultipleItems([
|
|
202
|
+
{ key: 'playerData', value: JSON.stringify({ level: 10 }) },
|
|
203
|
+
{ key: 'settings', value: JSON.stringify({ sound: true }) }
|
|
204
|
+
])
|
|
205
|
+
// Remove multiple items at once
|
|
206
|
+
await VenusApi.storage.removeMultipleItems(['playerData', 'settings'])
|
|
207
|
+
// Get all items from storage
|
|
208
|
+
await VenusApi.storage.getAllItems()
|
|
209
|
+
```
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
### LLM API
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Request chat completion from AI model
|
|
216
|
+
const response = await VenusApi.ai.requestChatCompletionAsync({
|
|
217
|
+
model: 'chatGPT',
|
|
218
|
+
messages: [
|
|
219
|
+
{
|
|
220
|
+
role: 'user',
|
|
221
|
+
content: 'What is the best strategy for this level?'
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// Get available completion models
|
|
227
|
+
const models = await VenusApi.ai.getAvailableCompletionModels()
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Simulation API
|
|
231
|
+
|
|
232
|
+
The Simulation API manages game state, recipe execution, and slot systems.
|
|
233
|
+
|
|
234
|
+
#### State Management
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// Get current simulation state
|
|
238
|
+
const state = await VenusAPI.simulation.getStateAsync(roomId?)
|
|
239
|
+
// Returns: { entities, inventory, currencies, timers, etc. }
|
|
240
|
+
|
|
241
|
+
// Get simulation configuration
|
|
242
|
+
const config = await VenusAPI.simulation.getConfigAsync(roomId?)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### Recipe Execution
|
|
246
|
+
|
|
247
|
+
Recipes are server-authoritative game actions (crafting, battles, upgrades, etc.):
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// Execute a recipe
|
|
251
|
+
const result = await VenusAPI.simulation.executeRecipeAsync(
|
|
252
|
+
'craft_sword',
|
|
253
|
+
{ materials: ['iron', 'wood'] },
|
|
254
|
+
{ skipNotification: false }
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
// Execute a scoped recipe (entity-specific)
|
|
258
|
+
const result = await VenusAPI.simulation.executeScopedRecipeAsync(
|
|
259
|
+
'upgrade_weapon',
|
|
260
|
+
'sword_123',
|
|
261
|
+
{ level: 5 }
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
// Get active recipe runs (for time-based recipes)
|
|
265
|
+
const runs = await VenusAPI.simulation.getActiveRunsAsync()
|
|
266
|
+
|
|
267
|
+
// Collect completed recipe
|
|
268
|
+
const result = await VenusAPI.simulation.collectRecipeAsync(runId)
|
|
269
|
+
|
|
270
|
+
// Trigger recipe chain
|
|
271
|
+
await VenusAPI.simulation.triggerRecipeChainAsync('battle_complete')
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### Recipe Requirements
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// Check requirements for a single recipe
|
|
278
|
+
const requirements = await VenusAPI.simulation.getRecipeRequirementsAsync(
|
|
279
|
+
'craft_sword',
|
|
280
|
+
'player',
|
|
281
|
+
1
|
|
282
|
+
)
|
|
283
|
+
// Returns: { recipeId, entity, amount?, inputs, canAfford, disabled }
|
|
284
|
+
|
|
285
|
+
// Batch check multiple recipes
|
|
286
|
+
const results = await VenusAPI.simulation.getBatchRecipeRequirementsAsync([
|
|
287
|
+
{ recipeId: 'craft_sword', batchAmount: 1 },
|
|
288
|
+
{ recipeId: 'craft_shield', batchAmount: 2 }
|
|
289
|
+
])
|
|
290
|
+
// Returns: { success, results, errors? }
|
|
291
|
+
|
|
292
|
+
// Get available recipes
|
|
293
|
+
const recipes = await VenusAPI.simulation.getAvailableRecipesAsync({
|
|
294
|
+
roomId: 'room_123',
|
|
295
|
+
includeActorRecipes: true
|
|
296
|
+
})
|
|
297
|
+
// Returns: { success, recipes }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### Slot Management
|
|
301
|
+
|
|
302
|
+
Slots represent equipment, loadouts, teams, or any item container system:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// Get all slot containers
|
|
306
|
+
const containers = await VenusAPI.simulation.getSlotContainersAsync()
|
|
307
|
+
// Returns: [{ id: 'equipment', slots: [...] }, { id: 'team', slots: [...] }]
|
|
308
|
+
|
|
309
|
+
// Get slot assignments for a container
|
|
310
|
+
const assignments = await VenusAPI.simulation.getSlotAssignmentsAsync('equipment')
|
|
311
|
+
// Returns: [{ slotId: 'weapon', itemId: 'sword_123' }, ...]
|
|
312
|
+
|
|
313
|
+
// Assign item to slot
|
|
314
|
+
await VenusAPI.simulation.assignItemToSlotAsync('equipment', 'weapon', 'sword_123')
|
|
315
|
+
|
|
316
|
+
// Remove item from slot
|
|
317
|
+
await VenusAPI.simulation.removeItemFromSlotAsync('equipment', 'weapon')
|
|
318
|
+
|
|
319
|
+
// Get available items for a slot
|
|
320
|
+
const items = await VenusAPI.simulation.getAvailableItemsAsync('equipment', 'weapon')
|
|
321
|
+
|
|
322
|
+
// Preview power calculation before assignment
|
|
323
|
+
const preview = await VenusAPI.simulation.calculatePowerPreviewAsync(
|
|
324
|
+
'equipment',
|
|
325
|
+
'weapon',
|
|
326
|
+
'sword_456'
|
|
327
|
+
)
|
|
328
|
+
// Returns: { currentPower: 100, newPower: 150, delta: +50 }
|
|
329
|
+
|
|
330
|
+
// Validate slot assignment
|
|
331
|
+
const valid = await VenusAPI.simulation.validateSlotAssignmentAsync(
|
|
332
|
+
'equipment',
|
|
333
|
+
'weapon',
|
|
334
|
+
'shield_123' // Wrong type
|
|
335
|
+
)
|
|
336
|
+
// Returns: { valid: false, reason: 'Type mismatch' }
|
|
337
|
+
|
|
338
|
+
// Batch operations (atomic)
|
|
339
|
+
await VenusAPI.simulation.executeBatchOperationsAsync([
|
|
340
|
+
{ type: 'assign', containerId: 'equipment', slotId: 'weapon', itemId: 'sword' },
|
|
341
|
+
{ type: 'assign', containerId: 'equipment', slotId: 'armor', itemId: 'plate' },
|
|
342
|
+
{ type: 'remove', containerId: 'equipment', slotId: 'boots' }
|
|
343
|
+
], false) // false = execute, true = validate only
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### Field Resolution & Metadata
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// Resolve dynamic field values
|
|
350
|
+
const value = await VenusAPI.simulation.resolveFieldValueAsync(
|
|
351
|
+
'player_123',
|
|
352
|
+
'stats.power',
|
|
353
|
+
'player'
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
// Get entity metadata
|
|
357
|
+
const metadata = await VenusAPI.simulation.getEntityMetadataAsync('sword_123')
|
|
358
|
+
```
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
#### Utility Methods
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
// Sum stat contributions
|
|
365
|
+
const totalPower = VenusAPI.simulation.sumContributions(
|
|
366
|
+
[{ power: 10 }, { power: 20 }, { power: 15 }],
|
|
367
|
+
'power'
|
|
368
|
+
)
|
|
369
|
+
// Returns: 45
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
### Storage API
|
|
375
|
+
|
|
376
|
+
Three-tier storage system with different data scopes:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// Device Cache - persists across all apps for the device
|
|
380
|
+
await VenusAPI.deviceCache.setItem('lastUserId', '12345')
|
|
381
|
+
const userId = await VenusAPI.deviceCache.getItem('lastUserId')
|
|
382
|
+
|
|
383
|
+
// App Storage - app-specific persistent storage
|
|
384
|
+
await VenusAPI.appStorage.setItem('highScore', JSON.stringify(1000))
|
|
385
|
+
await VenusAPI.appStorage.setItem('playerData', JSON.stringify({ level: 5, gold: 1000 }))
|
|
386
|
+
|
|
387
|
+
// Global Storage - shared across all apps for the user
|
|
388
|
+
await VenusAPI.globalStorage.setItem('preferences', JSON.stringify({ theme: 'dark' }))
|
|
389
|
+
|
|
390
|
+
// All storage APIs support:
|
|
391
|
+
const value = await storage.getItem(key)
|
|
392
|
+
await storage.setItem(key, value)
|
|
393
|
+
await storage.removeItem(key)
|
|
394
|
+
await storage.clear()
|
|
395
|
+
const count = await storage.length()
|
|
396
|
+
const keyName = await storage.key(index)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
### Popups API
|
|
403
|
+
|
|
404
|
+
Display native-style UI popups:
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
// Toast messages
|
|
408
|
+
await VenusAPI.popups.showToast('Game saved!', {
|
|
409
|
+
duration: 3000,
|
|
410
|
+
variant: 'success',
|
|
411
|
+
action: { label: 'Undo' }
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
// Alert dialog
|
|
415
|
+
await VenusAPI.popups.showAlert(
|
|
416
|
+
'Warning',
|
|
417
|
+
'This action cannot be undone',
|
|
418
|
+
{ buttonText: 'OK' }
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
// Confirm dialog
|
|
422
|
+
const confirmed = await VenusAPI.popups.showConfirm(
|
|
423
|
+
'Delete Item',
|
|
424
|
+
'Are you sure you want to delete this item?',
|
|
425
|
+
{ confirmText: 'Delete', cancelText: 'Cancel' }
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
if (confirmed) {
|
|
429
|
+
// User confirmed
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Action sheet
|
|
433
|
+
const selected = await VenusAPI.popups.showActionSheet(
|
|
434
|
+
[
|
|
435
|
+
{ id: 'edit', label: 'Edit' },
|
|
436
|
+
{ id: 'share', label: 'Share' },
|
|
437
|
+
{ id: 'delete', label: 'Delete' }
|
|
438
|
+
],
|
|
439
|
+
{
|
|
440
|
+
title: 'Choose Action',
|
|
441
|
+
cancelButtonText: 'Cancel'
|
|
442
|
+
}
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
if (selected === 'delete') {
|
|
446
|
+
// User selected delete
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
### Navigation API
|
|
453
|
+
|
|
454
|
+
Stack-based navigation system:
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
// Get current stack information (synchronous)
|
|
458
|
+
const stack = VenusAPI.navigation.getStackInfo()
|
|
459
|
+
// Returns: { isInStack, stackPosition, isTopOfStack, stackDepth, parentInstanceId }
|
|
460
|
+
|
|
461
|
+
// Push new app to stack
|
|
462
|
+
await VenusAPI.navigation.pushApp('bird-flap', {
|
|
463
|
+
contextData: { level: 5, difficulty: 'hard' }
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
// Pop from stack (returns to previous app)
|
|
467
|
+
await VenusAPI.navigation.popApp()
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### Post API
|
|
473
|
+
|
|
474
|
+
Social interaction APIs for posts:
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
// Get post interaction data
|
|
478
|
+
const postInfo = await VenusAPI.post.getPostInfo()
|
|
479
|
+
// Returns: { isLiked, isFollowing, likesCount, commentsCount }
|
|
480
|
+
|
|
481
|
+
// Toggle like
|
|
482
|
+
const likeResult = await VenusAPI.post.toggleLikeAsync()
|
|
483
|
+
// Returns: { isLiked, likesCount, action: 'liked' | 'unliked' }
|
|
484
|
+
|
|
485
|
+
// Toggle follow
|
|
486
|
+
const followResult = await VenusAPI.post.toggleFollowAsync()
|
|
487
|
+
// Returns: { isFollowing, action: 'followed' | 'unfollowed' }
|
|
488
|
+
|
|
489
|
+
// Open comments UI
|
|
490
|
+
const commentsResult = await VenusAPI.post.openCommentsAsync()
|
|
491
|
+
// Returns: { opened, commentsCount }
|
|
492
|
+
|
|
493
|
+
// Share post
|
|
494
|
+
const shareResult = await VenusAPI.post.sharePostAsync({
|
|
495
|
+
message: 'Check this out!',
|
|
496
|
+
title: 'My Post'
|
|
497
|
+
})
|
|
498
|
+
// Returns: { shared, platform, customMessage? }
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
### Social API (BETA)
|
|
504
|
+
|
|
505
|
+
Share links and QR codes for challenges and UGC (user-generated content):
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
// Share a high score challenge - automatically opens share dialog!
|
|
509
|
+
const { shareUrl } = await VenusAPI.social.shareLinkAsync({
|
|
510
|
+
launchParams: {
|
|
511
|
+
challengeType: 'highscore',
|
|
512
|
+
scoreTobeat: '1500',
|
|
513
|
+
challengerId: VenusAPI.profile.getCurrentProfile().id
|
|
514
|
+
},
|
|
515
|
+
metadata: {
|
|
516
|
+
title: 'Beat my score!',
|
|
517
|
+
description: 'Can you beat 1500 points in Word Search?',
|
|
518
|
+
imageUrl: 'https://cdn.example.com/share-highscore.png'
|
|
519
|
+
}
|
|
520
|
+
})
|
|
521
|
+
// This function automatically triggers the share UI:
|
|
522
|
+
// - iOS/Android: Opens native share sheet
|
|
523
|
+
// - Web: Uses navigator.share() if available, otherwise copies to clipboard
|
|
524
|
+
// The returned shareUrl is the generated link that was shared
|
|
525
|
+
|
|
526
|
+
// Generate QR code for in-person challenges (does NOT open share dialog)
|
|
527
|
+
const { shareUrl, qrCode } = await VenusAPI.social.createQRCodeAsync({
|
|
528
|
+
launchParams: {
|
|
529
|
+
challengeType: 'daily',
|
|
530
|
+
dailyPuzzleId: '2024-11-04',
|
|
531
|
+
invitedBy: VenusAPI.profile.getCurrentProfile().username
|
|
532
|
+
},
|
|
533
|
+
metadata: {
|
|
534
|
+
title: 'Daily Challenge',
|
|
535
|
+
description: 'Scan to play today\'s puzzle with me!'
|
|
536
|
+
},
|
|
537
|
+
qrOptions: {
|
|
538
|
+
size: 512, // Size in pixels (default: 256)
|
|
539
|
+
margin: 4, // Margin in modules (default: 2)
|
|
540
|
+
format: 'png' // 'png' or 'svg'
|
|
541
|
+
}
|
|
542
|
+
})
|
|
543
|
+
// This only generates the QR code - you control how to display it
|
|
544
|
+
|
|
545
|
+
// Display the QR code in your UI
|
|
546
|
+
const img = document.createElement('img')
|
|
547
|
+
img.src = qrCode // Data URL (e.g., 'data:image/png;base64,...')
|
|
548
|
+
document.body.appendChild(img)
|
|
549
|
+
|
|
550
|
+
// Handle incoming challenge when app opens from share
|
|
551
|
+
const launchParams = VenusAPI.getLaunchParams()
|
|
552
|
+
if (launchParams.challengeType === 'highscore') {
|
|
553
|
+
const targetScore = parseInt(launchParams.scoreTobeat, 10)
|
|
554
|
+
const challengerId = launchParams.challengerId
|
|
555
|
+
startHighScoreChallenge(targetScore, challengerId)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Handle daily puzzle invites
|
|
559
|
+
if (launchParams.challengeType === 'daily') {
|
|
560
|
+
const puzzleId = launchParams.dailyPuzzleId
|
|
561
|
+
const inviter = launchParams.invitedBy
|
|
562
|
+
loadDailyPuzzle(puzzleId, inviter)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Referral system example
|
|
566
|
+
if (launchParams.referredBy) {
|
|
567
|
+
await rewardReferrer(launchParams.referredBy)
|
|
568
|
+
showMessage(`You were invited by ${launchParams.referredBy}!`)
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
**Launch Parameters:**
|
|
573
|
+
|
|
574
|
+
Launch parameters are completely arbitrary - define whatever your game needs:
|
|
575
|
+
|
|
576
|
+
Examples from the code above:
|
|
577
|
+
- `challengeType`, `scoreTobeat`, `challengerId` - For beat-my-score challenges
|
|
578
|
+
- `dailyPuzzleId`, `invitedBy` - For daily puzzle invites
|
|
579
|
+
- `referredBy` - For referral tracking
|
|
580
|
+
- `levelId`, `replayId`, `customMapId` - For UGC content
|
|
581
|
+
|
|
582
|
+
All parameters are custom game data - no reserved or required fields.
|
|
583
|
+
|
|
584
|
+
**Size Limits:**
|
|
585
|
+
|
|
586
|
+
- **Max total size**: Keep `launchParams` under ~100KB (share data stored in Firestore with 1MB document limit)
|
|
587
|
+
- **Use compact formats**: IDs and short strings work best
|
|
588
|
+
- **Best practice**: Store large UGC separately and only pass an ID in `launchParams`
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
### Analytics API
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
// Log custom event
|
|
596
|
+
await VenusAPI.analytics.logEvent('level_complete', {
|
|
597
|
+
level: 5,
|
|
598
|
+
score: 1000,
|
|
599
|
+
timeElapsed: 120
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
// Set user properties
|
|
603
|
+
await VenusAPI.analytics.setUserProperty('vip_status', 'gold')
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
### Avatar 3D API
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
// Load current avatar configuration
|
|
612
|
+
const avatar = await VenusAPI.avatar3d.loadAvatar()
|
|
613
|
+
// Or load a specific avatar
|
|
614
|
+
const avatarConfig = await VenusAPI.avatar3d.loadAvatar('avatar_123')
|
|
615
|
+
|
|
616
|
+
// Show avatar editor
|
|
617
|
+
const result = await VenusAPI.avatar3d.showEditor({
|
|
618
|
+
currentAvatar: avatarConfig,
|
|
619
|
+
contextData: { source: 'settings' }
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
if (result.wasChanged && result.savedAvatarId) {
|
|
623
|
+
console.log('Avatar updated:', result.savedAvatarId)
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Save avatar
|
|
627
|
+
const avatarId = await VenusAPI.avatar3d.saveAvatar(avatarConfig)
|
|
628
|
+
|
|
629
|
+
// Delete avatar
|
|
630
|
+
await VenusAPI.avatar3d.deleteAvatar()
|
|
631
|
+
|
|
632
|
+
// Download avatar manifest
|
|
633
|
+
const manifest = await VenusAPI.avatar3d.downloadManifest()
|
|
634
|
+
|
|
635
|
+
// Download asset paths
|
|
636
|
+
const assetPaths = await VenusAPI.avatar3d.downloadAssetPaths()
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
### Rooms API
|
|
642
|
+
|
|
643
|
+
Multi-user room management:
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
// Get current room
|
|
647
|
+
const room = await VenusAPI.rooms.getCurrentRoom()
|
|
648
|
+
// Returns: { id, name, participants, metadata }
|
|
649
|
+
|
|
650
|
+
// Join room
|
|
651
|
+
await VenusAPI.rooms.join('room_123')
|
|
652
|
+
|
|
653
|
+
// Leave room
|
|
654
|
+
await VenusAPI.rooms.leave()
|
|
655
|
+
|
|
656
|
+
// Send room message
|
|
657
|
+
await VenusAPI.rooms.sendMessage({
|
|
658
|
+
type: 'game_action',
|
|
659
|
+
data: { action: 'move', x: 10, y: 20 }
|
|
660
|
+
})
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
### Time API
|
|
666
|
+
|
|
667
|
+
Server time synchronization and formatting with automatic locale detection:
|
|
668
|
+
|
|
669
|
+
```typescript
|
|
670
|
+
// Get server time
|
|
671
|
+
const serverTimeData = await VenusAPI.time.requestTimeAsync()
|
|
672
|
+
// Returns: { serverTime, localTime, timezoneOffset, formattedTime, locale }
|
|
673
|
+
|
|
674
|
+
// Format time (locale is automatically determined from user settings)
|
|
675
|
+
const formatted = VenusAPI.time.formatTime(Date.now(), {
|
|
676
|
+
dateStyle: 'full', // 'full', 'long', 'medium', 'short'
|
|
677
|
+
timeStyle: 'medium',
|
|
678
|
+
hour12: true
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
// Format number (locale is automatically determined from user settings)
|
|
682
|
+
const formattedNumber = VenusAPI.time.formatNumber(1234567.89, {
|
|
683
|
+
style: 'currency',
|
|
684
|
+
currency: 'USD',
|
|
685
|
+
minimumFractionDigits: 2,
|
|
686
|
+
maximumFractionDigits: 2
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
// Get future time
|
|
690
|
+
const futureTime = await VenusAPI.time.getFutureTimeAsync({
|
|
691
|
+
days: 1,
|
|
692
|
+
hours: 2,
|
|
693
|
+
minutes: 30
|
|
694
|
+
})
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
### CDN API
|
|
700
|
+
|
|
701
|
+
```typescript
|
|
702
|
+
// Resolve asset URLs (synchronous)
|
|
703
|
+
const assetUrl = VenusAPI.cdn.resolveAssetUrl('images/logo.png')
|
|
704
|
+
const avatarUrl = VenusAPI.cdn.resolveAvatarAssetUrl('avatars/model.glb')
|
|
705
|
+
const libUrl = VenusAPI.cdn.resolveSharedLibUrl('libs/helper.js')
|
|
706
|
+
|
|
707
|
+
// Get CDN base URL
|
|
708
|
+
const baseUrl = VenusAPI.cdn.getAssetCdnBaseUrl()
|
|
709
|
+
|
|
710
|
+
// Fetch from CDN
|
|
711
|
+
const response = await VenusAPI.cdn.fetchFromCdn('https://cdn.example.com/file.json')
|
|
712
|
+
const blob = await VenusAPI.cdn.fetchBlob('path/to/asset.png')
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
### Features API
|
|
718
|
+
|
|
719
|
+
Feature flags, experiments, and A/B testing:
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
// Get feature flag value (returns boolean)
|
|
723
|
+
const enabled = await VenusAPI.features.getFeatureFlag('new_ui_enabled')
|
|
724
|
+
|
|
725
|
+
// Get feature gate (returns boolean)
|
|
726
|
+
const canAccess = await VenusAPI.features.getFeatureGate('beta_features')
|
|
727
|
+
|
|
728
|
+
// Get experiment variant
|
|
729
|
+
const experiment = await VenusAPI.features.getExperiment('checkout_flow')
|
|
730
|
+
// Returns: { name, ruleID, value, groupName } | null
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### Lifecycle API
|
|
734
|
+
|
|
735
|
+
App lifecycle event handlers:
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
// Register lifecycle callbacks
|
|
739
|
+
VenusAPI.lifecycle.onReady(() => {
|
|
740
|
+
console.log('App ready')
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
VenusAPI.lifecycle.onShow((context) => {
|
|
744
|
+
console.log('App shown', context.hudInsets)
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
VenusAPI.lifecycle.onPlay((context) => {
|
|
748
|
+
console.log('App playing', context.hudInsets)
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
VenusAPI.lifecycle.onPause(() => {
|
|
752
|
+
console.log('App paused')
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
VenusAPI.lifecycle.onResume(() => {
|
|
756
|
+
console.log('App resumed')
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
VenusAPI.lifecycle.onHidden(() => {
|
|
760
|
+
console.log('App hidden')
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
VenusAPI.lifecycle.onQuit(() => {
|
|
764
|
+
console.log('App quitting')
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
// Quit the app
|
|
768
|
+
await VenusAPI.lifecycle.quit()
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
---
|
|
772
|
+
|
|
773
|
+
### Logging API
|
|
774
|
+
|
|
775
|
+
```typescript
|
|
776
|
+
// Log messages
|
|
777
|
+
VenusAPI.logging.log('Info message', { data: 'value' })
|
|
778
|
+
VenusAPI.logging.error('Error message', error)
|
|
779
|
+
VenusAPI.logging.debug('Debug info')
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
### Leaderboard API (BETA)
|
|
785
|
+
|
|
786
|
+
Competitive leaderboards with three security levels. Choose based on your game's requirements.
|
|
787
|
+
|
|
788
|
+
---
|
|
789
|
+
|
|
790
|
+
#### 🟢 Simple Mode (Default - Casual Games)
|
|
791
|
+
|
|
792
|
+
Submit scores directly without tokens:
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
// One call - no token needed!
|
|
796
|
+
const result = await VenusAPI.leaderboard.submitScoreAsync({
|
|
797
|
+
score: 1500,
|
|
798
|
+
duration: 120,
|
|
799
|
+
mode: 'classic',
|
|
800
|
+
metadata: {
|
|
801
|
+
levelCompleted: 10,
|
|
802
|
+
powerUpsUsed: 3
|
|
803
|
+
},
|
|
804
|
+
telemetry: {
|
|
805
|
+
clickCount: 245,
|
|
806
|
+
avgReactionTime: 0.32
|
|
807
|
+
}
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
console.log(`Your rank: ${result.rank}`)
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
**Configuration:** None needed (uses defaults)
|
|
814
|
+
|
|
815
|
+
**Security provided:**
|
|
816
|
+
- ✅ Score/duration bounds validation
|
|
817
|
+
- ✅ Rate limiting (60 second cooldown per player)
|
|
818
|
+
- ✅ Trust scores & shadow banning for repeat offenders
|
|
819
|
+
- ❌ No session replay protection
|
|
820
|
+
- ❌ No tamper protection
|
|
821
|
+
|
|
822
|
+
**Best for:** Simple integration
|
|
823
|
+
|
|
824
|
+
---
|
|
825
|
+
|
|
826
|
+
#### 🟡 Token Mode (Competitive Games)
|
|
827
|
+
|
|
828
|
+
Add session validation for replay protection:
|
|
829
|
+
|
|
830
|
+
```typescript
|
|
831
|
+
// Step 1: Create score token
|
|
832
|
+
const scoreToken = await VenusAPI.leaderboard.createScoreTokenAsync({
|
|
833
|
+
mode: 'ranked'
|
|
834
|
+
})
|
|
835
|
+
// Returns: { token, startTime, expiresAt, mode }
|
|
836
|
+
|
|
837
|
+
// Step 2: Play game...
|
|
838
|
+
|
|
839
|
+
// Step 3: Submit with token
|
|
840
|
+
const result = await VenusAPI.leaderboard.submitScoreAsync({
|
|
841
|
+
token: scoreToken.token,
|
|
842
|
+
score: 1500,
|
|
843
|
+
duration: 120,
|
|
844
|
+
metadata: { ... }
|
|
845
|
+
})
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
**Configuration:**
|
|
849
|
+
```json
|
|
850
|
+
{
|
|
851
|
+
"leaderboard": {
|
|
852
|
+
"requiresToken": true
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
**Additional security:**
|
|
858
|
+
- ✅ All simple mode security
|
|
859
|
+
- ✅ Session validation (tokens expire in 1 hour)
|
|
860
|
+
- ✅ Replay attack prevention (one-time use)
|
|
861
|
+
- ✅ Mode locking (token locks game mode)
|
|
862
|
+
- ❌ No tamper protection
|
|
863
|
+
|
|
864
|
+
**Best for:** Preventing replay attacks
|
|
865
|
+
|
|
866
|
+
---
|
|
867
|
+
|
|
868
|
+
#### 🔴 Score Sealing Mode (High-Stakes Games)
|
|
869
|
+
|
|
870
|
+
Add cryptographic tamper protection:
|
|
871
|
+
|
|
872
|
+
```typescript
|
|
873
|
+
// Step 1: Create score token (sealing data included automatically if enabled)
|
|
874
|
+
const scoreToken = await VenusAPI.leaderboard.createScoreTokenAsync({
|
|
875
|
+
mode: 'tournament'
|
|
876
|
+
})
|
|
877
|
+
// Returns: { token, sealingNonce, sealingSecret, ... }
|
|
878
|
+
|
|
879
|
+
// Step 2: Play game...
|
|
880
|
+
|
|
881
|
+
// Step 3: Submit score - SDK auto-computes hash internally!
|
|
882
|
+
const result = await VenusAPI.leaderboard.submitScoreAsync({
|
|
883
|
+
token: scoreToken.token,
|
|
884
|
+
score: 1500,
|
|
885
|
+
duration: 120,
|
|
886
|
+
})
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
**Configuration:**
|
|
890
|
+
```json
|
|
891
|
+
{
|
|
892
|
+
"leaderboard": {
|
|
893
|
+
"requiresToken": true,
|
|
894
|
+
"enableScoreSealing": true,
|
|
895
|
+
"scoreSealingSecret": "your-secret-key-change-in-production"
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
**Note:** Hash computation is handled internally by the SDK using Web Crypto API (HMAC-SHA256). Games never need to implement cryptographic hashing manually. The hash always includes: `score`, `duration`, and `token`.
|
|
901
|
+
|
|
902
|
+
**Maximum security:**
|
|
903
|
+
- ✅ All token mode security
|
|
904
|
+
- ✅ Tamper-proof scores (HMAC-SHA256 verification)
|
|
905
|
+
- ✅ Client-side cheat detection
|
|
906
|
+
- ✅ Automatic hash computation (no crypto code needed in games)
|
|
907
|
+
|
|
908
|
+
**Best for:** Reduced hacking
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
#### Query Methods (Same for All Modes)
|
|
913
|
+
|
|
914
|
+
All query methods work identically regardless of security mode:
|
|
915
|
+
|
|
916
|
+
```typescript
|
|
917
|
+
// Get paginated scores
|
|
918
|
+
const pagedScores = await VenusAPI.leaderboard.getPagedScoresAsync({
|
|
919
|
+
mode: 'classic',
|
|
920
|
+
period: 'daily',
|
|
921
|
+
limit: 50,
|
|
922
|
+
cursor: nextCursor
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
// Get podium + player context
|
|
926
|
+
const podiumScores = await VenusAPI.leaderboard.getPodiumScoresAsync({
|
|
927
|
+
mode: 'classic',
|
|
928
|
+
period: 'daily',
|
|
929
|
+
topCount: 3,
|
|
930
|
+
contextAhead: 4,
|
|
931
|
+
contextBehind: 2
|
|
932
|
+
})
|
|
933
|
+
|
|
934
|
+
// Get my rank
|
|
935
|
+
const rank = await VenusAPI.leaderboard.getMyRankAsync({
|
|
936
|
+
mode: 'classic',
|
|
937
|
+
period: 'daily'
|
|
938
|
+
})
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
---
|
|
942
|
+
|
|
943
|
+
#### Configuration Reference
|
|
944
|
+
|
|
945
|
+
Add a `leaderboard` object to your game's `config.json` file:
|
|
946
|
+
|
|
947
|
+
```json
|
|
948
|
+
{
|
|
949
|
+
"leaderboard": {
|
|
950
|
+
// Basic settings
|
|
951
|
+
"minScore": 0,
|
|
952
|
+
"maxScore": 999999999,
|
|
953
|
+
"minDurationSec": 10,
|
|
954
|
+
"maxDurationSec": 600,
|
|
955
|
+
|
|
956
|
+
// Security (progressive levels)
|
|
957
|
+
"requiresToken": false, // Simple mode (default)
|
|
958
|
+
"enableScoreSealing": false, // Requires requiresToken=true
|
|
959
|
+
"scoreSealingSecret": "secret", // Required if enableScoreSealing=true
|
|
960
|
+
|
|
961
|
+
// Game modes
|
|
962
|
+
"modes": {
|
|
963
|
+
"default": { "displayName": "Normal" },
|
|
964
|
+
"hard": { "displayName": "Hard Mode" }
|
|
965
|
+
},
|
|
966
|
+
|
|
967
|
+
// Time periods
|
|
968
|
+
"periods": {
|
|
969
|
+
"daily": { "displayName": "Daily", "type": "daily" },
|
|
970
|
+
"alltime": { "displayName": "All Time", "type": "alltime" }
|
|
971
|
+
},
|
|
972
|
+
|
|
973
|
+
// Anti-cheat
|
|
974
|
+
"antiCheat": {
|
|
975
|
+
"enableRateLimit": true,
|
|
976
|
+
"minTimeBetweenSubmissionsSec": 60,
|
|
977
|
+
"trustScoreDecayPerFlag": 10,
|
|
978
|
+
"shadowBanThreshold": 20,
|
|
979
|
+
"enableZScoreDetection": false,
|
|
980
|
+
"zScoreThreshold": 3
|
|
981
|
+
},
|
|
982
|
+
|
|
983
|
+
// Display
|
|
984
|
+
"displaySettings": {
|
|
985
|
+
"maxEntriesPerPage": 50
|
|
986
|
+
},
|
|
987
|
+
|
|
988
|
+
// Seed data (optional)
|
|
989
|
+
"seedEntries": {
|
|
990
|
+
"default": {
|
|
991
|
+
"daily": [
|
|
992
|
+
{
|
|
993
|
+
"score": 18500,
|
|
994
|
+
"username": "ProPlayer",
|
|
995
|
+
"duration": 180
|
|
996
|
+
},
|
|
997
|
+
{
|
|
998
|
+
"score": 15200,
|
|
999
|
+
"username": "Challenger",
|
|
1000
|
+
"duration": 210
|
|
1001
|
+
}
|
|
1002
|
+
]
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
**Features:**
|
|
1013
|
+
- **Three Security Levels**: Simple, Token, Sealed - choose based on game stakes
|
|
1014
|
+
- **Multiple Modes**: Support different game modes (classic, hard, etc.)
|
|
1015
|
+
- **Time Periods**: Daily, weekly, monthly, and all-time leaderboards
|
|
1016
|
+
- **Anti-Cheat**: Rate limiting, trust scores, shadow banning, optional session validation & sealing
|
|
1017
|
+
- **Seed Entries**: Pre-populate leaderboards with NPC scores
|
|
1018
|
+
- **Pagination**: Cursor-based pagination for large leaderboards
|
|
1019
|
+
- **UTC-Based Periods**: All players globally compete in same daily/weekly/monthly periods
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
## Mock Mode
|
|
1025
|
+
|
|
1026
|
+
All APIs have complete mock implementations for local development:
|
|
1027
|
+
|
|
1028
|
+
```typescript
|
|
1029
|
+
import { default as VenusAPI } from '@series-inc/venus-sdk/api'
|
|
1030
|
+
|
|
1031
|
+
await VenusAPI.initializeAsync({ mock: true })
|
|
1032
|
+
|
|
1033
|
+
// All APIs work the same, but with simulated responses
|
|
1034
|
+
const state = await VenusAPI.simulation.getStateAsync()
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
### Custom Mock Data
|
|
1038
|
+
|
|
1039
|
+
```typescript
|
|
1040
|
+
await VenusAPI.initializeAsync({
|
|
1041
|
+
mock: {
|
|
1042
|
+
simulation: {
|
|
1043
|
+
state: { customData: 'value' }
|
|
1044
|
+
},
|
|
1045
|
+
profile: {
|
|
1046
|
+
name: 'Test User',
|
|
1047
|
+
username: 'test_user'
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
})
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
---
|
|
1054
|
+
|
|
1055
|
+
## RPC Transport
|
|
1056
|
+
|
|
1057
|
+
The SDK uses an RPC (Remote Procedure Call) architecture for communication:
|
|
1058
|
+
|
|
1059
|
+
```typescript
|
|
1060
|
+
import { RpcClient, VenusTransport } from 'venus-sdk'
|
|
1061
|
+
|
|
1062
|
+
// Low-level RPC client
|
|
1063
|
+
const transport = new VenusTransport()
|
|
1064
|
+
const rpcClient = new RpcClient(transport)
|
|
1065
|
+
|
|
1066
|
+
// Send request
|
|
1067
|
+
const response = await rpcClient.request({
|
|
1068
|
+
method: 'simulation.getState',
|
|
1069
|
+
params: { roomId: 'room_123' }
|
|
1070
|
+
})
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
---
|
|
1074
|
+
|
|
1075
|
+
## Build Configuration
|
|
1076
|
+
|
|
1077
|
+
The package is built with `tsup` and provides multiple module formats:
|
|
1078
|
+
|
|
1079
|
+
```json
|
|
1080
|
+
{
|
|
1081
|
+
"main": "./dist/index.cjs",
|
|
1082
|
+
"module": "./dist/index.mjs",
|
|
1083
|
+
"types": "./dist/index.d.ts",
|
|
1084
|
+
"exports": {
|
|
1085
|
+
".": {
|
|
1086
|
+
"types": "./dist/index.d.ts",
|
|
1087
|
+
"import": "./dist/index.mjs",
|
|
1088
|
+
"require": "./dist/index.cjs"
|
|
1089
|
+
},
|
|
1090
|
+
"./api": {
|
|
1091
|
+
"types": "./dist/venus-api/index.d.ts",
|
|
1092
|
+
"import": "./dist/venus-api/index.mjs",
|
|
1093
|
+
"require": "./dist/venus-api/index.cjs"
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
---
|
|
1100
|
+
|
|
1101
|
+
## TypeScript Configuration
|
|
1102
|
+
|
|
1103
|
+
The package uses TypeScript 5.9+ with strict type checking:
|
|
1104
|
+
|
|
1105
|
+
```json
|
|
1106
|
+
{
|
|
1107
|
+
"compilerOptions": {
|
|
1108
|
+
"strict": true,
|
|
1109
|
+
"target": "ES2020",
|
|
1110
|
+
"module": "ESNext",
|
|
1111
|
+
"moduleResolution": "bundler"
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
---
|
|
1117
|
+
|
|
1118
|
+
## Testing
|
|
1119
|
+
|
|
1120
|
+
```bash
|
|
1121
|
+
# Run unit tests
|
|
1122
|
+
npm test
|
|
1123
|
+
|
|
1124
|
+
# Run tests in watch mode
|
|
1125
|
+
npm test -- --watch
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
Tests are written using Jest with JSDOM environment for browser API simulation.
|
|
1129
|
+
|
|
1130
|
+
---
|
|
1131
|
+
|
|
1132
|
+
## Embedded Libraries (Vite)
|
|
1133
|
+
|
|
1134
|
+
For **Vite projects**, use the included plugin to automatically reduce bundle size by using embedded libraries:
|
|
1135
|
+
|
|
1136
|
+
```bash
|
|
1137
|
+
npm install @series-inc/venus-sdk
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
```typescript
|
|
1141
|
+
// vite.config.ts
|
|
1142
|
+
import { venusLibrariesPlugin } from '@series-inc/venus-sdk/vite';
|
|
1143
|
+
|
|
1144
|
+
export default defineConfig({
|
|
1145
|
+
plugins: [venusLibrariesPlugin({ appName: 'my-game' })],
|
|
1146
|
+
build: { target: 'es2022' } // Required for top-level await
|
|
1147
|
+
});
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
**Build commands:**
|
|
1151
|
+
- `npm run build` → Embedded libraries (default, optimal)
|
|
1152
|
+
- `VENUS_DISABLE_EMBEDDED_LIBS=true npm run build` → Bundled (standalone)
|
|
1153
|
+
|
|
1154
|
+
**Supported libraries:** Phaser, React, ReactDOM, Three.js, Matter.js, inkjs, Zustand
|
|
1155
|
+
|
|
1156
|
+
See `src/vite/README.md` for details.
|
|
1157
|
+
|
|
1158
|
+
### For Non-Vite Bundlers
|
|
1159
|
+
|
|
1160
|
+
The Vite plugin does three things you can recreate in any bundler:
|
|
1161
|
+
|
|
1162
|
+
**1. Externalize library imports** - Replace imports with virtual modules:
|
|
1163
|
+
```javascript
|
|
1164
|
+
// Input: import Phaser from 'phaser';
|
|
1165
|
+
// Output: const lib = await (async () => {
|
|
1166
|
+
// await window.__venusLibraryShim.ready();
|
|
1167
|
+
// return window.__venusLibraryExports['phaser@3.90.0'];
|
|
1168
|
+
// })();
|
|
1169
|
+
// export default lib;
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
**2. Inject config into HTML `<head>`:**
|
|
1173
|
+
```html
|
|
1174
|
+
<script>
|
|
1175
|
+
window.__venusLibrariesConfig = {
|
|
1176
|
+
enabled: true,
|
|
1177
|
+
required: ["phaser@3.90.0"],
|
|
1178
|
+
manifest: { "phaser@3.90.0": { cdnPath: "...", globalVar: "Phaser" } },
|
|
1179
|
+
cdnBase: "https://venus-static-01293ak.web.app/libs/"
|
|
1180
|
+
};
|
|
1181
|
+
</script>
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
**3. Inject minimal shim** (loads from CDN, resolves `ready()` promise):
|
|
1185
|
+
```javascript
|
|
1186
|
+
window.__venusLibraryShim = { ready: () => Promise };
|
|
1187
|
+
// Fetch libraries from CDN, evaluate, register in __venusLibraryExports
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
See `embeddedLibrariesManifest.ts` for library definitions and `webviewLibraryShimSource.ts` for the full shim (used on mobile).
|
|
1191
|
+
|
|
1192
|
+
---
|
|
28
1193
|
|
|
29
|
-
|
|
1194
|
+
## Related
|
|
30
1195
|
|
|
31
|
-
|
|
1196
|
+
- [Main Repository](https://github.com/SeriesAI/venus-sdk)
|
|
1197
|
+
- [npm Package](https://www.npmjs.com/package/venus-sdk)
|
|
1198
|
+
- [Issue Tracker](https://github.com/SeriesAI/venus-sdk/issues)
|
|
32
1199
|
|
|
33
|
-
- [Join our Discord!](https://discord.gg/NcjhKQHx)
|