@oasiz/sdk 1.0.0 → 1.0.1

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,11 +1,11 @@
1
1
  # @oasiz/sdk
2
2
 
3
- Typed SDK for integrating games with the Oasiz platform. Handles score submission, haptic feedback, cross-session state persistence, multiplayer room codes, and app lifecycle events for local development.
3
+ Typed SDK for integrating games with the Oasiz platform. Handles score submission, haptic feedback, cross-session state persistence, multiplayer room codes, and app lifecycle events.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install @oasiz/sdk@^0.1.0
8
+ npm install @oasiz/sdk
9
9
  ```
10
10
 
11
11
  ## Quick start
@@ -13,27 +13,17 @@ npm install @oasiz/sdk@^0.1.0
13
13
  ```ts
14
14
  import { oasiz } from "@oasiz/sdk";
15
15
 
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
16
+ // 1. Load persisted state at the start of each session
27
17
  const state = oasiz.loadGameState();
28
18
  let level = typeof state.level === "number" ? state.level : 1;
29
19
 
30
- // 3. Save state at checkpoints
20
+ // 2. Save state at checkpoints
31
21
  oasiz.saveGameState({ level, coins: 42 });
32
22
 
33
- // 4. Trigger haptics on key events
23
+ // 3. Trigger haptics on key events
34
24
  oasiz.triggerHaptic("medium");
35
25
 
36
- // 5. Submit score when the game ends
26
+ // 4. Submit score when the game ends
37
27
  oasiz.submitScore(score);
38
28
  ```
39
29
 
@@ -43,44 +33,14 @@ oasiz.submitScore(score);
43
33
 
44
34
  ### `oasiz.submitScore(score: number)`
45
35
 
46
- 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.
36
+ Submit the player's score. The platform handles leaderboard persistence — do not track high scores locally.
47
37
 
48
38
  ```ts
49
- private onGameOver(): void {
50
- oasiz.submitScore(Math.floor(this.score));
51
- }
39
+ oasiz.submitScore(this.score);
52
40
  ```
53
41
 
54
42
  - `score` must be a non-negative integer. Floats are floored automatically.
55
- - Do not call on intermediate scores or level completions, only on final game over.
56
-
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`
43
+ - When and how often you call this depends on your game type (once at game over, end of each level, or throttled for long sessions).
84
44
 
85
45
  ---
86
46
 
@@ -99,23 +59,14 @@ type HapticType = "light" | "medium" | "heavy" | "success" | "error";
99
59
  | `"light"` | UI button taps, menu navigation, D-pad press |
100
60
  | `"medium"` | Collecting items, standard collisions, scoring |
101
61
  | `"heavy"` | Explosions, major impacts, screen shake |
102
- | `"success"` | Level complete, new high score, achievement unlocked |
62
+ | `"success"` | Level complete, achievement unlocked |
103
63
  | `"error"` | Damage taken, game over, invalid action |
104
64
 
105
65
  ```ts
106
- // UI buttons — always light
107
66
  button.addEventListener("click", () => {
108
67
  oasiz.triggerHaptic("light");
109
68
  });
110
69
 
111
- // Tiered hit feedback
112
- private onBallHit(zone: "center" | "edge"): void {
113
- if (this.settings.haptics) {
114
- oasiz.triggerHaptic(zone === "center" ? "success" : "medium");
115
- }
116
- }
117
-
118
- // Game over
119
70
  private onGameOver(): void {
120
71
  oasiz.submitScore(this.score);
121
72
  if (this.settings.haptics) {
@@ -124,75 +75,54 @@ private onGameOver(): void {
124
75
  }
125
76
  ```
126
77
 
127
- Haptics are throttled internally (50ms cooldown) to prevent spam.
128
-
129
78
  ---
130
79
 
131
80
  ## Game state persistence
132
81
 
133
- 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.
82
+ Persist cross-session data such as unlocked levels, inventory, or lifetime stats. Available across devices and app reinstalls.
134
83
 
135
84
  ### `oasiz.loadGameState(): Record<string, unknown>`
136
85
 
137
- Returns the player's saved state synchronously. Returns `{}` if no state has been saved yet. Call once at the start of the game.
86
+ Returns the player's saved state synchronously. Returns `{}` if no state has been saved yet.
138
87
 
139
88
  ```ts
140
- private initFromSavedState(): void {
141
- const state = oasiz.loadGameState();
142
- this.level = typeof state.level === "number" ? state.level : 1;
143
- this.lifetimeHits = typeof state.lifetimeHits === "number" ? state.lifetimeHits : 0;
144
- this.unlockedSkins = Array.isArray(state.unlockedSkins) ? state.unlockedSkins : [];
145
- }
89
+ const state = oasiz.loadGameState();
90
+ this.level = typeof state.level === "number" ? state.level : 1;
146
91
  ```
147
92
 
148
- Always validate the shape of loaded data — it may be `{}` on first play.
149
-
150
93
  ### `oasiz.saveGameState(state: Record<string, unknown>)`
151
94
 
152
- Queues a debounced save. Saves are batched automatically — call freely at checkpoints without worrying about request spam.
95
+ Queues a debounced save. Call freely at checkpoints.
153
96
 
