@oasiz/sdk 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22,6 +22,897 @@ function triggerHaptic(type) {
22
22
  }
23
23
  }
24
24
 
25
+ // src/log-overlay.ts
26
+ var CONSOLE_METHODS = [
27
+ "debug",
28
+ "log",
29
+ "info",
30
+ "warn",
31
+ "error"
32
+ ];
33
+ var DEFAULT_MAX_ENTRIES = 200;
34
+ var DEFAULT_TITLE = "SDK Logs";
35
+ var OVERLAY_MARGIN = 12;
36
+ var DEFAULT_COLLAPSED_WIDTH = 156;
37
+ var DEFAULT_COLLAPSED_HEIGHT = 52;
38
+ var DEFAULT_EXPANDED_WIDTH = 565;
39
+ var DEFAULT_EXPANDED_HEIGHT = 372;
40
+ var DRAG_THRESHOLD_PX = 6;
41
+ var MIN_EXPANDED_WIDTH = 160;
42
+ var MIN_EXPANDED_HEIGHT = 110;
43
+ var RESIZE_HOTSPOT_PX = 28;
44
+ var TOP_DRAG_ZONE_PX = 44;
45
+ var NOOP_HANDLE = {
46
+ clear() {
47
+ },
48
+ destroy() {
49
+ },
50
+ hide() {
51
+ },
52
+ isVisible() {
53
+ return false;
54
+ },
55
+ show() {
56
+ }
57
+ };
58
+ function getBrowserWindow() {
59
+ if (typeof window === "undefined") {
60
+ return void 0;
61
+ }
62
+ return window;
63
+ }
64
+ function getDocument() {
65
+ if (typeof document === "undefined") {
66
+ return void 0;
67
+ }
68
+ return document;
69
+ }
70
+ function clampMaxEntries(value) {
71
+ if (!Number.isFinite(value)) {
72
+ return DEFAULT_MAX_ENTRIES;
73
+ }
74
+ return Math.max(10, Math.floor(value));
75
+ }
76
+ function createConsoleSnapshot() {
77
+ const fallback = console.log.bind(console);
78
+ return {
79
+ debug: typeof console.debug === "function" ? console.debug.bind(console) : fallback,
80
+ log: fallback,
81
+ info: typeof console.info === "function" ? console.info.bind(console) : fallback,
82
+ warn: typeof console.warn === "function" ? console.warn.bind(console) : fallback,
83
+ error: typeof console.error === "function" ? console.error.bind(console) : fallback
84
+ };
85
+ }
86
+ function formatTimestamp(timestamp) {
87
+ const date = new Date(timestamp);
88
+ const hours = String(date.getHours()).padStart(2, "0");
89
+ const minutes = String(date.getMinutes()).padStart(2, "0");
90
+ const seconds = String(date.getSeconds()).padStart(2, "0");
91
+ const milliseconds = String(date.getMilliseconds()).padStart(3, "0");
92
+ return "[" + hours + ":" + minutes + ":" + seconds + "." + milliseconds + "]";
93
+ }
94
+ function safeStringify(value) {
95
+ const seen = /* @__PURE__ */ new WeakSet();
96
+ try {
97
+ return JSON.stringify(
98
+ value,
99
+ (_key, candidate) => {
100
+ if (typeof candidate === "bigint") {
101
+ return candidate.toString() + "n";
102
+ }
103
+ if (typeof candidate === "object" && candidate !== null) {
104
+ if (seen.has(candidate)) {
105
+ return "[Circular]";
106
+ }
107
+ seen.add(candidate);
108
+ }
109
+ return candidate;
110
+ },
111
+ 2
112
+ ) ?? String(value);
113
+ } catch {
114
+ return String(value);
115
+ }
116
+ }
117
+ function formatArg(value) {
118
+ if (typeof value === "string") {
119
+ return value;
120
+ }
121
+ if (value instanceof Error) {
122
+ if (value.stack) {
123
+ return value.stack;
124
+ }
125
+ return value.name + ": " + value.message;
126
+ }
127
+ if (typeof value === "undefined") {
128
+ return "undefined";
129
+ }
130
+ if (typeof value === "function") {
131
+ return "[Function " + (value.name || "anonymous") + "]";
132
+ }
133
+ return safeStringify(value);
134
+ }
135
+ function formatEntryMessage(args) {
136
+ const message = args.map(formatArg).join(" ");
137
+ if (message.length <= 4e3) {
138
+ return message;
139
+ }
140
+ return message.slice(0, 3997) + "...";
141
+ }
142
+ function createEntry(level, args, id) {
143
+ return {
144
+ id,
145
+ level,
146
+ message: formatEntryMessage(args),
147
+ timestamp: Date.now()
148
+ };
149
+ }
150
+ function createButton(label) {
151
+ const button = document.createElement("button");
152
+ button.type = "button";
153
+ button.textContent = label;
154
+ button.style.cssText = [
155
+ "appearance:none",
156
+ "border:1px solid rgba(255,255,255,0.18)",
157
+ "background:rgba(255,255,255,0.06)",
158
+ "color:#f8fafc",
159
+ "border-radius:999px",
160
+ "padding:6px 10px",
161
+ "font:600 12px/1.1 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
162
+ "cursor:pointer"
163
+ ].join(";");
164
+ return button;
165
+ }
166
+ function getLevelAccent(level) {
167
+ if (level === "error") {
168
+ return {
169
+ lineBackground: "rgba(255, 109, 122, 0.08)"
170
+ };
171
+ }
172
+ if (level === "warn") {
173
+ return {
174
+ lineBackground: "rgba(255, 196, 94, 0.07)"
175
+ };
176
+ }
177
+ if (level === "info") {
178
+ return {
179
+ lineBackground: "rgba(82, 187, 255, 0.07)"
180
+ };
181
+ }
182
+ if (level === "debug") {
183
+ return {
184
+ lineBackground: "rgba(166, 137, 255, 0.07)"
185
+ };
186
+ }
187
+ return {
188
+ lineBackground: "rgba(117, 235, 191, 0.06)"
189
+ };
190
+ }
191
+ function getViewportSize() {
192
+ const browserWindow = getBrowserWindow();
193
+ return {
194
+ width: Math.max(320, browserWindow?.innerWidth ?? 1280),
195
+ height: Math.max(240, browserWindow?.innerHeight ?? 720)
196
+ };
197
+ }
198
+ function clampPanelSize(size) {
199
+ const viewport = getViewportSize();
200
+ const maxWidth = Math.max(MIN_EXPANDED_WIDTH, viewport.width - OVERLAY_MARGIN * 2);
201
+ const maxHeight = Math.max(
202
+ MIN_EXPANDED_HEIGHT,
203
+ viewport.height - OVERLAY_MARGIN * 2
204
+ );
205
+ return {
206
+ width: Math.min(maxWidth, Math.max(MIN_EXPANDED_WIDTH, size.width)),
207
+ height: Math.min(maxHeight, Math.max(MIN_EXPANDED_HEIGHT, size.height))
208
+ };
209
+ }
210
+ function getOverlaySize(state) {
211
+ if (state.expanded && state.panelSize) {
212
+ return state.panelSize;
213
+ }
214
+ const rect = state.ui?.root && typeof state.ui.root.getBoundingClientRect === "function" ? state.ui.root.getBoundingClientRect() : null;
215
+ if (rect && Number.isFinite(rect.width) && Number.isFinite(rect.height)) {
216
+ return {
217
+ width: Math.max(1, rect.width),
218
+ height: Math.max(1, rect.height)
219
+ };
220
+ }
221
+ return state.expanded ? clampPanelSize({
222
+ width: DEFAULT_EXPANDED_WIDTH,
223
+ height: DEFAULT_EXPANDED_HEIGHT
224
+ }) : { width: DEFAULT_COLLAPSED_WIDTH, height: DEFAULT_COLLAPSED_HEIGHT };
225
+ }
226
+ function applyPanelSize(state) {
227
+ if (!state.ui) {
228
+ return;
229
+ }
230
+ if (!state.expanded) {
231
+ state.ui.root.style.width = "auto";
232
+ state.ui.panel.style.width = "min(565px, calc(100vw - 24px))";
233
+ state.ui.panel.style.height = "auto";
234
+ state.ui.entries.style.maxHeight = "min(36vh, 280px)";
235
+ return;
236
+ }
237
+ if (!state.panelSize) {
238
+ state.ui.root.style.width = "min(565px, calc(100vw - 24px))";
239
+ state.ui.panel.style.width = "min(565px, calc(100vw - 24px))";
240
+ state.ui.panel.style.height = "auto";
241
+ state.ui.entries.style.maxHeight = "min(36vh, 280px)";
242
+ return;
243
+ }
244
+ const nextSize = clampPanelSize(
245
+ state.panelSize
246
+ );
247
+ state.panelSize = nextSize;
248
+ state.ui.root.style.width = nextSize.width + "px";
249
+ state.ui.panel.style.width = "100%";
250
+ state.ui.panel.style.height = nextSize.height + "px";
251
+ state.ui.entries.style.maxHeight = Math.max(72, nextSize.height - 88) + "px";
252
+ }
253
+ function clampPosition(point, state) {
254
+ const viewport = getViewportSize();
255
+ const size = getOverlaySize(state);
256
+ const maxX = Math.max(OVERLAY_MARGIN, viewport.width - size.width - OVERLAY_MARGIN);
257
+ const maxY = Math.max(
258
+ OVERLAY_MARGIN,
259
+ viewport.height - size.height - OVERLAY_MARGIN
260
+ );
261
+ return {
262
+ x: Math.min(maxX, Math.max(OVERLAY_MARGIN, point.x)),
263
+ y: Math.min(maxY, Math.max(OVERLAY_MARGIN, point.y))
264
+ };
265
+ }
266
+ function applyOverlayPosition(state) {
267
+ if (!state.ui) {
268
+ return;
269
+ }
270
+ if (!state.position) {
271
+ const viewport = getViewportSize();
272
+ const size = getOverlaySize(state);
273
+ state.position = clampPosition(
274
+ {
275
+ x: viewport.width - size.width - OVERLAY_MARGIN,
276
+ y: viewport.height - size.height - OVERLAY_MARGIN
277
+ },
278
+ state
279
+ );
280
+ } else {
281
+ state.position = clampPosition(state.position, state);
282
+ }
283
+ state.ui.root.style.left = state.position.x + "px";
284
+ state.ui.root.style.top = state.position.y + "px";
285
+ }
286
+ function getPointFromMouseEvent(event) {
287
+ return { x: event.clientX, y: event.clientY };
288
+ }
289
+ function getPointFromTouchEvent(event) {
290
+ const touch = event.touches[0] ?? event.changedTouches[0];
291
+ if (!touch) {
292
+ return null;
293
+ }
294
+ return { x: touch.clientX, y: touch.clientY };
295
+ }
296
+ function stopDragging(state) {
297
+ if (state.isDragging || state.isResizing) {
298
+ state.suppressToggleClickUntil = Date.now() + 180;
299
+ }
300
+ state.isDragging = false;
301
+ state.dragStartPoint = null;
302
+ state.lastDragPoint = null;
303
+ state.removeDragListeners?.();
304
+ state.removeDragListeners = null;
305
+ if (state.ui) {
306
+ state.ui.panel.style.cursor = "default";
307
+ state.ui.dragZone.style.cursor = "grab";
308
+ state.ui.toggleButton.style.cursor = "grab";
309
+ }
310
+ }
311
+ function stopResizing(state) {
312
+ if (state.isResizing) {
313
+ state.suppressToggleClickUntil = Date.now() + 180;
314
+ }
315
+ state.isResizing = false;
316
+ state.resizeStartPoint = null;
317
+ state.resizeStartSize = null;
318
+ state.removeResizeListeners?.();
319
+ state.removeResizeListeners = null;
320
+ }
321
+ function beginDragTracking(state, startPoint) {
322
+ const doc = getDocument();
323
+ if (!doc) {
324
+ return;
325
+ }
326
+ stopResizing(state);
327
+ stopDragging(state);
328
+ state.dragMoved = false;
329
+ state.dragStartPoint = startPoint;
330
+ state.lastDragPoint = startPoint;
331
+ const handlePointerMove = (nextPoint) => {
332
+ if (!state.dragStartPoint || !state.lastDragPoint || !nextPoint) {
333
+ return;
334
+ }
335
+ if (!state.isDragging) {
336
+ const deltaFromStartX = nextPoint.x - state.dragStartPoint.x;
337
+ const deltaFromStartY = nextPoint.y - state.dragStartPoint.y;
338
+ const distance = Math.sqrt(
339
+ deltaFromStartX * deltaFromStartX + deltaFromStartY * deltaFromStartY
340
+ );
341
+ if (distance < DRAG_THRESHOLD_PX) {
342
+ return;
343
+ }
344
+ state.isDragging = true;
345
+ state.dragMoved = true;
346
+ if (state.ui) {
347
+ state.ui.panel.style.cursor = "grabbing";
348
+ state.ui.dragZone.style.cursor = "grabbing";
349
+ state.ui.toggleButton.style.cursor = "grabbing";
350
+ }
351
+ }
352
+ const currentPosition = state.position ?? { x: OVERLAY_MARGIN, y: OVERLAY_MARGIN };
353
+ state.position = clampPosition(
354
+ {
355
+ x: currentPosition.x + (nextPoint.x - state.lastDragPoint.x),
356
+ y: currentPosition.y + (nextPoint.y - state.lastDragPoint.y)
357
+ },
358
+ state
359
+ );
360
+ state.lastDragPoint = nextPoint;
361
+ applyOverlayPosition(state);
362
+ };
363
+ const handleMouseMove = (event) => {
364
+ handlePointerMove(getPointFromMouseEvent(event));
365
+ };
366
+ const handleTouchMove = (event) => {
367
+ handlePointerMove(getPointFromTouchEvent(event));
368
+ };
369
+ const handleMouseUp = () => {
370
+ stopDragging(state);
371
+ };
372
+ const handleTouchEnd = () => {
373
+ stopDragging(state);
374
+ };
375
+ doc.addEventListener("mousemove", handleMouseMove);
376
+ doc.addEventListener("mouseup", handleMouseUp);
377
+ doc.addEventListener("touchmove", handleTouchMove, { passive: true });
378
+ doc.addEventListener("touchend", handleTouchEnd);
379
+ doc.addEventListener("touchcancel", handleTouchEnd);
380
+ state.removeDragListeners = () => {
381
+ doc.removeEventListener("mousemove", handleMouseMove);
382
+ doc.removeEventListener("mouseup", handleMouseUp);
383
+ doc.removeEventListener("touchmove", handleTouchMove);
384
+ doc.removeEventListener("touchend", handleTouchEnd);
385
+ doc.removeEventListener("touchcancel", handleTouchEnd);
386
+ };
387
+ }
388
+ function startResizing(state, startPoint) {
389
+ const doc = getDocument();
390
+ if (!doc) {
391
+ return;
392
+ }
393
+ stopDragging(state);
394
+ stopResizing(state);
395
+ state.isResizing = true;
396
+ state.resizeStartPoint = startPoint;
397
+ state.resizeStartSize = state.panelSize ?? clampPanelSize({ width: DEFAULT_EXPANDED_WIDTH, height: DEFAULT_EXPANDED_HEIGHT });
398
+ const handleResizeMove = (nextPoint) => {
399
+ if (!state.isResizing || !state.resizeStartPoint || !state.resizeStartSize || !nextPoint) {
400
+ return;
401
+ }
402
+ state.panelSize = clampPanelSize({
403
+ width: state.resizeStartSize.width + (nextPoint.x - state.resizeStartPoint.x),
404
+ height: state.resizeStartSize.height + (nextPoint.y - state.resizeStartPoint.y)
405
+ });
406
+ applyPanelSize(state);
407
+ applyOverlayPosition(state);
408
+ };
409
+ const handleMouseMove = (event) => {
410
+ handleResizeMove(getPointFromMouseEvent(event));
411
+ };
412
+ const handleTouchMove = (event) => {
413
+ handleResizeMove(getPointFromTouchEvent(event));
414
+ };
415
+ const handleFinish = () => {
416
+ stopResizing(state);
417
+ };
418
+ doc.addEventListener("mousemove", handleMouseMove);
419
+ doc.addEventListener("mouseup", handleFinish);
420
+ doc.addEventListener("touchmove", handleTouchMove, { passive: true });
421
+ doc.addEventListener("touchend", handleFinish);
422
+ doc.addEventListener("touchcancel", handleFinish);
423
+ state.removeResizeListeners = () => {
424
+ doc.removeEventListener("mousemove", handleMouseMove);
425
+ doc.removeEventListener("mouseup", handleFinish);
426
+ doc.removeEventListener("touchmove", handleTouchMove);
427
+ doc.removeEventListener("touchend", handleFinish);
428
+ doc.removeEventListener("touchcancel", handleFinish);
429
+ };
430
+ }
431
+ function isInBottomRightResizeZone(element, point) {
432
+ const rect = element.getBoundingClientRect();
433
+ return point.x >= rect.right - RESIZE_HOTSPOT_PX && point.x <= rect.right && point.y >= rect.bottom - RESIZE_HOTSPOT_PX && point.y <= rect.bottom;
434
+ }
435
+ function canStartDragFromTarget(target) {
436
+ if (!(target instanceof Element)) {
437
+ return true;
438
+ }
439
+ if (target.closest("button") || target.closest("a") || target.closest("input") || target.closest("textarea") || target.closest("select")) {
440
+ return false;
441
+ }
442
+ return true;
443
+ }
444
+ function isInTopDragZone(element, point, zoneHeight) {
445
+ const rect = element.getBoundingClientRect();
446
+ return point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.top + zoneHeight;
447
+ }
448
+ function attachDragStartListeners(element, state, options = {}) {
449
+ element.addEventListener("mousedown", (event) => {
450
+ if (!options.allowInteractiveTarget && !canStartDragFromTarget(event.target)) {
451
+ return;
452
+ }
453
+ const point = getPointFromMouseEvent(event);
454
+ if (typeof options.topZoneHeight === "number" && !isInTopDragZone(element, point, options.topZoneHeight)) {
455
+ return;
456
+ }
457
+ beginDragTracking(state, point);
458
+ });
459
+ element.addEventListener("touchstart", (event) => {
460
+ if (!options.allowInteractiveTarget && !canStartDragFromTarget(event.target)) {
461
+ return;
462
+ }
463
+ const point = getPointFromTouchEvent(event);
464
+ if (!point) {
465
+ return;
466
+ }
467
+ if (typeof options.topZoneHeight === "number" && !isInTopDragZone(element, point, options.topZoneHeight)) {
468
+ return;
469
+ }
470
+ beginDragTracking(state, point);
471
+ });
472
+ }
473
+ function attachCollapsedToggleListeners(element, state) {
474
+ const startToggleInteraction = (startPoint) => {
475
+ const doc = getDocument();
476
+ if (!doc) {
477
+ return;
478
+ }
479
+ beginDragTracking(state, startPoint);
480
+ const finishInteraction = () => {
481
+ releaseListeners();
482
+ if (state.dragMoved || Date.now() < state.suppressToggleClickUntil) {
483
+ return;
484
+ }
485
+ state.expanded = true;
486
+ state.unreadCount = 0;
487
+ renderOverlay(state);
488
+ };
489
+ const releaseListeners = () => {
490
+ doc.removeEventListener("mouseup", finishInteraction);
491
+ doc.removeEventListener("touchend", finishInteraction);
492
+ doc.removeEventListener("touchcancel", finishInteraction);
493
+ };
494
+ doc.addEventListener("mouseup", finishInteraction);
495
+ doc.addEventListener("touchend", finishInteraction);
496
+ doc.addEventListener("touchcancel", finishInteraction);
497
+ };
498
+ element.addEventListener("mousedown", (event) => {
499
+ event.preventDefault();
500
+ startToggleInteraction(getPointFromMouseEvent(event));
501
+ });
502
+ element.addEventListener("touchstart", (event) => {
503
+ const point = getPointFromTouchEvent(event);
504
+ if (!point) {
505
+ return;
506
+ }
507
+ event.preventDefault();
508
+ startToggleInteraction(point);
509
+ });
510
+ }
511
+ function attachPanelResizeListeners(element, state) {
512
+ element.addEventListener("mousedown", (event) => {
513
+ if (!state.expanded || !canStartDragFromTarget(event.target)) {
514
+ return;
515
+ }
516
+ const point = getPointFromMouseEvent(event);
517
+ if (!isInBottomRightResizeZone(element, point)) {
518
+ return;
519
+ }
520
+ event.preventDefault();
521
+ event.stopPropagation();
522
+ startResizing(state, point);
523
+ });
524
+ element.addEventListener("touchstart", (event) => {
525
+ if (!state.expanded || !canStartDragFromTarget(event.target)) {
526
+ return;
527
+ }
528
+ const point = getPointFromTouchEvent(event);
529
+ if (!point || !isInBottomRightResizeZone(element, point)) {
530
+ return;
531
+ }
532
+ event.preventDefault();
533
+ event.stopPropagation();
534
+ startResizing(state, point);
535
+ });
536
+ }
537
+ function createOverlayUi(state) {
538
+ const root = document.createElement("div");
539
+ root.style.cssText = [
540
+ "position:fixed",
541
+ "left:12px",
542
+ "top:12px",
543
+ "z-index:2147483647",
544
+ "display:flex",
545
+ "flex-direction:column",
546
+ "align-items:stretch",
547
+ "gap:8px",
548
+ "width:min(565px, calc(100vw - 24px))",
549
+ "pointer-events:none"
550
+ ].join(";");
551
+ const toggleButton = createButton("Logs");
552
+ toggleButton.style.pointerEvents = "auto";
553
+ toggleButton.style.alignSelf = "flex-end";
554
+ toggleButton.style.display = "inline-flex";
555
+ toggleButton.style.alignItems = "center";
556
+ toggleButton.style.justifyContent = "center";
557
+ toggleButton.style.minHeight = "40px";
558
+ toggleButton.style.minWidth = "76px";
559
+ toggleButton.style.padding = "8px 14px";
560
+ toggleButton.style.textAlign = "center";
561
+ toggleButton.style.border = "1px solid rgba(122, 212, 255, 0.22)";
562
+ toggleButton.style.background = "linear-gradient(180deg, rgba(13,31,54,0.98), rgba(8,19,37,0.98))";
563
+ toggleButton.style.boxShadow = "0 18px 40px rgba(4,12,24,0.34)";
564
+ toggleButton.style.cursor = "grab";
565
+ toggleButton.style.touchAction = "none";
566
+ const panel = document.createElement("div");
567
+ panel.style.cssText = [
568
+ "position:relative",
569
+ "display:flex",
570
+ "flex-direction:column",
571
+ "width:min(565px, calc(100vw - 24px))",
572
+ "max-height:min(48vh, 372px)",
573
+ "border-radius:18px",
574
+ "border:1px solid rgba(116,167,255,0.16)",
575
+ "background:linear-gradient(180deg, rgba(9,19,37,0.98), rgba(5,12,24,0.98))",
576
+ "box-shadow:0 28px 64px rgba(2,8,18,0.46)",
577
+ "backdrop-filter:blur(16px)",
578
+ "overflow:hidden",
579
+ "cursor:default",
580
+ "pointer-events:auto"
581
+ ].join(";");
582
+ const dragZone = document.createElement("div");
583
+ dragZone.style.cssText = [
584
+ "position:absolute",
585
+ "top:0",
586
+ "left:0",
587
+ "right:0",
588
+ "height:" + String(TOP_DRAG_ZONE_PX) + "px",
589
+ "z-index:1",
590
+ "cursor:grab",
591
+ "background:transparent",
592
+ "pointer-events:auto"
593
+ ].join(";");
594
+ const controls = document.createElement("div");
595
+ controls.style.cssText = [
596
+ "position:absolute",
597
+ "top:22px",
598
+ "right:22px",
599
+ "z-index:2",
600
+ "display:flex",
601
+ "align-items:center",
602
+ "gap:8px",
603
+ "pointer-events:auto"
604
+ ].join(";");
605
+ const clearButton = createButton("Clear");
606
+ clearButton.style.background = "rgba(255,255,255,0.1)";
607
+ clearButton.style.border = "1px solid rgba(255,255,255,0.16)";
608
+ clearButton.style.color = "#eef6ff";
609
+ clearButton.style.minHeight = "30px";
610
+ clearButton.style.padding = "4px 9px";
611
+ clearButton.style.fontSize = "11px";
612
+ clearButton.style.backdropFilter = "blur(8px)";
613
+ const collapseButton = createButton("Hide");
614
+ collapseButton.style.background = "rgba(113, 171, 255, 0.12)";
615
+ collapseButton.style.border = "1px solid rgba(113, 171, 255, 0.2)";
616
+ collapseButton.style.color = "#d9ebff";
617
+ collapseButton.style.minHeight = "30px";
618
+ collapseButton.style.padding = "4px 9px";
619
+ collapseButton.style.fontSize = "11px";
620
+ collapseButton.style.backdropFilter = "blur(8px)";
621
+ const body = document.createElement("div");
622
+ body.style.cssText = [
623
+ "position:relative",
624
+ "display:flex",
625
+ "flex-direction:column",
626
+ "padding:12px",
627
+ "background:linear-gradient(180deg, rgba(4,10,20,0.88), rgba(3,8,18,0.98))",
628
+ "flex:1 1 auto",
629
+ "min-height:0"
630
+ ].join(";");
631
+ const entries = document.createElement("div");
632
+ entries.style.cssText = [
633
+ "display:flex",
634
+ "flex-direction:column",
635
+ "gap:0",
636
+ "overflow:auto",
637
+ "flex:1 1 auto",
638
+ "min-height:96px",
639
+ "max-height:min(36vh, 280px)",
640
+ "padding:0",
641
+ "border:1px solid rgba(115,153,212,0.14)",
642
+ "border-radius:12px",
643
+ "background:rgba(4,10,20,0.82)"
644
+ ].join(";");
645
+ const emptyState = document.createElement("div");
646
+ emptyState.style.cssText = [
647
+ "display:flex",
648
+ "align-items:center",
649
+ "justify-content:center",
650
+ "flex:1 1 auto",
651
+ "min-height:96px",
652
+ "color:rgba(204,222,250,0.6)",
653
+ "font:500 12px/1.5 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
654
+ "text-align:center",
655
+ "padding:18px"
656
+ ].join(";");
657
+ emptyState.textContent = "Console output will appear here.";
658
+ collapseButton.addEventListener("click", (event) => {
659
+ event.stopPropagation();
660
+ state.expanded = false;
661
+ renderOverlay(state);
662
+ });
663
+ clearButton.addEventListener("click", (event) => {
664
+ event.stopPropagation();
665
+ state.entries = [];
666
+ state.unreadCount = 0;
667
+ renderOverlay(state);
668
+ });
669
+ controls.appendChild(clearButton);
670
+ controls.appendChild(collapseButton);
671
+ entries.appendChild(emptyState);
672
+ body.appendChild(entries);
673
+ body.appendChild(controls);
674
+ panel.appendChild(dragZone);
675
+ panel.appendChild(body);
676
+ attachDragStartListeners(dragZone, state);
677
+ attachPanelResizeListeners(panel, state);
678
+ attachCollapsedToggleListeners(toggleButton, state);
679
+ root.appendChild(panel);
680
+ root.appendChild(toggleButton);
681
+ return {
682
+ body,
683
+ clearButton,
684
+ collapseButton,
685
+ controls,
686
+ dragZone,
687
+ emptyState,
688
+ entries,
689
+ panel,
690
+ root,
691
+ toggleButton
692
+ };
693
+ }
694
+ function renderOverlay(state) {
695
+ if (!state.ui) {
696
+ return;
697
+ }
698
+ state.ui.panel.style.display = state.expanded ? "flex" : "none";
699
+ state.ui.toggleButton.style.display = state.expanded ? "none" : "inline-flex";
700
+ state.ui.toggleButton.textContent = "Logs";
701
+ if (state.entries.length === 0) {
702
+ state.ui.entries.style.display = "flex";
703
+ state.ui.emptyState.style.display = "flex";
704
+ state.ui.entries.replaceChildren(state.ui.emptyState);
705
+ applyPanelSize(state);
706
+ applyOverlayPosition(state);
707
+ return;
708
+ }
709
+ state.ui.entries.style.display = "flex";
710
+ state.ui.emptyState.style.display = "none";
711
+ const nextChildren = state.entries.map((entry) => {
712
+ const accent = getLevelAccent(entry.level);
713
+ const row = document.createElement("div");
714
+ row.style.cssText = [
715
+ "display:flex",
716
+ "align-items:flex-start",
717
+ "gap:0",
718
+ "padding:4px 12px",
719
+ "background:" + accent.lineBackground
720
+ ].join(";");
721
+ const line = document.createElement("div");
722
+ line.textContent = formatTimestamp(entry.timestamp) + " " + entry.level.toUpperCase() + " " + entry.message;
723
+ line.style.cssText = [
724
+ "color:#ecf4ff",
725
+ "font:500 12px/1.5 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
726
+ "white-space:pre-wrap",
727
+ "word-break:break-word",
728
+ "flex:1 1 auto"
729
+ ].join(";");
730
+ row.appendChild(line);
731
+ return row;
732
+ });
733
+ state.ui.entries.replaceChildren(...nextChildren);
734
+ state.ui.entries.scrollTop = state.ui.entries.scrollHeight;
735
+ applyPanelSize(state);
736
+ applyOverlayPosition(state);
737
+ }
738
+ function mountOverlay(state) {
739
+ const doc = getDocument();
740
+ if (!doc?.body || state.ui) {
741
+ return;
742
+ }
743
+ state.ui = createOverlayUi(state);
744
+ doc.body.appendChild(state.ui.root);
745
+ applyPanelSize(state);
746
+ applyOverlayPosition(state);
747
+ renderOverlay(state);
748
+ }
749
+ function enqueueEntry(state, level, args) {
750
+ state.entries.push(createEntry(level, args, state.nextEntryId));
751
+ state.nextEntryId += 1;
752
+ if (state.entries.length > state.maxEntries) {
753
+ state.entries.splice(0, state.entries.length - state.maxEntries);
754
+ }
755
+ if (!state.expanded) {
756
+ state.unreadCount += 1;
757
+ }
758
+ renderOverlay(state);
759
+ }
760
+ function restoreConsole(snapshot) {
761
+ for (const method of CONSOLE_METHODS) {
762
+ console[method] = snapshot[method];
763
+ }
764
+ }
765
+ function patchConsole(state) {
766
+ for (const method of CONSOLE_METHODS) {
767
+ const original = state.originalConsole[method];
768
+ console[method] = (...args) => {
769
+ enqueueEntry(state, method, args);
770
+ original(...args);
771
+ };
772
+ }
773
+ }
774
+ function cleanupOverlay(browserWindow, state) {
775
+ restoreConsole(state.originalConsole);
776
+ stopResizing(state);
777
+ stopDragging(state);
778
+ if (state.domReadyHandler) {
779
+ getDocument()?.removeEventListener("DOMContentLoaded", state.domReadyHandler);
780
+ state.domReadyHandler = void 0;
781
+ }
782
+ if (state.resizeHandler && typeof browserWindow.removeEventListener === "function") {
783
+ browserWindow.removeEventListener("resize", state.resizeHandler);
784
+ state.resizeHandler = void 0;
785
+ }
786
+ state.ui?.root.remove();
787
+ state.ui = null;
788
+ delete browserWindow.__oasizLogOverlayController__;
789
+ delete browserWindow.__oasizLogOverlayState__;
790
+ }
791
+ function createController(browserWindow, state) {
792
+ return {
793
+ retain() {
794
+ state.refCount += 1;
795
+ this.ensureMounted();
796
+ },
797
+ ensureMounted() {
798
+ const doc = getDocument();
799
+ if (!doc) {
800
+ return;
801
+ }
802
+ if (doc.body) {
803
+ mountOverlay(state);
804
+ applyOverlayPosition(state);
805
+ return;
806
+ }
807
+ if (!state.domReadyHandler) {
808
+ state.domReadyHandler = () => {
809
+ mountOverlay(state);
810
+ state.domReadyHandler = void 0;
811
+ };
812
+ doc.addEventListener("DOMContentLoaded", state.domReadyHandler, {
813
+ once: true
814
+ });
815
+ }
816
+ },
817
+ clear() {
818
+ state.entries = [];
819
+ state.unreadCount = 0;
820
+ renderOverlay(state);
821
+ },
822
+ show() {
823
+ state.expanded = true;
824
+ state.unreadCount = 0;
825
+ renderOverlay(state);
826
+ },
827
+ hide() {
828
+ state.expanded = false;
829
+ renderOverlay(state);
830
+ },
831
+ isVisible() {
832
+ return state.expanded;
833
+ },
834
+ destroy() {
835
+ state.refCount = Math.max(0, state.refCount - 1);
836
+ if (state.refCount === 0) {
837
+ cleanupOverlay(browserWindow, state);
838
+ }
839
+ }
840
+ };
841
+ }
842
+ function enableLogOverlay(options = {}) {
843
+ if (options.enabled === false) {
844
+ return NOOP_HANDLE;
845
+ }
846
+ const browserWindow = getBrowserWindow();
847
+ const doc = getDocument();
848
+ if (!browserWindow || !doc) {
849
+ return NOOP_HANDLE;
850
+ }
851
+ const existingController = browserWindow.__oasizLogOverlayController__;
852
+ const existingState = browserWindow.__oasizLogOverlayState__;
853
+ if (existingController && existingState) {
854
+ existingController.retain();
855
+ if (typeof options.maxEntries === "number") {
856
+ existingState.maxEntries = clampMaxEntries(options.maxEntries);
857
+ if (existingState.entries.length > existingState.maxEntries) {
858
+ existingState.entries.splice(
859
+ 0,
860
+ existingState.entries.length - existingState.maxEntries
861
+ );
862
+ }
863
+ }
864
+ if (typeof options.collapsed === "boolean") {
865
+ existingState.expanded = !options.collapsed;
866
+ applyPanelSize(existingState);
867
+ if (existingState.expanded) {
868
+ existingState.unreadCount = 0;
869
+ }
870
+ }
871
+ if (typeof options.title === "string" && options.title.trim().length > 0) {
872
+ existingState.title = options.title.trim();
873
+ }
874
+ existingController.ensureMounted();
875
+ renderOverlay(existingState);
876
+ return existingController;
877
+ }
878
+ const state = {
879
+ dragMoved: false,
880
+ dragStartPoint: null,
881
+ entries: [],
882
+ expanded: options.collapsed !== true,
883
+ isDragging: false,
884
+ isResizing: false,
885
+ lastDragPoint: null,
886
+ maxEntries: clampMaxEntries(options.maxEntries),
887
+ nextEntryId: 1,
888
+ originalConsole: createConsoleSnapshot(),
889
+ panelSize: null,
890
+ position: null,
891
+ refCount: 1,
892
+ removeDragListeners: null,
893
+ removeResizeListeners: null,
894
+ resizeStartPoint: null,
895
+ resizeStartSize: null,
896
+ suppressToggleClickUntil: 0,
897
+ title: typeof options.title === "string" && options.title.trim().length > 0 ? options.title.trim() : DEFAULT_TITLE,
898
+ resizeHandler: void 0,
899
+ ui: null,
900
+ unreadCount: 0
901
+ };
902
+ const controller = createController(browserWindow, state);
903
+ browserWindow.__oasizLogOverlayState__ = state;
904
+ browserWindow.__oasizLogOverlayController__ = controller;
905
+ patchConsole(state);
906
+ if (typeof browserWindow.addEventListener === "function") {
907
+ state.resizeHandler = () => {
908
+ applyOverlayPosition(state);
909
+ };
910
+ browserWindow.addEventListener("resize", state.resizeHandler);
911
+ }
912
+ controller.ensureMounted();
913
+ return controller;
914
+ }
915
+
25
916
  // src/multiplayer.ts
