@oasiz/sdk 1.6.1 → 1.7.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/dist/index.cjs CHANGED
@@ -20,38 +20,38 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- addScore: () => addScore,
24
- enableBackButtonTesting: () => enableBackButtonTesting,
25
- enableLogOverlay: () => enableLogOverlay,
23
+ consume: () => consume,
24
+ emitScoreConfig: () => emitScoreConfig,
26
25
  flushGameState: () => flushGameState,
26
+ getEntitlements: () => getEntitlements,
27
27
  getGameId: () => getGameId,
28
- getGraphicsPerformance: () => getGraphicsPerformance,
28
+ getJemBalance: () => getJemBalance,
29
29
  getPlayerAvatar: () => getPlayerAvatar,
30
- getPlayerCharacter: () => getPlayerCharacter,
31
- getPlayerId: () => getPlayerId,
32
30
  getPlayerName: () => getPlayerName,
31
+ getProducts: () => getProducts,
32
+ getQuantity: () => getQuantity,
33
33
  getRoomCode: () => getRoomCode,
34
- getSafeAreaTop: () => getSafeAreaTop,
35
- getViewportInsets: () => getViewportInsets,
34
+ hasEntitlement: () => hasEntitlement,
36
35
  leaveGame: () => leaveGame,
37
36
  loadGameState: () => loadGameState,
38
37
  oasiz: () => oasiz,
39
38
  onBackButton: () => onBackButton,
39
+ onEntitlementsChanged: () => onEntitlementsChanged,
40
+ onJemBalanceChanged: () => onJemBalanceChanged,
40
41
  onLeaveGame: () => onLeaveGame,
41
42
  onPause: () => onPause,
42
43
  onResume: () => onResume,
43
- openInviteModal: () => openInviteModal,
44
+ purchase: () => purchase,
44
45
  saveGameState: () => saveGameState,
45
- setLeaderboardVisible: () => setLeaderboardVisible,
46
- setScore: () => setScore,
47
46
  share: () => share,
48
47
  shareRoomCode: () => shareRoomCode,
49
48
  submitScore: () => submitScore,
49
+ syncProducts: () => syncProducts,
50
50
  triggerHaptic: () => triggerHaptic
51
51
  });
52
52
  module.exports = __toCommonJS(index_exports);
53
53
 
54
- // src/character.ts
54
+ // src/haptics.ts
55
55
  function isDevelopment() {
56
56
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
57
57
  return nodeEnv !== "production";
@@ -62,1014 +62,72 @@ function getBridgeWindow() {
62
62
  }
63
63
  return window;
64
64
  }