154
97
  ```ts
155
- // Save after each level completion
156
- private onLevelComplete(): void {
157
- this.level += 1;
158
- oasiz.saveGameState({
159
- level: this.level,
160
- lifetimeHits: this.lifetimeHits,
161
- unlockedSkins: this.unlockedSkins,
162
- });
163
- }
98
+ oasiz.saveGameState({ level: this.level, unlockedSkins: this.unlockedSkins });
164
99
  ```
165
100
 
166
- **Rules:**
167
101
  - State must be a plain JSON object (not an array or primitive).
168
- - Do not use `localStorage` for cross-session progress — use `saveGameState` so data syncs across platforms.
169
- - Do not store scores here — scores are submitted via `submitScore`.
102
+ - Use `saveGameState` instead of `localStorage` for cross-session progress.
103
+ - Do not store scores here — use `submitScore`.
170
104
 
171
105
  ### `oasiz.flushGameState()`
172
106
 
173
- Forces an immediate write, bypassing the debounce. Use at important checkpoints like game over or before the page unloads.
107
+ Forces an immediate write, bypassing the debounce. Use at game over or before page unload.
174
108
 
175
109
  ```ts
176
- private onGameOver(): void {
177
- oasiz.saveGameState({ level: this.level, lifetimeHits: this.lifetimeHits });
178
- oasiz.flushGameState(); // ensure it lands before the page closes
179
- oasiz.submitScore(this.score);
180
- }
110
+ oasiz.saveGameState({ level: this.level });
111
+ oasiz.flushGameState();
112
+ oasiz.submitScore(this.score);
181
113
  ```
182
114
 
183
115
  ---
184
116
 
185
117
  ## Lifecycle
186
118
 
187
- 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
-
189
119
  ### `oasiz.onPause(callback: () => void): Unsubscribe`
190
120
  ### `oasiz.onResume(callback: () => void): Unsubscribe`
191
121
 
192
122
  Both return an unsubscribe function.
193
123
 
194
124
  ```ts
195
- const offPause = oasiz.onPause(() => {
125
+ const offPause = oasiz.onPause(() => {
196
126
  this.gameLoop.stop();
197
127
  this.bgMusic.pause();
198
128
  });
@@ -202,7 +132,6 @@ const offResume = oasiz.onResume(() => {
202
132
  this.bgMusic.play();
203
133
  });
204
134
 
205
- // Clean up when the game is destroyed
206
135
  offPause();
207
136
  offResume();
208
137
  ```
@@ -213,47 +142,29 @@ offResume();
213
142
 
214
143
  ### `oasiz.shareRoomCode(code: string | null)`
215
144
 
216
- Notify the platform of the active multiplayer room so friends can join via the invite system. Pass `null` when leaving a room.
145
+ Notify the platform of the active multiplayer room. Pass `null` when leaving.
217
146
 
218
147
  ```ts
219
- import { insertCoin, getRoomCode } from "playroomkit";
220
- import { oasiz } from "@oasiz/sdk";
221
-
222
- await insertCoin({ skipLobby: true });
223
148
  oasiz.shareRoomCode(getRoomCode());
224
-
225
- // On disconnect
226
149
  oasiz.shareRoomCode(null);
227
150
  ```
228
151
 
229
- ### Read-only injected values
230
-
231
- These are populated by the platform before the game loads. Always check for `undefined` before using.
152
+ ### Read-only properties
232
153
 
233
- ```ts
234
- // The platform's internal game ID
235
- const gameId = oasiz.gameId;
236
-
237
- // Pre-filled room code for auto-joining a friend's session
238
- if (oasiz.roomCode) {
239
- await connectToRoom(oasiz.roomCode);
240
- }
241
-
242
- // Player identity for multiplayer games
243
- const name = oasiz.playerName;
244
- const avatar = oasiz.playerAvatar;
245
- ```
154
+ | Property | Type | Description |
155
+ |---|---|---|
156
+ | `oasiz.gameId` | string \| undefined | The platform's game ID |
157
+ | `oasiz.roomCode` | string \| undefined | Pre-filled room code from invite |
158
+ | `oasiz.playerName` | string \| undefined | Player's display name |
159
+ | `oasiz.playerAvatar` | string \| undefined | Player's profile picture URL |
246
160
 
247
161
  ---
248
162
 
249
163
  ## Named exports
250
164
 
251
- All methods are also available as named exports if you prefer not to use the `oasiz` namespace object:
252
-
253
165
  ```ts
254
166
  import {
255
167
  submitScore,
256
- emitScoreConfig,
257
168
  triggerHaptic,
258
169
  loadGameState,
259
170
  saveGameState,
@@ -273,17 +184,15 @@ import {
273
184
  ## TypeScript types
274
185
 
275
186
  ```ts
276
- import type { HapticType, ScoreConfig, ScoreAnchor, GameState } from "@oasiz/sdk";
187
+ import type { HapticType, GameState } from "@oasiz/sdk";
277
188
  ```
278
189
 
279
190
  ---
280
191
 
281
192
  ## Local development
282
193
 
283
- 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:
194
+ All methods safely no-op when the platform bridges are not injected. In development mode a console warning is logged:
284
195
 
285
196
  ```
286
197
  [oasiz/sdk] submitScore bridge is unavailable. This is expected in local development.
