@oasiz/sdk 1.4.0 → 1.5.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 CHANGED
@@ -1,47 +1,63 @@
1
- # @oasiz/sdk
1
+ # Oasiz game SDKs
2
2
 
3
- Typed SDK for integrating games with the Oasiz platform. Handles score submission, haptic feedback, cross-session state persistence, multiplayer room codes, navigation hooks, and app lifecycle events for local development.
3
+ Games on Oasiz can integrate using either of these official SDKs:
4
4
 
5
- ## Install
5
+ | Platform | Package | Use for |
6
+ | --- | --- | --- |
7
+ | **HTML5 / TypeScript** | [`@oasiz/sdk`](#html5--typescript-oasizsdk) (npm) | Canvas, Phaser, custom JS/TS, any browser game |
8
+ | **Unity WebGL** | [Unity runtime](#unity-webgl-sdk) in this repo (`packages/OasizSDK/`) | Unity projects targeting WebGL |
9
+
10
+ Both talk to the same host bridges (`window.submitScore`, `__oasizLeaveGame`, layout APIs, custom DOM events such as `oasiz:pause`, etc.). Unsupported hosts no-op safely; local dev usually logs warnings instead of crashing.
11
+
12
+ ---
13
+
14
+ ## HTML5 / TypeScript (`@oasiz/sdk`)
15
+
16
+ Typed SDK for integrating browser games with the Oasiz platform: score, haptics, cross-session state, multiplayer hooks, layout (safe area, leaderboard visibility), navigation (back / leave), and lifecycle events.
17
+
18
+ ### Install
6
19
 
7
20
  ```bash
8
- npm install @oasiz/sdk@^0.1.0
21
+ npm install @oasiz/sdk
9
22
  ```
10
23
 
11
- ## Quick start
24
+ ### Quick start
12
25
 
13
26
  ```ts
14
27
  import { oasiz } from "@oasiz/sdk";
15
28
 
16
- // 1. Emit score normalization config once on init
17
- oasiz.emitScoreConfig({
18
- anchors: [
19
- { raw: 30, normalized: 100 },
20
- { raw: 60, normalized: 300 },
21
- { raw: 120, normalized: 600 },
22
- { raw: 300, normalized: 950 },
23
- ],
24
- });
25
-
26
- // 2. Load persisted state at the start of each session
29
+ // 1. Load persisted state at the start of each session
27
30
  const state = oasiz.loadGameState();
28
31
  let level = typeof state.level === "number" ? state.level : 1;
29
32
 
30
- // 3. Save state at checkpoints
33
+ // 2. Save state at checkpoints
31
34
  oasiz.saveGameState({ level, coins: 42 });
32
35
 
33
- // 4. Trigger haptics on key events
36
+ // 3. Trigger haptics on key events
34
37
  oasiz.triggerHaptic("medium");
35
38
 
39
+ // 4. Respect the host's top safe area
40
+ document.documentElement.style.setProperty(
41
+ "--safe-top",
42
+ `${oasiz.safeAreaTop}px`,
43
+ );
44
+
36
45
  // 5. Submit score when the game ends
37
46
  oasiz.submitScore(score);
38
- ```
39
47
 
40
- ---
48
+ // 6. Optionally hide the leaderboard while a custom overlay is open
49
+ oasiz.setLeaderboardVisible(false);
50
+
51
+ // 7. Optionally surface console logs in-game while debugging
52
+ oasiz.enableLogOverlay({
53
+ enabled: new URLSearchParams(window.location.search).has("oasizLogs"),
54
+ collapsed: true,
55
+ });
56
+ ```
41
57
 
42
- ## Score
58
+ ### Score
43
59
 
44
- ### `oasiz.submitScore(score: number)`
60
+ #### `oasiz.submitScore(score: number)`
45
61
 
46
62
  Submit the player's final score at game over. Call this exactly once per session, when the game ends. The platform handles leaderboard persistence — do not track high scores locally.
47
63
 
@@ -54,39 +70,9 @@ private onGameOver(): void {
54
70
  - `score` must be a non-negative integer. Floats are floored automatically.
55
71
  - Do not call on intermediate scores or level completions, only on final game over.
56
72
 
57
- ---
58
-
59
- ### `oasiz.emitScoreConfig(config)`
60
-
61
- Maps raw score values to the platform's normalized 0–1000 scale. Call once during initialization, not every frame.
62
-
63
- ```ts
64
- oasiz.emitScoreConfig({
65
- anchors: [
66
- { raw: 10, normalized: 100 }, // beginner
67
- { raw: 30, normalized: 300 }, // good
68
- { raw: 75, normalized: 600 }, // great
69
- { raw: 200, normalized: 950 }, // godlike
70
- ],
71
- });
72
- ```
73
-
74
- **Anchor rules:**
75
- - Exactly 4 anchors required.
76
- - `raw` values must be strictly increasing.
77
- - `normalized` values must end at exactly `950`.
78
- - Choose thresholds based on realistic player skill bands.
79
-
80
- **Practical guidance by game type:**
81
- - Survival / time games → use seconds survived as `raw`
82
- - Score accumulation games → use points as `raw`
83
- - Puzzle games → use level reached or stars earned as `raw`
84
-
85
- ---
86
-
87
- ## Haptics
73
+ ### Haptics
88
74
 
89
- ### `oasiz.triggerHaptic(type: HapticType)`
75
+ #### `oasiz.triggerHaptic(type: HapticType)`
90
76
 
91
77
  Trigger native haptic feedback. Always guard with the user's haptics setting.
92
78
 
@@ -95,7 +81,7 @@ type HapticType = "light" | "medium" | "heavy" | "success" | "error";
95
81
  ```
96
82
 
97
83
  | Type | When to use |
98
- |---|---|
84
+ | --- | --- |
99
85
  | `"light"` | UI button taps, menu navigation, D-pad press |
100
86
  | `"medium"` | Collecting items, standard collisions, scoring |
101
87
  | `"heavy"` | Explosions, major impacts, screen shake |
@@ -126,20 +112,45 @@ private onGameOver(): void {
126
112
 
127
113
  Haptics are throttled internally (50ms cooldown) to prevent spam.
128
114
 
129
- ---
115
+ ### Debugging
116
+
117
+ #### `oasiz.enableLogOverlay(options?: LogOverlayOptions)`
130
118
 
131
- ## Game state persistence
119
+ Mount an opt-in in-game console viewer for local debugging, QA sessions, or creator support. It mirrors `console.log`, `console.info`, `console.warn`, `console.error`, and `console.debug` into a floating overlay inside the game iframe. The overlay can be collapsed, repositioned by dragging the top bar, and resized from the bottom-right corner while the action buttons remain clickable.
120
+
121
+ ```ts
122
+ const logOverlay = oasiz.enableLogOverlay({
123
+ enabled: new URLSearchParams(window.location.search).has("oasizLogs"),
124
+ collapsed: true,
125
+ });
126
+
127
+ console.log("[Boot] Scene ready");
128
+
129
+ // Optional cleanup if your game tears down and remounts
130
+ logOverlay.destroy();
131
+ ```
132
+
133
+ Options:
134
+
135
+ - `enabled`: defaults to `true`. Pass your own flag or query-param check here.
136
+ - `collapsed`: start with only the toggle pill visible.
137
+ - `maxEntries`: cap retained log lines. Defaults to `200`.
138
+ - `title`: optional label shown at the top of the panel. Defaults to `SDK Logs`.
139
+
140
+ The returned handle supports `show()`, `hide()`, `clear()`, `isVisible()`, and `destroy()`.
141
+
142
+ ### Game state persistence
132
143
 
133
144
  Persist cross-session data such as unlocked levels, inventory, or lifetime stats. State is stored per-user per-game in the Oasiz backend — available across devices and app reinstalls.
134
145
 
135
- ### `oasiz.loadGameState(): Record<string, unknown>`
146
+ #### `oasiz.loadGameState(): Record<string, unknown>`
136
147
 
137
148
  Returns the player's saved state synchronously. Returns `{}` if no state has been saved yet. Call once at the start of the game.
138
149
 
139
150
  ```ts
140
151
  private initFromSavedState(): void {
141
152
  const state = oasiz.loadGameState();
142
- this.level = typeof state.level === "number" ? state.level : 1;
153
+ this.level = typeof state.level === "number" ? state.level : 1;
143
154
  this.lifetimeHits = typeof state.lifetimeHits === "number" ? state.lifetimeHits : 0;
144
155
  this.unlockedSkins = Array.isArray(state.unlockedSkins) ? state.unlockedSkins : [];
145
156
  }
@@ -147,7 +158,7 @@ private initFromSavedState(): void {
147
158
 
148
159
  Always validate the shape of loaded data — it may be `{}` on first play.
149
160
 
150
- ### `oasiz.saveGameState(state: Record<string, unknown>)`
161
+ #### `oasiz.saveGameState(state: Record<string, unknown>)`
151
162
 
152
163
  Queues a debounced save. Saves are batched automatically — call freely at checkpoints without worrying about request spam.
153
164
 
@@ -164,11 +175,12 @@ private onLevelComplete(): void {
164
175
  ```
165
176
 
166
177
  **Rules:**
178
+
167
179
  - State must be a plain JSON object (not an array or primitive).
168
180
  - Do not use `localStorage` for cross-session progress — use `saveGameState` so data syncs across platforms.
169
181
  - Do not store scores here — scores are submitted via `submitScore`.
170
182
 
171
- ### `oasiz.flushGameState()`
183
+ #### `oasiz.flushGameState()`
172
184
 
173
185
  Forces an immediate write, bypassing the debounce. Use at important checkpoints like game over or before the page unloads.
174
186
 
@@ -180,19 +192,63 @@ private onGameOver(): void {
180
192
  }
181
193
  ```
182
194
 
183
- ---
195
+ ### Layout
196
+
197
+ Use the runtime safe-area value instead of hardcoded top offsets. The host reports the current top inset in **CSS pixels** for persistent chrome such as the back button and top controls.
198
+
199
+ #### `oasiz.getSafeAreaTop(): number`
200
+
201
+ Returns the current top inset. The host may expose `window.getSafeAreaTop()` or `window.__OASIZ_SAFE_AREA_TOP__`. Unsupported hosts return `0`.
202
+
203
+ ```ts
204
+ const safeTop = oasiz.getSafeAreaTop();
205
+ document.documentElement.style.setProperty("--safe-top", `${safeTop}px`);
206
+ ```
184
207
 
185
- ## Lifecycle
208
+ #### `oasiz.safeAreaTop`
209
+
210
+ Getter alias for `getSafeAreaTop()`.
211
+
212
+ Recommended CSS pattern:
213
+
214
+ ```css
215
+ :root {
216
+ --safe-top: 0px;
217
+ }
218
+
219
+ #top-bar {
220
+ padding-top: var(--safe-top);
221
+ }
222
+ ```
223
+
224
+ #### `oasiz.setLeaderboardVisible(visible: boolean): void`
225
+
226
+ Show or hide the host leaderboard UI from inside the game. This only affects the leaderboard; back and social controls remain visible. Calls `window.__oasizSetLeaderboardVisible` when present.
227
+
228
+ ```ts
229
+ function openCustomOverlay(): void {
230
+ oasiz.setLeaderboardVisible(false);
231
+ }
232
+
233
+ function closeCustomOverlay(): void {
234
+ oasiz.setLeaderboardVisible(true);
235
+ }
236
+ ```
237
+
238
+ Unsupported hosts safely no-op.
239
+
240
+ ### Lifecycle
186
241
 
187
242
  The platform dispatches lifecycle events when the app goes to the background or returns to the foreground. Subscribe to pause game loops and audio accordingly.
188
243
 
189
- ### `oasiz.onPause(callback: () => void): Unsubscribe`
190
- ### `oasiz.onResume(callback: () => void): Unsubscribe`
244
+ #### `oasiz.onPause(callback: () => void): Unsubscribe`
245
+
246
+ #### `oasiz.onResume(callback: () => void): Unsubscribe`
191
247
 
192
248
  Both return an unsubscribe function.
193
249
 
194
250
  ```ts
195
- const offPause = oasiz.onPause(() => {
251
+ const offPause = oasiz.onPause(() => {
196
252
  this.gameLoop.stop();
197
253
  this.bgMusic.pause();
198
254
  });
@@ -207,18 +263,18 @@ offPause();
207
263
  offResume();
208
264
  ```
209
265
 
210
- ---
211
-
212
- ## Navigation
266
+ ### Navigation
213
267
 
214
268
  Use navigation hooks when your game needs to control back behavior (Android back / web Escape) or participate in host-driven close events.
215
269
 
216
- ### `oasiz.onBackButton(callback: () => void): Unsubscribe`
270
+ #### `oasiz.onBackButton(callback: () => void): Unsubscribe`
217
271
 
218
272
  Registers a callback for platform back actions. While at least one back listener is subscribed, back actions are routed to your game instead of immediately closing it.
219
273
 
220
274
  Use this for pause menus, in-game overlays, or custom back-stack behavior.
221
275
 
276
+ **If your callback throws**, the SDK calls `leaveGame()` (host close) and **rethrows** the error so you still see it in devtools or error reporting. Non-`Error` throws are normalized to an `Error` (strings become the message; otherwise `"Back button callback failed."`).
277
+
222
278
  ```ts
223
279
  const offBack = oasiz.onBackButton(() => {
224
280
  if (this.isPauseMenuOpen) {
@@ -232,7 +288,7 @@ const offBack = oasiz.onBackButton(() => {
232
288
  offBack();
233
289
  ```
234
290
 
235
- ### `oasiz.leaveGame(): void`
291
+ #### `oasiz.leaveGame(): void`
236
292
 
237
293
  Programmatically request the host to close the current game (for example, from a Quit button inside your game UI).
238
294
 
@@ -242,7 +298,7 @@ quitButton.addEventListener("click", () => {
242
298
  });
243
299
  ```
244
300
 
245
- ### `oasiz.onLeaveGame(callback: () => void): Unsubscribe`
301
+ #### `oasiz.onLeaveGame(callback: () => void): Unsubscribe`
246
302
 
247
303
  Registers a callback fired when the host initiates closing the game (for example, close button, gesture, or host navigation). Use this for lightweight cleanup.
248
304
 
@@ -256,14 +312,14 @@ const offLeave = oasiz.onLeaveGame(() => {
256
312
  offLeave();
257
313
  ```
258
314
 
259
- ---
260
-
261
- ## Multiplayer
315
+ ### Multiplayer
262
316
 
263
- ### `oasiz.shareRoomCode(code: string | null)`
317
+ #### `oasiz.shareRoomCode(code: string | null, options?: { inviteOverride?: boolean })`
264
318
 
265
319
  Notify the platform of the active multiplayer room so friends can join via the invite system. Pass `null` when leaving a room.
266
320
 
321
+ Set `inviteOverride: true` when your game wants to hide the platform invite pill and render its own invite button/UI. The platform still tracks the room code, but your game owns the invite entry point.
322
+
267
323
  ```ts
268
324
  import { insertCoin, getRoomCode } from "playroomkit";
269
325
  import { oasiz } from "@oasiz/sdk";
@@ -275,7 +331,26 @@ oasiz.shareRoomCode(getRoomCode());
275
331
  oasiz.shareRoomCode(null);
276
332
  ```
277
333
 
278
- ### Read-only injected values
334
+ ```ts
335
+ // Game-owned invite UI: hide the platform pill, keep room tracking
336
+ oasiz.shareRoomCode(getRoomCode(), { inviteOverride: true });
337
+ ```
338
+
339
+ #### `oasiz.openInviteModal(): void`
340
+
341
+ Opens the platform invite-friends UI when the bridge is available. Typically used together with `shareRoomCode` (for example, your own invite button calls this).
342
+
343
+ ```ts
344
+ import { openInviteModal, shareRoomCode } from "@oasiz/sdk";
345
+
346
+ shareRoomCode("ABCD", { inviteOverride: true });
347
+
348
+ inviteButton.addEventListener("click", () => {
349
+ openInviteModal();
350
+ });
351
+ ```
352
+
353
+ #### Read-only injected values
279
354
 
280
355
  These are populated by the platform before the game loads. Always check for `undefined` before using.
281
356
 
@@ -289,25 +364,26 @@ if (oasiz.roomCode) {
289
364
  }
290
365
 
291
366
  // Player identity for multiplayer games
292
- const name = oasiz.playerName;
367
+ const name = oasiz.playerName;
293
368
  const avatar = oasiz.playerAvatar;
294
369
  ```
295
370
 
296
- ---
297
-
298
- ## Named exports
371
+ ### Named exports
299
372
 
300
373
  All methods are also available as named exports if you prefer not to use the `oasiz` namespace object:
301
374
 
302
375
  ```ts
303
376
  import {
304
377
  submitScore,
305
- emitScoreConfig,
306
378
  triggerHaptic,
307
379
  loadGameState,
308
380
  saveGameState,
309
381
  flushGameState,
310
382
  shareRoomCode,
383
+ openInviteModal,
384
+ enableLogOverlay,
385
+ getSafeAreaTop,
386
+ setLeaderboardVisible,
311
387
  onPause,
312
388
  onResume,
313
389
  onBackButton,
@@ -320,18 +396,70 @@ import {
320
396
  } from "@oasiz/sdk";
321
397
  ```
322
398
 
323
- ---
324
-
325
- ## TypeScript types
399
+ ### TypeScript types
326
400
 
327
401
  ```ts
328
- import type { HapticType, ScoreConfig, ScoreAnchor, GameState } from "@oasiz/sdk";
402
+ import type {
403
+ GameState,
404
+ HapticType,
405
+ LogOverlayHandle,
406
+ LogOverlayOptions,
407
+ ShareRoomCodeOptions,
408
+ Unsubscribe,
409
+ } from "@oasiz/sdk";
329
410
  ```
330
411
 
331
412
  ---
332
413
 
414
+ ## Unity WebGL SDK
415
+
416
+ C# API and **WebGL-only** `OasizBridge.jslib` live in this repository at **`packages/OasizSDK/`**. Copy the **`OasizSDK`** folder into your Unity project under **`Assets/`** (for example `Assets/OasizSDK`).
417
+
418
+ ### Setup
419
+
420
+ 1. Copy `packages/OasizSDK` from this repo into `Assets/OasizSDK`.
421
+ 2. Ensure the **WebGL** platform is selected for release builds; the `.jslib` under `Runtime/Plugins/WebGL/` is included automatically for WebGL.
422
+ 3. Add an **`OasizSDK`** component to a persistent GameObject early (for example a bootstrap scene), **or** rely on `OasizSDK.Instance` which creates a `DontDestroyOnLoad` object. The component registers listeners for `oasiz:pause`, `oasiz:resume`, `oasiz:back`, and `oasiz:leave` via `SendMessage`.
423
+
424
+ ### API parity (TypeScript → C#)
425
+
426
+ | HTML5 (`@oasiz/sdk`) | Unity (`Oasiz` namespace) |
427
+ | --- | --- |
428
+ | `oasiz.submitScore(n)` | `OasizSDK.SubmitScore(int)` |
429
+ | `oasiz.triggerHaptic(type)` | `OasizSDK.TriggerHaptic(HapticType)` |
430
+ | `oasiz.loadGameState()` | `OasizSDK.LoadGameState()` → `Dictionary<string, object>` |
431
+ | `oasiz.saveGameState(obj)` | `OasizSDK.SaveGameState(Dictionary<string, object>)` |
432
+ | `oasiz.flushGameState()` | `OasizSDK.FlushGameState()` |
433
+ | `oasiz.getSafeAreaTop()` / `safeAreaTop` | `OasizSDK.GetSafeAreaTop()` / `OasizSDK.SafeAreaTop` (`float`, CSS px) |
434
+ | `oasiz.setLeaderboardVisible(v)` | `OasizSDK.SetLeaderboardVisible(bool)` |
435
+ | `oasiz.onPause` / `onResume` | `OasizSDK.OnPause` / `OnResume` static events |
436
+ | `oasiz.onBackButton` | `OasizSDK.OnBackButton` or `SubscribeBackButton(Action)` (reference-counts `__oasizSetBackOverride`) |
437
+ | `oasiz.onLeaveGame` | `OasizSDK.OnLeaveGame` |
438
+ | `oasiz.leaveGame()` | `OasizSDK.LeaveGame()` |
439
+ | `oasiz.shareRoomCode` | `OasizSDK.ShareRoomCode(string, ShareRoomCodeOptions)` |
440
+ | `oasiz.openInviteModal()` | `OasizSDK.OpenInviteModal()` |
441
+ | `oasiz.gameId` / `roomCode` / … | `OasizSDK.GameId` / `RoomCode` / `PlayerName` / `PlayerAvatar` |
442
+ | — | `OasizSDK.EmitScoreConfig(ScoreConfig)` → `window.emitScoreConfig` (Unity-only helper for normalized score UI) |
443
+ | `oasiz.enableLogOverlay` | `OasizSDK.EnableLogOverlay(LogOverlayOptions)` (see note below) |
444
+
445
+ ### Back button and errors
446
+
447
+ Matching the HTML5 SDK: if any **`OnBackButton`** handler throws, **`OasizSDK.LeaveGame()`** is invoked and the **original exception is rethrown** (`throw;` preserves the stack trace). Use **`SubscribeBackButton`** when you want an unsubscribe delegate; you can also use `OnBackButton +=` / `-=` directly.
448
+
449
+ ### Editor vs WebGL builds
450
+
451
+ In the **Unity Editor**, bridge calls are mostly **logged** and return safe defaults (for example safe area `0`, `null` platform IDs). Real host integration applies to **WebGL player** builds running inside Oasiz.
452
+
453
+ ### Log overlay (Unity)
454
+
455
+ The C# API for the log overlay exists for API compatibility, but the **default `OasizBridge.jslib` in this repo does not inject DOM UI** — `EnableLogOverlay` / `AppendLogOverlay` are no-ops at the JavaScript layer. Use Unity’s console and device logs for debugging unless you replace or extend the `.jslib` on your side.
456
+
457
+ ---
458
+
333
459
  ## Local development
334
460
 
461
+ ### HTML5 / TypeScript
462
+
335
463
  All methods safely no-op when the platform bridges are not injected. In development mode a console warning is logged so you know the call was made:
336
464
 
337
465
  ```
@@ -339,3 +467,7 @@ All methods safely no-op when the platform bridges are not injected. In developm
339
467
  ```
340
468
 
341
469
  No crashes, no special setup required for local dev.
470
+
471
+ ### Unity WebGL
472
+
473
+ The `.jslib` logs warnings when `window.*` bridges are missing (for example `submitScore`, `__oasizLeaveGame`). The Editor path avoids calling native plugins and prints `Debug.Log` for most operations instead.