65
- function warnMissingBridge(methodName) {
66
- if (isDevelopment()) {
67
- console.warn(
68
- "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
69
- );
70
- }
71
- }
72
- async function getPlayerCharacter() {
73
- const bridge = getBridgeWindow();
74
- if (typeof bridge?.__oasizGetPlayerCharacter !== "function") {
75
- warnMissingBridge("getPlayerCharacter");
76
- return null;
77
- }
78
- try {
79
- const result = await bridge.__oasizGetPlayerCharacter();
80
- return result ?? null;
81
- } catch (error) {
82
- if (isDevelopment()) {
83
- console.error("[oasiz/sdk] getPlayerCharacter failed:", error);
84
- }
85
- return null;
86
- }
87
- }
88
-
89
- // src/haptics.ts
90
- function isDevelopment2() {
91
- const nodeEnv = globalThis.process?.env?.NODE_ENV;
92
- return nodeEnv !== "production";
93
- }
94
- function getBridgeWindow2() {
95
- if (typeof window === "undefined") {
96
- return void 0;
97
- }
98
- return window;
99
- }
100
65
  function triggerHaptic(type) {
101
- const bridge = getBridgeWindow2();
66
+ const bridge = getBridgeWindow();
102
67
  if (typeof bridge?.triggerHaptic === "function") {
103
68
  bridge.triggerHaptic(type);
104
69
  return;
105
70
  }
106
- if (isDevelopment2()) {
71
+ if (isDevelopment()) {
107
72
  console.warn(
108
73
  "[oasiz/sdk] triggerHaptic bridge is unavailable. This is expected in local development."
109
74
  );
110
75
  }
111
76
  }
112
77
 
113
- // src/log-overlay.ts
114
- var CONSOLE_METHODS = [
115
- "debug",
116
- "log",
117
- "info",
118
- "warn",
119
- "error"
120
- ];
121
- var DEFAULT_MAX_ENTRIES = 200;
122
- var DEFAULT_TITLE = "SDK Logs";
123
- var OVERLAY_MARGIN = 12;
124
- var DEFAULT_COLLAPSED_WIDTH = 156;
125
- var DEFAULT_COLLAPSED_HEIGHT = 52;
126
- var DEFAULT_EXPANDED_WIDTH = 565;
127
- var DEFAULT_EXPANDED_HEIGHT = 372;
128
- var DRAG_THRESHOLD_PX = 6;
129
- var MIN_EXPANDED_WIDTH = 160;
130
- var MIN_EXPANDED_HEIGHT = 110;
131
- var RESIZE_HOTSPOT_PX = 28;
132
- var TOP_DRAG_ZONE_PX = 44;
133
- var NOOP_HANDLE = {
134
- clear() {
135
- },
136
- destroy() {
137
- },
138
- hide() {
139
- },
140
- isVisible() {
141
- return false;
142
- },
143
- show() {
144
- }
145
- };
146
- function getBrowserWindow() {
147
- if (typeof window === "undefined") {
148
- return void 0;
149
- }
150
- return window;
151
- }
152
- function getDocument() {
153
- if (typeof document === "undefined") {
154
- return void 0;
155
- }
156
- return document;
157
- }
158
- function clampMaxEntries(value) {
159
- if (!Number.isFinite(value)) {
160
- return DEFAULT_MAX_ENTRIES;
161
- }
162
- return Math.max(10, Math.floor(value));
163
- }
164
- function createConsoleSnapshot() {
165
- const fallback = console.log.bind(console);
166
- return {
167
- debug: typeof console.debug === "function" ? console.debug.bind(console) : fallback,
168
- log: fallback,
169
- info: typeof console.info === "function" ? console.info.bind(console) : fallback,
170
- warn: typeof console.warn === "function" ? console.warn.bind(console) : fallback,
171
- error: typeof console.error === "function" ? console.error.bind(console) : fallback
172
- };
173
- }
174
- function formatTimestamp(timestamp) {
175
- const date = new Date(timestamp);
176
- const hours = String(date.getHours()).padStart(2, "0");
177
- const minutes = String(date.getMinutes()).padStart(2, "0");
178
- const seconds = String(date.getSeconds()).padStart(2, "0");
179
- const milliseconds = String(date.getMilliseconds()).padStart(3, "0");
180
- return "[" + hours + ":" + minutes + ":" + seconds + "." + milliseconds + "]";
181
- }
182
- function safeStringify(value) {
183
- const seen = /* @__PURE__ */ new WeakSet();
184
- try {
185
- return JSON.stringify(
186
- value,
187
- (_key, candidate) => {
188
- if (typeof candidate === "bigint") {
189
- return candidate.toString() + "n";
190
- }
191
- if (typeof candidate === "object" && candidate !== null) {
192
- if (seen.has(candidate)) {
193
- return "[Circular]";
194
- }
195
- seen.add(candidate);
196
- }
197
- return candidate;
198
- },
199
- 2
200
- ) ?? String(value);
201
- } catch {
202
- return String(value);
203
- }
204
- }
205
- function formatArg(value) {
206
- if (typeof value === "string") {
207
- return value;
208
- }
209
- if (value instanceof Error) {
210
- if (value.stack) {
211
- return value.stack;
212
- }
213
- return value.name + ": " + value.message;
214
- }
215
- if (typeof value === "undefined") {
216
- return "undefined";
217
- }
218
- if (typeof value === "function") {
219
- return "[Function " + (value.name || "anonymous") + "]";
220
- }
221
- return safeStringify(value);
222
- }
223
- function formatEntryMessage(args) {
224
- const message = args.map(formatArg).join(" ");
225
- if (message.length <= 4e3) {
226
- return message;
227
- }
228
- return message.slice(0, 3997) + "...";
229
- }
230
- function createEntry(level, args, id) {
231
- return {
232
- id,
233
- level,
234
- message: formatEntryMessage(args),
235
- timestamp: Date.now()
236
- };
237
- }
238
- function createButton(label) {
239
- const button = document.createElement("button");
240
- button.type = "button";
241
- button.textContent = label;
242
- button.style.cssText = [
243
- "appearance:none",
244
- "border:1px solid rgba(255,255,255,0.18)",
245
- "background:rgba(255,255,255,0.06)",
246
- "color:#f8fafc",
247
- "border-radius:999px",
248
- "padding:6px 10px",
249
- "font:600 12px/1.1 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
250
- "cursor:pointer"
251
- ].join(";");
252
- return button;
253
- }
254
- function getLevelAccent(level) {
255
- if (level === "error") {
256
- return {
257
- lineBackground: "rgba(255, 109, 122, 0.08)"
258
- };
259
- }
260
- if (level === "warn") {
261
- return {
262
- lineBackground: "rgba(255, 196, 94, 0.07)"
263
- };
264
- }
265
- if (level === "info") {
266
- return {
267
- lineBackground: "rgba(82, 187, 255, 0.07)"
268
- };
269
- }
270
- if (level === "debug") {
271
- return {
272
- lineBackground: "rgba(166, 137, 255, 0.07)"
273
- };
274
- }
275
- return {
276
- lineBackground: "rgba(117, 235, 191, 0.06)"
277
- };
278
- }
279
- function getViewportSize() {
280
- const browserWindow = getBrowserWindow();
281
- return {
282
- width: Math.max(320, browserWindow?.innerWidth ?? 1280),
283
- height: Math.max(240, browserWindow?.innerHeight ?? 720)
284
- };
285
- }
286
- function clampPanelSize(size) {
287
- const viewport = getViewportSize();
288
- const maxWidth = Math.max(MIN_EXPANDED_WIDTH, viewport.width - OVERLAY_MARGIN * 2);
289
- const maxHeight = Math.max(
290
- MIN_EXPANDED_HEIGHT,
291
- viewport.height - OVERLAY_MARGIN * 2
292
- );
293
- return {
294
- width: Math.min(maxWidth, Math.max(MIN_EXPANDED_WIDTH, size.width)),
295
- height: Math.min(maxHeight, Math.max(MIN_EXPANDED_HEIGHT, size.height))
296
- };
297
- }
298
- function getOverlaySize(state) {
299
- if (state.expanded && state.panelSize) {
300
- return state.panelSize;
301
- }
302
- const rect = state.ui?.root && typeof state.ui.root.getBoundingClientRect === "function" ? state.ui.root.getBoundingClientRect() : null;
303
- if (rect && Number.isFinite(rect.width) && Number.isFinite(rect.height)) {
304
- return {
305
- width: Math.max(1, rect.width),
306
- height: Math.max(1, rect.height)
307
- };
308
- }
309
- return state.expanded ? clampPanelSize({
310
- width: DEFAULT_EXPANDED_WIDTH,
311
- height: DEFAULT_EXPANDED_HEIGHT
312
- }) : { width: DEFAULT_COLLAPSED_WIDTH, height: DEFAULT_COLLAPSED_HEIGHT };
313
- }
314
- function applyPanelSize(state) {
315
- if (!state.ui) {
316
- return;
317
- }
318
- if (!state.expanded) {
319
- state.ui.root.style.width = "auto";
320
- state.ui.panel.style.width = "min(565px, calc(100vw - 24px))";
321
- state.ui.panel.style.height = "auto";
322
- state.ui.entries.style.maxHeight = "min(36vh, 280px)";
323
- return;
324
- }
325
- if (!state.panelSize) {
326
- state.ui.root.style.width = "min(565px, calc(100vw - 24px))";
327
- state.ui.panel.style.width = "min(565px, calc(100vw - 24px))";
328
- state.ui.panel.style.height = "auto";
329
- state.ui.entries.style.maxHeight = "min(36vh, 280px)";
330
- return;
331
- }
332
- const nextSize = clampPanelSize(
333
- state.panelSize
334
- );
335
- state.panelSize = nextSize;
336
- state.ui.root.style.width = nextSize.width + "px";
337
- state.ui.panel.style.width = "100%";
338
- state.ui.panel.style.height = nextSize.height + "px";
339
- state.ui.entries.style.maxHeight = Math.max(72, nextSize.height - 88) + "px";
340
- }
341
- function clampPosition(point, state) {
342
- const viewport = getViewportSize();
343
- const size = getOverlaySize(state);
344
- const maxX = Math.max(OVERLAY_MARGIN, viewport.width - size.width - OVERLAY_MARGIN);
345
- const maxY = Math.max(
346
- OVERLAY_MARGIN,
347
- viewport.height - size.height - OVERLAY_MARGIN
348
- );
349
- return {
350
- x: Math.min(maxX, Math.max(OVERLAY_MARGIN, point.x)),
351
- y: Math.min(maxY, Math.max(OVERLAY_MARGIN, point.y))
352
- };
353
- }
354
- function applyOverlayPosition(state) {
355
- if (!state.ui) {
356
- return;
357
- }
358
- if (!state.position) {
359
- const viewport = getViewportSize();
360
- const size = getOverlaySize(state);
361
- state.position = clampPosition(
362
- {
363
- x: viewport.width - size.width - OVERLAY_MARGIN,
364
- y: viewport.height - size.height - OVERLAY_MARGIN
365
- },
366
- state
367
- );
368
- } else {
369
- state.position = clampPosition(state.position, state);
370
- }
371
- state.ui.root.style.left = state.position.x + "px";
372
- state.ui.root.style.top = state.position.y + "px";
373
- }
374
- function getPointFromMouseEvent(event) {
375
- return { x: event.clientX, y: event.clientY };
376
- }
377
- function getPointFromTouchEvent(event) {
378
- const touch = event.touches[0] ?? event.changedTouches[0];
379
- if (!touch) {
380
- return null;
381
- }
382
- return { x: touch.clientX, y: touch.clientY };
383
- }
384
- function stopDragging(state) {
385
- if (state.isDragging || state.isResizing) {
386
- state.suppressToggleClickUntil = Date.now() + 180;
387
- }
388
- state.isDragging = false;
389
- state.dragStartPoint = null;
390
- state.lastDragPoint = null;
391
- state.removeDragListeners?.();
392
- state.removeDragListeners = null;
393
- if (state.ui) {
394
- state.ui.panel.style.cursor = "default";
395
- state.ui.dragZone.style.cursor = "grab";
396
- state.ui.toggleButton.style.cursor = "grab";
397
- }
398
- }
399
- function stopResizing(state) {
400
- if (state.isResizing) {
401
- state.suppressToggleClickUntil = Date.now() + 180;
402
- }
403
- state.isResizing = false;
404
- state.resizeStartPoint = null;
405
- state.resizeStartSize = null;
406
- state.removeResizeListeners?.();
407
- state.removeResizeListeners = null;
408
- }
409
- function beginDragTracking(state, startPoint) {
410
- const doc = getDocument();
411
- if (!doc) {
412
- return;
413
- }
414
- stopResizing(state);
415
- stopDragging(state);
416
- state.dragMoved = false;
417
- state.dragStartPoint = startPoint;
418
- state.lastDragPoint = startPoint;
419
- const handlePointerMove = (nextPoint) => {
420
- if (!state.dragStartPoint || !state.lastDragPoint || !nextPoint) {
421
- return;
422
- }
423
- if (!state.isDragging) {
424
- const deltaFromStartX = nextPoint.x - state.dragStartPoint.x;
425
- const deltaFromStartY = nextPoint.y - state.dragStartPoint.y;
426
- const distance = Math.sqrt(
427
- deltaFromStartX * deltaFromStartX + deltaFromStartY * deltaFromStartY
428
- );
429
- if (distance < DRAG_THRESHOLD_PX) {
430
- return;
431
- }
432
- state.isDragging = true;
433
- state.dragMoved = true;
434
- if (state.ui) {
435
- state.ui.panel.style.cursor = "grabbing";
436
- state.ui.dragZone.style.cursor = "grabbing";
437
- state.ui.toggleButton.style.cursor = "grabbing";
438
- }
439
- }
440
- const currentPosition = state.position ?? { x: OVERLAY_MARGIN, y: OVERLAY_MARGIN };
441
- state.position = clampPosition(
442
- {
443
- x: currentPosition.x + (nextPoint.x - state.lastDragPoint.x),
444
- y: currentPosition.y + (nextPoint.y - state.lastDragPoint.y)
445
- },
446
- state
447
- );
448
- state.lastDragPoint = nextPoint;
449
- applyOverlayPosition(state);
450
- };
451
- const handleMouseMove = (event) => {
452
- handlePointerMove(getPointFromMouseEvent(event));
453
- };
454
- const handleTouchMove = (event) => {
455
- handlePointerMove(getPointFromTouchEvent(event));
456
- };
457
- const handleMouseUp = () => {
458
- stopDragging(state);
459
- };
460
- const handleTouchEnd = () => {
461
- stopDragging(state);
462
- };
463
- doc.addEventListener("mousemove", handleMouseMove);
464
- doc.addEventListener("mouseup", handleMouseUp);
465
- doc.addEventListener("touchmove", handleTouchMove, { passive: true });
466
- doc.addEventListener("touchend", handleTouchEnd);
467
- doc.addEventListener("touchcancel", handleTouchEnd);
468
- state.removeDragListeners = () => {
469
- doc.removeEventListener("mousemove", handleMouseMove);
470
- doc.removeEventListener("mouseup", handleMouseUp);
471
- doc.removeEventListener("touchmove", handleTouchMove);
472
- doc.removeEventListener("touchend", handleTouchEnd);
473
- doc.removeEventListener("touchcancel", handleTouchEnd);
474
- };
475
- }
476
- function startResizing(state, startPoint) {
477
- const doc = getDocument();
478
- if (!doc) {
479
- return;
480
- }
481
- stopDragging(state);
482
- stopResizing(state);
483
- state.isResizing = true;
484
- state.resizeStartPoint = startPoint;
485
- state.resizeStartSize = state.panelSize ?? clampPanelSize({ width: DEFAULT_EXPANDED_WIDTH, height: DEFAULT_EXPANDED_HEIGHT });
486
- const handleResizeMove = (nextPoint) => {
487
- if (!state.isResizing || !state.resizeStartPoint || !state.resizeStartSize || !nextPoint) {
488
- return;
489
- }
490
- state.panelSize = clampPanelSize({
491
- width: state.resizeStartSize.width + (nextPoint.x - state.resizeStartPoint.x),
492
- height: state.resizeStartSize.height + (nextPoint.y - state.resizeStartPoint.y)
493
- });
494
- applyPanelSize(state);
495
- applyOverlayPosition(state);
496
- };
497
- const handleMouseMove = (event) => {
498
- handleResizeMove(getPointFromMouseEvent(event));
499
- };
500
- const handleTouchMove = (event) => {
501
- handleResizeMove(getPointFromTouchEvent(event));
502
- };
503
- const handleFinish = () => {
504
- stopResizing(state);
505
- };
506
- doc.addEventListener("mousemove", handleMouseMove);
507
- doc.addEventListener("mouseup", handleFinish);
508
- doc.addEventListener("touchmove", handleTouchMove, { passive: true });
509
- doc.addEventListener("touchend", handleFinish);
510
- doc.addEventListener("touchcancel", handleFinish);
511
- state.removeResizeListeners = () => {
512
- doc.removeEventListener("mousemove", handleMouseMove);
513
- doc.removeEventListener("mouseup", handleFinish);
514
- doc.removeEventListener("touchmove", handleTouchMove);
515
- doc.removeEventListener("touchend", handleFinish);
516
- doc.removeEventListener("touchcancel", handleFinish);
517
- };
518
- }
519
- function isInBottomRightResizeZone(element, point) {
520
- const rect = element.getBoundingClientRect();
521
- return point.x >= rect.right - RESIZE_HOTSPOT_PX && point.x <= rect.right && point.y >= rect.bottom - RESIZE_HOTSPOT_PX && point.y <= rect.bottom;
522
- }
523
- function canStartDragFromTarget(target) {
524
- if (!(target instanceof Element)) {
525
- return true;
526
- }
527
- if (target.closest("button") || target.closest("a") || target.closest("input") || target.closest("textarea") || target.closest("select")) {
528
- return false;
529
- }
530
- return true;
531
- }
532
- function isInTopDragZone(element, point, zoneHeight) {
533
- const rect = element.getBoundingClientRect();
534
- return point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.top + zoneHeight;
535
- }
536
- function attachDragStartListeners(element, state, options = {}) {
537
- element.addEventListener("mousedown", (event) => {
538
- if (!options.allowInteractiveTarget && !canStartDragFromTarget(event.target)) {
539
- return;
540
- }
541
- const point = getPointFromMouseEvent(event);
542
- if (typeof options.topZoneHeight === "number" && !isInTopDragZone(element, point, options.topZoneHeight)) {
543
- return;
544
- }
545
- beginDragTracking(state, point);
546
- });
547
- element.addEventListener("touchstart", (event) => {
548
- if (!options.allowInteractiveTarget && !canStartDragFromTarget(event.target)) {
549
- return;
550
- }
551
- const point = getPointFromTouchEvent(event);
552
- if (!point) {
553
- return;
554
- }
555
- if (typeof options.topZoneHeight === "number" && !isInTopDragZone(element, point, options.topZoneHeight)) {
556
- return;
557
- }
558
- beginDragTracking(state, point);
559
- });
560
- }
561
- function attachCollapsedToggleListeners(element, state) {
562
- const startToggleInteraction = (startPoint) => {
563
- const doc = getDocument();
564
- if (!doc) {
565
- return;
566
- }
567
- beginDragTracking(state, startPoint);
568
- const finishInteraction = () => {
569
- releaseListeners();
570
- if (state.dragMoved || Date.now() < state.suppressToggleClickUntil) {
571
- return;
572
- }
573
- state.expanded = true;
574
- state.unreadCount = 0;
575
- renderOverlay(state);
576
- };
577
- const releaseListeners = () => {
578
- doc.removeEventListener("mouseup", finishInteraction);
579
- doc.removeEventListener("touchend", finishInteraction);
580
- doc.removeEventListener("touchcancel", finishInteraction);
581
- };
582
- doc.addEventListener("mouseup", finishInteraction);
583
- doc.addEventListener("touchend", finishInteraction);
584
- doc.addEventListener("touchcancel", finishInteraction);
585
- };
586
- element.addEventListener("mousedown", (event) => {
587
- event.preventDefault();
588
- startToggleInteraction(getPointFromMouseEvent(event));
589
- });
590
- element.addEventListener("touchstart", (event) => {
591
- const point = getPointFromTouchEvent(event);
592
- if (!point) {
593
- return;
594
- }
595
- event.preventDefault();
596
- startToggleInteraction(point);
597
- });
598
- }
599
- function attachPanelResizeListeners(element, state) {
600
- element.addEventListener("mousedown", (event) => {
601
- if (!state.expanded || !canStartDragFromTarget(event.target)) {
602
- return;
603
- }
604
- const point = getPointFromMouseEvent(event);
605
- if (!isInBottomRightResizeZone(element, point)) {
606
- return;
607
- }
608
- event.preventDefault();
609
- event.stopPropagation();
610
- startResizing(state, point);
611
- });
612
- element.addEventListener("touchstart", (event) => {
613
- if (!state.expanded || !canStartDragFromTarget(event.target)) {
614
- return;
615
- }
616
- const point = getPointFromTouchEvent(event);
617
- if (!point || !isInBottomRightResizeZone(element, point)) {
618
- return;
619
- }
620
- event.preventDefault();
621
- event.stopPropagation();
622
- startResizing(state, point);
623
- });
624
- }
625
- function createOverlayUi(state) {
626
- const root = document.createElement("div");
627
- root.style.cssText = [
628
- "position:fixed",
629
- "left:12px",
630
- "top:12px",
631
- "z-index:2147483647",
632
- "display:flex",
633
- "flex-direction:column",
634
- "align-items:stretch",
635
- "gap:8px",
636
- "width:min(565px, calc(100vw - 24px))",
637
- "pointer-events:none"
638
- ].join(";");
639
- const toggleButton = createButton("Logs");
640
- toggleButton.style.pointerEvents = "auto";
641
- toggleButton.style.alignSelf = "flex-end";
642
- toggleButton.style.display = "inline-flex";
643
- toggleButton.style.alignItems = "center";
644
- toggleButton.style.justifyContent = "center";
645
- toggleButton.style.minHeight = "40px";
646
- toggleButton.style.minWidth = "76px";
647
- toggleButton.style.padding = "8px 14px";
648
- toggleButton.style.textAlign = "center";
649
- toggleButton.style.border = "1px solid rgba(122, 212, 255, 0.22)";
650
- toggleButton.style.background = "linear-gradient(180deg, rgba(13,31,54,0.98), rgba(8,19,37,0.98))";
651
- toggleButton.style.boxShadow = "0 18px 40px rgba(4,12,24,0.34)";
652
- toggleButton.style.cursor = "grab";
653
- toggleButton.style.touchAction = "none";
654
- const panel = document.createElement("div");
655
- panel.style.cssText = [
656
- "position:relative",
657
- "display:flex",
658
- "flex-direction:column",
659
- "width:min(565px, calc(100vw - 24px))",
660
- "max-height:min(48vh, 372px)",
661
- "border-radius:18px",
662
- "border:1px solid rgba(116,167,255,0.16)",
663
- "background:linear-gradient(180deg, rgba(9,19,37,0.98), rgba(5,12,24,0.98))",
664
- "box-shadow:0 28px 64px rgba(2,8,18,0.46)",
665
- "backdrop-filter:blur(16px)",
666
- "overflow:hidden",
667
- "cursor:default",
668
- "pointer-events:auto"
669
- ].join(";");
670
- const dragZone = document.createElement("div");
671
- dragZone.style.cssText = [
672
- "position:absolute",
673
- "top:0",
674
- "left:0",
675
- "right:0",
676
- "height:" + String(TOP_DRAG_ZONE_PX) + "px",
677
- "z-index:1",
678
- "cursor:grab",
679
- "background:transparent",
680
- "pointer-events:auto"
681
- ].join(";");
682
- const controls = document.createElement("div");
683
- controls.style.cssText = [
684
- "position:absolute",
685
- "top:22px",
686
- "right:22px",
687
- "z-index:2",
688
- "display:flex",
689
- "align-items:center",
690
- "gap:8px",
691
- "pointer-events:auto"
692
- ].join(";");
693
- const clearButton = createButton("Clear");
694
- clearButton.style.background = "rgba(255,255,255,0.1)";
695
- clearButton.style.border = "1px solid rgba(255,255,255,0.16)";
696
- clearButton.style.color = "#eef6ff";
697
- clearButton.style.minHeight = "30px";
698
- clearButton.style.padding = "4px 9px";
699
- clearButton.style.fontSize = "11px";
700
- clearButton.style.backdropFilter = "blur(8px)";
701
- const collapseButton = createButton("Hide");
702
- collapseButton.style.background = "rgba(113, 171, 255, 0.12)";
703
- collapseButton.style.border = "1px solid rgba(113, 171, 255, 0.2)";
704
- collapseButton.style.color = "#d9ebff";
705
- collapseButton.style.minHeight = "30px";
706
- collapseButton.style.padding = "4px 9px";
707
- collapseButton.style.fontSize = "11px";
708
- collapseButton.style.backdropFilter = "blur(8px)";
709
- const body = document.createElement("div");
710
- body.style.cssText = [
711
- "position:relative",
712
- "display:flex",
713
- "flex-direction:column",
714
- "padding:12px",
715
- "background:linear-gradient(180deg, rgba(4,10,20,0.88), rgba(3,8,18,0.98))",
716
- "flex:1 1 auto",
717
- "min-height:0"
718
- ].join(";");
719
- const entries = document.createElement("div");
720
- entries.style.cssText = [
721
- "display:flex",
722
- "flex-direction:column",
723
- "gap:0",
724
- "overflow:auto",
725
- "flex:1 1 auto",
726
- "min-height:96px",
727
- "max-height:min(36vh, 280px)",
728
- "padding:0",
729
- "border:1px solid rgba(115,153,212,0.14)",
730
- "border-radius:12px",
731
- "background:rgba(4,10,20,0.82)"
732
- ].join(";");
733
- const emptyState = document.createElement("div");
734
- emptyState.style.cssText = [
735
- "display:flex",
736
- "align-items:center",
737
- "justify-content:center",
738
- "flex:1 1 auto",
739
- "min-height:96px",
740
- "color:rgba(204,222,250,0.6)",
741
- "font:500 12px/1.5 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
742
- "text-align:center",
743
- "padding:18px"
744
- ].join(";");
745
- emptyState.textContent = "Console output will appear here.";
746
- collapseButton.addEventListener("click", (event) => {
747
- event.stopPropagation();
748
- state.expanded = false;
749
- renderOverlay(state);
750
- });
751
- clearButton.addEventListener("click", (event) => {
752
- event.stopPropagation();
753
- state.entries = [];
754
- state.unreadCount = 0;
755
- renderOverlay(state);
756
- });
757
- controls.appendChild(clearButton);
758
- controls.appendChild(collapseButton);
759
- entries.appendChild(emptyState);
760
- body.appendChild(entries);
761
- body.appendChild(controls);
762
- panel.appendChild(dragZone);
763
- panel.appendChild(body);
764
- attachDragStartListeners(dragZone, state);
765
- attachPanelResizeListeners(panel, state);
766
- attachCollapsedToggleListeners(toggleButton, state);
767
- root.appendChild(panel);
768
- root.appendChild(toggleButton);
769
- return {
770
- body,
771
- clearButton,
772
- collapseButton,
773
- controls,
774
- dragZone,
775
- emptyState,
776
- entries,
777
- panel,
778
- root,
779
- toggleButton
780
- };
781
- }
782
- function renderOverlay(state) {
783
- if (!state.ui) {
784
- return;
785
- }
786
- state.ui.panel.style.display = state.expanded ? "flex" : "none";
787
- state.ui.toggleButton.style.display = state.expanded ? "none" : "inline-flex";
788
- state.ui.toggleButton.textContent = "Logs";
789
- if (state.entries.length === 0) {
790
- state.ui.entries.style.display = "flex";
791
- state.ui.emptyState.style.display = "flex";
792
- state.ui.entries.replaceChildren(state.ui.emptyState);
793
- applyPanelSize(state);
794
- applyOverlayPosition(state);
795
- return;
796
- }
797
- state.ui.entries.style.display = "flex";
798
- state.ui.emptyState.style.display = "none";
799
- const nextChildren = state.entries.map((entry) => {
800
- const accent = getLevelAccent(entry.level);
801
- const row = document.createElement("div");
802
- row.style.cssText = [
803
- "display:flex",
804
- "align-items:flex-start",
805
- "gap:0",
806
- "padding:4px 12px",
807
- "background:" + accent.lineBackground
808
- ].join(";");
809
- const line = document.createElement("div");
810
- line.textContent = formatTimestamp(entry.timestamp) + " " + entry.level.toUpperCase() + " " + entry.message;
811
- line.style.cssText = [
812
- "color:#ecf4ff",
813
- "font:500 12px/1.5 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
814
- "white-space:pre-wrap",
815
- "word-break:break-word",
816
- "flex:1 1 auto"
817
- ].join(";");
818
- row.appendChild(line);
819
- return row;
820
- });
821
- state.ui.entries.replaceChildren(...nextChildren);
822
- state.ui.entries.scrollTop = state.ui.entries.scrollHeight;
823
- applyPanelSize(state);
824
- applyOverlayPosition(state);
825
- }
826
- function mountOverlay(state) {
827
- const doc = getDocument();
828
- if (!doc?.body || state.ui) {
829
- return;
830
- }
831
- state.ui = createOverlayUi(state);
832
- doc.body.appendChild(state.ui.root);
833
- applyPanelSize(state);
834
- applyOverlayPosition(state);
835
- renderOverlay(state);
836
- }
837
- function enqueueEntry(state, level, args) {
838
- state.entries.push(createEntry(level, args, state.nextEntryId));
839
- state.nextEntryId += 1;
840
- if (state.entries.length > state.maxEntries) {
841
- state.entries.splice(0, state.entries.length - state.maxEntries);
842
- }
843
- if (!state.expanded) {
844
- state.unreadCount += 1;
845
- }
846
- renderOverlay(state);
847
- }
848
- function restoreConsole(snapshot) {
849
- for (const method of CONSOLE_METHODS) {
850
- console[method] = snapshot[method];
851
- }
852
- }
853
- function patchConsole(state) {
854
- for (const method of CONSOLE_METHODS) {
855
- const original = state.originalConsole[method];
856
- console[method] = (...args) => {
857
- enqueueEntry(state, method, args);
858
- original(...args);
859
- };
860
- }
861
- }
862
- function cleanupOverlay(browserWindow, state) {
863
- restoreConsole(state.originalConsole);
864
- stopResizing(state);
865
- stopDragging(state);
866
- if (state.domReadyHandler) {
867
- getDocument()?.removeEventListener("DOMContentLoaded", state.domReadyHandler);
868
- state.domReadyHandler = void 0;
869
- }
870
- if (state.resizeHandler && typeof browserWindow.removeEventListener === "function") {
871
- browserWindow.removeEventListener("resize", state.resizeHandler);
872
- state.resizeHandler = void 0;
873
- }
874
- state.ui?.root.remove();
875
- state.ui = null;
876
- delete browserWindow.__oasizLogOverlayController__;
877
- delete browserWindow.__oasizLogOverlayState__;
878
- }
879
- function createController(browserWindow, state) {
880
- return {
881
- retain() {
882
- state.refCount += 1;
883
- this.ensureMounted();
884
- },
885
- ensureMounted() {
886
- const doc = getDocument();
887
- if (!doc) {
888
- return;
889
- }
890
- if (doc.body) {
891
- mountOverlay(state);
892
- applyOverlayPosition(state);
893
- return;
894
- }
895
- if (!state.domReadyHandler) {
896
- state.domReadyHandler = () => {
897
- mountOverlay(state);
898
- state.domReadyHandler = void 0;
899
- };
900
- doc.addEventListener("DOMContentLoaded", state.domReadyHandler, {
901
- once: true
902
- });
903
- }
904
- },
905
- clear() {
906
- state.entries = [];
907
- state.unreadCount = 0;
908
- renderOverlay(state);
909
- },
910
- show() {
911
- state.expanded = true;
912
- state.unreadCount = 0;
913
- renderOverlay(state);
914
- },
915
- hide() {
916
- state.expanded = false;
917
- renderOverlay(state);
918
- },
919
- isVisible() {
920
- return state.expanded;
921
- },
922
- destroy() {
923
- state.refCount = Math.max(0, state.refCount - 1);
924
- if (state.refCount === 0) {
925
- cleanupOverlay(browserWindow, state);
926
- }
927
- }
928
- };
929
- }
930
- function enableLogOverlay(options = {}) {
931
- if (options.enabled === false) {
932
- return NOOP_HANDLE;
933
- }
934
- const browserWindow = getBrowserWindow();
935
- const doc = getDocument();
936
- if (!browserWindow || !doc) {
937
- return NOOP_HANDLE;
938
- }
939
- const existingController = browserWindow.__oasizLogOverlayController__;
940
- const existingState = browserWindow.__oasizLogOverlayState__;
941
- if (existingController && existingState) {
942
- existingController.retain();
943
- if (typeof options.maxEntries === "number") {
944
- existingState.maxEntries = clampMaxEntries(options.maxEntries);
945
- if (existingState.entries.length > existingState.maxEntries) {
946
- existingState.entries.splice(
947
- 0,
948
- existingState.entries.length - existingState.maxEntries
949
- );
950
- }
951
- }
952
- if (typeof options.collapsed === "boolean") {
953
- existingState.expanded = !options.collapsed;
954
- applyPanelSize(existingState);
955
- if (existingState.expanded) {
956
- existingState.unreadCount = 0;
957
- }
958
- }
959
- if (typeof options.title === "string" && options.title.trim().length > 0) {
960
- existingState.title = options.title.trim();
961
- }
962
- existingController.ensureMounted();
963
- renderOverlay(existingState);
964
- return existingController;
965
- }
966
- const state = {
967
- dragMoved: false,
968
- dragStartPoint: null,
969
- entries: [],
970
- expanded: options.collapsed !== true,
971
- isDragging: false,
972
- isResizing: false,
973
- lastDragPoint: null,
974
- maxEntries: clampMaxEntries(options.maxEntries),
975
- nextEntryId: 1,
976
- originalConsole: createConsoleSnapshot(),
977
- panelSize: null,
978
- position: null,
979
- refCount: 1,
980
- removeDragListeners: null,
981
- removeResizeListeners: null,
982
- resizeStartPoint: null,
983
- resizeStartSize: null,
984
- suppressToggleClickUntil: 0,
985
- title: typeof options.title === "string" && options.title.trim().length > 0 ? options.title.trim() : DEFAULT_TITLE,
986
- resizeHandler: void 0,
987
- ui: null,
988
- unreadCount: 0
989
- };
990
- const controller = createController(browserWindow, state);
991
- browserWindow.__oasizLogOverlayState__ = state;
992
- browserWindow.__oasizLogOverlayController__ = controller;
993
- patchConsole(state);
994
- if (typeof browserWindow.addEventListener === "function") {
995
- state.resizeHandler = () => {
996
- applyOverlayPosition(state);
997
- };
998
- browserWindow.addEventListener("resize", state.resizeHandler);
999
- }
1000
- controller.ensureMounted();
1001
- return controller;
1002
- }
1003
-
1004
78
  // src/multiplayer.ts
1005
- function isDevelopment3() {
79
+ function isDevelopment2() {
1006
80
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1007
81
  return nodeEnv !== "production";
1008
82
  }
1009
- function getBridgeWindow3() {
83
+ function getBridgeWindow2() {
1010
84
  if (typeof window === "undefined") {
1011
85
  return void 0;
1012
86
  }
1013
87
  return window;
1014
88
  }
1015
- function shareRoomCode(roomCode, options) {
1016
- const bridge = getBridgeWindow3();
89
+ function shareRoomCode(roomCode) {
90
+ const bridge = getBridgeWindow2();
1017
91
  if (typeof bridge?.shareRoomCode === "function") {
1018
- bridge.shareRoomCode(roomCode, options);
92
+ bridge.shareRoomCode(roomCode);
1019
93
  return;
1020
94
  }
1021
- if (isDevelopment3()) {
95
+ if (isDevelopment2()) {
1022
96
  console.warn(
1023
97
  "[oasiz/sdk] shareRoomCode bridge is unavailable. This is expected in local development."
1024
98
  );
1025
99
  }
1026
100
  }
1027
- function openInviteModal() {
1028
- const bridge = getBridgeWindow3();
1029
- if (typeof bridge?.openInviteModal === "function") {
1030
- bridge.openInviteModal();
1031
- return;
1032
- }
1033
- if (isDevelopment3()) {
1034
- console.warn(
1035
- "[oasiz/sdk] openInviteModal bridge is unavailable. This is expected in local development."
1036
- );
1037
- }
1038
- }
1039
101
  function getGameId() {
1040
- const bridge = getBridgeWindow3();
102
+ const bridge = getBridgeWindow2();
1041
103
  return bridge?.__GAME_ID__;
1042
104
  }
1043
105
  function getRoomCode() {
1044
- const bridge = getBridgeWindow3();
106
+ const bridge = getBridgeWindow2();
1045
107
  return bridge?.__ROOM_CODE__;
1046
108
  }
1047
- function getPlayerId() {
1048
- const bridge = getBridgeWindow3();
1049
- return bridge?.__PLAYER_ID__;
1050
- }
1051
109
  function getPlayerName() {
1052
- const bridge = getBridgeWindow3();
110
+ const bridge = getBridgeWindow2();
1053
111
  return bridge?.__PLAYER_NAME__;
1054
112
  }
1055
113
  function getPlayerAvatar() {
1056
- const bridge = getBridgeWindow3();
114
+ const bridge = getBridgeWindow2();
1057
115
  return bridge?.__PLAYER_AVATAR__;
1058
116
  }
1059
117
 
1060
118
  // src/score.ts
1061
- function isDevelopment4() {
119
+ function isDevelopment3() {
1062
120
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1063
121
  return nodeEnv !== "production";
1064
122
  }
1065
- function warnMissingBridge2(methodName) {
1066
- if (isDevelopment4()) {
123
+ function warnMissingBridge(methodName) {
124
+ if (isDevelopment3()) {
1067
125
  console.warn(
1068
126
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1069
127
  );
1070
128
  }
1071
129
  }
1072
- function getBridgeWindow4() {
130
+ function getBridgeWindow3() {
1073
131
  if (typeof window === "undefined") {
1074
132
  return void 0;
1075
133
  }
@@ -1077,92 +135,41 @@ function getBridgeWindow4() {
1077
135
  }
1078
136
  function submitScore(score) {
1079
137
  if (!Number.isFinite(score)) {
1080
- if (isDevelopment4()) {
138
+ if (isDevelopment3()) {
1081
139
  console.warn("[oasiz/sdk] submitScore expected a finite number:", score);
1082
140
  }
1083
141
  return;
1084
142
  }
1085
- const bridge = getBridgeWindow4();
143
+ const bridge = getBridgeWindow3();
1086
144
  const normalizedScore = Math.max(0, Math.floor(score));
1087
145
  if (typeof bridge?.submitScore === "function") {
1088
146
  bridge.submitScore(normalizedScore);
1089
147
  return;
1090
148
  }
1091
- warnMissingBridge2("submitScore");
149
+ warnMissingBridge("submitScore");
1092
150
  }
1093
-
1094
- // src/score-edit.ts
1095
- function isDevelopment5() {
1096
- const nodeEnv = globalThis.process?.env?.NODE_ENV;
1097
- return nodeEnv !== "production";
1098
- }
1099
- function getBridgeWindow5() {
1100
- if (typeof window === "undefined") {
1101
- return void 0;
1102
- }
1103
- return window;
1104
- }
1105
- function warnMissingBridge3(methodName) {
1106
- if (isDevelopment5()) {
1107
- console.warn(
1108
- "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1109
- );
1110
- }
1111
- }
1112
- async function editScore(payload, methodName) {
1113
- const bridge = getBridgeWindow5();
1114
- if (typeof bridge?.__oasizEditScore !== "function") {
1115
- warnMissingBridge3(methodName);
1116
- return null;
1117
- }
1118
- try {
1119
- const result = await bridge.__oasizEditScore(payload);
1120
- return result ?? null;
1121
- } catch (error) {
1122
- if (isDevelopment5()) {
1123
- console.error("[oasiz/sdk] " + methodName + " failed:", error);
1124
- }
1125
- return null;
1126
- }
1127
- }
1128
- async function addScore(delta) {
1129
- if (!Number.isInteger(delta)) {
1130
- if (isDevelopment5()) {
1131
- console.warn("[oasiz/sdk] addScore expected an integer:", delta);
1132
- }
1133
- return null;
1134
- }
1135
- if (delta === 0) {
1136
- return null;
1137
- }
1138
- return editScore({ delta }, "addScore");
1139
- }
1140
- async function setScore(score) {
1141
- if (!Number.isInteger(score) || score < 0) {
1142
- if (isDevelopment5()) {
1143
- console.warn(
1144
- "[oasiz/sdk] setScore expected a non-negative integer:",
1145
- score
1146
- );
1147
- }
1148
- return null;
151
+ function emitScoreConfig(config) {
152
+ const bridge = getBridgeWindow3();
153
+ if (typeof bridge?.emitScoreConfig === "function") {
154
+ bridge.emitScoreConfig(config);
155
+ return;
1149
156
  }
1150
- return editScore({ score }, "setScore");
157
+ warnMissingBridge("emitScoreConfig");
1151
158
  }
1152
159
 
1153
160
  // src/share.ts
1154
- function isDevelopment6() {
161
+ function isDevelopment4() {
1155
162
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1156
163
  return nodeEnv !== "production";
1157
164
  }
1158
- function getBridgeWindow6() {
165
+ function getBridgeWindow4() {
1159
166
  if (typeof window === "undefined") {
1160
167
  return void 0;
1161
168
  }
1162
169
  return window;
1163
170
  }
1164
- function warnMissingBridge4(methodName) {
1165
- if (isDevelopment6()) {
171
+ function warnMissingBridge2(methodName) {
172
+ if (isDevelopment4()) {
1166
173
  console.warn(
1167
174
  "[oasiz/sdk] " + methodName + " share bridge is unavailable. This is expected in local development."
1168
175
  );
@@ -1193,458 +200,131 @@ function validateRequest(options) {
1193
200
  }
1194
201
  }
1195
202
  if (hasImage && !isValidImageReference(options.image)) {
1196
- throw new Error(
1197
- "Share image must be an http(s) URL or a data:image/... base64 string."
1198
- );
1199
- }
1200
- return {
1201
- ...hasText ? { text } : {},
1202
- ...hasScore ? { score: options.score } : {},
1203
- ...hasImage ? { image: options.image } : {}
1204
- };
1205
- }
1206
- async function share(options) {
1207
- const request = validateRequest(options);
1208
- const bridge = getBridgeWindow6();
1209
- if (typeof bridge?.__oasizShareRequest !== "function") {
1210
- warnMissingBridge4("__oasizShareRequest");
1211
- throw new Error("Share bridge unavailable");
1212
- }
1213
- await bridge.__oasizShareRequest(request);
1214
- }
1215
-
1216
- // src/state.ts
1217
- function isDevelopment7() {
1218
- const nodeEnv = globalThis.process?.env?.NODE_ENV;
1219
- return nodeEnv !== "production";
1220
- }
1221
- function getBridgeWindow7() {
1222
- if (typeof window === "undefined") {
1223
- return void 0;
1224
- }
1225
- return window;
1226
- }
1227
- function isPlainObject(value) {
1228
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1229
- return false;
1230
- }
1231
- const proto = Object.getPrototypeOf(value);
1232
- return proto === Object.prototype || proto === null;
1233
- }
1234
- function warnMissingBridge5(methodName) {
1235
- if (isDevelopment7()) {
1236
- console.warn(
1237
- "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1238
- );
1239
- }
1240
- }
1241
- function loadGameState() {
1242
- const bridge = getBridgeWindow7();
1243
- if (typeof bridge?.loadGameState !== "function") {
1244
- warnMissingBridge5("loadGameState");
1245
- return {};
1246
- }
1247
- const state = bridge.loadGameState();
1248
- if (!isPlainObject(state)) {
1249
- if (isDevelopment7()) {
1250
- console.warn(
1251
- "[oasiz/sdk] loadGameState returned invalid data. Falling back to empty object."
1252
- );
1253
- }
1254
- return {};
1255
- }
1256
- return state;
1257
- }
1258
- function saveGameState(state) {
1259
- if (!isPlainObject(state)) {
1260
- if (isDevelopment7()) {
1261
- console.warn("[oasiz/sdk] saveGameState expected a plain object:", state);
1262
- }
1263
- return;
1264
- }
1265
- const bridge = getBridgeWindow7();
1266
- if (typeof bridge?.saveGameState === "function") {
1267
- bridge.saveGameState(state);
1268
- return;
1269
- }
1270
- warnMissingBridge5("saveGameState");
1271
- }
1272
- function flushGameState() {
1273
- const bridge = getBridgeWindow7();
1274
- if (typeof bridge?.flushGameState === "function") {
1275
- bridge.flushGameState();
1276
- return;
1277
- }
1278
- warnMissingBridge5("flushGameState");
1279
- }
1280
-
1281
- // src/lifecycle.ts
1282
- function isDevelopment8() {
1283
- const nodeEnv = globalThis.process?.env?.NODE_ENV;
1284
- return nodeEnv !== "production";
1285
- }
1286
- function addLifecycleListener(eventName, callback) {
1287
- if (typeof window === "undefined") {
1288
- if (isDevelopment8()) {
1289
- console.warn(
1290
- "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1291
- );
1292
- }
1293
- return () => {
1294
- };
1295
- }
1296
- const handler = () => callback();
1297
- window.addEventListener(eventName, handler);
1298
- return () => window.removeEventListener(eventName, handler);
1299
- }
1300
- function onPause(callback) {
1301
- return addLifecycleListener("oasiz:pause", callback);
1302
- }
1303
- function onResume(callback) {
1304
- return addLifecycleListener("oasiz:resume", callback);
1305
- }
1306
-
1307
- // src/layout.ts
1308
- var INSET_SIDES = ["top", "right", "bottom", "left"];
1309
- var SIDE_TO_AXIS = {
1310
- top: "vertical",
1311
- right: "horizontal",
1312
- bottom: "vertical",
1313
- left: "horizontal"
1314
- };
1315
- function createInsetEdges(value) {
1316
- return {
1317
- top: value,
1318
- right: value,
1319
- bottom: value,
1320
- left: value
1321
- };
1322
- }
1323
- function isDevelopment9() {
1324
- const nodeEnv = globalThis.process?.env?.NODE_ENV;
1325
- return nodeEnv !== "production";
1326
- }
1327
- function getBridgeWindow8() {
1328
- if (typeof window === "undefined") {
1329
- return void 0;
1330
- }
1331
- return window;
1332
- }
1333
- function warnMissingBridge6(methodName) {
1334
- if (isDevelopment9()) {
1335
- console.warn(
1336
- "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1337
- );
1338
- }
1339
- }
1340
- function isRecord(value) {
1341
- return typeof value === "object" && value !== null;
1342
- }
1343
- function toFiniteNumber(value) {
1344
- if (typeof value !== "number" || !Number.isFinite(value)) {
1345
- if (typeof value !== "string") {
1346
- return void 0;
1347
- }
1348
- const parsed = Number.parseFloat(value.trim());
1349
- if (!Number.isFinite(parsed)) {
1350
- return void 0;
1351
- }
1352
- return parsed;
1353
- }
1354
- return value;
1355
- }
1356
- function clampInsetPixels(value) {
1357
- const numeric = toFiniteNumber(value);
1358
- if (typeof numeric === "undefined") {
1359
- return void 0;
1360
- }
1361
- return Math.max(0, numeric);
1362
- }
1363
- function normalizeInsetPercent(value) {
1364
- const numeric = toFiniteNumber(value);
1365
- if (typeof numeric === "undefined") {
1366
- return void 0;
1367
- }
1368
- return Math.min(100, Math.max(0, numeric));
1369
- }
1370
- function getViewportSize2(bridge, axis) {
1371
- const visualViewportSize = axis === "vertical" ? bridge.visualViewport?.height : bridge.visualViewport?.width;
1372
- if (typeof visualViewportSize === "number" && Number.isFinite(visualViewportSize) && visualViewportSize > 0) {
1373
- return visualViewportSize;
1374
- }
1375
- const innerSize = axis === "vertical" ? bridge.innerHeight : bridge.innerWidth;
1376
- if (typeof innerSize === "number" && Number.isFinite(innerSize) && innerSize > 0) {
1377
- return innerSize;
1378
- }
1379
- const documentSize = axis === "vertical" ? bridge.document?.documentElement?.clientHeight : bridge.document?.documentElement?.clientWidth;
1380
- if (typeof documentSize === "number" && Number.isFinite(documentSize) && documentSize > 0) {
1381
- return documentSize;
1382
- }
1383
- const bodySize = axis === "vertical" ? bridge.document?.body?.clientHeight : bridge.document?.body?.clientWidth;
1384
- if (typeof bodySize === "number" && Number.isFinite(bodySize) && bodySize > 0) {
1385
- return bodySize;
1386
- }
1387
- return 0;
1388
- }
1389
- function readCssSafeAreaValue(bridge, cssValue) {
1390
- const doc = bridge.document;
1391
- const root = doc?.body ?? doc?.documentElement;
1392
- if (!doc || !root || typeof doc.createElement !== "function" || typeof root.appendChild !== "function" || typeof bridge.getComputedStyle !== "function") {
1393
- return 0;
1394
- }
1395
- const probe = doc.createElement("div");
1396
- probe.style.position = "fixed";
1397
- probe.style.top = "0";
1398
- probe.style.left = "0";
1399
- probe.style.width = "0";
1400
- probe.style.height = "0";
1401
- probe.style.visibility = "hidden";
1402
- probe.style.pointerEvents = "none";
1403
- probe.style.paddingTop = cssValue;
1404
- root.appendChild(probe);
1405
- try {
1406
- return clampInsetPixels(bridge.getComputedStyle(probe).paddingTop) ?? 0;
1407
- } finally {
1408
- if (typeof probe.remove === "function") {
1409
- probe.remove();
1410
- } else {
1411
- probe.parentNode?.removeChild(probe);
1412
- }
1413
- }
1414
- }
1415
- function readCssSafeAreaPixels(bridge, side) {
1416
- const envPixels = readCssSafeAreaValue(
1417
- bridge,
1418
- "env(safe-area-inset-" + side + ")"
1419
- );
1420
- if (envPixels > 0) {
1421
- return envPixels;
1422
- }
1423
- return readCssSafeAreaValue(
1424
- bridge,
1425
- "constant(safe-area-inset-" + side + ")"
1426
- );
1427
- }
1428
- function getDevicePixelRatio(bridge) {
1429
- const dpr = bridge.devicePixelRatio;
1430
- if (typeof dpr !== "number" || !Number.isFinite(dpr) || dpr <= 0) {
1431
- return 1;
1432
- }
1433
- return dpr;
1434
- }
1435
- function roughlyEqualPixels(a, b) {
1436
- return Math.abs(a - b) <= 2;
1437
- }
1438
- function normalizeInsetPixels(value, bridge, side) {
1439
- const pixels = clampInsetPixels(value);
1440
- if (typeof pixels === "undefined") {
1441
- return void 0;
1442
- }
1443
- const cssEnvPixels = readCssSafeAreaPixels(bridge, side);
1444
- if (pixels <= 0) {
1445
- return cssEnvPixels;
1446
- }
1447
- const dpr = getDevicePixelRatio(bridge);
1448
- if (cssEnvPixels > 0 && dpr > 1 && roughlyEqualPixels(pixels / dpr, cssEnvPixels)) {
1449
- return cssEnvPixels;
1450
- }
1451
- return pixels;
1452
- }
1453
- function pixelsToPercentOfViewport(pixels, bridge, side) {
1454
- const size = getViewportSize2(bridge, SIDE_TO_AXIS[side]);
1455
- if (size <= 0) {
1456
- return 0;
203
+ throw new Error(
204
+ "Share image must be an http(s) URL or a data:image/... base64 string."
205
+ );
1457
206
  }
1458
- return normalizeInsetPercent(pixels / size * 100) ?? 0;
207
+ return {
208
+ ...hasText ? { text } : {},
209
+ ...hasScore ? { score: options.score } : {},
210
+ ...hasImage ? { image: options.image } : {}
211
+ };
1459
212
  }
1460
- function percentToPixelsOfViewport(percent, bridge, side) {
1461
- const size = getViewportSize2(bridge, SIDE_TO_AXIS[side]);
1462
- if (size <= 0) {
1463
- return 0;
213
+ async function share(options) {
214
+ const request = validateRequest(options);
215
+ const bridge = getBridgeWindow4();
216
+ if (typeof bridge?.__oasizShareRequest !== "function") {
217
+ warnMissingBridge2("__oasizShareRequest");
218
+ throw new Error("Share bridge unavailable");
1464
219
  }
1465
- return percent / 100 * size;
1466
- }
1467
- function cssSafeAreaPercent(bridge, side) {
1468
- return pixelsToPercentOfViewport(readCssSafeAreaPixels(bridge, side), bridge, side);
220
+ await bridge.__oasizShareRequest(request);
1469
221
  }
1470
- function resolvePercentValue(value, bridge, side) {
1471
- const percent = normalizeInsetPercent(value);
1472
- if (typeof percent === "undefined") {
1473
- return void 0;
1474
- }
1475
- return percent > 0 ? percent : cssSafeAreaPercent(bridge, side);
222
+
223
+ // src/state.ts
224
+ function isDevelopment5() {
225
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
226
+ return nodeEnv !== "production";
1476
227
  }
1477
- function resolvePixelValue(value, bridge, side) {
1478
- const numeric = toFiniteNumber(value);
1479
- if (typeof numeric === "undefined") {
228
+ function getBridgeWindow5() {
229
+ if (typeof window === "undefined") {
1480
230
  return void 0;
1481
231
  }
1482
- return normalizeInsetPixels(numeric, bridge, side);
232
+ return window;
1483
233
  }
1484
- function sideSuffix(side) {
1485
- switch (side) {
1486
- case "top":
1487
- return "Top";
1488
- case "right":
1489
- return "Right";
1490
- case "bottom":
1491
- return "Bottom";
1492
- case "left":
1493
- return "Left";
234
+ function isPlainObject(value) {
235
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
236
+ return false;
1494
237
  }
238
+ const proto = Object.getPrototypeOf(value);
239
+ return proto === Object.prototype || proto === null;
1495
240
  }
1496
- function callBridgeFunction(bridge, name) {
1497
- const fn = bridge[name];
1498
- if (typeof fn !== "function") {
1499
- return void 0;
1500
- }
1501
- try {
1502
- return fn.call(bridge);
1503
- } catch (error) {
1504
- console.error("[oasiz/sdk] " + String(name) + " failed:", error);
1505
- return void 0;
241
+ function warnMissingBridge3(methodName) {
242
+ if (isDevelopment5()) {
243
+ console.warn(
244
+ "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
245
+ );
1506
246
  }
1507
247
  }
1508
- function readInsetObjectValue(value, side, group) {
1509
- if (!isRecord(value)) {
1510
- return void 0;
248
+ function loadGameState() {
249
+ const bridge = getBridgeWindow5();
250
+ if (typeof bridge?.loadGameState !== "function") {
251
+ warnMissingBridge3("loadGameState");
252
+ return {};
1511
253
  }
1512
- if (group) {
1513
- return isRecord(value[group]) ? value[group][side] : void 0;
254
+ const state = bridge.loadGameState();
255
+ if (!isPlainObject(state)) {
256
+ if (isDevelopment5()) {
257
+ console.warn(
258
+ "[oasiz/sdk] loadGameState returned invalid data. Falling back to empty object."
259
+ );
260
+ }
261
+ return {};
1514
262
  }
1515
- return value[side];
1516
- }
1517
- function firstDefined(...values) {
1518
- return values.find((value) => typeof value !== "undefined");
1519
- }
1520
- function readHostInsetSources(bridge) {
1521
- return {
1522
- globalPixels: bridge.__OASIZ_VIEWPORT_INSETS__,
1523
- globalPercent: bridge.__OASIZ_VIEWPORT_INSETS_PERCENT__,
1524
- methodPixels: callBridgeFunction(bridge, "getViewportInsets"),
1525
- methodPercent: callBridgeFunction(bridge, "getViewportInsetsPercent")
1526
- };
1527
- }
1528
- function readIndividualPercentValue(bridge, side) {
1529
- const suffix = sideSuffix(side);
1530
- return firstDefined(
1531
- callBridgeFunction(
1532
- bridge,
1533
- "getSafeArea" + suffix + "Percent"
1534
- ),
1535
- bridge["__OASIZ_SAFE_AREA_" + side.toUpperCase() + "_PERCENT__"]
1536
- );
1537
- }
1538
- function readIndividualPixelValue(bridge, side) {
1539
- const suffix = sideSuffix(side);
1540
- return firstDefined(
1541
- callBridgeFunction(
1542
- bridge,
1543
- "getSafeArea" + suffix
1544
- ),
1545
- bridge["__OASIZ_SAFE_AREA_" + side.toUpperCase() + "__"]
1546
- );
263
+ return state;
1547
264
  }
1548
- function resolveInsetSide(bridge, sources, side) {
1549
- const percentCandidate = firstDefined(
1550
- readInsetObjectValue(sources.methodPercent, side, "percent"),
1551
- readInsetObjectValue(sources.methodPercent, side),
1552
- readInsetObjectValue(sources.globalPercent, side, "percent"),
1553
- readInsetObjectValue(sources.globalPercent, side),
1554
- readInsetObjectValue(sources.methodPixels, side, "percent"),
1555
- readInsetObjectValue(sources.globalPixels, side, "percent"),
1556
- readIndividualPercentValue(bridge, side)
1557
- );
1558
- const percent = resolvePercentValue(percentCandidate, bridge, side);
1559
- if (typeof percent !== "undefined") {
1560
- return {
1561
- pixels: percentToPixelsOfViewport(percent, bridge, side),
1562
- percent
1563
- };
265
+ function saveGameState(state) {
266
+ if (!isPlainObject(state)) {
267
+ if (isDevelopment5()) {
268
+ console.warn("[oasiz/sdk] saveGameState expected a plain object:", state);
269
+ }
270
+ return;
1564
271
  }
1565
- const pixelCandidate = firstDefined(
1566
- readInsetObjectValue(sources.methodPixels, side, "pixels"),
1567
- readInsetObjectValue(sources.methodPixels, side),
1568
- readInsetObjectValue(sources.globalPixels, side, "pixels"),
1569
- readInsetObjectValue(sources.globalPixels, side),
1570
- readIndividualPixelValue(bridge, side)
1571
- );
1572
- const pixels = resolvePixelValue(pixelCandidate, bridge, side);
1573
- if (typeof pixels !== "undefined") {
1574
- return {
1575
- pixels,
1576
- percent: pixelsToPercentOfViewport(pixels, bridge, side)
1577
- };
272
+ const bridge = getBridgeWindow5();
273
+ if (typeof bridge?.saveGameState === "function") {
274
+ bridge.saveGameState(state);
275
+ return;
1578
276
  }
1579
- const cssPixels = readCssSafeAreaPixels(bridge, side);
1580
- return {
1581
- pixels: cssPixels,
1582
- percent: pixelsToPercentOfViewport(cssPixels, bridge, side)
1583
- };
277
+ warnMissingBridge3("saveGameState");
1584
278
  }
1585
- function getViewportInsets() {
1586
- const bridge = getBridgeWindow8();
1587
- if (!bridge) {
1588
- return {
1589
- pixels: createInsetEdges(0),
1590
- percent: createInsetEdges(0)
1591
- };
1592
- }
1593
- const sources = readHostInsetSources(bridge);
1594
- const pixels = createInsetEdges(0);
1595
- const percent = createInsetEdges(0);
1596
- for (const side of INSET_SIDES) {
1597
- const resolved = resolveInsetSide(bridge, sources, side);
1598
- pixels[side] = resolved.pixels;
1599
- percent[side] = resolved.percent;
279
+ function flushGameState() {
280
+ const bridge = getBridgeWindow5();
281
+ if (typeof bridge?.flushGameState === "function") {
282
+ bridge.flushGameState();
283
+ return;
1600
284
  }
1601
- return { pixels, percent };
285
+ warnMissingBridge3("flushGameState");
1602
286
  }
1603
- function getSafeAreaTop() {
1604
- const bridge = getBridgeWindow8();
1605
- if (!bridge) {
1606
- return 0;
1607
- }
1608
- const top = getViewportInsets().percent.top;
1609
- if (top <= 0) {
1610
- warnMissingBridge6("getSafeAreaTop");
1611
- }
1612
- return top;
287
+
288
+ // src/lifecycle.ts
289
+ function isDevelopment6() {
290
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
291
+ return nodeEnv !== "production";
1613
292
  }
1614
- function setLeaderboardVisible(visible) {
1615
- if (typeof visible !== "boolean") {
1616
- if (isDevelopment9()) {
293
+ function addLifecycleListener(eventName, callback) {
294
+ if (typeof window === "undefined") {
295
+ if (isDevelopment6()) {
1617
296
  console.warn(
1618
- "[oasiz/sdk] setLeaderboardVisible expected a boolean:",
1619
- visible
297
+ "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1620
298
  );
1621
299
  }
1622
- return;
1623
- }
1624
- const bridge = getBridgeWindow8();
1625
- if (typeof bridge?.__oasizSetLeaderboardVisible === "function") {
1626
- bridge.__oasizSetLeaderboardVisible(visible);
1627
- return;
300
+ return () => {
301
+ };
1628
302
  }
1629
- warnMissingBridge6("__oasizSetLeaderboardVisible");
303
+ const handler = () => callback();
304
+ window.addEventListener(eventName, handler);
305
+ return () => window.removeEventListener(eventName, handler);
306
+ }
307
+ function onPause(callback) {
308
+ return addLifecycleListener("oasiz:pause", callback);
309
+ }
310
+ function onResume(callback) {
311
+ return addLifecycleListener("oasiz:resume", callback);
1630
312
  }
1631
313
 
1632
314
  // src/navigation.ts
1633
- var BACK_BUTTON_TEST_STATE_KEY = "__oasizBackButtonTest";
1634
315
  var activeBackListeners = 0;
1635
- var activeBackButtonTestingHandle;
1636
- function isDevelopment10() {
316
+ function isDevelopment7() {
1637
317
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1638
318
  return nodeEnv !== "production";
1639
319
  }
1640
- function getBridgeWindow9() {
320
+ function getBridgeWindow6() {
1641
321
  if (typeof window === "undefined") {
1642
322
  return void 0;
1643
323
  }
1644
324
  return window;
1645
325
  }
1646
- function warnMissingBridge7(methodName) {
1647
- if (isDevelopment10()) {
326
+ function warnMissingBridge4(methodName) {
327
+ if (isDevelopment7()) {
1648
328
  console.warn(
1649
329
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1650
330
  );
@@ -1658,19 +338,9 @@ function normalizeNavigationError(error) {
1658
338
  typeof error === "string" ? error : "Back button callback failed."
1659
339
  );
1660
340
  }
1661
- function isRecord2(value) {
1662
- return typeof value === "object" && value !== null;
1663
- }
1664
- function dispatchNavigationEvent(eventName) {
1665
- const bridge = getBridgeWindow9();
1666
- if (!bridge || typeof bridge.dispatchEvent !== "function") {
1667
- return;
1668
- }
1669
- bridge.dispatchEvent(new Event(eventName));
1670
- }
1671
341
  function addNavigationListener(eventName, callback) {
1672
342
  if (typeof window === "undefined") {
1673
- if (isDevelopment10()) {
343
+ if (isDevelopment7()) {
1674
344
  console.warn(
1675
345
  "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1676
346
  );
@@ -1682,151 +352,6 @@ function addNavigationListener(eventName, callback) {
1682
352
  window.addEventListener(eventName, handler);
1683
353
  return () => window.removeEventListener(eventName, handler);
1684
354
  }
1685
- function enableBackButtonTesting(options = {}) {
1686
- activeBackButtonTestingHandle?.destroy();
1687
- const bridge = getBridgeWindow9();
1688
- if (!bridge) {
1689
- if (isDevelopment10()) {
1690
- console.warn(
1691
- "[oasiz/sdk] enableBackButtonTesting requires a browser window."
1692
- );
1693
- }
1694
- return {
1695
- destroy: () => {
1696
- },
1697
- isBackOverrideActive: () => false,
1698
- triggerBack: () => {
1699
- },
1700
- triggerLeave: () => {
1701
- }
1702
- };
1703
- }
1704
- const bridgeWindow = bridge;
1705
- const keyboard = options.keyboard ?? true;
1706
- const browserHistory = options.browserHistory ?? true;
1707
- const log = options.log === true;
1708
- const previousSetBackOverride = bridgeWindow.__oasizSetBackOverride;
1709
- const previousLeaveGame = bridgeWindow.__oasizLeaveGame;
1710
- let destroyed = false;
1711
- let backOverrideActive = false;
1712
- let historyTrapArmed = false;
1713
- function maybeLog(message) {
1714
- if (log) {
1715
- console.info("[oasiz/sdk] " + message);
1716
- }
1717
- }
1718
- function canUseHistoryTrap() {
1719
- return browserHistory && typeof bridgeWindow.history?.pushState === "function" && typeof bridgeWindow.history?.replaceState === "function" && typeof bridgeWindow.location?.href === "string";
1720
- }
1721
- function ensureHistoryTrap() {
1722
- if (!backOverrideActive || historyTrapArmed || !canUseHistoryTrap()) {
1723
- return;
1724
- }
1725
- try {
1726
- const currentState = isRecord2(bridgeWindow.history.state) ? bridgeWindow.history.state : {};
1727
- bridgeWindow.history.replaceState(
1728
- { ...currentState, [BACK_BUTTON_TEST_STATE_KEY]: "base" },
1729
- "",
1730
- bridgeWindow.location.href
1731
- );
1732
- bridgeWindow.history.pushState(
1733
- { [BACK_BUTTON_TEST_STATE_KEY]: "trap" },
1734
- "",
1735
- bridgeWindow.location.href
1736
- );
1737
- historyTrapArmed = true;
1738
- maybeLog("Local browser Back testing is armed.");
1739
- } catch (error) {
1740
- historyTrapArmed = false;
1741
- if (log) {
1742
- console.warn("[oasiz/sdk] Failed to arm browser Back testing:", error);
1743
- }
1744
- }
1745
- }
1746
- function triggerBack() {
1747
- dispatchNavigationEvent("oasiz:back");
1748
- }
1749
- function triggerLeave() {
1750
- dispatchNavigationEvent("oasiz:leave");
1751
- }
1752
- function setBackOverride(active) {
1753
- backOverrideActive = active;
1754
- if (active) {
1755
- ensureHistoryTrap();
1756
- }
1757
- if (typeof previousSetBackOverride === "function") {
1758
- previousSetBackOverride(active);
1759
- }
1760
- maybeLog("Back override " + (active ? "enabled" : "disabled") + ".");
1761
- }
1762
- function stopBackEvent(event) {
1763
- event.preventDefault();
1764
- event.stopPropagation();
1765
- event.stopImmediatePropagation?.();
1766
- }
1767
- const handleKeyDown = (event) => {
1768
- if (!backOverrideActive || event.key !== "Escape") {
1769
- return;
1770
- }
1771
- stopBackEvent(event);
1772
- triggerBack();
1773
- };
1774
- const handlePopState = (event) => {
1775
- if (!backOverrideActive) {
1776
- historyTrapArmed = false;
1777
- return;
1778
- }
1779
- stopBackEvent(event);
1780
- triggerBack();
1781
- historyTrapArmed = false;
1782
- ensureHistoryTrap();
1783
- };
1784
- const testLeaveGame = () => {
1785
- triggerLeave();
1786
- if (typeof previousLeaveGame === "function") {
1787
- previousLeaveGame();
1788
- }
1789
- };
1790
- bridgeWindow.__oasizSetBackOverride = setBackOverride;
1791
- bridgeWindow.__oasizLeaveGame = testLeaveGame;
1792
- if (keyboard) {
1793
- bridgeWindow.addEventListener("keydown", handleKeyDown);
1794
- }
1795
- if (browserHistory) {
1796
- bridgeWindow.addEventListener("popstate", handlePopState);
1797
- }
1798
- if (activeBackListeners > 0) {
1799
- setBackOverride(true);
1800
- }
1801
- maybeLog("Back button testing bridge installed.");
1802
- const handle = {
1803
- destroy: () => {
1804
- if (destroyed) return;
1805
- destroyed = true;
1806
- if (keyboard) {
1807
- bridgeWindow.removeEventListener("keydown", handleKeyDown);
1808
- }
1809
- if (browserHistory) {
1810
- bridgeWindow.removeEventListener("popstate", handlePopState);
1811
- }
1812
- if (bridgeWindow.__oasizSetBackOverride === setBackOverride) {
1813
- bridgeWindow.__oasizSetBackOverride = previousSetBackOverride;
1814
- }
1815
- if (bridgeWindow.__oasizLeaveGame === testLeaveGame) {
1816
- bridgeWindow.__oasizLeaveGame = previousLeaveGame;
1817
- }
1818
- if (activeBackButtonTestingHandle === handle) {
1819
- activeBackButtonTestingHandle = void 0;
1820
- }
1821
- maybeLog("Back button testing bridge removed.");
1822
- },
1823
- isBackOverrideActive: () => backOverrideActive,
1824
- triggerBack,
1825
- triggerLeave
1826
- };
1827
- activeBackButtonTestingHandle = handle;
1828
- return handle;
1829
- }
1830
355
  function onBackButton(callback) {
1831
356
  const off = addNavigationListener("oasiz:back", () => {
1832
357
  try {
@@ -1836,24 +361,24 @@ function onBackButton(callback) {
1836
361
  throw normalizeNavigationError(error);
1837
362
  }
1838
363
  });
1839
- const bridge = getBridgeWindow9();
364
+ const bridge = getBridgeWindow6();
1840
365
  activeBackListeners += 1;
1841
366
  if (activeBackListeners === 1) {
1842
367
  if (typeof bridge?.__oasizSetBackOverride === "function") {
1843
368
  bridge.__oasizSetBackOverride(true);
1844
369
  } else {
1845
- warnMissingBridge7("__oasizSetBackOverride");
370
+ warnMissingBridge4("__oasizSetBackOverride");
1846
371
  }
1847
372
  }
1848
373
  return () => {
1849
374
  off();
1850
375
  activeBackListeners = Math.max(0, activeBackListeners - 1);
1851
376
  if (activeBackListeners === 0) {
1852
- const currentBridge = getBridgeWindow9();
377
+ const currentBridge = getBridgeWindow6();
1853
378
  if (typeof currentBridge?.__oasizSetBackOverride === "function") {
1854
379
  currentBridge.__oasizSetBackOverride(false);
1855
380
  } else {
1856
- warnMissingBridge7("__oasizSetBackOverride");
381
+ warnMissingBridge4("__oasizSetBackOverride");
1857
382
  }
1858
383
  }
1859
384
  };
@@ -1862,414 +387,177 @@ function onLeaveGame(callback) {
1862
387
  return addNavigationListener("oasiz:leave", callback);
1863
388
  }
1864
389
  function leaveGame() {
1865
- const bridge = getBridgeWindow9();
390
+ const bridge = getBridgeWindow6();
1866
391
  if (typeof bridge?.__oasizLeaveGame === "function") {
1867
392
  bridge.__oasizLeaveGame();
1868
393
  return;
1869
394
  }
1870
- warnMissingBridge7("__oasizLeaveGame");
395
+ warnMissingBridge4("__oasizLeaveGame");
1871
396
  }
1872
397
 
1873
- // src/performance.ts
1874
- var DEFAULT_GRAPHICS_PERFORMANCE = {
1875
- fps: 45,
1876
- tier: "medium"
1877
- };
1878
- var TIER_DEFAULT_FPS = {
1879
- minimal: 24,
1880
- low: 30,
1881
- medium: 45,
1882
- high: 60
1883
- };
1884
- var cachedEstimatedGraphicsPerformance;
1885
- function getBridgeWindow10() {
398
+ // src/store.ts
399
+ function isDevelopment8() {
400
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
401
+ return nodeEnv !== "production";
402
+ }
403
+ function getBridgeWindow7() {
1886
404
  if (typeof window === "undefined") {
1887
405
  return void 0;
1888
406
  }
1889
407
  return window;
1890
408
  }
1891
- function isRecord3(value) {
1892
- return typeof value === "object" && value !== null;
1893
- }
1894
- function toFiniteNumber2(value) {
1895
- if (typeof value === "number" && Number.isFinite(value)) {
1896
- return value;
1897
- }
1898
- if (typeof value === "string") {
1899
- const parsed = Number.parseFloat(value.trim());
1900
- if (Number.isFinite(parsed)) {
1901
- return parsed;
1902
- }
409
+ function warnMissingBridge5(methodName) {
410
+ if (isDevelopment8()) {
411
+ console.warn(
412
+ "[oasiz/sdk] " + methodName + " store bridge is unavailable. This is expected in local development."
413
+ );
1903
414
  }
1904
- return void 0;
1905
- }
1906
- function clampScore(value) {
1907
- return Math.min(100, Math.max(0, Math.round(value)));
1908
- }
1909
- function clampFps(value) {
1910
- return Math.min(240, Math.max(1, Math.round(value)));
1911
- }
1912
- function tierFromScore(score) {
1913
- if (score < 25) return "minimal";
1914
- if (score < 40) return "low";
1915
- if (score < 70) return "medium";
1916
- return "high";
1917
- }
1918
- function tierFromFps(fps) {
1919
- if (fps < 30) return "minimal";
1920
- if (fps < 45) return "low";
1921
- if (fps < 58) return "medium";
1922
- return "high";
1923
- }
1924
- function fpsFromScore(score) {
1925
- return TIER_DEFAULT_FPS[tierFromScore(score)];
1926
415
  }
1927
- function normalizeTier(value) {
1928
- if (typeof value !== "string") {
1929
- return void 0;
416
+ async function requestStoreBridge(action, payload) {
417
+ const bridge = getBridgeWindow7();
418
+ if (typeof bridge?.__oasizStoreBridgeRequest !== "function") {
419
+ warnMissingBridge5(action);
420
+ throw new Error("Store bridge unavailable");
1930
421
  }
1931
- const normalized = value.trim().toLowerCase();
1932
- if (normalized === "minimal" || normalized === "safe") return "minimal";
1933
- if (normalized === "high") return "high";
1934
- if (normalized === "medium") return "medium";
1935
- if (normalized === "low") return "low";
1936
- return void 0;
422
+ return await bridge.__oasizStoreBridgeRequest({
423
+ action,
424
+ payload
425
+ });
1937
426
  }
1938
- function firstDefined2(...values) {
1939
- return values.find((value) => typeof value !== "undefined");
427
+ async function getFreshState() {
428
+ return await requestStoreBridge("getStoreState");
1940
429
  }
1941
- function normalizeMetric(value) {
1942
- if (typeof value === "undefined" || value === null) {
1943
- return void 0;
1944
- }
1945
- if (typeof value === "number" || typeof value === "string") {
1946
- const numeric = toFiniteNumber2(value);
1947
- if (typeof numeric !== "undefined") {
1948
- const fps2 = clampFps(numeric);
1949
- return { fps: fps2, tier: tierFromFps(fps2) };
1950
- }
1951
- const tier2 = normalizeTier(value);
1952
- if (tier2) {
1953
- return { fps: TIER_DEFAULT_FPS[tier2], tier: tier2 };
1954
- }
1955
- return void 0;
1956
- }
1957
- if (!isRecord3(value)) {
1958
- return void 0;
1959
- }
1960
- const fpsCandidate = firstDefined2(
1961
- value.fps,
1962
- value.targetFps,
1963
- value.frameRate,
1964
- value.framesPerSecond
1965
- );
1966
- const legacyScoreCandidate = firstDefined2(
1967
- value.score,
1968
- value.metric,
1969
- value.value,
1970
- value.performance,
1971
- value.performanceScore,
1972
- value.graphicsScore
1973
- );
1974
- const tierCandidate = firstDefined2(
1975
- value.tier,
1976
- value.graphicsTier,
1977
- value.performanceTier,
1978
- value.qualityTier,
1979
- value.recommendedTier
1980
- );
1981
- const tier = normalizeTier(tierCandidate);
1982
- const fpsNumeric = toFiniteNumber2(fpsCandidate);
1983
- const scoreNumeric = toFiniteNumber2(legacyScoreCandidate);
1984
- if (typeof fpsNumeric === "undefined" && typeof scoreNumeric === "undefined" && !tier) {
1985
- return void 0;
430
+ function subscribeToStoreEvent(eventName, callback) {
431
+ if (typeof window === "undefined") {
432
+ return () => {
433
+ };
1986
434
  }
1987
- const fps = clampFps(
1988
- typeof fpsNumeric !== "undefined" ? fpsNumeric : typeof scoreNumeric !== "undefined" ? fpsFromScore(clampScore(scoreNumeric)) : TIER_DEFAULT_FPS[tier]
1989
- );
1990
- return {
1991
- fps,
1992
- tier: tier ?? tierFromFps(fps)
435
+ const handler = (event) => {
436
+ const customEvent = event;
437
+ callback(customEvent.detail);
438
+ };
439
+ window.addEventListener(eventName, handler);
440
+ return () => {
441
+ window.removeEventListener(eventName, handler);
1993
442
  };
1994
443
  }
1995
- function callBridgeMetric(bridge, name) {
1996
- const fn = bridge[name];
1997
- if (typeof fn !== "function") {
1998
- return void 0;
1999
- }
2000
- try {
2001
- return fn.call(bridge);
2002
- } catch (error) {
2003
- console.error("[oasiz/sdk] " + String(name) + " failed:", error);
2004
- return void 0;
2005
- }
2006
- }
2007
- function readHostGraphicsPerformance(bridge) {
2008
- const candidates = [
2009
- callBridgeMetric(bridge, "getGraphicsPerformance"),
2010
- callBridgeMetric(bridge, "getGraphicsPerformanceMetric"),
2011
- callBridgeMetric(bridge, "getPerformanceMetric"),
2012
- bridge.__OASIZ_GRAPHICS_PERFORMANCE__,
2013
- bridge.__OASIZ_PERFORMANCE_METRIC__
2014
- ];
2015
- for (const candidate of candidates) {
2016
- const metric = normalizeMetric(candidate);
2017
- if (metric) {
2018
- return metric;
2019
- }
2020
- }
2021
- return void 0;
2022
- }
2023
- function getDevicePixelRatio2(bridge) {
2024
- const dpr = bridge.devicePixelRatio;
2025
- return typeof dpr === "number" && Number.isFinite(dpr) && dpr > 0 ? dpr : 1;
2026
- }
2027
- function getNavigatorValue(bridge) {
2028
- return bridge.navigator;
2029
- }
2030
- function parseIosMajorVersion(userAgent) {
2031
- const match = /\bOS (\d+)_/i.exec(userAgent);
2032
- if (!match) {
2033
- return void 0;
2034
- }
2035
- const parsed = Number.parseInt(match[1] ?? "", 10);
2036
- return Number.isFinite(parsed) ? parsed : void 0;
2037
- }
2038
- function isAppleMobileDevice(bridge) {
2039
- const userAgent = bridge.navigator?.userAgent ?? "";
2040
- return /iP(hone|ad|od)/i.test(userAgent) || /Macintosh/i.test(userAgent) && (bridge.navigator?.maxTouchPoints ?? 0) > 1;
2041
- }
2042
- function getLongestScreenEdge(bridge) {
2043
- const screenWidth = bridge.screen?.width;
2044
- const screenHeight = bridge.screen?.height;
2045
- const innerWidth = bridge.innerWidth;
2046
- const innerHeight = bridge.innerHeight;
2047
- return Math.max(
2048
- typeof screenWidth === "number" ? screenWidth : 0,
2049
- typeof screenHeight === "number" ? screenHeight : 0,
2050
- typeof innerWidth === "number" ? innerWidth : 0,
2051
- typeof innerHeight === "number" ? innerHeight : 0
2052
- );
444
+ async function syncProducts(products, expectedVersion) {
445
+ return await requestStoreBridge("syncProducts", {
446
+ expectedVersion: expectedVersion ?? null,
447
+ products
448
+ });
2053
449
  }
2054
- function isCoarsePointer(bridge) {
2055
- try {
2056
- return bridge.matchMedia?.("(pointer: coarse)").matches === true;
2057
- } catch {
2058
- return false;
2059
- }
450
+ async function getProducts() {
451
+ const state = await getFreshState();
452
+ return state.products;
2060
453
  }
2061
- function getWebGlInfo(bridge) {
2062
- const canvas = bridge.document?.createElement?.("canvas");
2063
- if (!canvas || typeof canvas.getContext !== "function") {
2064
- return { webglVersion: 0 };
2065
- }
2066
- const gl2 = canvas.getContext("webgl2");
2067
- const gl = gl2 ?? canvas.getContext("webgl") ?? canvas.getContext("experimental-webgl");
2068
- if (!gl) {
2069
- return { webglVersion: 0 };
2070
- }
2071
- const context = gl;
2072
- let maxTextureSize;
2073
- let renderer;
2074
- try {
2075
- const value = context.getParameter(context.MAX_TEXTURE_SIZE);
2076
- if (typeof value === "number" && Number.isFinite(value)) {
2077
- maxTextureSize = value;
2078
- }
2079
- } catch {
2080
- }
2081
- try {
2082
- const debugInfo = context.getExtension("WEBGL_debug_renderer_info");
2083
- const rendererParam = debugInfo?.UNMASKED_RENDERER_WEBGL ?? context.RENDERER;
2084
- const value = context.getParameter(rendererParam);
2085
- if (typeof value === "string") {
2086
- renderer = value;
2087
- }
2088
- } catch {
2089
- }
2090
- return {
2091
- maxTextureSize,
2092
- renderer,
2093
- webglVersion: gl2 ? 2 : 1
2094
- };
454
+ async function getEntitlements() {
455
+ const state = await getFreshState();
456
+ return state.entitlements;
2095
457
  }
2096
- function applyMemoryScore(score, memory) {
2097
- if (typeof memory !== "number" || !Number.isFinite(memory) || memory <= 0) {
2098
- return score;
2099
- }
2100
- if (memory <= 1) return score - 18;
2101
- if (memory <= 2) return score - 10;
2102
- if (memory >= 8) return score + 16;
2103
- if (memory >= 6) return score + 10;
2104
- return score;
458
+ async function hasEntitlement(productId) {
459
+ const state = await getFreshState();
460
+ return state.entitlements.find((item) => item.productId === productId)?.owned === true;
2105
461
  }
2106
- function applyCoreScore(score, cores) {
2107
- if (typeof cores !== "number" || !Number.isFinite(cores) || cores <= 0) {
2108
- return score;
2109
- }
2110
- if (cores <= 2) return score - 12;
2111
- if (cores <= 4) return score;
2112
- if (cores >= 8) return score + 12;
2113
- return score + 8;
462
+ async function getQuantity(productId) {
463
+ const state = await getFreshState();
464
+ return state.entitlements.find((item) => item.productId === productId)?.quantity ?? 0;
2114
465
  }
2115
- function applyWebGlScore(score, info) {
2116
- let next = score;
2117
- if (info.webglVersion === 2) {
2118
- next += 16;
2119
- } else if (info.webglVersion === 1) {
2120
- next += 6;
2121
- } else {
2122
- next -= 25;
2123
- }
2124
- if (typeof info.maxTextureSize === "number") {
2125
- if (info.maxTextureSize >= 8192) next += 10;
2126
- else if (info.maxTextureSize >= 4096) next += 3;
2127
- else next -= 8;
2128
- }
2129
- const renderer = info.renderer?.toLowerCase() ?? "";
2130
- if (/swiftshader|llvmpipe|software/.test(renderer)) {
2131
- next = Math.min(next, 35);
2132
- } else if (/\bm[1-9]\b|apple gpu|a1[6-9]|rtx|radeon|adreno 7|adreno 8/.test(renderer)) {
2133
- next += 8;
2134
- }
2135
- return next;
466
+ async function purchase(productId, quantity = 1) {
467
+ return await requestStoreBridge("purchaseProduct", {
468
+ productId,
469
+ quantity
470
+ });
2136
471
  }
2137
- function applyMobileCostScore(score, bridge) {
2138
- if (!isCoarsePointer(bridge)) {
2139
- return score;
2140
- }
2141
- const dpr = getDevicePixelRatio2(bridge);
2142
- const width = bridge.screen?.width ?? bridge.innerWidth ?? 0;
2143
- const height = bridge.screen?.height ?? bridge.innerHeight ?? 0;
2144
- const physicalPixels = width * height * dpr * dpr;
2145
- if (physicalPixels > 4e6) {
2146
- return score - 5;
2147
- }
2148
- return score;
472
+ async function consume(productId, quantity = 1) {
473
+ return await requestStoreBridge("consumeProduct", {
474
+ productId,
475
+ quantity
476
+ });
2149
477
  }
2150
- function applyAppleMobileRules(score, bridge) {
2151
- if (!isAppleMobileDevice(bridge)) {
2152
- return score;
2153
- }
2154
- const userAgent = bridge.navigator?.userAgent ?? "";
2155
- const iosMajorVersion = parseIosMajorVersion(userAgent);
2156
- const highDensityDisplay = getDevicePixelRatio2(bridge) >= 3;
2157
- const longestScreenEdge = getLongestScreenEdge(bridge);
2158
- if (typeof iosMajorVersion === "number" && iosMajorVersion < 15) {
2159
- return Math.min(score, 35);
2160
- }
2161
- if (highDensityDisplay && longestScreenEdge >= 430 && (typeof iosMajorVersion === "undefined" || iosMajorVersion >= 17)) {
2162
- return Math.max(score, 78);
2163
- }
2164
- if (highDensityDisplay && longestScreenEdge >= 414 && (typeof iosMajorVersion === "undefined" || iosMajorVersion >= 16)) {
2165
- return Math.max(score, 62);
2166
- }
2167
- return Math.min(score, 48);
478
+ async function getJemBalance() {
479
+ const state = await getFreshState();
480
+ return state.jemBalance;
2168
481
  }
2169
- function estimateGraphicsPerformance(bridge) {
2170
- const navigatorValue = getNavigatorValue(bridge);
2171
- let score = 45;
2172
- score = applyMemoryScore(score, navigatorValue?.deviceMemory);
2173
- score = applyCoreScore(score, navigatorValue?.hardwareConcurrency);
2174
- score = applyWebGlScore(score, getWebGlInfo(bridge));
2175
- score = applyMobileCostScore(score, bridge);
2176
- score = applyAppleMobileRules(score, bridge);
2177
- const normalizedScore = clampScore(score);
2178
- return {
2179
- fps: fpsFromScore(normalizedScore),
2180
- tier: tierFromScore(normalizedScore)
2181
- };
482
+ function onEntitlementsChanged(callback) {
483
+ return subscribeToStoreEvent("oasiz:entitlements-changed", (state) => {
484
+ callback(state.entitlements);
485
+ });
2182
486
  }
2183
- function getGraphicsPerformance() {
2184
- const bridge = getBridgeWindow10();
2185
- if (!bridge) {
2186
- return { ...DEFAULT_GRAPHICS_PERFORMANCE };
2187
- }
2188
- const hostMetric = readHostGraphicsPerformance(bridge);
2189
- if (hostMetric) {
2190
- return hostMetric;
2191
- }
2192
- cachedEstimatedGraphicsPerformance ??= estimateGraphicsPerformance(bridge);
2193
- return { ...cachedEstimatedGraphicsPerformance };
487
+ function onJemBalanceChanged(callback) {
488
+ return subscribeToStoreEvent("oasiz:jem-balance-changed", (state) => {
489
+ callback(state.jemBalance);
490
+ });
2194
491
  }
2195
492
 
2196
493
  // src/index.ts
2197
494
  var oasiz = {
2198
495
  submitScore,
2199
- addScore,
2200
- setScore,
2201
- getPlayerCharacter,
496
+ emitScoreConfig,
2202
497
  share,
2203
498
  triggerHaptic,
2204
- enableLogOverlay,
2205
499
  loadGameState,
2206
500
  saveGameState,
2207
501
  flushGameState,
2208
502
  shareRoomCode,
2209
- openInviteModal,
2210
503
  onPause,
2211
504
  onResume,
2212
- getSafeAreaTop,
2213
- getViewportInsets,
2214
- setLeaderboardVisible,
2215
- getGraphicsPerformance,
2216
- enableBackButtonTesting,
2217
505
  onBackButton,
2218
506
  onLeaveGame,
2219
507
  leaveGame,
508
+ store: {
509
+ syncProducts,
510
+ getProducts,
511
+ getEntitlements,
512
+ hasEntitlement,
513
+ getQuantity,
514
+ purchase,
515
+ consume,
516
+ getJemBalance,
517
+ onEntitlementsChanged,
518
+ onJemBalanceChanged
519
+ },
2220
520
  get gameId() {
2221
521
  return getGameId();
2222
522
  },
2223
523
  get roomCode() {
2224
524
  return getRoomCode();
2225
525
  },
2226
- get playerId() {
2227
- return getPlayerId();
2228
- },
2229
526
  get playerName() {
2230
527
  return getPlayerName();
2231
528
  },
2232
529
  get playerAvatar() {
2233
530
  return getPlayerAvatar();
2234
- },
2235
- get safeAreaTop() {
2236
- return getSafeAreaTop();
2237
- },
2238
- get viewportInsets() {
2239
- return getViewportInsets();
2240
- },
2241
- get graphicsPerformance() {
2242
- return getGraphicsPerformance();
2243
531
  }
2244
532
  };
2245
533
  // Annotate the CommonJS export names for ESM import in node:
2246
534
  0 && (module.exports = {
2247
- addScore,
2248
- enableBackButtonTesting,
2249
- enableLogOverlay,
535
+ consume,
536
+ emitScoreConfig,
2250
537
  flushGameState,
538
+ getEntitlements,
2251
539
  getGameId,
2252
- getGraphicsPerformance,
540
+ getJemBalance,
2253
541
  getPlayerAvatar,
2254
- getPlayerCharacter,
2255
- getPlayerId,
2256
542
  getPlayerName,
543
+ getProducts,
544
+ getQuantity,
2257
545
  getRoomCode,
2258
- getSafeAreaTop,
2259
- getViewportInsets,
546
+ hasEntitlement,
2260
547
  leaveGame,
2261
548
  loadGameState,
2262
549
  oasiz,
2263
550
  onBackButton,
551
+ onEntitlementsChanged,
552
+ onJemBalanceChanged,
2264
553
  onLeaveGame,
2265
554
  onPause,
2266
555
  onResume,
2267
- openInviteModal,
556
+ purchase,
2268
557
  saveGameState,
2269
- setLeaderboardVisible,
2270
- setScore,
2271
558
  share,
2272
559
  shareRoomCode,
2273
560
  submitScore,
561
+ syncProducts,
2274
562
  triggerHaptic
2275
563
  });