287
198
  ```
288
-
289
- No crashes, no special setup required for local dev.
package/dist/index.cjs CHANGED
@@ -20,14 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- emitScoreConfig: () => emitScoreConfig,
24
23
  flushGameState: () => flushGameState,
25
24
  getGameId: () => getGameId,
26
25
  getPlayerAvatar: () => getPlayerAvatar,
27
26
  getPlayerName: () => getPlayerName,
28
27
  getRoomCode: () => getRoomCode,
29
28
  loadGameState: () => loadGameState,
30
- oasis: () => oasis,
29
+ oasiz: () => oasiz,
31
30
  onPause: () => onPause,
32
31
  onResume: () => onResume,
33
32
  saveGameState: () => saveGameState,
@@ -134,14 +133,6 @@ function submitScore(score) {
134
133
  }
135
134
  warnMissingBridge("submitScore");
136
135
  }
137
- function emitScoreConfig(config) {
138
- const bridge = getBridgeWindow3();
139
- if (typeof bridge?.emitScoreConfig === "function") {
140
- bridge.emitScoreConfig(config);
141
- return;
142
- }
143
- warnMissingBridge("emitScoreConfig");
144
- }
145
136
 
146
137
  // src/state.ts
147
138
  function isDevelopment4() {
@@ -235,9 +226,8 @@ function onResume(callback) {
235
226
  }
236
227
 
237
228
  // src/index.ts
238
- var oasis = {
229
+ var oasiz = {
239
230
  submitScore,
240
- emitScoreConfig,
241
231
  triggerHaptic,
242
232
  loadGameState,
243
233
  saveGameState,
@@ -260,14 +250,13 @@ var oasis = {
260
250
  };
261
251
  // Annotate the CommonJS export names for ESM import in node:
262
252
  0 && (module.exports = {
263
- emitScoreConfig,
264
253
  flushGameState,
265
254
  getGameId,
266
255
  getPlayerAvatar,
267
256
  getPlayerName,
268
257
  getRoomCode,
269
258
  loadGameState,
270
- oasis,
259
+ oasiz,
271
260
  onPause,
272
261
  onResume,
273
262
  saveGameState,
package/dist/index.d.cts CHANGED
@@ -1,35 +1,245 @@
1
+ /**
2
+ * Intensity level for native haptic feedback.
3
+ *
4
+ * | Type | Use case |
5
+ * |-------------|-------------------------------------------------|
6
+ * | `"light"` | UI button taps, menu navigation, D-pad press |
7
+ * | `"medium"` | Collecting items, standard collisions, scoring |
8
+ * | `"heavy"` | Explosions, major impacts, screen shake |
9
+ * | `"success"` | Level complete, achievement unlocked |
10
+ * | `"error"` | Damage taken, game over, invalid action |
11
+ *
12
+ * @category Haptics
13
+ */
1
14
  type HapticType = "light" | "medium" | "heavy" | "success" | "error";
2
- interface ScoreAnchor {
3
- raw: number;
4
- normalized: 100 | 300 | 600 | 950;
5
- }
6
- interface ScoreConfig {
7
- anchors: [ScoreAnchor, ScoreAnchor, ScoreAnchor, ScoreAnchor];
8
- }
15
+ /**
16
+ * A plain JSON-serializable object used for cross-session game state
17
+ * persistence.
18
+ *
19
+ * @category State
20
+ */
9
21
  type GameState = Record<string, unknown>;
10
22
 
23
+ /**
24
+ * Trigger native haptic feedback on the player's device.
25
+ *
26
+ * Always guard calls with the user's haptics preference toggle.
27
+ * The bridge is a no-op in local development (a console warning is logged).
28
+ *
29
+ * @param type - The haptic intensity level.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * // UI button tap
34
+ * oasiz.triggerHaptic("light");
35
+ *
36
+ * // Tiered hit feedback
37
+ * oasiz.triggerHaptic(isPerfectHit ? "success" : "medium");
38
+ *
39
+ * // Game over
40
+ * oasiz.triggerHaptic("error");
41
+ * ```
42
+ *
43
+ * @category Haptics
44
+ */
11
45
  declare function triggerHaptic(type: HapticType): void;
12
46
 
47
+ /**
48
+ * Notify the platform of the active multiplayer room so friends can join
49
+ * via the invite system. Pass `null` when leaving a room.
50
+ *
51
+ * @param roomCode - The room code string, or `null` to clear.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { insertCoin, getRoomCode } from "playroomkit";
56
+ *
57
+ * await insertCoin({ skipLobby: true });
58
+ * oasiz.shareRoomCode(getRoomCode());
59
+ *
60
+ * // On disconnect
61
+ * oasiz.shareRoomCode(null);
62
+ * ```
63
+ *
64
+ * @category Multiplayer
65
+ */
13
66
  declare function shareRoomCode(roomCode: string | null): void;
67
+ /**
68
+ * Get the platform's internal game ID, injected before the game loads.
69
+ *
70
+ * @returns The game ID string, or `undefined` if not yet injected.
71
+ *
72
+ * @category Multiplayer
73
+ */
14
74
  declare function getGameId(): string | undefined;
