alabit-ui 0.1.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.
@@ -0,0 +1,649 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+ var __export = (target, all) => {
24
+ for (var name in all)
25
+ __defProp(target, name, { get: all[name], enumerable: true });
26
+ };
27
+ var __copyProps = (to, from, except, desc) => {
28
+ if (from && typeof from === "object" || typeof from === "function") {
29
+ for (let key of __getOwnPropNames(from))
30
+ if (!__hasOwnProp.call(to, key) && key !== except)
31
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
32
+ }
33
+ return to;
34
+ };
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
+
37
+ // src/server.ts
38
+ var server_exports = {};
39
+ __export(server_exports, {
40
+ createResponsiveSession: () => createResponsiveSession,
41
+ deleteResponsiveSession: () => deleteResponsiveSession,
42
+ getResponsiveSession: () => getResponsiveSession,
43
+ performResponsiveAction: () => performResponsiveAction
44
+ });
45
+ module.exports = __toCommonJS(server_exports);
46
+
47
+ // src/lib/responsive/server.ts
48
+ var import_node_crypto = require("crypto");
49
+ var import_promises = require("fs/promises");
50
+ var import_node_fs = require("fs");
51
+ var import_playwright_core = require("playwright-core");
52
+
53
+ // src/lib/responsive/shared.ts
54
+ var DEVICE_PRESETS = [
55
+ {
56
+ id: "mobile-sm",
57
+ name: "Mobile S",
58
+ width: 360,
59
+ height: 780,
60
+ kind: "phone",
61
+ category: "phone",
62
+ brand: "generic",
63
+ line: "generic-smartphone",
64
+ tags: ["sm", "360", "small"]
65
+ },
66
+ {
67
+ id: "iphone-12-pro",
68
+ name: "iPhone 12 Pro",
69
+ width: 390,
70
+ height: 844,
71
+ kind: "phone",
72
+ category: "phone",
73
+ brand: "apple",
74
+ line: "iphone",
75
+ tags: ["apple", "ios", "390"]
76
+ },
77
+ {
78
+ id: "mobile-lg",
79
+ name: "Mobile L",
80
+ width: 640,
81
+ height: 960,
82
+ kind: "phone",
83
+ category: "phone",
84
+ brand: "generic",
85
+ line: "generic-smartphone",
86
+ tags: ["lg", "640", "large"]
87
+ },
88
+ {
89
+ id: "ipad-mini",
90
+ name: "iPad Mini",
91
+ width: 744,
92
+ height: 1133,
93
+ kind: "tablet",
94
+ category: "tablet",
95
+ brand: "apple",
96
+ line: "ipad",
97
+ tags: ["apple", "ipad", "mini"]
98
+ },
99
+ {
100
+ id: "ipad",
101
+ name: "iPad",
102
+ width: 768,
103
+ height: 1024,
104
+ kind: "tablet",
105
+ category: "tablet",
106
+ brand: "apple",
107
+ line: "ipad",
108
+ tags: ["apple", "ipad", "768"]
109
+ },
110
+ {
111
+ id: "ipad-air",
112
+ name: "iPad Air",
113
+ width: 820,
114
+ height: 1180,
115
+ kind: "tablet",
116
+ category: "tablet",
117
+ brand: "apple",
118
+ line: "ipad",
119
+ tags: ["apple", "ipad", "air"]
120
+ },
121
+ {
122
+ id: "ipad-pro-11",
123
+ name: 'iPad Pro 11"',
124
+ width: 834,
125
+ height: 1194,
126
+ kind: "tablet",
127
+ category: "tablet",
128
+ brand: "apple",
129
+ line: "ipad",
130
+ tags: ["apple", "ipad", "pro", "11"]
131
+ },
132
+ {
133
+ id: "ipad-pro-12-9",
134
+ name: 'iPad Pro 12.9"',
135
+ width: 1024,
136
+ height: 1366,
137
+ kind: "tablet",
138
+ category: "tablet",
139
+ brand: "apple",
140
+ line: "ipad",
141
+ tags: ["apple", "ipad", "pro", "12"]
142
+ },
143
+ {
144
+ id: "galaxy-tab-s9",
145
+ name: "Galaxy Tab S9",
146
+ width: 847,
147
+ height: 1210,
148
+ kind: "tablet",
149
+ category: "tablet",
150
+ brand: "samsung",
151
+ line: "galaxy-tab",
152
+ tags: ["samsung", "android", "galaxy-tab"]
153
+ },
154
+ {
155
+ id: "tablet-generic",
156
+ name: "Tablet Gen\xE9rica",
157
+ width: 800,
158
+ height: 1280,
159
+ kind: "tablet",
160
+ category: "tablet",
161
+ brand: "generic",
162
+ line: "generic-tablet",
163
+ tags: ["generic", "android"]
164
+ },
165
+ {
166
+ id: "laptop",
167
+ name: "Laptop",
168
+ width: 1024,
169
+ height: 768,
170
+ kind: "desktop",
171
+ category: "laptop",
172
+ brand: "generic",
173
+ line: "generic-laptop",
174
+ tags: ["1024", "lg-screen"]
175
+ },
176
+ {
177
+ id: "macbook-air-13",
178
+ name: 'MacBook Air 13"',
179
+ width: 1280,
180
+ height: 832,
181
+ kind: "desktop",
182
+ category: "laptop",
183
+ brand: "apple",
184
+ line: "macbook",
185
+ tags: ["apple", "mac", "macbook", "air", "13"]
186
+ },
187
+ {
188
+ id: "macbook-pro-14",
189
+ name: 'MacBook Pro 14"',
190
+ width: 1512,
191
+ height: 982,
192
+ kind: "desktop",
193
+ category: "laptop",
194
+ brand: "apple",
195
+ line: "macbook",
196
+ tags: ["apple", "mac", "macbook", "pro", "14"]
197
+ },
198
+ {
199
+ id: "macbook-pro-16",
200
+ name: 'MacBook Pro 16"',
201
+ width: 1728,
202
+ height: 1117,
203
+ kind: "desktop",
204
+ category: "laptop",
205
+ brand: "apple",
206
+ line: "macbook",
207
+ tags: ["apple", "mac", "macbook", "pro", "16"]
208
+ },
209
+ {
210
+ id: "desktop",
211
+ name: "Desktop",
212
+ width: 1280,
213
+ height: 800,
214
+ kind: "desktop",
215
+ category: "desktop",
216
+ brand: "monitor",
217
+ line: "generic-desktop",
218
+ tags: ["1280", "hd", "screen"]
219
+ },
220
+ {
221
+ id: "imac-24",
222
+ name: 'iMac 24"',
223
+ width: 2240,
224
+ height: 1260,
225
+ kind: "desktop",
226
+ category: "aio",
227
+ brand: "apple",
228
+ line: "imac",
229
+ tags: ["apple", "mac", "imac", "24", "screen"]
230
+ },
231
+ {
232
+ id: "desktop-xl",
233
+ name: "Desktop XL",
234
+ width: 1536,
235
+ height: 960,
236
+ kind: "desktop",
237
+ category: "desktop",
238
+ brand: "monitor",
239
+ line: "pro-monitor",
240
+ tags: ["1536", "2xl", "screen"]
241
+ },
242
+ {
243
+ id: "imac-27",
244
+ name: 'iMac 27"',
245
+ width: 2560,
246
+ height: 1440,
247
+ kind: "desktop",
248
+ category: "aio",
249
+ brand: "apple",
250
+ line: "imac",
251
+ tags: ["apple", "mac", "imac", "27", "5k", "screen"]
252
+ },
253
+ {
254
+ id: "full-hd",
255
+ name: "Full HD",
256
+ width: 1920,
257
+ height: 1080,
258
+ kind: "desktop",
259
+ category: "desktop",
260
+ brand: "monitor",
261
+ line: "pro-monitor",
262
+ tags: ["1920", "1080", "fhd", "screen"]
263
+ },
264
+ {
265
+ id: "wqxga",
266
+ name: "WQXGA 2.5K",
267
+ width: 2560,
268
+ height: 1600,
269
+ kind: "desktop",
270
+ category: "desktop",
271
+ brand: "monitor",
272
+ line: "pro-monitor",
273
+ tags: ["2560", "1600", "2k", "qhd", "screen"]
274
+ },
275
+ {
276
+ id: "uhd-4k",
277
+ name: "4K UHD",
278
+ width: 3840,
279
+ height: 2160,
280
+ kind: "desktop",
281
+ category: "desktop",
282
+ brand: "monitor",
283
+ line: "pro-monitor",
284
+ tags: ["3840", "2160", "4k", "uhd", "screen"]
285
+ },
286
+ {
287
+ id: "custom",
288
+ name: "Personalizado",
289
+ width: 480,
290
+ height: 720,
291
+ kind: "phone",
292
+ category: "custom",
293
+ brand: "custom",
294
+ line: "custom",
295
+ tags: ["custom", "user"]
296
+ }
297
+ ];
298
+ function getViewportSize(device, orientation) {
299
+ const baseIsLandscape = device.width >= device.height;
300
+ if (orientation === "landscape") {
301
+ return baseIsLandscape ? { width: device.width, height: device.height } : { width: device.height, height: device.width };
302
+ }
303
+ return baseIsLandscape ? { width: device.height, height: device.width } : { width: device.width, height: device.height };
304
+ }
305
+
306
+ // src/lib/responsive/server.ts
307
+ var _a;
308
+ var DEFAULT_CHROME_PATH = (_a = process.env.RESPONSIVE_CHROME_PATH) != null ? _a : "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
309
+ var SESSION_TTL_MS = 1e3 * 60 * 15;
310
+ var ACTION_SETTLE_MS = 350;
311
+ var NAVIGATION_TIMEOUT_MS = 3e4;
312
+ var SCREENSHOT_TIMEOUT_MS = 6e4;
313
+ function getStore() {
314
+ if (!globalThis.__responsiveStore__) {
315
+ globalThis.__responsiveStore__ = {
316
+ browserPromise: null,
317
+ sessions: /* @__PURE__ */ new Map(),
318
+ cleanupTimer: null
319
+ };
320
+ }
321
+ if (!globalThis.__responsiveStore__.cleanupTimer) {
322
+ globalThis.__responsiveStore__.cleanupTimer = setInterval(() => {
323
+ void cleanupExpiredSessions();
324
+ }, 6e4);
325
+ }
326
+ return globalThis.__responsiveStore__;
327
+ }
328
+ async function resolveBrowser() {
329
+ const store = getStore();
330
+ if (!store.browserPromise) {
331
+ store.browserPromise = (async () => {
332
+ await (0, import_promises.access)(DEFAULT_CHROME_PATH, import_node_fs.constants.X_OK);
333
+ return import_playwright_core.chromium.launch({
334
+ headless: true,
335
+ executablePath: DEFAULT_CHROME_PATH,
336
+ args: [
337
+ "--disable-dev-shm-usage",
338
+ "--disable-background-timer-throttling",
339
+ "--disable-renderer-backgrounding"
340
+ ]
341
+ });
342
+ })().catch((error) => {
343
+ store.browserPromise = null;
344
+ throw error;
345
+ });
346
+ }
347
+ return store.browserPromise;
348
+ }
349
+ function sanitizeUrl(rawUrl) {
350
+ try {
351
+ const url = new URL(rawUrl);
352
+ if (!["http:", "https:"].includes(url.protocol)) {
353
+ throw new Error("Solo se permiten URLs http/https.");
354
+ }
355
+ return url.toString();
356
+ } catch (e) {
357
+ throw new Error("URL invalida. Usa una ruta local completa o una URL externa valida.");
358
+ }
359
+ }
360
+ function emptySnapshot(device, orientation, targetUrl) {
361
+ const viewport = getViewportSize(device, orientation);
362
+ return {
363
+ deviceId: device.id,
364
+ name: device.name,
365
+ width: viewport.width,
366
+ height: viewport.height,
367
+ screenshotDataUrl: null,
368
+ status: "loading",
369
+ currentUrl: targetUrl,
370
+ title: "",
371
+ error: null
372
+ };
373
+ }
374
+ async function waitForPageReady(page) {
375
+ await page.waitForLoadState("domcontentloaded", { timeout: NAVIGATION_TIMEOUT_MS });
376
+ try {
377
+ await page.waitForLoadState("networkidle", { timeout: 2e3 });
378
+ } catch (e) {
379
+ }
380
+ }
381
+ function getBrowserNavigationError(page) {
382
+ const currentUrl = page.url();
383
+ if (currentUrl.startsWith("chrome-error://")) {
384
+ return "Chrome no pudo cargar la URL destino.";
385
+ }
386
+ return null;
387
+ }
388
+ async function captureFrame(frame) {
389
+ const { page } = frame;
390
+ try {
391
+ const navigationError = getBrowserNavigationError(page);
392
+ if (navigationError) {
393
+ throw new Error(navigationError);
394
+ }
395
+ await page.waitForTimeout(60);
396
+ const buffer = await page.screenshot({
397
+ type: "jpeg",
398
+ quality: 65,
399
+ animations: "disabled",
400
+ caret: "hide",
401
+ timeout: SCREENSHOT_TIMEOUT_MS
402
+ });
403
+ frame.snapshot = __spreadProps(__spreadValues({}, frame.snapshot), {
404
+ screenshotDataUrl: `data:image/jpeg;base64,${buffer.toString("base64")}`,
405
+ status: "ready",
406
+ currentUrl: page.url(),
407
+ title: await page.title(),
408
+ error: null
409
+ });
410
+ } catch (error) {
411
+ frame.snapshot = __spreadProps(__spreadValues({}, frame.snapshot), {
412
+ screenshotDataUrl: null,
413
+ status: "error",
414
+ currentUrl: page.url(),
415
+ title: "",
416
+ error: error instanceof Error ? error.message : "No se pudo capturar el viewport."
417
+ });
418
+ }
419
+ }
420
+ async function refreshSession(session) {
421
+ for (const frame of session.frames.values()) {
422
+ await captureFrame(frame);
423
+ }
424
+ session.updatedAt = Date.now();
425
+ }
426
+ async function closeSessionResources(session) {
427
+ if (session.closed) {
428
+ return;
429
+ }
430
+ session.closed = true;
431
+ await Promise.all(
432
+ Array.from(session.frames.values()).map(async (frame) => {
433
+ await frame.context.close().catch(() => void 0);
434
+ })
435
+ );
436
+ session.frames.clear();
437
+ }
438
+ async function cleanupExpiredSessions() {
439
+ const store = getStore();
440
+ const now = Date.now();
441
+ const expired = Array.from(store.sessions.values()).filter(
442
+ (session) => now - session.updatedAt > SESSION_TTL_MS
443
+ );
444
+ await Promise.all(
445
+ expired.map(async (session) => {
446
+ store.sessions.delete(session.id);
447
+ await closeSessionResources(session);
448
+ })
449
+ );
450
+ }
451
+ function requireSession(sessionId) {
452
+ const store = getStore();
453
+ const session = store.sessions.get(sessionId);
454
+ if (!session || session.closed) {
455
+ throw new Error("La sesion ya no existe. Abri la URL de nuevo.");
456
+ }
457
+ session.updatedAt = Date.now();
458
+ return session;
459
+ }
460
+ function toSnapshot(session) {
461
+ return {
462
+ sessionId: session.id,
463
+ targetUrl: session.targetUrl,
464
+ orientation: session.orientation,
465
+ updatedAt: session.updatedAt,
466
+ frames: DEVICE_PRESETS.map((device) => {
467
+ var _a2;
468
+ const frame = session.frames.get(device.id);
469
+ return (_a2 = frame == null ? void 0 : frame.snapshot) != null ? _a2 : emptySnapshot(device, session.orientation, session.targetUrl);
470
+ })
471
+ };
472
+ }
473
+ async function createFrame(browser, device, targetUrl, orientation) {
474
+ const viewport = getViewportSize(device, orientation);
475
+ const context = await browser.newContext({
476
+ viewport,
477
+ screen: viewport,
478
+ deviceScaleFactor: 1,
479
+ isMobile: device.kind === "phone",
480
+ hasTouch: device.kind !== "desktop"
481
+ });
482
+ const page = await context.newPage();
483
+ const snapshot = emptySnapshot(device, orientation, targetUrl);
484
+ try {
485
+ await page.goto(targetUrl, {
486
+ waitUntil: "domcontentloaded",
487
+ timeout: NAVIGATION_TIMEOUT_MS
488
+ });
489
+ await waitForPageReady(page);
490
+ const navigationError = getBrowserNavigationError(page);
491
+ if (navigationError) {
492
+ throw new Error(navigationError);
493
+ }
494
+ } catch (error) {
495
+ snapshot.status = "error";
496
+ snapshot.error = error instanceof Error ? error.message : "No se pudo abrir la URL.";
497
+ }
498
+ const frame = {
499
+ device,
500
+ context,
501
+ page,
502
+ snapshot: __spreadProps(__spreadValues({}, snapshot), {
503
+ currentUrl: page.url() || targetUrl
504
+ })
505
+ };
506
+ await captureFrame(frame);
507
+ return frame;
508
+ }
509
+ async function createResponsiveSession(targetUrl, orientation) {
510
+ const sanitizedUrl = sanitizeUrl(targetUrl);
511
+ const browser = await resolveBrowser();
512
+ const frames = [];
513
+ for (const device of DEVICE_PRESETS) {
514
+ frames.push(await createFrame(browser, device, sanitizedUrl, orientation));
515
+ }
516
+ const session = {
517
+ id: (0, import_node_crypto.randomUUID)(),
518
+ targetUrl: sanitizedUrl,
519
+ orientation,
520
+ frames: new Map(frames.map((frame) => [frame.device.id, frame])),
521
+ updatedAt: Date.now(),
522
+ closed: false
523
+ };
524
+ getStore().sessions.set(session.id, session);
525
+ return toSnapshot(session);
526
+ }
527
+ async function getResponsiveSession(sessionId) {
528
+ const session = requireSession(sessionId);
529
+ await refreshSession(session);
530
+ return toSnapshot(session);
531
+ }
532
+ async function deleteResponsiveSession(sessionId) {
533
+ const store = getStore();
534
+ const session = store.sessions.get(sessionId);
535
+ if (!session) {
536
+ return;
537
+ }
538
+ store.sessions.delete(sessionId);
539
+ await closeSessionResources(session);
540
+ }
541
+ function clampRatio(value) {
542
+ return Math.max(0, Math.min(1, value));
543
+ }
544
+ async function settleSession(session) {
545
+ await Promise.all(
546
+ Array.from(session.frames.values()).map(async ({ page }) => {
547
+ try {
548
+ await page.waitForTimeout(ACTION_SETTLE_MS);
549
+ } catch (e) {
550
+ return void 0;
551
+ }
552
+ })
553
+ );
554
+ }
555
+ async function performResponsiveAction(sessionId, input) {
556
+ var _a2, _b;
557
+ const session = requireSession(sessionId);
558
+ if (input.type === "reload") {
559
+ await Promise.all(
560
+ Array.from(session.frames.values()).map(async ({ page, snapshot }) => {
561
+ snapshot.status = "loading";
562
+ try {
563
+ await page.reload({ waitUntil: "domcontentloaded", timeout: NAVIGATION_TIMEOUT_MS });
564
+ await waitForPageReady(page);
565
+ const navigationError = getBrowserNavigationError(page);
566
+ if (navigationError) {
567
+ throw new Error(navigationError);
568
+ }
569
+ } catch (error) {
570
+ snapshot.status = "error";
571
+ snapshot.error = error instanceof Error ? error.message : "No se pudo recargar.";
572
+ }
573
+ })
574
+ );
575
+ }
576
+ if (input.type === "set-orientation") {
577
+ session.orientation = input.orientation;
578
+ await Promise.all(
579
+ Array.from(session.frames.values()).map(async (frame) => {
580
+ const viewport = getViewportSize(frame.device, input.orientation);
581
+ frame.snapshot = __spreadProps(__spreadValues({}, frame.snapshot), {
582
+ width: viewport.width,
583
+ height: viewport.height,
584
+ status: "loading",
585
+ error: null
586
+ });
587
+ await frame.page.setViewportSize(viewport);
588
+ })
589
+ );
590
+ }
591
+ if (input.type === "navigate") {
592
+ const targetUrl = sanitizeUrl(input.targetUrl);
593
+ session.targetUrl = targetUrl;
594
+ await Promise.all(
595
+ Array.from(session.frames.values()).map(async ({ page, snapshot }) => {
596
+ snapshot.status = "loading";
597
+ try {
598
+ await page.goto(targetUrl, {
599
+ waitUntil: "domcontentloaded",
600
+ timeout: NAVIGATION_TIMEOUT_MS
601
+ });
602
+ await waitForPageReady(page);
603
+ const navigationError = getBrowserNavigationError(page);
604
+ if (navigationError) {
605
+ throw new Error(navigationError);
606
+ }
607
+ } catch (error) {
608
+ snapshot.status = "error";
609
+ snapshot.error = error instanceof Error ? error.message : "No se pudo navegar.";
610
+ }
611
+ })
612
+ );
613
+ }
614
+ if (input.type === "click") {
615
+ await Promise.all(
616
+ Array.from(session.frames.values()).map(async (frame) => {
617
+ const { width, height } = getViewportSize(frame.device, session.orientation);
618
+ await frame.page.mouse.click(
619
+ clampRatio(input.xRatio) * width,
620
+ clampRatio(input.yRatio) * height
621
+ );
622
+ })
623
+ );
624
+ }
625
+ if (input.type === "scroll") {
626
+ const deltaX = (_a2 = input.deltaX) != null ? _a2 : 0;
627
+ const deltaY = (_b = input.deltaY) != null ? _b : 0;
628
+ await Promise.all(
629
+ Array.from(session.frames.values()).map(
630
+ ({ page }) => page.evaluate(
631
+ ({ nextX, nextY }) => {
632
+ window.scrollBy(nextX, nextY);
633
+ },
634
+ { nextX: deltaX, nextY: deltaY }
635
+ )
636
+ )
637
+ );
638
+ }
639
+ await settleSession(session);
640
+ await refreshSession(session);
641
+ return toSnapshot(session);
642
+ }
643
+ // Annotate the CommonJS export names for ESM import in node:
644
+ 0 && (module.exports = {
645
+ createResponsiveSession,
646
+ deleteResponsiveSession,
647
+ getResponsiveSession,
648
+ performResponsiveAction
649
+ });