26
917
  function isDevelopment2() {
27
918
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
@@ -33,10 +924,10 @@ function getBridgeWindow2() {
33
924
  }
34
925
  return window;
35
926
  }
36
- function shareRoomCode(roomCode) {
927
+ function shareRoomCode(roomCode, options) {
37
928
  const bridge = getBridgeWindow2();
38
929
  if (typeof bridge?.shareRoomCode === "function") {
39
- bridge.shareRoomCode(roomCode);
930
+ bridge.shareRoomCode(roomCode, options);
40
931
  return;
41
932
  }
42
933
  if (isDevelopment2()) {
@@ -45,6 +936,18 @@ function shareRoomCode(roomCode) {
45
936
  );
46
937
  }
47
938
  }
939
+ function openInviteModal() {
940
+ const bridge = getBridgeWindow2();
941
+ if (typeof bridge?.openInviteModal === "function") {
942
+ bridge.openInviteModal();
943
+ return;
944
+ }
945
+ if (isDevelopment2()) {
946
+ console.warn(
947
+ "[oasiz/sdk] openInviteModal bridge is unavailable. This is expected in local development."
948
+ );
949
+ }
950
+ }
48
951
  function getGameId() {
49
952
  const bridge = getBridgeWindow2();
50
953
  return bridge?.__GAME_ID__;
@@ -95,14 +998,6 @@ function submitScore(score) {
95
998
  }
96
999
  warnMissingBridge("submitScore");
97
1000
  }
98
- function emitScoreConfig(config) {
99
- const bridge = getBridgeWindow3();
100
- if (typeof bridge?.emitScoreConfig === "function") {
101
- bridge.emitScoreConfig(config);
102
- return;
103
- }
104
- warnMissingBridge("emitScoreConfig");
105
- }
106
1001
 
107
1002
  // src/state.ts
108
1003
  function isDevelopment4() {
@@ -195,8 +1090,7 @@ function onResume(callback) {
195
1090
  return addLifecycleListener("oasiz:resume", callback);
196
1091
  }
197
1092
 
198
- // src/navigation.ts
199
- var activeBackListeners = 0;
1093
+ // src/layout.ts
200
1094
  function isDevelopment6() {
201
1095
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
202
1096
  return nodeEnv !== "production";
@@ -214,9 +1108,74 @@ function warnMissingBridge3(methodName) {
214
1108
  );
215
1109
  }
216
1110
  }
1111
+ function normalizeSafeAreaTop(value) {
1112
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1113
+ return 0;
1114
+ }
1115
+ return Math.max(0, value);
1116
+ }
1117
+ function getSafeAreaTop() {
1118
+ const bridge = getBridgeWindow5();
1119
+ if (!bridge) {
1120
+ return 0;
1121
+ }
1122
+ if (typeof bridge.getSafeAreaTop === "function") {
1123
+ return normalizeSafeAreaTop(bridge.getSafeAreaTop());
1124
+ }
1125
+ if (typeof bridge.__OASIZ_SAFE_AREA_TOP__ !== "undefined") {
1126
+ return normalizeSafeAreaTop(bridge.__OASIZ_SAFE_AREA_TOP__);
1127
+ }
1128
+ warnMissingBridge3("getSafeAreaTop");
1129
+ return 0;
1130
+ }
1131
+ function setLeaderboardVisible(visible) {
1132
+ if (typeof visible !== "boolean") {
1133
+ if (isDevelopment6()) {
1134
+ console.warn(
1135
+ "[oasiz/sdk] setLeaderboardVisible expected a boolean:",
1136
+ visible
1137
+ );
1138
+ }
1139
+ return;
1140
+ }
1141
+ const bridge = getBridgeWindow5();
1142
+ if (typeof bridge?.__oasizSetLeaderboardVisible === "function") {
1143
+ bridge.__oasizSetLeaderboardVisible(visible);
1144
+ return;
1145
+ }
1146
+ warnMissingBridge3("__oasizSetLeaderboardVisible");
1147
+ }
1148
+
1149
+ // src/navigation.ts
1150
+ var activeBackListeners = 0;
1151
+ function isDevelopment7() {
1152
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
1153
+ return nodeEnv !== "production";
1154
+ }
1155
+ function getBridgeWindow6() {
1156
+ if (typeof window === "undefined") {
1157
+ return void 0;
1158
+ }
1159
+ return window;
1160
+ }
1161
+ function warnMissingBridge4(methodName) {
1162
+ if (isDevelopment7()) {
1163
+ console.warn(
1164
+ "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1165
+ );
1166
+ }
1167
+ }
1168
+ function normalizeNavigationError(error) {
1169
+ if (error instanceof Error) {
1170
+ return error;
1171
+ }
1172
+ return new Error(
1173
+ typeof error === "string" ? error : "Back button callback failed."
1174
+ );
1175
+ }
217
1176
  function addNavigationListener(eventName, callback) {
218
1177
  if (typeof window === "undefined") {
219
- if (isDevelopment6()) {
1178
+ if (isDevelopment7()) {
220
1179
  console.warn(
221
1180
  "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
222
1181
  );
@@ -229,25 +1188,32 @@ function addNavigationListener(eventName, callback) {
229
1188
  return () => window.removeEventListener(eventName, handler);
230
1189
  }
231
1190
  function onBackButton(callback) {
232
- const off = addNavigationListener("oasiz:back", callback);
233
- const bridge = getBridgeWindow5();
1191
+ const off = addNavigationListener("oasiz:back", () => {
1192
+ try {
1193
+ callback();
1194
+ } catch (error) {
1195
+ leaveGame();
1196
+ throw normalizeNavigationError(error);
1197
+ }
1198
+ });
1199
+ const bridge = getBridgeWindow6();
234
1200
  activeBackListeners += 1;
235
1201
  if (activeBackListeners === 1) {
236
1202
  if (typeof bridge?.__oasizSetBackOverride === "function") {
237
1203
  bridge.__oasizSetBackOverride(true);
238
1204
  } else {
239
- warnMissingBridge3("__oasizSetBackOverride");
1205
+ warnMissingBridge4("__oasizSetBackOverride");
240
1206
  }
241
1207
  }
242
1208
  return () => {
243
1209
  off();
244
1210
  activeBackListeners = Math.max(0, activeBackListeners - 1);
245
1211
  if (activeBackListeners === 0) {
246
- const currentBridge = getBridgeWindow5();
1212
+ const currentBridge = getBridgeWindow6();
247
1213
  if (typeof currentBridge?.__oasizSetBackOverride === "function") {
248
1214
  currentBridge.__oasizSetBackOverride(false);
249
1215
  } else {
250
- warnMissingBridge3("__oasizSetBackOverride");
1216
+ warnMissingBridge4("__oasizSetBackOverride");
251
1217
  }
252
1218
  }
253
1219
  };
@@ -256,135 +1222,31 @@ function onLeaveGame(callback) {
256
1222
  return addNavigationListener("oasiz:leave", callback);
257
1223
  }
258
1224
  function leaveGame() {
259
- const bridge = getBridgeWindow5();
1225
+ const bridge = getBridgeWindow6();
260
1226
  if (typeof bridge?.__oasizLeaveGame === "function") {
261
1227
  bridge.__oasizLeaveGame();
262
1228
  return;
263
1229
  }
264
- warnMissingBridge3("__oasizLeaveGame");
265
- }
266
-
267
- // src/store.ts
268
- function isDevelopment7() {
269
- const nodeEnv = globalThis.process?.env?.NODE_ENV;
270
- return nodeEnv !== "production";
271
- }
272
- function getBridgeWindow6() {
273
- if (typeof window === "undefined") {
274
- return void 0;
275
- }
276
- return window;
277
- }
278
- function warnMissingBridge4(methodName) {
279
- if (isDevelopment7()) {
280
- console.warn(
281
- "[oasiz/sdk] " + methodName + " store bridge is unavailable. This is expected in local development."
282
- );
283
- }
284
- }
285
- async function requestStoreBridge(action, payload) {
286
- const bridge = getBridgeWindow6();
287
- if (typeof bridge?.__oasizStoreBridgeRequest !== "function") {
288
- warnMissingBridge4(action);
289
- throw new Error("Store bridge unavailable");
290
- }
291
- return await bridge.__oasizStoreBridgeRequest({
292
- action,
293
- payload
294
- });
295
- }
296
- async function getFreshState() {
297
- return await requestStoreBridge("getStoreState");
298
- }
299
- function subscribeToStoreEvent(eventName, callback) {
300
- if (typeof window === "undefined") {
301
- return () => {
302
- };
303
- }
304
- const handler = (event) => {
305
- const customEvent = event;
306
- callback(customEvent.detail);
307
- };
308
- window.addEventListener(eventName, handler);
309
- return () => {
310
- window.removeEventListener(eventName, handler);
311
- };
312
- }
313
- async function syncProducts(products, expectedVersion) {
314
- return await requestStoreBridge("syncProducts", {
315
- expectedVersion: expectedVersion ?? null,
316
- products
317
- });
318
- }
319
- async function getProducts() {
320
- const state = await getFreshState();
321
- return state.products;
322
- }
323
- async function getEntitlements() {
324
- const state = await getFreshState();
325
- return state.entitlements;
326
- }
327
- async function hasEntitlement(productId) {
328
- const state = await getFreshState();
329
- return state.entitlements.find((item) => item.productId === productId)?.owned === true;
330
- }
331
- async function getQuantity(productId) {
332
- const state = await getFreshState();
333
- return state.entitlements.find((item) => item.productId === productId)?.quantity ?? 0;
334
- }
335
- async function purchase(productId, quantity = 1) {
336
- return await requestStoreBridge("purchaseProduct", {
337
- productId,
338
- quantity
339
- });
340
- }
341
- async function consume(productId, quantity = 1) {
342
- return await requestStoreBridge("consumeProduct", {
343
- productId,
344
- quantity
345
- });
346
- }
347
- async function getJemBalance() {
348
- const state = await getFreshState();
349
- return state.jemBalance;
350
- }
351
- function onEntitlementsChanged(callback) {
352
- return subscribeToStoreEvent("oasiz:entitlements-changed", (state) => {
353
- callback(state.entitlements);
354
- });
355
- }
356
- function onJemBalanceChanged(callback) {
357
- return subscribeToStoreEvent("oasiz:jem-balance-changed", (state) => {
358
- callback(state.jemBalance);
359
- });
1230
+ warnMissingBridge4("__oasizLeaveGame");
360
1231
  }
361
1232
 
362
1233
  // src/index.ts
363
1234
  var oasiz = {
364
1235
  submitScore,
365
- emitScoreConfig,
366
1236
  triggerHaptic,
1237
+ enableLogOverlay,
367
1238
  loadGameState,
368
1239
  saveGameState,
369
1240
  flushGameState,
370
1241
  shareRoomCode,
1242
+ openInviteModal,
371
1243
  onPause,
372
1244
  onResume,
1245
+ getSafeAreaTop,
1246
+ setLeaderboardVisible,
373
1247
  onBackButton,
374
1248
  onLeaveGame,
375
1249
  leaveGame,
376
- store: {
377
- syncProducts,
378
- getProducts,
379
- getEntitlements,
380
- hasEntitlement,
381
- getQuantity,
382
- purchase,
383
- consume,
384
- getJemBalance,
385
- onEntitlementsChanged,
386
- onJemBalanceChanged
387
- },
388
1250
  get gameId() {
389
1251
  return getGameId();
390
1252
  },
@@ -396,34 +1258,30 @@ var oasiz = {
396
1258
  },
397
1259
  get playerAvatar() {
398
1260
  return getPlayerAvatar();
1261
+ },
1262
+ get safeAreaTop() {
1263
+ return getSafeAreaTop();
399
1264
  }
400
1265
  };
401
1266
  export {
402
- consume,
403
- emitScoreConfig,
1267
+ enableLogOverlay,
404
1268
  flushGameState,
405
- getEntitlements,
406
1269
  getGameId,
407
- getJemBalance,
408
1270
  getPlayerAvatar,
409
1271
  getPlayerName,
410
- getProducts,
411
- getQuantity,
412
1272
  getRoomCode,
413
- hasEntitlement,
1273
+ getSafeAreaTop,
414
1274
  leaveGame,
415
1275
  loadGameState,
416
1276
  oasiz,
417
1277
  onBackButton,
418
- onEntitlementsChanged,
419
- onJemBalanceChanged,
420
1278
  onLeaveGame,
421
1279
  onPause,
422
1280
  onResume,
423
- purchase,
1281
+ openInviteModal,
424
1282
  saveGameState,
1283
+ setLeaderboardVisible,
425
1284
  shareRoomCode,
426
1285
  submitScore,
427
- syncProducts,
428
1286
  triggerHaptic
429
1287
  };