75
+ /**
76
+ * Get the pre-filled room code for auto-joining a friend's multiplayer session.
77
+ *
78
+ * @returns The room code string, or `undefined` if not in a multiplayer context.
79
+ *
80
+ * @category Multiplayer
81
+ */
15
82
  declare function getRoomCode(): string | undefined;
83
+ /**
84
+ * Get the current player's display name, injected by the platform.
85
+ *
86
+ * @returns The player name, or `undefined` if not available.
87
+ *
88
+ * @category Multiplayer
89
+ */
16
90
  declare function getPlayerName(): string | undefined;
91
+ /**
92
+ * Get the current player's avatar URL, injected by the platform.
93
+ *
94
+ * @returns The avatar URL string, or `undefined` if not available.
95
+ *
96
+ * @category Multiplayer
97
+ */
17
98
  declare function getPlayerAvatar(): string | undefined;
18
99
 
100
+ /**
101
+ * Submit the player's score.
102
+ *
103
+ * The platform handles leaderboard persistence — do not track high scores locally.
104
+ *
105
+ * - `score` must be a finite number. Floats are floored, negatives are clamped to 0.
106
+ * - In development mode a console warning is logged when the bridge is unavailable.
107
+ *
108
+ * @param score - The player's score (non-negative integer).
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * oasiz.submitScore(Math.floor(this.score));
113
+ * ```
114
+ *
115
+ * @category Score
116
+ */
19
117
  declare function submitScore(score: number): void;
20
- declare function emitScoreConfig(config: ScoreConfig): void;
21
118
 
119
+ /**
120
+ * Load the player's persisted game state.
121
+ *
122
+ * Returns the saved state synchronously. Returns `{}` if no state has been
123
+ * saved yet or if the bridge is unavailable. Always validate the shape of
124
+ * the returned data — it may be empty on first play.
125
+ *
126
+ * Call once at the start of the game.
127
+ *
128
+ * @returns The player's saved {@link GameState}, or an empty object.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * const state = oasiz.loadGameState();
133
+ * this.level = typeof state.level === "number" ? state.level : 1;
134
+ * ```
135
+ *
136
+ * @category State
137
+ */
22
138
  declare function loadGameState(): GameState;
139
+ /**
140
+ * Queue a debounced save of the player's game state.
141
+ *
142
+ * Saves are batched automatically — call freely at checkpoints without
143
+ * worrying about request spam. State must be a plain JSON-serializable object.
144
+ *
145
+ * @param state - A plain object to persist. Do not pass arrays or primitives.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * oasiz.saveGameState({
150
+ * level: this.level,
151
+ * lifetimeHits: this.lifetimeHits,
152
+ * unlockedSkins: this.unlockedSkins,
153
+ * });
154
+ * ```
155
+ *
156
+ * @category State
157
+ */
23
158
  declare function saveGameState(state: GameState): void;
159
+ /**
160
+ * Force an immediate write of the pending game state, bypassing the debounce.
161
+ *
162
+ * Use at critical checkpoints like game over or before the page unloads
163
+ * to ensure data is persisted.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * oasiz.saveGameState({ level: this.level });
168
+ * oasiz.flushGameState(); // ensure it lands before the page closes
169
+ * oasiz.submitScore(this.score);
170
+ * ```
171
+ *
172
+ * @category State
173
+ */
24
174
  declare function flushGameState(): void;
25
175
 
176
+ /**
177
+ * A function that removes an event listener when called.
178
+ * Returned by {@link onPause} and {@link onResume}.
179
+ *
180
+ * @category Lifecycle
181
+ */
26
182
  type Unsubscribe = () => void;
183
+ /**
184
+ * Register a callback that fires when the app goes to the background.
185
+ *
186
+ * Use this to pause game loops, audio, and animations so the game
187
+ * doesn't continue running while the player is away.
188
+ *
189
+ * @param callback - Invoked when the platform dispatches `oasiz:pause`.
190
+ * @returns An {@link Unsubscribe} function that removes the listener.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * const off = oasiz.onPause(() => {
195
+ * this.gameLoop.stop();
196
+ * this.bgMusic.pause();
197
+ * });
198
+ *
199
+ * // Later, clean up:
200
+ * off();
201
+ * ```
202
+ *
203
+ * @category Lifecycle
204
+ */
27
205
  declare function onPause(callback: () => void): Unsubscribe;
206
+ /**
207
+ * Register a callback that fires when the app returns to the foreground.
208
+ *
209
+ * Use this to resume game loops, audio, and animations after a pause.
210
+ *
211
+ * @param callback - Invoked when the platform dispatches `oasiz:resume`.
212
+ * @returns An {@link Unsubscribe} function that removes the listener.
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * const off = oasiz.onResume(() => {
217
+ * this.gameLoop.start();
218
+ * this.bgMusic.play();
219
+ * });
220
+ *
221
+ * // Later, clean up:
222
+ * off();
223
+ * ```
224
+ *
225
+ * @category Lifecycle
226
+ */
28
227
  declare function onResume(callback: () => void): Unsubscribe;
29
228
 
