@oasiz/sdk 1.5.1 → 1.5.3

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
@@ -21,7 +21,9 @@ Typed SDK for integrating browser games with the Oasiz platform: score, haptics,
21
21
  npm install @oasiz/sdk
22
22
  ```
23
23
 
24
- ### Quick start
24
+ The published package includes ESM, CommonJS, and TypeScript declarations.
25
+
26
+ ## Quick start
25
27
 
26
28
  ```ts
27
29
  import { oasiz } from "@oasiz/sdk";
@@ -36,10 +38,10 @@ oasiz.saveGameState({ level, coins: 42 });
36
38
  // 3. Trigger haptics on key events
37
39
  oasiz.triggerHaptic("medium");
38
40
 
39
- // 4. Respect the host's top safe area
41
+ // 4. Respect the host's top safe area (percent of viewport height → CSS vh)
40
42
  document.documentElement.style.setProperty(
41
43
  "--safe-top",
42
- `${oasiz.safeAreaTop}px`,
44
+ `${oasiz.safeAreaTop}vh`,
43
45
  );
44
46
 
45
47
  // 5. Submit score when the game ends
@@ -194,15 +196,15 @@ private onGameOver(): void {
194
196
 
195
197
  ### Layout
196
198
 
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.
199
+ Use the runtime safe-area value instead of hardcoded top offsets. The SDK returns the top inset as **a percentage of the viewport height (0–100)**. If the host exposes CSS pixels via `window.getSafeAreaTop()` or `window.__OASIZ_SAFE_AREA_TOP__`, the SDK converts using `window.innerHeight`. The host may instead set **`window.getSafeAreaTopPercent()`** or **`window.__OASIZ_SAFE_AREA_TOP_PERCENT__`** (0–100) and that value is used directly.
198
200
 
199
201
  #### `oasiz.getSafeAreaTop(): number`
200
202
 
201
- Returns the current top inset. The host may expose `window.getSafeAreaTop()` or `window.__OASIZ_SAFE_AREA_TOP__`. Unsupported hosts return `0`.
203
+ Returns the top inset as a percentage of viewport height (0–100). To get pixels in JavaScript, use `(getSafeAreaTop() / 100) * window.innerHeight`. In CSS, the same value matches **`vh`** units (for example `12.5vh` for 12.5% of the viewport height). Unsupported hosts return `0`.
202
204
 
203
205
  ```ts
204
- const safeTop = oasiz.getSafeAreaTop();
205
- document.documentElement.style.setProperty("--safe-top", `${safeTop}px`);
206
+ const safeTopPct = oasiz.getSafeAreaTop();
207
+ document.documentElement.style.setProperty("--safe-top", `${safeTopPct}vh`);
206
208
  ```
207
209
 
208
210
  #### `oasiz.safeAreaTop`
@@ -298,7 +300,18 @@ quitButton.addEventListener("click", () => {
298
300
  });
299
301
  ```
300
302
 
301
- #### `oasiz.onLeaveGame(callback: () => void): Unsubscribe`
303
+ ### `oasiz.share(options: { text?: string; score?: number; image?: string }): Promise<void>`
304
+
305
+ Ask the host to open the same share flow Oasiz already uses today. Use `text` to customize the share message, `score` to trigger a challenge-style share, and `image` to share an `http(s)` URL or `data:image/...` payload.
306
+
307
+ ```ts
308
+ await oasiz.share({
309
+ text: "I made it to level 9!",
310
+ score: 4200,
311
+ });
312
+ ```
313
+
314
+ ### `oasiz.onLeaveGame(callback: () => void): Unsubscribe`
302
315
 
303
316
  Registers a callback fired when the host initiates closing the game (for example, close button, gesture, or host navigation). Use this for lightweight cleanup.
304
317
 
@@ -375,6 +388,7 @@ All methods are also available as named exports if you prefer not to use the `oa
375
388
  ```ts
376
389
  import {
377
390
  submitScore,
391
+ share,
378
392
  triggerHaptic,
379
393
  loadGameState,
380
394
  saveGameState,
@@ -406,6 +420,7 @@ import type {
406
420
  LogOverlayHandle,
407
421
  LogOverlayLevel,
408
422
  LogOverlayOptions,
423
+ ShareRequest,
409
424
  ShareRoomCodeOptions,
410
425
  Unsubscribe,
411
426
  } from "@oasiz/sdk";
@@ -440,9 +455,10 @@ public class GameManager : MonoBehaviour
440
455
  OasizSDK.OnPause += OnPause;
441
456
  OasizSDK.OnResume += OnResume;
442
457
 
443
- // Offset UI for the host's top safe area
444
- float safeTop = OasizSDK.SafeAreaTop;
445
- Debug.Log($"Safe area top: {safeTop}px");
458
+ // Offset UI for the host's top safe area (0–100 percent of Screen.height)
459
+ float safeTopPct = OasizSDK.SafeAreaTop;
460
+ float safeTopPx = safeTopPct / 100f * Screen.height;
461
+ Debug.Log($"Safe area top: {safeTopPx}px ({safeTopPct}% of height)");
446
462
 
447
463
  // Emit score normalization anchors
448
464
  OasizSDK.EmitScoreConfig(new ScoreConfig(
@@ -485,12 +501,13 @@ public class GameManager : MonoBehaviour
485
501
  | `oasiz.loadGameState()` | `OasizSDK.LoadGameState()` → `Dictionary<string, object>` |
486
502
  | `oasiz.saveGameState(obj)` | `OasizSDK.SaveGameState(Dictionary<string, object>)` |
487
503
  | `oasiz.flushGameState()` | `OasizSDK.FlushGameState()` |
488
- | `oasiz.getSafeAreaTop()` / `safeAreaTop` | `OasizSDK.GetSafeAreaTop()` / `OasizSDK.SafeAreaTop` (`float`, CSS px) |
504
+ | `oasiz.getSafeAreaTop()` / `safeAreaTop` | `OasizSDK.GetSafeAreaTop()` / `OasizSDK.SafeAreaTop` (`float`, 0–100, % of viewport height) |
489
505
  | `oasiz.setLeaderboardVisible(v)` | `OasizSDK.SetLeaderboardVisible(bool)` |
490
506
  | `oasiz.onPause` / `onResume` | `OasizSDK.OnPause` / `OnResume` static events |
491
507
  | `oasiz.onBackButton` | `OasizSDK.OnBackButton` or `SubscribeBackButton(Action)` (reference-counts `__oasizSetBackOverride`) |
492
508
  | `oasiz.onLeaveGame` | `OasizSDK.OnLeaveGame` |
493
509
  | `oasiz.leaveGame()` | `OasizSDK.LeaveGame()` |
510
+ | `oasiz.share(request)` | `OasizSDK.Share(ShareRequest)` |
494
511
  | `oasiz.shareRoomCode` | `OasizSDK.ShareRoomCode(string, ShareRoomCodeOptions)` |
495
512
  | `oasiz.openInviteModal()` | `OasizSDK.OpenInviteModal()` |
496
513
  | `oasiz.gameId` / `roomCode` / ... | `OasizSDK.GameId` / `RoomCode` / `PlayerName` / `PlayerAvatar` |
@@ -498,6 +515,19 @@ public class GameManager : MonoBehaviour
498
515
  | `oasiz.enableLogOverlay` | `OasizSDK.EnableLogOverlay(LogOverlayOptions)` (see note below) |
499
516
  | -- | `OasizSDK.AppendLogOverlay(level, message, stackTrace)` (see note below) |
500
517
 
518
+ ### Share (Unity)
519
+
520
+ HTML5 **`oasiz.share`** returns a **Promise** you can `await`. Unity **`OasizSDK.Share(ShareRequest)`** returns **`void`**: C# validation throws **`ArgumentException`** with the same rules as TypeScript (at least one of text, score, or image; non-negative integer score; `http(s)` or `data:image/...;base64,...` image). The call forwards JSON to **`window.__oasizShareRequest`**. If the host promise rejects, the **WebGL `.jslib` logs the error** to the browser console.
521
+
522
+ ```csharp
523
+ OasizSDK.Share(new ShareRequest
524
+ {
525
+ Text = "Beat this run!",
526
+ Score = 1200,
527
+ Image = "https://example.com/card.png",
528
+ });
529
+ ```
530
+
501
531
  ### Types
502
532
 
503
533
  ```csharp
@@ -508,6 +538,14 @@ public enum HapticType { Light, Medium, Heavy, Success, Error }
508
538
  public struct ScoreAnchor { public int raw; public int normalized; }
509
539
  public struct ScoreConfig { public ScoreAnchor[] anchors; }
510
540
 
541
+ // Host share sheet (text / score / image URL or data URL)
542
+ public class ShareRequest
543
+ {
544
+ public string Text { get; set; }
545
+ public int? Score { get; set; }
546
+ public string Image { get; set; }
547
+ }
548
+
511
549
  // Multiplayer invite options
512
550
  public class ShareRoomCodeOptions { public bool InviteOverride { get; set; } }
513
551
 
package/dist/index.cjs CHANGED
@@ -37,6 +37,7 @@ __export(index_exports, {
37
37
  openInviteModal: () => openInviteModal,
38
38
  saveGameState: () => saveGameState,
39
39
  setLeaderboardVisible: () => setLeaderboardVisible,
40
+ share: () => share,
40
41
  shareRoomCode: () => shareRoomCode,
41
42
  submitScore: () => submitScore,
42
43
  triggerHaptic: () => triggerHaptic
@@ -1044,7 +1045,7 @@ function submitScore(score) {
1044
1045
  warnMissingBridge("submitScore");
1045
1046
  }
1046
1047
 
1047
- // src/state.ts
1048
+ // src/share.ts
1048
1049
  function isDevelopment4() {
1049
1050
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1050
1051
  return nodeEnv !== "production";
@@ -1055,6 +1056,69 @@ function getBridgeWindow4() {
1055
1056
  }
1056
1057
  return window;
1057
1058
  }
1059
+ function warnMissingBridge2(methodName) {
1060
+ if (isDevelopment4()) {
1061
+ console.warn(
1062
+ "[oasiz/sdk] " + methodName + " share bridge is unavailable. This is expected in local development."
1063
+ );
1064
+ }
1065
+ }
1066
+ function isValidImageReference(image) {
1067
+ if (/^data:image\/[a-zA-Z0-9.+-]+;base64,/.test(image)) {
1068
+ return true;
1069
+ }
1070
+ try {
1071
+ const parsed = new URL(image);
1072
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
1073
+ } catch {
1074
+ return false;
1075
+ }
1076
+ }
1077
+ function validateRequest(options) {
1078
+ const text = typeof options.text === "string" ? options.text.trim() : "";
1079
+ const hasText = text.length > 0;
1080
+ const hasScore = options.score !== void 0;
1081
+ const hasImage = typeof options.image === "string" && options.image.length > 0;
1082
+ if (!hasText && !hasScore && !hasImage) {
1083
+ throw new Error("Share request requires text, score, or image.");
1084
+ }
1085
+ if (hasScore) {
1086
+ if (typeof options.score !== "number" || !Number.isInteger(options.score) || options.score < 0) {
1087
+ throw new Error("Share score must be a non-negative integer.");
1088
+ }
1089
+ }
1090
+ if (hasImage && !isValidImageReference(options.image)) {
1091
+ throw new Error(
1092
+ "Share image must be an http(s) URL or a data:image/... base64 string."
1093
+ );
1094
+ }
1095
+ return {
1096
+ ...hasText ? { text } : {},
1097
+ ...hasScore ? { score: options.score } : {},
1098
+ ...hasImage ? { image: options.image } : {}
1099
+ };
1100
+ }
1101
+ async function share(options) {
1102
+ const request = validateRequest(options);
1103
+ const bridge = getBridgeWindow4();
1104
+ if (typeof bridge?.__oasizShareRequest !== "function") {
1105
+ warnMissingBridge2("__oasizShareRequest");
1106
+ throw new Error("Share bridge unavailable");
1107
+ }
1108
+ await bridge.__oasizShareRequest(request);
1109
+ }
1110
+
1111
+ // src/state.ts
1112
+ function isDevelopment5() {
1113
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
1114
+ return nodeEnv !== "production";
1115
+ }
1116
+ function getBridgeWindow5() {
1117
+ if (typeof window === "undefined") {
1118
+ return void 0;
1119
+ }
1120
+ return window;
1121
+ }
1058
1122
  function isPlainObject(value) {
1059
1123
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1060
1124
  return false;
@@ -1062,22 +1126,22 @@ function isPlainObject(value) {
1062
1126
  const proto = Object.getPrototypeOf(value);
1063
1127
  return proto === Object.prototype || proto === null;
1064
1128
  }
1065
- function warnMissingBridge2(methodName) {
1066
- if (isDevelopment4()) {
1129
+ function warnMissingBridge3(methodName) {
1130
+ if (isDevelopment5()) {
1067
1131
  console.warn(
1068
1132
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1069
1133
  );
1070
1134
  }
1071
1135
  }
1072
1136
  function loadGameState() {
1073
- const bridge = getBridgeWindow4();
1137
+ const bridge = getBridgeWindow5();
1074
1138
  if (typeof bridge?.loadGameState !== "function") {
1075
- warnMissingBridge2("loadGameState");
1139
+ warnMissingBridge3("loadGameState");
1076
1140
  return {};
1077
1141
  }
1078
1142
  const state = bridge.loadGameState();
1079
1143
  if (!isPlainObject(state)) {
1080
- if (isDevelopment4()) {
1144
+ if (isDevelopment5()) {
1081
1145
  console.warn(
1082
1146
  "[oasiz/sdk] loadGameState returned invalid data. Falling back to empty object."
1083
1147
  );
@@ -1088,35 +1152,35 @@ function loadGameState() {
1088
1152
  }
1089
1153
  function saveGameState(state) {
1090
1154
  if (!isPlainObject(state)) {
1091
- if (isDevelopment4()) {
1155
+ if (isDevelopment5()) {
1092
1156
  console.warn("[oasiz/sdk] saveGameState expected a plain object:", state);
1093
1157
  }
1094
1158
  return;
1095
1159
  }
1096
- const bridge = getBridgeWindow4();
1160
+ const bridge = getBridgeWindow5();
1097
1161
  if (typeof bridge?.saveGameState === "function") {
1098
1162
  bridge.saveGameState(state);
1099
1163
  return;
1100
1164
  }
1101
- warnMissingBridge2("saveGameState");
1165
+ warnMissingBridge3("saveGameState");
1102
1166
  }
1103
1167
  function flushGameState() {
1104
- const bridge = getBridgeWindow4();
1168
+ const bridge = getBridgeWindow5();
1105
1169
  if (typeof bridge?.flushGameState === "function") {
1106
1170
  bridge.flushGameState();
1107
1171
  return;
1108
1172
  }
1109
- warnMissingBridge2("flushGameState");
1173
+ warnMissingBridge3("flushGameState");
1110
1174
  }
1111
1175
 
1112
1176
  // src/lifecycle.ts
1113
- function isDevelopment5() {
1177
+ function isDevelopment6() {
1114
1178
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1115
1179
  return nodeEnv !== "production";
1116
1180
  }
1117
1181
  function addLifecycleListener(eventName, callback) {
1118
1182
  if (typeof window === "undefined") {
1119
- if (isDevelopment5()) {
1183
+ if (isDevelopment6()) {
1120
1184
  console.warn(
1121
1185
  "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1122
1186
  );
@@ -1136,46 +1200,74 @@ function onResume(callback) {
1136
1200
  }
1137
1201
 
1138
1202
  // src/layout.ts
1139
- function isDevelopment6() {
1203
+ function isDevelopment7() {
1140
1204
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1141
1205
  return nodeEnv !== "production";
1142
1206
  }
1143
- function getBridgeWindow5() {
1207
+ function getBridgeWindow6() {
1144
1208
  if (typeof window === "undefined") {
1145
1209
  return void 0;
1146
1210
  }
1147
1211
  return window;
1148
1212
  }
1149
- function warnMissingBridge3(methodName) {
1150
- if (isDevelopment6()) {
1213
+ function warnMissingBridge4(methodName) {
1214
+ if (isDevelopment7()) {
1151
1215
  console.warn(
1152
1216
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1153
1217
  );
1154
1218
  }
1155
1219
  }
1156
- function normalizeSafeAreaTop(value) {
1220
+ function normalizeSafeAreaTopPixels(value) {
1157
1221
  if (typeof value !== "number" || !Number.isFinite(value)) {
1158
1222
  return 0;
1159
1223
  }
1160
1224
  return Math.max(0, value);
1161
1225
  }
1226
+ function normalizeSafeAreaTopPercent(value) {
1227
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1228
+ return 0;
1229
+ }
1230
+ return Math.min(100, Math.max(0, value));
1231
+ }
1232
+ function viewportInnerHeight(bridge) {
1233
+ const h = bridge.innerHeight;
1234
+ if (typeof h !== "number" || !Number.isFinite(h) || h <= 0) {
1235
+ return 0;
1236
+ }
1237
+ return h;
1238
+ }
1239
+ function pixelsTopToPercentOfViewport(pixels, bridge) {
1240
+ const h = viewportInnerHeight(bridge);
1241
+ if (h <= 0) {
1242
+ return 0;
1243
+ }
1244
+ return normalizeSafeAreaTopPercent(pixels / h * 100);
1245
+ }
1162
1246
  function getSafeAreaTop() {
1163
- const bridge = getBridgeWindow5();
1247
+ const bridge = getBridgeWindow6();
1164
1248
  if (!bridge) {
1165
1249
  return 0;
1166
1250
  }
1251
+ if (typeof bridge.getSafeAreaTopPercent === "function") {
1252
+ return normalizeSafeAreaTopPercent(bridge.getSafeAreaTopPercent());
1253
+ }
1254
+ if (typeof bridge.__OASIZ_SAFE_AREA_TOP_PERCENT__ !== "undefined") {
1255
+ return normalizeSafeAreaTopPercent(bridge.__OASIZ_SAFE_AREA_TOP_PERCENT__);
1256
+ }
1167
1257
  if (typeof bridge.getSafeAreaTop === "function") {
1168
- return normalizeSafeAreaTop(bridge.getSafeAreaTop());
1258
+ const px = normalizeSafeAreaTopPixels(bridge.getSafeAreaTop());
1259
+ return pixelsTopToPercentOfViewport(px, bridge);
1169
1260
  }
1170
1261
  if (typeof bridge.__OASIZ_SAFE_AREA_TOP__ !== "undefined") {
1171
- return normalizeSafeAreaTop(bridge.__OASIZ_SAFE_AREA_TOP__);
1262
+ const px = normalizeSafeAreaTopPixels(bridge.__OASIZ_SAFE_AREA_TOP__);
1263
+ return pixelsTopToPercentOfViewport(px, bridge);
1172
1264
  }
1173
- warnMissingBridge3("getSafeAreaTop");
1265
+ warnMissingBridge4("getSafeAreaTop");
1174
1266
  return 0;
1175
1267
  }
1176
1268
  function setLeaderboardVisible(visible) {
1177
1269
  if (typeof visible !== "boolean") {
1178
- if (isDevelopment6()) {
1270
+ if (isDevelopment7()) {
1179
1271
  console.warn(
1180
1272
  "[oasiz/sdk] setLeaderboardVisible expected a boolean:",
1181
1273
  visible
@@ -1183,28 +1275,28 @@ function setLeaderboardVisible(visible) {
1183
1275
  }
1184
1276
  return;
1185
1277
  }
1186
- const bridge = getBridgeWindow5();
1278
+ const bridge = getBridgeWindow6();
1187
1279
  if (typeof bridge?.__oasizSetLeaderboardVisible === "function") {
1188
1280
  bridge.__oasizSetLeaderboardVisible(visible);
1189
1281
  return;
1190
1282
  }
1191
- warnMissingBridge3("__oasizSetLeaderboardVisible");
1283
+ warnMissingBridge4("__oasizSetLeaderboardVisible");
1192
1284
  }
1193
1285
 
1194
1286
  // src/navigation.ts
1195
1287
  var activeBackListeners = 0;
1196
- function isDevelopment7() {
1288
+ function isDevelopment8() {
1197
1289
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1198
1290
  return nodeEnv !== "production";
1199
1291
  }
1200
- function getBridgeWindow6() {
1292
+ function getBridgeWindow7() {
1201
1293
  if (typeof window === "undefined") {
1202
1294
  return void 0;
1203
1295
  }
1204
1296
  return window;
1205
1297
  }
1206
- function warnMissingBridge4(methodName) {
1207
- if (isDevelopment7()) {
1298
+ function warnMissingBridge5(methodName) {
1299
+ if (isDevelopment8()) {
1208
1300
  console.warn(
1209
1301
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1210
1302
  );
@@ -1220,7 +1312,7 @@ function normalizeNavigationError(error) {
1220
1312
  }
1221
1313
  function addNavigationListener(eventName, callback) {
1222
1314
  if (typeof window === "undefined") {
1223
- if (isDevelopment7()) {
1315
+ if (isDevelopment8()) {
1224
1316
  console.warn(
1225
1317
  "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1226
1318
  );
@@ -1241,24 +1333,24 @@ function onBackButton(callback) {
1241
1333
  throw normalizeNavigationError(error);
1242
1334
  }
1243
1335
  });
1244
- const bridge = getBridgeWindow6();
1336
+ const bridge = getBridgeWindow7();
1245
1337
  activeBackListeners += 1;
1246
1338
  if (activeBackListeners === 1) {
1247
1339
  if (typeof bridge?.__oasizSetBackOverride === "function") {
1248
1340
  bridge.__oasizSetBackOverride(true);
1249
1341
  } else {
1250
- warnMissingBridge4("__oasizSetBackOverride");
1342
+ warnMissingBridge5("__oasizSetBackOverride");
1251
1343
  }
1252
1344
  }
1253
1345
  return () => {
1254
1346
  off();
1255
1347
  activeBackListeners = Math.max(0, activeBackListeners - 1);
1256
1348
  if (activeBackListeners === 0) {
1257
- const currentBridge = getBridgeWindow6();
1349
+ const currentBridge = getBridgeWindow7();
1258
1350
  if (typeof currentBridge?.__oasizSetBackOverride === "function") {
1259
1351
  currentBridge.__oasizSetBackOverride(false);
1260
1352
  } else {
1261
- warnMissingBridge4("__oasizSetBackOverride");
1353
+ warnMissingBridge5("__oasizSetBackOverride");
1262
1354
  }
1263
1355
  }
1264
1356
  };
@@ -1267,17 +1359,18 @@ function onLeaveGame(callback) {
1267
1359
  return addNavigationListener("oasiz:leave", callback);
1268
1360
  }
1269
1361
  function leaveGame() {
1270
- const bridge = getBridgeWindow6();
1362
+ const bridge = getBridgeWindow7();
1271
1363
  if (typeof bridge?.__oasizLeaveGame === "function") {
1272
1364
  bridge.__oasizLeaveGame();
1273
1365
  return;
1274
1366
  }
1275
- warnMissingBridge4("__oasizLeaveGame");
1367
+ warnMissingBridge5("__oasizLeaveGame");
1276
1368
  }
1277
1369
 
1278
1370
  // src/index.ts
1279
1371
  var oasiz = {
1280
1372
  submitScore,
1373
+ share,
1281
1374
  triggerHaptic,
1282
1375
  enableLogOverlay,
1283
1376
  loadGameState,
@@ -1327,6 +1420,7 @@ var oasiz = {
1327
1420
  openInviteModal,
1328
1421
  saveGameState,
1329
1422
  setLeaderboardVisible,
1423
+ share,
1330
1424
  shareRoomCode,
1331
1425
  submitScore,
1332
1426
  triggerHaptic
package/dist/index.d.cts CHANGED
@@ -20,6 +20,11 @@ interface LogOverlayHandle {
20
20
  show: () => void;
21
21
  }
22
22
  type GameState = Record<string, unknown>;
23
+ interface ShareRequest {
24
+ image?: string;
25
+ score?: number;
26
+ text?: string;
27
+ }
23
28
 
24
29
  declare function triggerHaptic(type: HapticType): void;
25
30
 
@@ -47,6 +52,8 @@ declare function getPlayerAvatar(): string | undefined;
47
52
 
48
53
  declare function submitScore(score: number): void;
49
54
 
55
+ declare function share(options: ShareRequest): Promise<void>;
56
+
50
57
  declare function loadGameState(): GameState;
51
58
  declare function saveGameState(state: GameState): void;
52
59
  declare function flushGameState(): void;
@@ -55,6 +62,12 @@ type Unsubscribe = () => void;
55
62
  declare function onPause(callback: () => void): Unsubscribe;
56
63
  declare function onResume(callback: () => void): Unsubscribe;
57
64
 
65
+ /**
66
+ * Top safe-area inset as a percentage of the viewport height (0–100).
67
+ * The host may expose CSS pixels via `getSafeAreaTop` / `__OASIZ_SAFE_AREA_TOP__`
68
+ * (converted using `window.innerHeight`), or percentages via
69
+ * `getSafeAreaTopPercent` / `__OASIZ_SAFE_AREA_TOP_PERCENT__`.
70
+ */
58
71
  declare function getSafeAreaTop(): number;
59
72
  declare function setLeaderboardVisible(visible: boolean): void;
60
73
 
@@ -64,6 +77,7 @@ declare function leaveGame(): void;
64
77
 
65
78
  declare const oasiz: {
66
79
  submitScore: typeof submitScore;
80
+ share: typeof share;
67
81
  triggerHaptic: typeof triggerHaptic;
68
82
  enableLogOverlay: typeof enableLogOverlay;
69
83
  loadGameState: typeof loadGameState;
@@ -85,4 +99,4 @@ declare const oasiz: {
85
99
  readonly safeAreaTop: number;
86
100
  };
87
101
 
88
- export { type GameState, type HapticType, type LogOverlayEntry, type LogOverlayHandle, type LogOverlayLevel, type LogOverlayOptions, type ShareRoomCodeOptions, type Unsubscribe, enableLogOverlay, flushGameState, getGameId, getPlayerAvatar, getPlayerName, getRoomCode, getSafeAreaTop, leaveGame, loadGameState, oasiz, onBackButton, onLeaveGame, onPause, onResume, openInviteModal, saveGameState, setLeaderboardVisible, shareRoomCode, submitScore, triggerHaptic };
102
+ export { type GameState, type HapticType, type LogOverlayEntry, type LogOverlayHandle, type LogOverlayLevel, type LogOverlayOptions, type ShareRequest, type ShareRoomCodeOptions, type Unsubscribe, enableLogOverlay, flushGameState, getGameId, getPlayerAvatar, getPlayerName, getRoomCode, getSafeAreaTop, leaveGame, loadGameState, oasiz, onBackButton, onLeaveGame, onPause, onResume, openInviteModal, saveGameState, setLeaderboardVisible, share, shareRoomCode, submitScore, triggerHaptic };
package/dist/index.d.ts CHANGED
@@ -20,6 +20,11 @@ interface LogOverlayHandle {
20
20
  show: () => void;
21
21
  }
22
22
  type GameState = Record<string, unknown>;
23
+ interface ShareRequest {
24
+ image?: string;
25
+ score?: number;
26
+ text?: string;
27
+ }
23
28
 
24
29
  declare function triggerHaptic(type: HapticType): void;
25
30
 
@@ -47,6 +52,8 @@ declare function getPlayerAvatar(): string | undefined;
47
52
 
48
53
  declare function submitScore(score: number): void;
49
54
 
55
+ declare function share(options: ShareRequest): Promise<void>;
56
+
50
57
  declare function loadGameState(): GameState;
51
58
  declare function saveGameState(state: GameState): void;
52
59
  declare function flushGameState(): void;
@@ -55,6 +62,12 @@ type Unsubscribe = () => void;
55
62
  declare function onPause(callback: () => void): Unsubscribe;
56
63
  declare function onResume(callback: () => void): Unsubscribe;
57
64
 
65
+ /**
66
+ * Top safe-area inset as a percentage of the viewport height (0–100).
67
+ * The host may expose CSS pixels via `getSafeAreaTop` / `__OASIZ_SAFE_AREA_TOP__`
68
+ * (converted using `window.innerHeight`), or percentages via
69
+ * `getSafeAreaTopPercent` / `__OASIZ_SAFE_AREA_TOP_PERCENT__`.
70
+ */
58
71
  declare function getSafeAreaTop(): number;
59
72
  declare function setLeaderboardVisible(visible: boolean): void;
60
73
 
@@ -64,6 +77,7 @@ declare function leaveGame(): void;
64
77
 
65
78
  declare const oasiz: {
66
79
  submitScore: typeof submitScore;
80
+ share: typeof share;
67
81
  triggerHaptic: typeof triggerHaptic;
68
82
  enableLogOverlay: typeof enableLogOverlay;
69
83
  loadGameState: typeof loadGameState;
@@ -85,4 +99,4 @@ declare const oasiz: {
85
99
  readonly safeAreaTop: number;
86
100
  };
87
101
 
88
- export { type GameState, type HapticType, type LogOverlayEntry, type LogOverlayHandle, type LogOverlayLevel, type LogOverlayOptions, type ShareRoomCodeOptions, type Unsubscribe, enableLogOverlay, flushGameState, getGameId, getPlayerAvatar, getPlayerName, getRoomCode, getSafeAreaTop, leaveGame, loadGameState, oasiz, onBackButton, onLeaveGame, onPause, onResume, openInviteModal, saveGameState, setLeaderboardVisible, shareRoomCode, submitScore, triggerHaptic };
102
+ export { type GameState, type HapticType, type LogOverlayEntry, type LogOverlayHandle, type LogOverlayLevel, type LogOverlayOptions, type ShareRequest, type ShareRoomCodeOptions, type Unsubscribe, enableLogOverlay, flushGameState, getGameId, getPlayerAvatar, getPlayerName, getRoomCode, getSafeAreaTop, leaveGame, loadGameState, oasiz, onBackButton, onLeaveGame, onPause, onResume, openInviteModal, saveGameState, setLeaderboardVisible, share, shareRoomCode, submitScore, triggerHaptic };
package/dist/index.js CHANGED
@@ -999,7 +999,7 @@ function submitScore(score) {
999
999
  warnMissingBridge("submitScore");
1000
1000
  }
1001
1001
 
1002
- // src/state.ts
1002
+ // src/share.ts
1003
1003
  function isDevelopment4() {
1004
1004
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1005
1005
  return nodeEnv !== "production";
@@ -1010,6 +1010,69 @@ function getBridgeWindow4() {
1010
1010
  }
1011
1011
  return window;
1012
1012
  }
1013
+ function warnMissingBridge2(methodName) {
1014
+ if (isDevelopment4()) {
1015
+ console.warn(
1016
+ "[oasiz/sdk] " + methodName + " share bridge is unavailable. This is expected in local development."
1017
+ );
1018
+ }
1019
+ }
1020
+ function isValidImageReference(image) {
1021
+ if (/^data:image\/[a-zA-Z0-9.+-]+;base64,/.test(image)) {
1022
+ return true;
1023
+ }
1024
+ try {
1025
+ const parsed = new URL(image);
1026
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
1027
+ } catch {
1028
+ return false;
1029
+ }
1030
+ }
1031
+ function validateRequest(options) {
1032
+ const text = typeof options.text === "string" ? options.text.trim() : "";
1033
+ const hasText = text.length > 0;
1034
+ const hasScore = options.score !== void 0;
1035
+ const hasImage = typeof options.image === "string" && options.image.length > 0;
1036
+ if (!hasText && !hasScore && !hasImage) {
1037
+ throw new Error("Share request requires text, score, or image.");
1038
+ }
1039
+ if (hasScore) {
1040
+ if (typeof options.score !== "number" || !Number.isInteger(options.score) || options.score < 0) {
1041
+ throw new Error("Share score must be a non-negative integer.");
1042
+ }
1043
+ }
1044
+ if (hasImage && !isValidImageReference(options.image)) {
1045
+ throw new Error(
1046
+ "Share image must be an http(s) URL or a data:image/... base64 string."
1047
+ );
1048
+ }
1049
+ return {
1050
+ ...hasText ? { text } : {},
1051
+ ...hasScore ? { score: options.score } : {},
1052
+ ...hasImage ? { image: options.image } : {}
1053
+ };
1054
+ }
1055
+ async function share(options) {
1056
+ const request = validateRequest(options);
1057
+ const bridge = getBridgeWindow4();
1058
+ if (typeof bridge?.__oasizShareRequest !== "function") {
1059
+ warnMissingBridge2("__oasizShareRequest");
1060
+ throw new Error("Share bridge unavailable");
1061
+ }
1062
+ await bridge.__oasizShareRequest(request);
1063
+ }
1064
+
1065
+ // src/state.ts
1066
+ function isDevelopment5() {
1067
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
1068
+ return nodeEnv !== "production";
1069
+ }
1070
+ function getBridgeWindow5() {
1071
+ if (typeof window === "undefined") {
1072
+ return void 0;
1073
+ }
1074
+ return window;
1075
+ }
1013
1076
  function isPlainObject(value) {
1014
1077
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1015
1078
  return false;
@@ -1017,22 +1080,22 @@ function isPlainObject(value) {
1017
1080
  const proto = Object.getPrototypeOf(value);
1018
1081
  return proto === Object.prototype || proto === null;
1019
1082
  }
1020
- function warnMissingBridge2(methodName) {
1021
- if (isDevelopment4()) {
1083
+ function warnMissingBridge3(methodName) {
1084
+ if (isDevelopment5()) {
1022
1085
  console.warn(
1023
1086
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1024
1087
  );
1025
1088
  }
1026
1089
  }
1027
1090
  function loadGameState() {
1028
- const bridge = getBridgeWindow4();
1091
+ const bridge = getBridgeWindow5();
1029
1092
  if (typeof bridge?.loadGameState !== "function") {
1030
- warnMissingBridge2("loadGameState");
1093
+ warnMissingBridge3("loadGameState");
1031
1094
  return {};
1032
1095
  }
1033
1096
  const state = bridge.loadGameState();
1034
1097
  if (!isPlainObject(state)) {
1035
- if (isDevelopment4()) {
1098
+ if (isDevelopment5()) {
1036
1099
  console.warn(
1037
1100
  "[oasiz/sdk] loadGameState returned invalid data. Falling back to empty object."
1038
1101
  );
@@ -1043,35 +1106,35 @@ function loadGameState() {
1043
1106
  }
1044
1107
  function saveGameState(state) {
1045
1108
  if (!isPlainObject(state)) {
1046
- if (isDevelopment4()) {
1109
+ if (isDevelopment5()) {
1047
1110
  console.warn("[oasiz/sdk] saveGameState expected a plain object:", state);
1048
1111
  }
1049
1112
  return;
1050
1113
  }
1051
- const bridge = getBridgeWindow4();
1114
+ const bridge = getBridgeWindow5();
1052
1115
  if (typeof bridge?.saveGameState === "function") {
1053
1116
  bridge.saveGameState(state);
1054
1117
  return;
1055
1118
  }
1056
- warnMissingBridge2("saveGameState");
1119
+ warnMissingBridge3("saveGameState");
1057
1120
  }
1058
1121
  function flushGameState() {
1059
- const bridge = getBridgeWindow4();
1122
+ const bridge = getBridgeWindow5();
1060
1123
  if (typeof bridge?.flushGameState === "function") {
1061
1124
  bridge.flushGameState();
1062
1125
  return;
1063
1126
  }
1064
- warnMissingBridge2("flushGameState");
1127
+ warnMissingBridge3("flushGameState");
1065
1128
  }
1066
1129
 
1067
1130
  // src/lifecycle.ts
1068
- function isDevelopment5() {
1131
+ function isDevelopment6() {
1069
1132
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1070
1133
  return nodeEnv !== "production";
1071
1134
  }
1072
1135
  function addLifecycleListener(eventName, callback) {
1073
1136
  if (typeof window === "undefined") {
1074
- if (isDevelopment5()) {
1137
+ if (isDevelopment6()) {
1075
1138
  console.warn(
1076
1139
  "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1077
1140
  );
@@ -1091,46 +1154,74 @@ function onResume(callback) {
1091
1154
  }
1092
1155
 
1093
1156
  // src/layout.ts
1094
- function isDevelopment6() {
1157
+ function isDevelopment7() {
1095
1158
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1096
1159
  return nodeEnv !== "production";
1097
1160
  }
1098
- function getBridgeWindow5() {
1161
+ function getBridgeWindow6() {
1099
1162
  if (typeof window === "undefined") {
1100
1163
  return void 0;
1101
1164
  }
1102
1165
  return window;
1103
1166
  }
1104
- function warnMissingBridge3(methodName) {
1105
- if (isDevelopment6()) {
1167
+ function warnMissingBridge4(methodName) {
1168
+ if (isDevelopment7()) {
1106
1169
  console.warn(
1107
1170
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1108
1171
  );
1109
1172
  }
1110
1173
  }
1111
- function normalizeSafeAreaTop(value) {
1174
+ function normalizeSafeAreaTopPixels(value) {
1112
1175
  if (typeof value !== "number" || !Number.isFinite(value)) {
1113
1176
  return 0;
1114
1177
  }
1115
1178
  return Math.max(0, value);
1116
1179
  }
1180
+ function normalizeSafeAreaTopPercent(value) {
1181
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1182
+ return 0;
1183
+ }
1184
+ return Math.min(100, Math.max(0, value));
1185
+ }
1186
+ function viewportInnerHeight(bridge) {
1187
+ const h = bridge.innerHeight;
1188
+ if (typeof h !== "number" || !Number.isFinite(h) || h <= 0) {
1189
+ return 0;
1190
+ }
1191
+ return h;
1192
+ }
1193
+ function pixelsTopToPercentOfViewport(pixels, bridge) {
1194
+ const h = viewportInnerHeight(bridge);
1195
+ if (h <= 0) {
1196
+ return 0;
1197
+ }
1198
+ return normalizeSafeAreaTopPercent(pixels / h * 100);
1199
+ }
1117
1200
  function getSafeAreaTop() {
1118
- const bridge = getBridgeWindow5();
1201
+ const bridge = getBridgeWindow6();
1119
1202
  if (!bridge) {
1120
1203
  return 0;
1121
1204
  }
1205
+ if (typeof bridge.getSafeAreaTopPercent === "function") {
1206
+ return normalizeSafeAreaTopPercent(bridge.getSafeAreaTopPercent());
1207
+ }
1208
+ if (typeof bridge.__OASIZ_SAFE_AREA_TOP_PERCENT__ !== "undefined") {
1209
+ return normalizeSafeAreaTopPercent(bridge.__OASIZ_SAFE_AREA_TOP_PERCENT__);
1210
+ }
1122
1211
  if (typeof bridge.getSafeAreaTop === "function") {
1123
- return normalizeSafeAreaTop(bridge.getSafeAreaTop());
1212
+ const px = normalizeSafeAreaTopPixels(bridge.getSafeAreaTop());
1213
+ return pixelsTopToPercentOfViewport(px, bridge);
1124
1214
  }
1125
1215
  if (typeof bridge.__OASIZ_SAFE_AREA_TOP__ !== "undefined") {
1126
- return normalizeSafeAreaTop(bridge.__OASIZ_SAFE_AREA_TOP__);
1216
+ const px = normalizeSafeAreaTopPixels(bridge.__OASIZ_SAFE_AREA_TOP__);
1217
+ return pixelsTopToPercentOfViewport(px, bridge);
1127
1218
  }
1128
- warnMissingBridge3("getSafeAreaTop");
1219
+ warnMissingBridge4("getSafeAreaTop");
1129
1220
  return 0;
1130
1221
  }
1131
1222
  function setLeaderboardVisible(visible) {
1132
1223
  if (typeof visible !== "boolean") {
1133
- if (isDevelopment6()) {
1224
+ if (isDevelopment7()) {
1134
1225
  console.warn(
1135
1226
  "[oasiz/sdk] setLeaderboardVisible expected a boolean:",
1136
1227
  visible
@@ -1138,28 +1229,28 @@ function setLeaderboardVisible(visible) {
1138
1229
  }
1139
1230
  return;
1140
1231
  }
1141
- const bridge = getBridgeWindow5();
1232
+ const bridge = getBridgeWindow6();
1142
1233
  if (typeof bridge?.__oasizSetLeaderboardVisible === "function") {
1143
1234
  bridge.__oasizSetLeaderboardVisible(visible);
1144
1235
  return;
1145
1236
  }
1146
- warnMissingBridge3("__oasizSetLeaderboardVisible");
1237
+ warnMissingBridge4("__oasizSetLeaderboardVisible");
1147
1238
  }
1148
1239
 
1149
1240
  // src/navigation.ts
1150
1241
  var activeBackListeners = 0;
1151
- function isDevelopment7() {
1242
+ function isDevelopment8() {
1152
1243
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1153
1244
  return nodeEnv !== "production";
1154
1245
  }
1155
- function getBridgeWindow6() {
1246
+ function getBridgeWindow7() {
1156
1247
  if (typeof window === "undefined") {
1157
1248
  return void 0;
1158
1249
  }
1159
1250
  return window;
1160
1251
  }
1161
- function warnMissingBridge4(methodName) {
1162
- if (isDevelopment7()) {
1252
+ function warnMissingBridge5(methodName) {
1253
+ if (isDevelopment8()) {
1163
1254
  console.warn(
1164
1255
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1165
1256
  );
@@ -1175,7 +1266,7 @@ function normalizeNavigationError(error) {
1175
1266
  }
1176
1267
  function addNavigationListener(eventName, callback) {
1177
1268
  if (typeof window === "undefined") {
1178
- if (isDevelopment7()) {
1269
+ if (isDevelopment8()) {
1179
1270
  console.warn(
1180
1271
  "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1181
1272
  );
@@ -1196,24 +1287,24 @@ function onBackButton(callback) {
1196
1287
  throw normalizeNavigationError(error);
1197
1288
  }
1198
1289
  });
1199
- const bridge = getBridgeWindow6();
1290
+ const bridge = getBridgeWindow7();
1200
1291
  activeBackListeners += 1;
1201
1292
  if (activeBackListeners === 1) {
1202
1293
  if (typeof bridge?.__oasizSetBackOverride === "function") {
1203
1294
  bridge.__oasizSetBackOverride(true);
1204
1295
  } else {
1205
- warnMissingBridge4("__oasizSetBackOverride");
1296
+ warnMissingBridge5("__oasizSetBackOverride");
1206
1297
  }
1207
1298
  }
1208
1299
  return () => {
1209
1300
  off();
1210
1301
  activeBackListeners = Math.max(0, activeBackListeners - 1);
1211
1302
  if (activeBackListeners === 0) {
1212
- const currentBridge = getBridgeWindow6();
1303
+ const currentBridge = getBridgeWindow7();
1213
1304
  if (typeof currentBridge?.__oasizSetBackOverride === "function") {
1214
1305
  currentBridge.__oasizSetBackOverride(false);
1215
1306
  } else {
1216
- warnMissingBridge4("__oasizSetBackOverride");
1307
+ warnMissingBridge5("__oasizSetBackOverride");
1217
1308
  }
1218
1309
  }
1219
1310
  };
@@ -1222,17 +1313,18 @@ function onLeaveGame(callback) {
1222
1313
  return addNavigationListener("oasiz:leave", callback);
1223
1314
  }
1224
1315
  function leaveGame() {
1225
- const bridge = getBridgeWindow6();
1316
+ const bridge = getBridgeWindow7();
1226
1317
  if (typeof bridge?.__oasizLeaveGame === "function") {
1227
1318
  bridge.__oasizLeaveGame();
1228
1319
  return;
1229
1320
  }
1230
- warnMissingBridge4("__oasizLeaveGame");
1321
+ warnMissingBridge5("__oasizLeaveGame");
1231
1322
  }
1232
1323
 
1233
1324
  // src/index.ts
1234
1325
  var oasiz = {
1235
1326
  submitScore,
1327
+ share,
1236
1328
  triggerHaptic,
1237
1329
  enableLogOverlay,
1238
1330
  loadGameState,
@@ -1281,6 +1373,7 @@ export {
1281
1373
  openInviteModal,
1282
1374
  saveGameState,
1283
1375
  setLeaderboardVisible,
1376
+ share,
1284
1377
  shareRoomCode,
1285
1378
  submitScore,
1286
1379
  triggerHaptic
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oasiz/sdk",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "Typed SDK for Oasiz game platform bridge APIs.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",