30
- declare const oasis: {
229
+ /**
230
+ * The main SDK namespace object. Provides convenient access to all
231
+ * platform bridge APIs as a single import.
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * import { oasiz } from "@oasiz/sdk";
236
+ *
237
+ * oasiz.triggerHaptic("medium");
238
+ * oasiz.submitScore(score);
239
+ * ```
240
+ */
241
+ declare const oasiz: {
31
242
  submitScore: typeof submitScore;
32
- emitScoreConfig: typeof emitScoreConfig;
33
243
  triggerHaptic: typeof triggerHaptic;
34
244
  loadGameState: typeof loadGameState;
35
245
  saveGameState: typeof saveGameState;
@@ -43,4 +253,4 @@ declare const oasis: {
43
253
  readonly playerAvatar: string | undefined;
44
254
  };
45
255
 
46
- export { type GameState, type HapticType, type ScoreAnchor, type ScoreConfig, type Unsubscribe, emitScoreConfig, flushGameState, getGameId, getPlayerAvatar, getPlayerName, getRoomCode, loadGameState, oasis, onPause, onResume, saveGameState, shareRoomCode, submitScore, triggerHaptic };
256
+ export { type GameState, type HapticType, type Unsubscribe, flushGameState, getGameId, getPlayerAvatar, getPlayerName, getRoomCode, loadGameState, oasiz, onPause, onResume, saveGameState, shareRoomCode, submitScore, triggerHaptic };
package/dist/index.d.ts CHANGED
@@ -1,35 +1,245 @@
1
+ /**
2
+ * Intensity level for native haptic feedback.
3
+ *
4
+ * | Type | Use case |
5
+ * |-------------|-------------------------------------------------|
6
+ * | `"light"` | UI button taps, menu navigation, D-pad press |
7
+ * | `"medium"` | Collecting items, standard collisions, scoring |
8
+ * | `"heavy"` | Explosions, major impacts, screen shake |
9
+ * | `"success"` | Level complete, achievement unlocked |
10
+ * | `"error"` | Damage taken, game over, invalid action |
11
+ *
12
+ * @category Haptics
13
+ */
1
14
  type HapticType = "light" | "medium" | "heavy" | "success" | "error";
2
- interface ScoreAnchor {
3
- raw: number;
4
- normalized: 100 | 300 | 600 | 950;
5
- }
6
- interface ScoreConfig {
7
- anchors: [ScoreAnchor, ScoreAnchor, ScoreAnchor, ScoreAnchor];
8
- }
15
+ /**
16
+ * A plain JSON-serializable object used for cross-session game state
17
+ * persistence.
18
+ *
19
+ * @category State
20
+ */
9
21
  type GameState = Record<string, unknown>;
10
22
 
23
+ /**
24
+ * Trigger native haptic feedback on the player's device.
25
+ *
26
+ * Always guard calls with the user's haptics preference toggle.
27
+ * The bridge is a no-op in local development (a console warning is logged).
28
+ *
29
+ * @param type - The haptic intensity level.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * // UI button tap
34
+ * oasiz.triggerHaptic("light");
35
+ *
36
+ * // Tiered hit feedback
37
+ * oasiz.triggerHaptic(isPerfectHit ? "success" : "medium");
38
+ *
39
+ * // Game over
40
+ * oasiz.triggerHaptic("error");
41
+ * ```
42
+ *
43
+ * @category Haptics
44
+ */
11
45
  declare function triggerHaptic(type: HapticType): void;
12
46
 
47
+ /**
48
+ * Notify the platform of the active multiplayer room so friends can join
49
+ * via the invite system. Pass `null` when leaving a room.
50
+ *
51
+ * @param roomCode - The room code string, or `null` to clear.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { insertCoin, getRoomCode } from "playroomkit";
56
+ *
57
+ * await insertCoin({ skipLobby: true });
58
+ * oasiz.shareRoomCode(getRoomCode());
59
+ *
60
+ * // On disconnect
61
+ * oasiz.shareRoomCode(null);
62
+ * ```
63
+ *
64
+ * @category Multiplayer
65
+ */
13
66
  declare function shareRoomCode(roomCode: string | null): void;
67
+ /**
68
+ * Get the platform's internal game ID, injected before the game loads.
69
+ *
70
+ * @returns The game ID string, or `undefined` if not yet injected.
71
+ *
72
+ * @category Multiplayer
73
+ */
14
74
  declare function getGameId(): string | undefined;
75
+ /**
76
+ * Get the pre-filled room code for auto-joining a friend's multiplayer session.
77
+ *
78
+ * @returns The room code string, or `undefined` if not in a multiplayer context.
79
+ *
80
+ * @category Multiplayer
81
+ */
15
82
  declare function getRoomCode(): string | undefined;
83
+ /**
84
+ * Get the current player's display name, injected by the platform.
85
+ *
86
+ * @returns The player name, or `undefined` if not available.
87
+ *
88
+ * @category Multiplayer
89
+ */
16
90
  declare function getPlayerName(): string | undefined;
91
+ /**
92
+ * Get the current player's avatar URL, injected by the platform.
93
+ *
94
+ * @returns The avatar URL string, or `undefined` if not available.
95
+ *
96
+ * @category Multiplayer
97
+ */
17
98
  declare function getPlayerAvatar(): string | undefined;
18
99
 
100
+ /**
101
+ * Submit the player's score.
102
+ *
103
+ * The platform handles leaderboard persistence — do not track high scores locally.
104
+ *
105
+ * - `score` must be a finite number. Floats are floored, negatives are clamped to 0.
106
+ * - In development mode a console warning is logged when the bridge is unavailable.
107
+ *
108
+ * @param score - The player's score (non-negative integer).
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * oasiz.submitScore(Math.floor(this.score));
113
+ * ```
114
+ *
115
+ * @category Score
116
+ */
19
117
  declare function submitScore(score: number): void;
20
- declare function emitScoreConfig(config: ScoreConfig): void;
21
118
 
119
+ /**
120
+ * Load the player's persisted game state.
121
+ *
122
+ * Returns the saved state synchronously. Returns `{}` if no state has been
123
+ * saved yet or if the bridge is unavailable. Always validate the shape of
124
+ * the returned data — it may be empty on first play.
125
+ *
126
+ * Call once at the start of the game.
127
+ *
128
+ * @returns The player's saved {@link GameState}, or an empty object.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * const state = oasiz.loadGameState();
133
+ * this.level = typeof state.level === "number" ? state.level : 1;
134
+ * ```
135
+ *
136
+ * @category State
137
+ */
22
138
  declare function loadGameState(): GameState;
139
+ /**
140
+ * Queue a debounced save of the player's game state.
141
+ *
142
+ * Saves are batched automatically — call freely at checkpoints without
143
+ * worrying about request spam. State must be a plain JSON-serializable object.
144
+ *
145
+ * @param state - A plain object to persist. Do not pass arrays or primitives.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * oasiz.saveGameState({
150
+ * level: this.level,
151
+ * lifetimeHits: this.lifetimeHits,
152
+ * unlockedSkins: this.unlockedSkins,
153
+ * });
154
+ * ```
155
+ *
156
+ * @category State
157
+ */
23
158
  declare function saveGameState(state: GameState): void;
159
+ /**
160
+ * Force an immediate write of the pending game state, bypassing the debounce.
161
+ *
162
+ * Use at critical checkpoints like game over or before the page unloads
163
+ * to ensure data is persisted.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * oasiz.saveGameState({ level: this.level });
168
+ * oasiz.flushGameState(); // ensure it lands before the page closes
169
+ * oasiz.submitScore(this.score);
170
+ * ```
171
+ *
172
+ * @category State
173
+ */
24
174
  declare function flushGameState(): void;
25
175
 
176
+ /**
177
+ * A function that removes an event listener when called.
178
+ * Returned by {@link onPause} and {@link onResume}.
179
+ *
180
+ * @category Lifecycle
181
+ */
26
182
  type Unsubscribe = () => void;
183
+ /**
184
+ * Register a callback that fires when the app goes to the background.
185
+ *
186
+ * Use this to pause game loops, audio, and animations so the game
187
+ * doesn't continue running while the player is away.
188
+ *
189
+ * @param callback - Invoked when the platform dispatches `oasiz:pause`.
190
+ * @returns An {@link Unsubscribe} function that removes the listener.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * const off = oasiz.onPause(() => {
195
+ * this.gameLoop.stop();
196
+ * this.bgMusic.pause();
197
+ * });
198
+ *
199
+ * // Later, clean up:
200
+ * off();
201
+ * ```
202
+ *
203
+ * @category Lifecycle
204
+ */
27
205
  declare function onPause(callback: () => void): Unsubscribe;
206
+ /**
207
+ * Register a callback that fires when the app returns to the foreground.
208
+ *
209
+ * Use this to resume game loops, audio, and animations after a pause.
210
+ *
211
+ * @param callback - Invoked when the platform dispatches `oasiz:resume`.
212
+ * @returns An {@link Unsubscribe} function that removes the listener.
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * const off = oasiz.onResume(() => {
217
+ * this.gameLoop.start();
218
+ * this.bgMusic.play();
219
+ * });
220
+ *
221
+ * // Later, clean up:
222
+ * off();
223
+ * ```
224
+ *
225
+ * @category Lifecycle
226
+ */
28
227
  declare function onResume(callback: () => void): Unsubscribe;
29
228
 
30
- declare const oasis: {
229
+ /**
230
+ * The main SDK namespace object. Provides convenient access to all
231
+ * platform bridge APIs as a single import.
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * import { oasiz } from "@oasiz/sdk";
236
+ *
237
+ * oasiz.triggerHaptic("medium");
238
+ * oasiz.submitScore(score);
239
+ * ```
240
+ */
241
+ declare const oasiz: {
31
242
  submitScore: typeof submitScore;
32
- emitScoreConfig: typeof emitScoreConfig;
33
243
  triggerHaptic: typeof triggerHaptic;
34
244
  loadGameState: typeof loadGameState;
35
245
  saveGameState: typeof saveGameState;
@@ -43,4 +253,4 @@ declare const oasis: {
43
253
  readonly playerAvatar: string | undefined;
44
254
  };
45
255
 
46
- export { type GameState, type HapticType, type ScoreAnchor, type ScoreConfig, type Unsubscribe, emitScoreConfig, flushGameState, getGameId, getPlayerAvatar, getPlayerName, getRoomCode, loadGameState, oasis, onPause, onResume, saveGameState, shareRoomCode, submitScore, triggerHaptic };
256
+ export { type GameState, type HapticType, type Unsubscribe, flushGameState, getGameId, getPlayerAvatar, getPlayerName, getRoomCode, loadGameState, oasiz, onPause, onResume, saveGameState, shareRoomCode, submitScore, triggerHaptic };
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+ var oasiz = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ flushGameState: () => flushGameState,
25
+ getGameId: () => getGameId,
26
+ getPlayerAvatar: () => getPlayerAvatar,
27
+ getPlayerName: () => getPlayerName,
28
+ getRoomCode: () => getRoomCode,
29
+ loadGameState: () => loadGameState,
30
+ oasiz: () => oasiz,
31
+ onPause: () => onPause,
32
+ onResume: () => onResume,
33
+ saveGameState: () => saveGameState,
34
+ shareRoomCode: () => shareRoomCode,
35
+ submitScore: () => submitScore,
36
+ triggerHaptic: () => triggerHaptic
37
+ });
38
+
39
+ // src/haptics.ts
40
+ function isDevelopment() {
41
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
42
+ return nodeEnv !== "production";
43
+ }
44
+ function getBridgeWindow() {
45
+ if (typeof window === "undefined") {
46
+ return void 0;
47
+ }
48
+ return window;
49
+ }
50
+ function triggerHaptic(type) {
51
+ const bridge = getBridgeWindow();
52
+ if (typeof bridge?.triggerHaptic === "function") {
53
+ bridge.triggerHaptic(type);
54
+ return;
55
+ }
56
+ if (isDevelopment()) {
57
+ console.warn(
58
+ "[oasiz/sdk] triggerHaptic bridge is unavailable. This is expected in local development."
59
+ );
60
+ }
61
+ }
62
+
63
+ // src/multiplayer.ts
64
+ function isDevelopment2() {
65
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
66
+ return nodeEnv !== "production";
67
+ }
68
+ function getBridgeWindow2() {
69
+ if (typeof window === "undefined") {
70
+ return void 0;
71
+ }
72
+ return window;
73
+ }
74
+ function shareRoomCode(roomCode) {
75
+ const bridge = getBridgeWindow2();
76
+ if (typeof bridge?.shareRoomCode === "function") {
77
+ bridge.shareRoomCode(roomCode);
78
+ return;
79
+ }
80
+ if (isDevelopment2()) {
81
+ console.warn(
82
+ "[oasiz/sdk] shareRoomCode bridge is unavailable. This is expected in local development."
83
+ );
84
+ }
85
+ }
86
+ function getGameId() {
87
+ const bridge = getBridgeWindow2();
88
+ return bridge?.__GAME_ID__;
89
+ }
90
+ function getRoomCode() {
91
+ const bridge = getBridgeWindow2();
92
+ return bridge?.__ROOM_CODE__;
93
+ }
94
+ function getPlayerName() {
95
+ const bridge = getBridgeWindow2();
96
+ return bridge?.__PLAYER_NAME__;
97
+ }
98
+ function getPlayerAvatar() {
99
+ const bridge = getBridgeWindow2();
100
+ return bridge?.__PLAYER_AVATAR__;
101
+ }
102
+
103
+ // src/score.ts
104
+ function isDevelopment3() {
105
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
106
+ return nodeEnv !== "production";
107
+ }
108
+ function warnMissingBridge(methodName) {
109
+ if (isDevelopment3()) {
110
+ console.warn(
111
+ "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
112
+ );
113
+ }
114
+ }
115
+ function getBridgeWindow3() {
116
+ if (typeof window === "undefined") {
117
+ return void 0;
118
+ }
119
+ return window;
120
+ }
121
+ function submitScore(score) {
122
+ if (!Number.isFinite(score)) {
123
+ if (isDevelopment3()) {
124
+ console.warn("[oasiz/sdk] submitScore expected a finite number:", score);
125
+ }
126
+ return;
127
+ }
128
+ const bridge = getBridgeWindow3();
129
+ const normalizedScore = Math.max(0, Math.floor(score));
130
+ if (typeof bridge?.submitScore === "function") {
131
+ bridge.submitScore(normalizedScore);
132
+ return;
133
+ }
134
+ warnMissingBridge("submitScore");
135
+ }
136
+
137
+ // src/state.ts
138
+ function isDevelopment4() {
139
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
140
+ return nodeEnv !== "production";
141
+ }
142
+ function getBridgeWindow4() {
143
+ if (typeof window === "undefined") {
144
+ return void 0;
145
+ }
146
+ return window;
147
+ }
148
+ function isPlainObject(value) {
149
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
150
+ return false;
151
+ }
152
+ const proto = Object.getPrototypeOf(value);
153
+ return proto === Object.prototype || proto === null;
154
+ }
155
+ function warnMissingBridge2(methodName) {
156
+ if (isDevelopment4()) {
157
+ console.warn(
158
+ "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
159
+ );
160
+ }
161
+ }
162
+ function loadGameState() {
163
+ const bridge = getBridgeWindow4();
164
+ if (typeof bridge?.loadGameState !== "function") {
165
+ warnMissingBridge2("loadGameState");
166
+ return {};
167
+ }
168
+ const state = bridge.loadGameState();
169
+ if (!isPlainObject(state)) {
170
+ if (isDevelopment4()) {
171
+ console.warn(
172
+ "[oasiz/sdk] loadGameState returned invalid data. Falling back to empty object."
173
+ );
174
+ }
175
+ return {};
176
+ }
177
+ return state;
178
+ }
179
+ function saveGameState(state) {
180
+ if (!isPlainObject(state)) {
181
+ if (isDevelopment4()) {
182
+ console.warn("[oasiz/sdk] saveGameState expected a plain object:", state);
183
+ }
184
+ return;
185
+ }
186
+ const bridge = getBridgeWindow4();
187
+ if (typeof bridge?.saveGameState === "function") {
188
+ bridge.saveGameState(state);
189
+ return;
190
+ }
191
+ warnMissingBridge2("saveGameState");
192
+ }
193
+ function flushGameState() {
194
+ const bridge = getBridgeWindow4();
195
+ if (typeof bridge?.flushGameState === "function") {
196
+ bridge.flushGameState();
197
+ return;
198
+ }
199
+ warnMissingBridge2("flushGameState");
200
+ }
201
+
202
+ // src/lifecycle.ts
203
+ function isDevelopment5() {
204
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
205
+ return nodeEnv !== "production";
206
+ }
207
+ function addLifecycleListener(eventName, callback) {
208
+ if (typeof window === "undefined") {
209
+ if (isDevelopment5()) {
210
+ console.warn(
211
+ "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
212
+ );
213
+ }
214
+ return () => {
215
+ };
216
+ }
217
+ const handler = () => callback();
218
+ window.addEventListener(eventName, handler);
219
+ return () => window.removeEventListener(eventName, handler);
220
+ }
221
+ function onPause(callback) {
222
+ return addLifecycleListener("oasiz:pause", callback);
223
+ }
224
+ function onResume(callback) {
225
+ return addLifecycleListener("oasiz:resume", callback);
226
+ }
227
+
228
+ // src/index.ts
229
+ var oasiz = {
230
+ submitScore,
231
+ triggerHaptic,
232
+ loadGameState,
233
+ saveGameState,
234
+ flushGameState,
235
+ shareRoomCode,
236
+ onPause,
237
+ onResume,
238
+ get gameId() {
239
+ return getGameId();
240
+ },
241
+ get roomCode() {
242
+ return getRoomCode();
243
+ },
244
+ get playerName() {
245
+ return getPlayerName();
246
+ },
247
+ get playerAvatar() {
248
+ return getPlayerAvatar();
249
+ }
250
+ };
251
+ return __toCommonJS(index_exports);
252
+ })();
package/dist/index.js CHANGED
@@ -95,14 +95,6 @@ function submitScore(score) {
95
95
  }
96
96
  warnMissingBridge("submitScore");
97
97
  }
98
- function emitScoreConfig(config) {
99
- const bridge = getBridgeWindow3();
100
- if (typeof bridge?.emitScoreConfig === "function") {
101
- bridge.emitScoreConfig(config);
102
- return;
103
- }
104
- warnMissingBridge("emitScoreConfig");
105
- }
106
98
 
107
99
  // src/state.ts
108
100
  function isDevelopment4() {
@@ -196,9 +188,8 @@ function onResume(callback) {
196
188
  }
197
189
 
198
190
  // src/index.ts
199
- var oasis = {
191
+ var oasiz = {
200
192
  submitScore,
201
- emitScoreConfig,
202
193
  triggerHaptic,
203
194
  loadGameState,
204
195
  saveGameState,
@@ -220,14 +211,13 @@ var oasis = {
220
211
  }
221
212
  };
222
213
  export {
223
- emitScoreConfig,
224
214
  flushGameState,
225
215
  getGameId,
226
216
  getPlayerAvatar,
227
217
  getPlayerName,
228
218
  getRoomCode,
229
219
  loadGameState,
230
- oasis,
220
+ oasiz,
231
221
  onPause,
232
222
  onResume,
233
223
  saveGameState,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oasiz/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Typed SDK for Oasiz game platform bridge APIs.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -10,7 +10,8 @@
10
10
  ".": {
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js",
13
- "require": "./dist/index.cjs"
13
+ "require": "./dist/index.cjs",
14
+ "script": "./dist/index.global.js"
14
15
  }
15
16
  },
16
17
  "files": [
@@ -18,9 +19,11 @@
18
19
  ],
19
20
  "sideEffects": false,
20
21
  "scripts": {
21
- "build": "tsup src/index.ts --format esm,cjs --dts",
22
+ "build": "tsup src/index.ts --format esm,cjs,iife --global-name oasiz --dts",
22
23
  "prepack": "bun run build",
23
- "test": "node --experimental-strip-types --test ./tests/**/*.test.ts"
24
+ "test": "node --experimental-strip-types --test ./tests/**/*.test.ts",
25
+ "docs": "typedoc",
26
+ "docs:watch": "typedoc --watch"
24
27
  },
25
28
  "publishConfig": {
26
29
  "access": "public"
@@ -47,6 +50,7 @@
47
50
  "@types/node": "^25.3.0",
48
51
  "semantic-release": "^25.0.3",
49
52
  "tsup": "^8.5.1",
53
+ "typedoc": "^0.28.17",
50
54
  "typescript": "^5.9.3"
51
55
  }
52
56
  }