@angular-helpers/browser-web-apis 21.1.0 → 21.2.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/README.es.md +418 -2
- package/README.md +389 -2
- package/fesm2022/angular-helpers-browser-web-apis.mjs +1104 -3
- package/package.json +1 -1
- package/types/angular-helpers-browser-web-apis.d.ts +400 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, inject, DestroyRef, PLATFORM_ID, signal, makeEnvironmentProviders } from '@angular/core';
|
|
2
|
+
import { Injectable, inject, DestroyRef, PLATFORM_ID, signal, computed, ElementRef, makeEnvironmentProviders } from '@angular/core';
|
|
3
3
|
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
|
|
4
|
-
import { Observable, fromEvent, Subject } from 'rxjs';
|
|
4
|
+
import { Observable, fromEvent, Subject, of, map as map$1 } from 'rxjs';
|
|
5
5
|
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
|
6
6
|
import { filter, distinctUntilChanged, map } from 'rxjs/operators';
|
|
7
7
|
import { Router } from '@angular/router';
|
|
@@ -480,6 +480,19 @@ const BROWSER_CAPABILITIES = [
|
|
|
480
480
|
{ id: 'webShare', label: 'Web Share', requiresSecureContext: true },
|
|
481
481
|
{ id: 'battery', label: 'Battery API', requiresSecureContext: false },
|
|
482
482
|
{ id: 'webSocket', label: 'WebSocket API', requiresSecureContext: false },
|
|
483
|
+
{ id: 'intersectionObserver', label: 'Intersection Observer', requiresSecureContext: false },
|
|
484
|
+
{ id: 'resizeObserver', label: 'Resize Observer', requiresSecureContext: false },
|
|
485
|
+
{ id: 'pageVisibility', label: 'Page Visibility API', requiresSecureContext: false },
|
|
486
|
+
{ id: 'broadcastChannel', label: 'Broadcast Channel API', requiresSecureContext: false },
|
|
487
|
+
{ id: 'networkInformation', label: 'Network Information API', requiresSecureContext: false },
|
|
488
|
+
{ id: 'screenWakeLock', label: 'Screen Wake Lock API', requiresSecureContext: true },
|
|
489
|
+
{ id: 'screenOrientation', label: 'Screen Orientation API', requiresSecureContext: false },
|
|
490
|
+
{ id: 'fullscreen', label: 'Fullscreen API', requiresSecureContext: false },
|
|
491
|
+
{ id: 'fileSystemAccess', label: 'File System Access API', requiresSecureContext: true },
|
|
492
|
+
{ id: 'mediaRecorder', label: 'MediaRecorder API', requiresSecureContext: true },
|
|
493
|
+
{ id: 'serverSentEvents', label: 'Server-Sent Events', requiresSecureContext: false },
|
|
494
|
+
{ id: 'vibration', label: 'Vibration API', requiresSecureContext: false },
|
|
495
|
+
{ id: 'speechSynthesis', label: 'Speech Synthesis API', requiresSecureContext: false },
|
|
483
496
|
];
|
|
484
497
|
class BrowserCapabilityService {
|
|
485
498
|
getCapabilities() {
|
|
@@ -512,6 +525,34 @@ class BrowserCapabilityService {
|
|
|
512
525
|
return typeof navigator !== 'undefined' && 'getBattery' in navigator;
|
|
513
526
|
case 'webSocket':
|
|
514
527
|
return typeof WebSocket !== 'undefined';
|
|
528
|
+
case 'intersectionObserver':
|
|
529
|
+
return typeof IntersectionObserver !== 'undefined';
|
|
530
|
+
case 'resizeObserver':
|
|
531
|
+
return typeof ResizeObserver !== 'undefined';
|
|
532
|
+
case 'pageVisibility':
|
|
533
|
+
return typeof document !== 'undefined' && 'hidden' in document;
|
|
534
|
+
case 'broadcastChannel':
|
|
535
|
+
return typeof BroadcastChannel !== 'undefined';
|
|
536
|
+
case 'networkInformation':
|
|
537
|
+
return (typeof navigator !== 'undefined' &&
|
|
538
|
+
('connection' in navigator || 'mozConnection' in navigator));
|
|
539
|
+
case 'screenWakeLock':
|
|
540
|
+
return typeof navigator !== 'undefined' && 'wakeLock' in navigator;
|
|
541
|
+
case 'screenOrientation':
|
|
542
|
+
return typeof screen !== 'undefined' && 'orientation' in screen;
|
|
543
|
+
case 'fullscreen':
|
|
544
|
+
return (typeof document !== 'undefined' &&
|
|
545
|
+
('fullscreenEnabled' in document || 'webkitFullscreenEnabled' in document));
|
|
546
|
+
case 'fileSystemAccess':
|
|
547
|
+
return typeof window !== 'undefined' && 'showOpenFilePicker' in window;
|
|
548
|
+
case 'mediaRecorder':
|
|
549
|
+
return typeof MediaRecorder !== 'undefined';
|
|
550
|
+
case 'serverSentEvents':
|
|
551
|
+
return typeof EventSource !== 'undefined';
|
|
552
|
+
case 'vibration':
|
|
553
|
+
return typeof navigator !== 'undefined' && 'vibrate' in navigator;
|
|
554
|
+
case 'speechSynthesis':
|
|
555
|
+
return typeof window !== 'undefined' && 'speechSynthesis' in window;
|
|
515
556
|
default:
|
|
516
557
|
return false;
|
|
517
558
|
}
|
|
@@ -1290,8 +1331,1003 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
1290
1331
|
type: Injectable
|
|
1291
1332
|
}] });
|
|
1292
1333
|
|
|
1334
|
+
function isIntersectionObserverSupported() {
|
|
1335
|
+
return typeof window !== 'undefined' && 'IntersectionObserver' in window;
|
|
1336
|
+
}
|
|
1337
|
+
function intersectionObserverStream(element, options = {}) {
|
|
1338
|
+
return new Observable((observer) => {
|
|
1339
|
+
const io = new IntersectionObserver((entries) => {
|
|
1340
|
+
const entry = entries[entries.length - 1];
|
|
1341
|
+
observer.next(entry.isIntersecting);
|
|
1342
|
+
}, options);
|
|
1343
|
+
io.observe(element);
|
|
1344
|
+
return () => {
|
|
1345
|
+
io.unobserve(element);
|
|
1346
|
+
io.disconnect();
|
|
1347
|
+
};
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
function intersectionObserverEntriesStream(element, options = {}) {
|
|
1351
|
+
return new Observable((observer) => {
|
|
1352
|
+
const io = new IntersectionObserver((entries) => observer.next(entries), options);
|
|
1353
|
+
io.observe(element);
|
|
1354
|
+
return () => {
|
|
1355
|
+
io.unobserve(element);
|
|
1356
|
+
io.disconnect();
|
|
1357
|
+
};
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
class IntersectionObserverService {
|
|
1362
|
+
platformId = inject(PLATFORM_ID);
|
|
1363
|
+
isSupported() {
|
|
1364
|
+
return isPlatformBrowser(this.platformId) && 'IntersectionObserver' in window;
|
|
1365
|
+
}
|
|
1366
|
+
observe(element, options = {}) {
|
|
1367
|
+
if (!this.isSupported()) {
|
|
1368
|
+
return new Observable((o) => o.error(new Error('IntersectionObserver API not supported')));
|
|
1369
|
+
}
|
|
1370
|
+
return intersectionObserverEntriesStream(element, options);
|
|
1371
|
+
}
|
|
1372
|
+
observeVisibility(element, options = {}) {
|
|
1373
|
+
if (!this.isSupported()) {
|
|
1374
|
+
return new Observable((o) => o.error(new Error('IntersectionObserver API not supported')));
|
|
1375
|
+
}
|
|
1376
|
+
return intersectionObserverStream(element, options);
|
|
1377
|
+
}
|
|
1378
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IntersectionObserverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1379
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IntersectionObserverService });
|
|
1380
|
+
}
|
|
1381
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IntersectionObserverService, decorators: [{
|
|
1382
|
+
type: Injectable
|
|
1383
|
+
}] });
|
|
1384
|
+
|
|
1385
|
+
function isResizeObserverSupported() {
|
|
1386
|
+
return typeof window !== 'undefined' && 'ResizeObserver' in window;
|
|
1387
|
+
}
|
|
1388
|
+
function resizeObserverStream(element, options = {}) {
|
|
1389
|
+
return new Observable((observer) => {
|
|
1390
|
+
const ro = new ResizeObserver((entries) => {
|
|
1391
|
+
const entry = entries[entries.length - 1];
|
|
1392
|
+
const contentRect = entry.contentRect;
|
|
1393
|
+
const borderBoxSize = entry.borderBoxSize?.[0];
|
|
1394
|
+
observer.next({
|
|
1395
|
+
width: contentRect.width,
|
|
1396
|
+
height: contentRect.height,
|
|
1397
|
+
inlineSize: borderBoxSize?.inlineSize ?? contentRect.width,
|
|
1398
|
+
blockSize: borderBoxSize?.blockSize ?? contentRect.height,
|
|
1399
|
+
});
|
|
1400
|
+
});
|
|
1401
|
+
ro.observe(element, options);
|
|
1402
|
+
return () => {
|
|
1403
|
+
ro.unobserve(element);
|
|
1404
|
+
ro.disconnect();
|
|
1405
|
+
};
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
function resizeObserverEntriesStream(element, options = {}) {
|
|
1409
|
+
return new Observable((observer) => {
|
|
1410
|
+
const ro = new ResizeObserver((entries) => observer.next(entries));
|
|
1411
|
+
ro.observe(element, options);
|
|
1412
|
+
return () => {
|
|
1413
|
+
ro.unobserve(element);
|
|
1414
|
+
ro.disconnect();
|
|
1415
|
+
};
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
class ResizeObserverService {
|
|
1420
|
+
platformId = inject(PLATFORM_ID);
|
|
1421
|
+
isSupported() {
|
|
1422
|
+
return isPlatformBrowser(this.platformId) && 'ResizeObserver' in window;
|
|
1423
|
+
}
|
|
1424
|
+
observe(element, options = {}) {
|
|
1425
|
+
if (!this.isSupported()) {
|
|
1426
|
+
return new Observable((o) => o.error(new Error('ResizeObserver API not supported')));
|
|
1427
|
+
}
|
|
1428
|
+
return resizeObserverEntriesStream(element, options);
|
|
1429
|
+
}
|
|
1430
|
+
observeSize(element, options = {}) {
|
|
1431
|
+
if (!this.isSupported()) {
|
|
1432
|
+
return new Observable((o) => o.error(new Error('ResizeObserver API not supported')));
|
|
1433
|
+
}
|
|
1434
|
+
return resizeObserverStream(element, options);
|
|
1435
|
+
}
|
|
1436
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ResizeObserverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1437
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ResizeObserverService });
|
|
1438
|
+
}
|
|
1439
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ResizeObserverService, decorators: [{
|
|
1440
|
+
type: Injectable
|
|
1441
|
+
}] });
|
|
1442
|
+
|
|
1443
|
+
function isPageVisibilitySupported() {
|
|
1444
|
+
return typeof document !== 'undefined' && 'hidden' in document;
|
|
1445
|
+
}
|
|
1446
|
+
function pageVisibilityStream() {
|
|
1447
|
+
if (!isPageVisibilitySupported()) {
|
|
1448
|
+
return of('visible');
|
|
1449
|
+
}
|
|
1450
|
+
return new Observable((observer) => {
|
|
1451
|
+
const handler = () => observer.next(document.visibilityState);
|
|
1452
|
+
document.addEventListener('visibilitychange', handler);
|
|
1453
|
+
observer.next(document.visibilityState);
|
|
1454
|
+
return () => document.removeEventListener('visibilitychange', handler);
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
class PageVisibilityService {
|
|
1459
|
+
platformId = inject(PLATFORM_ID);
|
|
1460
|
+
isSupported() {
|
|
1461
|
+
return isPlatformBrowser(this.platformId) && 'hidden' in document;
|
|
1462
|
+
}
|
|
1463
|
+
get isHidden() {
|
|
1464
|
+
if (!this.isSupported())
|
|
1465
|
+
return false;
|
|
1466
|
+
return document.hidden;
|
|
1467
|
+
}
|
|
1468
|
+
get visibilityState() {
|
|
1469
|
+
if (!this.isSupported())
|
|
1470
|
+
return 'visible';
|
|
1471
|
+
return document.visibilityState;
|
|
1472
|
+
}
|
|
1473
|
+
watch() {
|
|
1474
|
+
return pageVisibilityStream();
|
|
1475
|
+
}
|
|
1476
|
+
watchVisibility() {
|
|
1477
|
+
return pageVisibilityStream().pipe(map$1((s) => s === 'visible'));
|
|
1478
|
+
}
|
|
1479
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PageVisibilityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1480
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PageVisibilityService });
|
|
1481
|
+
}
|
|
1482
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PageVisibilityService, decorators: [{
|
|
1483
|
+
type: Injectable
|
|
1484
|
+
}] });
|
|
1485
|
+
|
|
1486
|
+
class BroadcastChannelService {
|
|
1487
|
+
destroyRef = inject(DestroyRef);
|
|
1488
|
+
platformId = inject(PLATFORM_ID);
|
|
1489
|
+
channels = new Map();
|
|
1490
|
+
isSupported() {
|
|
1491
|
+
return isPlatformBrowser(this.platformId) && 'BroadcastChannel' in window;
|
|
1492
|
+
}
|
|
1493
|
+
ensureSupport() {
|
|
1494
|
+
if (!this.isSupported()) {
|
|
1495
|
+
throw new Error('BroadcastChannel API not supported in this environment');
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
open(name) {
|
|
1499
|
+
this.ensureSupport();
|
|
1500
|
+
return new Observable((observer) => {
|
|
1501
|
+
let channel = this.channels.get(name);
|
|
1502
|
+
if (!channel) {
|
|
1503
|
+
channel = new BroadcastChannel(name);
|
|
1504
|
+
this.channels.set(name, channel);
|
|
1505
|
+
}
|
|
1506
|
+
const handler = (event) => observer.next(event.data);
|
|
1507
|
+
const errorHandler = () => observer.error(new Error(`BroadcastChannel "${name}" error`));
|
|
1508
|
+
channel.addEventListener('message', handler);
|
|
1509
|
+
channel.addEventListener('messageerror', errorHandler);
|
|
1510
|
+
const cleanup = () => {
|
|
1511
|
+
channel.removeEventListener('message', handler);
|
|
1512
|
+
channel.removeEventListener('messageerror', errorHandler);
|
|
1513
|
+
};
|
|
1514
|
+
this.destroyRef.onDestroy(() => this.close(name));
|
|
1515
|
+
return cleanup;
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
post(name, data) {
|
|
1519
|
+
this.ensureSupport();
|
|
1520
|
+
let channel = this.channels.get(name);
|
|
1521
|
+
if (!channel) {
|
|
1522
|
+
channel = new BroadcastChannel(name);
|
|
1523
|
+
this.channels.set(name, channel);
|
|
1524
|
+
this.destroyRef.onDestroy(() => this.close(name));
|
|
1525
|
+
}
|
|
1526
|
+
channel.postMessage(data);
|
|
1527
|
+
}
|
|
1528
|
+
close(name) {
|
|
1529
|
+
const channel = this.channels.get(name);
|
|
1530
|
+
if (channel) {
|
|
1531
|
+
channel.close();
|
|
1532
|
+
this.channels.delete(name);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
closeAll() {
|
|
1536
|
+
this.channels.forEach((channel) => channel.close());
|
|
1537
|
+
this.channels.clear();
|
|
1538
|
+
}
|
|
1539
|
+
getOpenChannels() {
|
|
1540
|
+
return Array.from(this.channels.keys());
|
|
1541
|
+
}
|
|
1542
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BroadcastChannelService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1543
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BroadcastChannelService });
|
|
1544
|
+
}
|
|
1545
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BroadcastChannelService, decorators: [{
|
|
1546
|
+
type: Injectable
|
|
1547
|
+
}] });
|
|
1548
|
+
|
|
1549
|
+
function isNetworkInformationSupported() {
|
|
1550
|
+
if (typeof navigator === 'undefined')
|
|
1551
|
+
return false;
|
|
1552
|
+
const nav = navigator;
|
|
1553
|
+
return 'connection' in nav || 'mozConnection' in nav || 'webkitConnection' in nav;
|
|
1554
|
+
}
|
|
1555
|
+
function getNetworkConnection() {
|
|
1556
|
+
if (typeof navigator === 'undefined')
|
|
1557
|
+
return undefined;
|
|
1558
|
+
const nav = navigator;
|
|
1559
|
+
return nav.connection ?? nav.mozConnection ?? nav.webkitConnection;
|
|
1560
|
+
}
|
|
1561
|
+
function getNetworkSnapshot() {
|
|
1562
|
+
const online = typeof navigator !== 'undefined' ? navigator.onLine : true;
|
|
1563
|
+
const conn = getNetworkConnection();
|
|
1564
|
+
return {
|
|
1565
|
+
online,
|
|
1566
|
+
type: conn?.type,
|
|
1567
|
+
effectiveType: conn?.effectiveType,
|
|
1568
|
+
downlink: conn?.downlink,
|
|
1569
|
+
downlinkMax: conn?.downlinkMax,
|
|
1570
|
+
rtt: conn?.rtt,
|
|
1571
|
+
saveData: conn?.saveData,
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
function networkInformationStream() {
|
|
1575
|
+
if (typeof window === 'undefined') {
|
|
1576
|
+
return of({ online: true });
|
|
1577
|
+
}
|
|
1578
|
+
return new Observable((observer) => {
|
|
1579
|
+
const emit = () => observer.next(getNetworkSnapshot());
|
|
1580
|
+
const conn = getNetworkConnection();
|
|
1581
|
+
if (conn)
|
|
1582
|
+
conn.addEventListener('change', emit);
|
|
1583
|
+
window.addEventListener('online', emit);
|
|
1584
|
+
window.addEventListener('offline', emit);
|
|
1585
|
+
emit();
|
|
1586
|
+
return () => {
|
|
1587
|
+
conn?.removeEventListener('change', emit);
|
|
1588
|
+
window.removeEventListener('online', emit);
|
|
1589
|
+
window.removeEventListener('offline', emit);
|
|
1590
|
+
};
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
class NetworkInformationService {
|
|
1595
|
+
platformId = inject(PLATFORM_ID);
|
|
1596
|
+
isSupported() {
|
|
1597
|
+
return isPlatformBrowser(this.platformId) && isNetworkInformationSupported();
|
|
1598
|
+
}
|
|
1599
|
+
getSnapshot() {
|
|
1600
|
+
return isPlatformBrowser(this.platformId) ? getNetworkSnapshot() : { online: true };
|
|
1601
|
+
}
|
|
1602
|
+
watch() {
|
|
1603
|
+
return networkInformationStream();
|
|
1604
|
+
}
|
|
1605
|
+
get isOnline() {
|
|
1606
|
+
return isPlatformBrowser(this.platformId) ? navigator.onLine : true;
|
|
1607
|
+
}
|
|
1608
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: NetworkInformationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1609
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: NetworkInformationService });
|
|
1610
|
+
}
|
|
1611
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: NetworkInformationService, decorators: [{
|
|
1612
|
+
type: Injectable
|
|
1613
|
+
}] });
|
|
1614
|
+
|
|
1615
|
+
class ScreenWakeLockService extends BrowserApiBaseService {
|
|
1616
|
+
sentinel = null;
|
|
1617
|
+
getApiName() {
|
|
1618
|
+
return 'screen-wake-lock';
|
|
1619
|
+
}
|
|
1620
|
+
isSupported() {
|
|
1621
|
+
return isPlatformBrowser(this.platformId) && 'wakeLock' in navigator;
|
|
1622
|
+
}
|
|
1623
|
+
get isActive() {
|
|
1624
|
+
return this.sentinel !== null && !this.sentinel.released;
|
|
1625
|
+
}
|
|
1626
|
+
async request(type = 'screen') {
|
|
1627
|
+
if (!this.isSupported()) {
|
|
1628
|
+
throw new Error('Screen Wake Lock API not supported in this browser');
|
|
1629
|
+
}
|
|
1630
|
+
if (!window.isSecureContext) {
|
|
1631
|
+
throw new Error('Screen Wake Lock API requires a secure context (HTTPS)');
|
|
1632
|
+
}
|
|
1633
|
+
try {
|
|
1634
|
+
this.sentinel = await navigator.wakeLock.request(type);
|
|
1635
|
+
this.sentinel.addEventListener('release', () => {
|
|
1636
|
+
this.sentinel = null;
|
|
1637
|
+
});
|
|
1638
|
+
this.destroyRef.onDestroy(() => this.release());
|
|
1639
|
+
return { active: true, type, released: false };
|
|
1640
|
+
}
|
|
1641
|
+
catch (error) {
|
|
1642
|
+
console.error('[ScreenWakeLockService] Failed to acquire wake lock:', error);
|
|
1643
|
+
throw this.createError('Failed to acquire wake lock', error);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
async release() {
|
|
1647
|
+
if (this.sentinel && !this.sentinel.released) {
|
|
1648
|
+
try {
|
|
1649
|
+
await this.sentinel.release();
|
|
1650
|
+
}
|
|
1651
|
+
catch {
|
|
1652
|
+
// Sentinel may already be released
|
|
1653
|
+
}
|
|
1654
|
+
finally {
|
|
1655
|
+
this.sentinel = null;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
watchStatus() {
|
|
1660
|
+
return new Observable((observer) => {
|
|
1661
|
+
const emit = () => observer.next({ active: this.isActive, released: !this.isActive });
|
|
1662
|
+
const handleVisibilityChange = async () => {
|
|
1663
|
+
if (document.visibilityState === 'visible' && !this.isActive) {
|
|
1664
|
+
try {
|
|
1665
|
+
await this.request();
|
|
1666
|
+
}
|
|
1667
|
+
catch {
|
|
1668
|
+
// Could not re-acquire
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
emit();
|
|
1672
|
+
};
|
|
1673
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
1674
|
+
emit();
|
|
1675
|
+
const cleanup = () => document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
1676
|
+
this.destroyRef.onDestroy(cleanup);
|
|
1677
|
+
return cleanup;
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ScreenWakeLockService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1681
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ScreenWakeLockService });
|
|
1682
|
+
}
|
|
1683
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ScreenWakeLockService, decorators: [{
|
|
1684
|
+
type: Injectable
|
|
1685
|
+
}] });
|
|
1686
|
+
|
|
1687
|
+
function isScreenOrientationSupported() {
|
|
1688
|
+
return typeof window !== 'undefined' && 'screen' in window && 'orientation' in screen;
|
|
1689
|
+
}
|
|
1690
|
+
function getOrientationSnapshot() {
|
|
1691
|
+
if (!isScreenOrientationSupported()) {
|
|
1692
|
+
return { type: 'portrait-primary', angle: 0 };
|
|
1693
|
+
}
|
|
1694
|
+
return {
|
|
1695
|
+
type: screen.orientation.type,
|
|
1696
|
+
angle: screen.orientation.angle,
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
function screenOrientationStream() {
|
|
1700
|
+
if (!isScreenOrientationSupported()) {
|
|
1701
|
+
return of({ type: 'portrait-primary', angle: 0 });
|
|
1702
|
+
}
|
|
1703
|
+
return new Observable((observer) => {
|
|
1704
|
+
const handler = () => observer.next(getOrientationSnapshot());
|
|
1705
|
+
screen.orientation.addEventListener('change', handler);
|
|
1706
|
+
observer.next(getOrientationSnapshot());
|
|
1707
|
+
return () => screen.orientation.removeEventListener('change', handler);
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
class ScreenOrientationService {
|
|
1712
|
+
platformId = inject(PLATFORM_ID);
|
|
1713
|
+
isSupported() {
|
|
1714
|
+
return isPlatformBrowser(this.platformId) && 'screen' in window && 'orientation' in screen;
|
|
1715
|
+
}
|
|
1716
|
+
getSnapshot() {
|
|
1717
|
+
return isPlatformBrowser(this.platformId)
|
|
1718
|
+
? getOrientationSnapshot()
|
|
1719
|
+
: { type: 'portrait-primary', angle: 0 };
|
|
1720
|
+
}
|
|
1721
|
+
get isPortrait() {
|
|
1722
|
+
return this.getSnapshot().type.startsWith('portrait');
|
|
1723
|
+
}
|
|
1724
|
+
get isLandscape() {
|
|
1725
|
+
return this.getSnapshot().type.startsWith('landscape');
|
|
1726
|
+
}
|
|
1727
|
+
watch() {
|
|
1728
|
+
return screenOrientationStream();
|
|
1729
|
+
}
|
|
1730
|
+
async lock(orientation) {
|
|
1731
|
+
if (!this.isSupported()) {
|
|
1732
|
+
throw new Error('Screen Orientation API not supported');
|
|
1733
|
+
}
|
|
1734
|
+
try {
|
|
1735
|
+
await screen.orientation.lock(orientation);
|
|
1736
|
+
}
|
|
1737
|
+
catch (error) {
|
|
1738
|
+
console.error('[ScreenOrientationService] Failed to lock orientation:', error);
|
|
1739
|
+
throw error;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
unlock() {
|
|
1743
|
+
if (this.isSupported()) {
|
|
1744
|
+
screen.orientation.unlock();
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ScreenOrientationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1748
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ScreenOrientationService });
|
|
1749
|
+
}
|
|
1750
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ScreenOrientationService, decorators: [{
|
|
1751
|
+
type: Injectable
|
|
1752
|
+
}] });
|
|
1753
|
+
|
|
1754
|
+
class FullscreenService {
|
|
1755
|
+
destroyRef = inject(DestroyRef);
|
|
1756
|
+
platformId = inject(PLATFORM_ID);
|
|
1757
|
+
isSupported() {
|
|
1758
|
+
if (!isPlatformBrowser(this.platformId))
|
|
1759
|
+
return false;
|
|
1760
|
+
return !!(document.fullscreenEnabled ??
|
|
1761
|
+
document.webkitFullscreenEnabled);
|
|
1762
|
+
}
|
|
1763
|
+
get isFullscreen() {
|
|
1764
|
+
if (!isPlatformBrowser(this.platformId))
|
|
1765
|
+
return false;
|
|
1766
|
+
return !!(document.fullscreenElement ??
|
|
1767
|
+
document.webkitFullscreenElement);
|
|
1768
|
+
}
|
|
1769
|
+
get fullscreenElement() {
|
|
1770
|
+
if (!isPlatformBrowser(this.platformId))
|
|
1771
|
+
return null;
|
|
1772
|
+
return (document.fullscreenElement ??
|
|
1773
|
+
document.webkitFullscreenElement ??
|
|
1774
|
+
null);
|
|
1775
|
+
}
|
|
1776
|
+
async request(element = document.documentElement) {
|
|
1777
|
+
if (!this.isSupported()) {
|
|
1778
|
+
throw new Error('Fullscreen API not supported in this browser');
|
|
1779
|
+
}
|
|
1780
|
+
try {
|
|
1781
|
+
const el = element;
|
|
1782
|
+
if (el.requestFullscreen) {
|
|
1783
|
+
await el.requestFullscreen();
|
|
1784
|
+
}
|
|
1785
|
+
else if (el.webkitRequestFullscreen) {
|
|
1786
|
+
await el.webkitRequestFullscreen();
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
catch (error) {
|
|
1790
|
+
console.error('[FullscreenService] Failed to enter fullscreen:', error);
|
|
1791
|
+
throw error;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
async exit() {
|
|
1795
|
+
if (!this.isFullscreen)
|
|
1796
|
+
return;
|
|
1797
|
+
try {
|
|
1798
|
+
const doc = document;
|
|
1799
|
+
if (doc.exitFullscreen) {
|
|
1800
|
+
await doc.exitFullscreen();
|
|
1801
|
+
}
|
|
1802
|
+
else if (doc.webkitExitFullscreen) {
|
|
1803
|
+
await doc.webkitExitFullscreen();
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
catch (error) {
|
|
1807
|
+
console.error('[FullscreenService] Failed to exit fullscreen:', error);
|
|
1808
|
+
throw error;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
async toggle(element = document.documentElement) {
|
|
1812
|
+
if (this.isFullscreen) {
|
|
1813
|
+
await this.exit();
|
|
1814
|
+
}
|
|
1815
|
+
else {
|
|
1816
|
+
await this.request(element);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
watch() {
|
|
1820
|
+
return new Observable((observer) => {
|
|
1821
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
1822
|
+
observer.next(false);
|
|
1823
|
+
observer.complete();
|
|
1824
|
+
return undefined;
|
|
1825
|
+
}
|
|
1826
|
+
const handler = () => observer.next(this.isFullscreen);
|
|
1827
|
+
document.addEventListener('fullscreenchange', handler);
|
|
1828
|
+
document.addEventListener('webkitfullscreenchange', handler);
|
|
1829
|
+
observer.next(this.isFullscreen);
|
|
1830
|
+
const cleanup = () => {
|
|
1831
|
+
document.removeEventListener('fullscreenchange', handler);
|
|
1832
|
+
document.removeEventListener('webkitfullscreenchange', handler);
|
|
1833
|
+
};
|
|
1834
|
+
this.destroyRef.onDestroy(cleanup);
|
|
1835
|
+
return cleanup;
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FullscreenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1839
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FullscreenService });
|
|
1840
|
+
}
|
|
1841
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FullscreenService, decorators: [{
|
|
1842
|
+
type: Injectable
|
|
1843
|
+
}] });
|
|
1844
|
+
|
|
1845
|
+
class FileSystemAccessService extends BrowserApiBaseService {
|
|
1846
|
+
getApiName() {
|
|
1847
|
+
return 'file-system-access';
|
|
1848
|
+
}
|
|
1849
|
+
isSupported() {
|
|
1850
|
+
return this.isBrowserEnvironment() && 'showOpenFilePicker' in window && window.isSecureContext;
|
|
1851
|
+
}
|
|
1852
|
+
get win() {
|
|
1853
|
+
return window;
|
|
1854
|
+
}
|
|
1855
|
+
ensureSupport() {
|
|
1856
|
+
if (this.isServerEnvironment()) {
|
|
1857
|
+
throw new Error('File System Access API not available in server environment');
|
|
1858
|
+
}
|
|
1859
|
+
if (!('showOpenFilePicker' in window)) {
|
|
1860
|
+
throw new Error('File System Access API not supported in this browser');
|
|
1861
|
+
}
|
|
1862
|
+
if (!window.isSecureContext) {
|
|
1863
|
+
throw new Error('File System Access API requires a secure context (HTTPS)');
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
async openFile(options = {}) {
|
|
1867
|
+
this.ensureSupport();
|
|
1868
|
+
try {
|
|
1869
|
+
const handles = await this.win.showOpenFilePicker(options);
|
|
1870
|
+
return Promise.all(handles.map((h) => h.getFile()));
|
|
1871
|
+
}
|
|
1872
|
+
catch (error) {
|
|
1873
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
1874
|
+
return [];
|
|
1875
|
+
}
|
|
1876
|
+
console.error('[FileSystemAccessService] Error opening file:', error);
|
|
1877
|
+
throw error;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
async saveFile(content, options = {}) {
|
|
1881
|
+
this.ensureSupport();
|
|
1882
|
+
if (!this.win.showSaveFilePicker) {
|
|
1883
|
+
throw new Error('showSaveFilePicker not supported');
|
|
1884
|
+
}
|
|
1885
|
+
try {
|
|
1886
|
+
const handle = await this.win.showSaveFilePicker(options);
|
|
1887
|
+
const writable = await handle.createWritable();
|
|
1888
|
+
await writable.write(content);
|
|
1889
|
+
await writable.close();
|
|
1890
|
+
}
|
|
1891
|
+
catch (error) {
|
|
1892
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
console.error('[FileSystemAccessService] Error saving file:', error);
|
|
1896
|
+
throw error;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
async openDirectory(options = {}) {
|
|
1900
|
+
this.ensureSupport();
|
|
1901
|
+
if (!this.win.showDirectoryPicker) {
|
|
1902
|
+
throw new Error('showDirectoryPicker not supported');
|
|
1903
|
+
}
|
|
1904
|
+
try {
|
|
1905
|
+
return await this.win.showDirectoryPicker(options);
|
|
1906
|
+
}
|
|
1907
|
+
catch (error) {
|
|
1908
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
console.error('[FileSystemAccessService] Error opening directory:', error);
|
|
1912
|
+
throw error;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
async readFileAsText(file) {
|
|
1916
|
+
return file.text();
|
|
1917
|
+
}
|
|
1918
|
+
async readFileAsArrayBuffer(file) {
|
|
1919
|
+
return file.arrayBuffer();
|
|
1920
|
+
}
|
|
1921
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FileSystemAccessService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1922
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FileSystemAccessService });
|
|
1923
|
+
}
|
|
1924
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FileSystemAccessService, decorators: [{
|
|
1925
|
+
type: Injectable
|
|
1926
|
+
}] });
|
|
1927
|
+
|
|
1928
|
+
class MediaRecorderService extends BrowserApiBaseService {
|
|
1929
|
+
recorder = null;
|
|
1930
|
+
chunks = [];
|
|
1931
|
+
startTime = 0;
|
|
1932
|
+
dataSubject = new Subject();
|
|
1933
|
+
stateSubject = new Subject();
|
|
1934
|
+
getApiName() {
|
|
1935
|
+
return 'media-recorder';
|
|
1936
|
+
}
|
|
1937
|
+
isSupported() {
|
|
1938
|
+
return this.isBrowserEnvironment() && 'MediaRecorder' in window;
|
|
1939
|
+
}
|
|
1940
|
+
get state() {
|
|
1941
|
+
return this.recorder?.state ?? 'inactive';
|
|
1942
|
+
}
|
|
1943
|
+
static isTypeSupported(mimeType) {
|
|
1944
|
+
return typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported(mimeType);
|
|
1945
|
+
}
|
|
1946
|
+
watchState() {
|
|
1947
|
+
return this.stateSubject.asObservable();
|
|
1948
|
+
}
|
|
1949
|
+
watchData() {
|
|
1950
|
+
return this.dataSubject.asObservable();
|
|
1951
|
+
}
|
|
1952
|
+
async start(stream, options = {}) {
|
|
1953
|
+
if (!this.isSupported()) {
|
|
1954
|
+
throw new Error('MediaRecorder API not supported in this browser');
|
|
1955
|
+
}
|
|
1956
|
+
if (!window.isSecureContext) {
|
|
1957
|
+
throw new Error('MediaRecorder requires a secure context (HTTPS)');
|
|
1958
|
+
}
|
|
1959
|
+
this.stop();
|
|
1960
|
+
this.chunks = [];
|
|
1961
|
+
this.startTime = Date.now();
|
|
1962
|
+
const { timeslice, ...recorderOptions } = options;
|
|
1963
|
+
try {
|
|
1964
|
+
this.recorder = new MediaRecorder(stream, recorderOptions);
|
|
1965
|
+
this.recorder.ondataavailable = (event) => {
|
|
1966
|
+
if (event.data.size > 0) {
|
|
1967
|
+
this.chunks.push(event.data);
|
|
1968
|
+
this.dataSubject.next(event.data);
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
this.recorder.onstart = () => this.stateSubject.next('recording');
|
|
1972
|
+
this.recorder.onpause = () => this.stateSubject.next('paused');
|
|
1973
|
+
this.recorder.onresume = () => this.stateSubject.next('recording');
|
|
1974
|
+
this.recorder.onstop = () => this.stateSubject.next('inactive');
|
|
1975
|
+
this.recorder.start(timeslice);
|
|
1976
|
+
}
|
|
1977
|
+
catch (error) {
|
|
1978
|
+
console.error('[MediaRecorderService] Failed to start recording:', error);
|
|
1979
|
+
throw this.createError('Failed to start recording', error);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
pause() {
|
|
1983
|
+
if (this.recorder?.state === 'recording') {
|
|
1984
|
+
this.recorder.pause();
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
resume() {
|
|
1988
|
+
if (this.recorder?.state === 'paused') {
|
|
1989
|
+
this.recorder.resume();
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
stop() {
|
|
1993
|
+
if (!this.recorder || this.recorder.state === 'inactive') {
|
|
1994
|
+
return null;
|
|
1995
|
+
}
|
|
1996
|
+
this.recorder.stop();
|
|
1997
|
+
const mimeType = this.recorder.mimeType;
|
|
1998
|
+
const duration = Date.now() - this.startTime;
|
|
1999
|
+
const blob = new Blob(this.chunks, { type: mimeType });
|
|
2000
|
+
const url = URL.createObjectURL(blob);
|
|
2001
|
+
this.recorder = null;
|
|
2002
|
+
this.chunks = [];
|
|
2003
|
+
return { blob, url, mimeType, duration };
|
|
2004
|
+
}
|
|
2005
|
+
getResult() {
|
|
2006
|
+
if (this.chunks.length === 0)
|
|
2007
|
+
return null;
|
|
2008
|
+
const mimeType = this.recorder?.mimeType ?? 'video/webm';
|
|
2009
|
+
const blob = new Blob(this.chunks, { type: mimeType });
|
|
2010
|
+
const url = URL.createObjectURL(blob);
|
|
2011
|
+
const duration = Date.now() - this.startTime;
|
|
2012
|
+
return { blob, url, mimeType, duration };
|
|
2013
|
+
}
|
|
2014
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: MediaRecorderService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
2015
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: MediaRecorderService });
|
|
2016
|
+
}
|
|
2017
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: MediaRecorderService, decorators: [{
|
|
2018
|
+
type: Injectable
|
|
2019
|
+
}] });
|
|
2020
|
+
|
|
2021
|
+
class ServerSentEventsService {
|
|
2022
|
+
destroyRef = inject(DestroyRef);
|
|
2023
|
+
platformId = inject(PLATFORM_ID);
|
|
2024
|
+
sources = new Map();
|
|
2025
|
+
isSupported() {
|
|
2026
|
+
return isPlatformBrowser(this.platformId) && 'EventSource' in window;
|
|
2027
|
+
}
|
|
2028
|
+
ensureSupport() {
|
|
2029
|
+
if (!this.isSupported()) {
|
|
2030
|
+
throw new Error('Server-Sent Events (EventSource) not supported in this environment');
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
connect(url, config = {}) {
|
|
2034
|
+
this.ensureSupport();
|
|
2035
|
+
return new Observable((observer) => {
|
|
2036
|
+
const source = new EventSource(url, { withCredentials: config.withCredentials ?? false });
|
|
2037
|
+
this.sources.set(url, source);
|
|
2038
|
+
const messageHandler = (event) => {
|
|
2039
|
+
try {
|
|
2040
|
+
observer.next({
|
|
2041
|
+
data: JSON.parse(event.data),
|
|
2042
|
+
type: event.type,
|
|
2043
|
+
lastEventId: event.lastEventId,
|
|
2044
|
+
origin: event.origin,
|
|
2045
|
+
});
|
|
2046
|
+
}
|
|
2047
|
+
catch {
|
|
2048
|
+
observer.next({
|
|
2049
|
+
data: event.data,
|
|
2050
|
+
type: event.type,
|
|
2051
|
+
lastEventId: event.lastEventId,
|
|
2052
|
+
origin: event.origin,
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
2056
|
+
const errorHandler = (event) => {
|
|
2057
|
+
if (source.readyState === EventSource.CLOSED) {
|
|
2058
|
+
observer.error(new Error('SSE connection closed unexpectedly'));
|
|
2059
|
+
}
|
|
2060
|
+
else {
|
|
2061
|
+
console.warn('[ServerSentEventsService] SSE connection error, reconnecting...', event);
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
source.addEventListener('message', messageHandler);
|
|
2065
|
+
source.addEventListener('error', errorHandler);
|
|
2066
|
+
if (config.eventTypes) {
|
|
2067
|
+
for (const type of config.eventTypes) {
|
|
2068
|
+
source.addEventListener(type, messageHandler);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
const cleanup = () => {
|
|
2072
|
+
this.disconnect(url);
|
|
2073
|
+
};
|
|
2074
|
+
this.destroyRef.onDestroy(cleanup);
|
|
2075
|
+
return cleanup;
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
disconnect(url) {
|
|
2079
|
+
const source = this.sources.get(url);
|
|
2080
|
+
if (source) {
|
|
2081
|
+
source.close();
|
|
2082
|
+
this.sources.delete(url);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
disconnectAll() {
|
|
2086
|
+
this.sources.forEach((source) => source.close());
|
|
2087
|
+
this.sources.clear();
|
|
2088
|
+
}
|
|
2089
|
+
getState(url) {
|
|
2090
|
+
const source = this.sources.get(url);
|
|
2091
|
+
if (!source)
|
|
2092
|
+
return 'closed';
|
|
2093
|
+
const states = ['connecting', 'open', 'closed'];
|
|
2094
|
+
return states[source.readyState] ?? 'closed';
|
|
2095
|
+
}
|
|
2096
|
+
getActiveConnections() {
|
|
2097
|
+
return Array.from(this.sources.keys());
|
|
2098
|
+
}
|
|
2099
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ServerSentEventsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2100
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ServerSentEventsService });
|
|
2101
|
+
}
|
|
2102
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ServerSentEventsService, decorators: [{
|
|
2103
|
+
type: Injectable
|
|
2104
|
+
}] });
|
|
2105
|
+
|
|
2106
|
+
class VibrationService {
|
|
2107
|
+
platformId = inject(PLATFORM_ID);
|
|
2108
|
+
presets = {
|
|
2109
|
+
success: [50, 30, 50],
|
|
2110
|
+
error: [100, 50, 100, 50, 100],
|
|
2111
|
+
notification: [200],
|
|
2112
|
+
doubleTap: [50, 100, 50],
|
|
2113
|
+
};
|
|
2114
|
+
isSupported() {
|
|
2115
|
+
return isPlatformBrowser(this.platformId) && 'vibrate' in navigator;
|
|
2116
|
+
}
|
|
2117
|
+
vibrate(pattern = 200) {
|
|
2118
|
+
if (!this.isSupported())
|
|
2119
|
+
return false;
|
|
2120
|
+
return navigator.vibrate(pattern);
|
|
2121
|
+
}
|
|
2122
|
+
success() {
|
|
2123
|
+
return this.vibrate(this.presets.success);
|
|
2124
|
+
}
|
|
2125
|
+
error() {
|
|
2126
|
+
return this.vibrate(this.presets.error);
|
|
2127
|
+
}
|
|
2128
|
+
notification() {
|
|
2129
|
+
return this.vibrate(this.presets.notification);
|
|
2130
|
+
}
|
|
2131
|
+
doubleTap() {
|
|
2132
|
+
return this.vibrate(this.presets.doubleTap);
|
|
2133
|
+
}
|
|
2134
|
+
stop() {
|
|
2135
|
+
return this.vibrate(0);
|
|
2136
|
+
}
|
|
2137
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: VibrationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2138
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: VibrationService });
|
|
2139
|
+
}
|
|
2140
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: VibrationService, decorators: [{
|
|
2141
|
+
type: Injectable
|
|
2142
|
+
}] });
|
|
2143
|
+
|
|
2144
|
+
class SpeechSynthesisService {
|
|
2145
|
+
destroyRef = inject(DestroyRef);
|
|
2146
|
+
platformId = inject(PLATFORM_ID);
|
|
2147
|
+
isSupported() {
|
|
2148
|
+
return isPlatformBrowser(this.platformId) && 'speechSynthesis' in window;
|
|
2149
|
+
}
|
|
2150
|
+
ensureSupport() {
|
|
2151
|
+
if (!this.isSupported()) {
|
|
2152
|
+
throw new Error('Speech Synthesis API not supported in this browser');
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
get state() {
|
|
2156
|
+
if (!this.isSupported())
|
|
2157
|
+
return 'idle';
|
|
2158
|
+
if (speechSynthesis.speaking && !speechSynthesis.paused)
|
|
2159
|
+
return 'speaking';
|
|
2160
|
+
if (speechSynthesis.paused)
|
|
2161
|
+
return 'paused';
|
|
2162
|
+
return 'idle';
|
|
2163
|
+
}
|
|
2164
|
+
get isPending() {
|
|
2165
|
+
return this.isSupported() && speechSynthesis.pending;
|
|
2166
|
+
}
|
|
2167
|
+
getVoices() {
|
|
2168
|
+
if (!this.isSupported())
|
|
2169
|
+
return [];
|
|
2170
|
+
return speechSynthesis.getVoices();
|
|
2171
|
+
}
|
|
2172
|
+
watchVoices() {
|
|
2173
|
+
return new Observable((observer) => {
|
|
2174
|
+
if (!this.isSupported()) {
|
|
2175
|
+
observer.next([]);
|
|
2176
|
+
observer.complete();
|
|
2177
|
+
return undefined;
|
|
2178
|
+
}
|
|
2179
|
+
const emit = () => observer.next(speechSynthesis.getVoices());
|
|
2180
|
+
speechSynthesis.addEventListener('voiceschanged', emit);
|
|
2181
|
+
emit();
|
|
2182
|
+
const cleanup = () => speechSynthesis.removeEventListener('voiceschanged', emit);
|
|
2183
|
+
this.destroyRef.onDestroy(cleanup);
|
|
2184
|
+
return cleanup;
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
speak(text, options = {}) {
|
|
2188
|
+
return new Observable((observer) => {
|
|
2189
|
+
this.ensureSupport();
|
|
2190
|
+
const utterance = new SpeechSynthesisUtterance(text);
|
|
2191
|
+
if (options.lang)
|
|
2192
|
+
utterance.lang = options.lang;
|
|
2193
|
+
if (options.voice)
|
|
2194
|
+
utterance.voice = options.voice;
|
|
2195
|
+
if (options.volume !== undefined)
|
|
2196
|
+
utterance.volume = options.volume;
|
|
2197
|
+
if (options.rate !== undefined)
|
|
2198
|
+
utterance.rate = options.rate;
|
|
2199
|
+
if (options.pitch !== undefined)
|
|
2200
|
+
utterance.pitch = options.pitch;
|
|
2201
|
+
utterance.onstart = () => observer.next('speaking');
|
|
2202
|
+
utterance.onpause = () => observer.next('paused');
|
|
2203
|
+
utterance.onresume = () => observer.next('speaking');
|
|
2204
|
+
utterance.onend = () => {
|
|
2205
|
+
observer.next('idle');
|
|
2206
|
+
observer.complete();
|
|
2207
|
+
};
|
|
2208
|
+
utterance.onerror = (event) => {
|
|
2209
|
+
observer.error(new Error(`Speech synthesis error: ${event.error}`));
|
|
2210
|
+
};
|
|
2211
|
+
observer.next('speaking');
|
|
2212
|
+
speechSynthesis.speak(utterance);
|
|
2213
|
+
const cleanup = () => {
|
|
2214
|
+
speechSynthesis.cancel();
|
|
2215
|
+
observer.next('idle');
|
|
2216
|
+
};
|
|
2217
|
+
this.destroyRef.onDestroy(cleanup);
|
|
2218
|
+
return cleanup;
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
pause() {
|
|
2222
|
+
if (this.isSupported())
|
|
2223
|
+
speechSynthesis.pause();
|
|
2224
|
+
}
|
|
2225
|
+
resume() {
|
|
2226
|
+
if (this.isSupported())
|
|
2227
|
+
speechSynthesis.resume();
|
|
2228
|
+
}
|
|
2229
|
+
cancel() {
|
|
2230
|
+
if (this.isSupported())
|
|
2231
|
+
speechSynthesis.cancel();
|
|
2232
|
+
}
|
|
2233
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SpeechSynthesisService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2234
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SpeechSynthesisService });
|
|
2235
|
+
}
|
|
2236
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SpeechSynthesisService, decorators: [{
|
|
2237
|
+
type: Injectable
|
|
2238
|
+
}] });
|
|
2239
|
+
|
|
1293
2240
|
// Common types for browser APIs
|
|
1294
2241
|
|
|
2242
|
+
function injectPageVisibility() {
|
|
2243
|
+
const destroyRef = inject(DestroyRef);
|
|
2244
|
+
const platformId = inject(PLATFORM_ID);
|
|
2245
|
+
const initial = isPlatformBrowser(platformId) && typeof document !== 'undefined'
|
|
2246
|
+
? document.visibilityState
|
|
2247
|
+
: 'visible';
|
|
2248
|
+
const state = signal(initial, ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
2249
|
+
const sub = pageVisibilityStream().subscribe((s) => state.set(s));
|
|
2250
|
+
destroyRef.onDestroy(() => sub.unsubscribe());
|
|
2251
|
+
return {
|
|
2252
|
+
state: state.asReadonly(),
|
|
2253
|
+
isVisible: computed(() => state() === 'visible'),
|
|
2254
|
+
isHidden: computed(() => state() !== 'visible'),
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
function injectResizeObserver(elementOrRef, options) {
|
|
2259
|
+
const destroyRef = inject(DestroyRef);
|
|
2260
|
+
const platformId = inject(PLATFORM_ID);
|
|
2261
|
+
const size = signal(null, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
2262
|
+
if (isPlatformBrowser(platformId) && isResizeObserverSupported()) {
|
|
2263
|
+
const el = elementOrRef instanceof ElementRef ? elementOrRef.nativeElement : elementOrRef;
|
|
2264
|
+
const sub = resizeObserverStream(el, options).subscribe((s) => size.set(s));
|
|
2265
|
+
destroyRef.onDestroy(() => sub.unsubscribe());
|
|
2266
|
+
}
|
|
2267
|
+
return {
|
|
2268
|
+
size: size.asReadonly(),
|
|
2269
|
+
width: computed(() => size()?.width ?? 0),
|
|
2270
|
+
height: computed(() => size()?.height ?? 0),
|
|
2271
|
+
inlineSize: computed(() => size()?.inlineSize ?? 0),
|
|
2272
|
+
blockSize: computed(() => size()?.blockSize ?? 0),
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
function injectIntersectionObserver(elementOrRef, options) {
|
|
2277
|
+
const destroyRef = inject(DestroyRef);
|
|
2278
|
+
const platformId = inject(PLATFORM_ID);
|
|
2279
|
+
const isIntersecting = signal(false, ...(ngDevMode ? [{ debugName: "isIntersecting" }] : /* istanbul ignore next */ []));
|
|
2280
|
+
if (isPlatformBrowser(platformId) && isIntersectionObserverSupported()) {
|
|
2281
|
+
const el = elementOrRef instanceof ElementRef ? elementOrRef.nativeElement : elementOrRef;
|
|
2282
|
+
const sub = intersectionObserverStream(el, options).subscribe((v) => isIntersecting.set(v));
|
|
2283
|
+
destroyRef.onDestroy(() => sub.unsubscribe());
|
|
2284
|
+
}
|
|
2285
|
+
return {
|
|
2286
|
+
isIntersecting: isIntersecting.asReadonly(),
|
|
2287
|
+
isVisible: computed(() => isIntersecting()),
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
function injectNetworkInformation() {
|
|
2292
|
+
const destroyRef = inject(DestroyRef);
|
|
2293
|
+
const platformId = inject(PLATFORM_ID);
|
|
2294
|
+
const snapshot = signal(isPlatformBrowser(platformId) ? getNetworkSnapshot() : { online: true }, ...(ngDevMode ? [{ debugName: "snapshot" }] : /* istanbul ignore next */ []));
|
|
2295
|
+
const sub = networkInformationStream().subscribe((n) => snapshot.set(n));
|
|
2296
|
+
destroyRef.onDestroy(() => sub.unsubscribe());
|
|
2297
|
+
return {
|
|
2298
|
+
snapshot: snapshot.asReadonly(),
|
|
2299
|
+
online: computed(() => snapshot().online),
|
|
2300
|
+
effectiveType: computed(() => snapshot().effectiveType),
|
|
2301
|
+
downlink: computed(() => snapshot().downlink),
|
|
2302
|
+
rtt: computed(() => snapshot().rtt),
|
|
2303
|
+
type: computed(() => snapshot().type),
|
|
2304
|
+
saveData: computed(() => snapshot().saveData),
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
function injectScreenOrientation() {
|
|
2309
|
+
const destroyRef = inject(DestroyRef);
|
|
2310
|
+
const platformId = inject(PLATFORM_ID);
|
|
2311
|
+
const orientation = signal(isPlatformBrowser(platformId)
|
|
2312
|
+
? getOrientationSnapshot()
|
|
2313
|
+
: { type: 'portrait-primary', angle: 0 }, ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
2314
|
+
const sub = screenOrientationStream().subscribe((o) => orientation.set(o));
|
|
2315
|
+
destroyRef.onDestroy(() => sub.unsubscribe());
|
|
2316
|
+
return {
|
|
2317
|
+
orientation: orientation.asReadonly(),
|
|
2318
|
+
type: computed(() => orientation().type),
|
|
2319
|
+
angle: computed(() => orientation().angle),
|
|
2320
|
+
isPortrait: computed(() => orientation().type.startsWith('portrait')),
|
|
2321
|
+
isLandscape: computed(() => orientation().type.startsWith('landscape')),
|
|
2322
|
+
async lock(o) {
|
|
2323
|
+
await screen.orientation.lock(o);
|
|
2324
|
+
},
|
|
2325
|
+
unlock() {
|
|
2326
|
+
screen.orientation.unlock();
|
|
2327
|
+
},
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
|
|
1295
2331
|
class BrowserSupportUtil {
|
|
1296
2332
|
static isSupported(feature) {
|
|
1297
2333
|
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
@@ -1417,6 +2453,19 @@ const defaultBrowserWebApisConfig = {
|
|
|
1417
2453
|
enableWebStorage: false,
|
|
1418
2454
|
enableWebSocket: false,
|
|
1419
2455
|
enableWebWorker: false,
|
|
2456
|
+
enableIntersectionObserver: false,
|
|
2457
|
+
enableResizeObserver: false,
|
|
2458
|
+
enablePageVisibility: false,
|
|
2459
|
+
enableBroadcastChannel: false,
|
|
2460
|
+
enableNetworkInformation: false,
|
|
2461
|
+
enableScreenWakeLock: false,
|
|
2462
|
+
enableScreenOrientation: false,
|
|
2463
|
+
enableFullscreen: false,
|
|
2464
|
+
enableFileSystemAccess: false,
|
|
2465
|
+
enableMediaRecorder: false,
|
|
2466
|
+
enableServerSentEvents: false,
|
|
2467
|
+
enableVibration: false,
|
|
2468
|
+
enableSpeechSynthesis: false,
|
|
1420
2469
|
};
|
|
1421
2470
|
function provideBrowserWebApis(config = {}) {
|
|
1422
2471
|
const mergedConfig = { ...defaultBrowserWebApisConfig, ...config };
|
|
@@ -1432,6 +2481,19 @@ function provideBrowserWebApis(config = {}) {
|
|
|
1432
2481
|
[mergedConfig.enableWebStorage, WebStorageService],
|
|
1433
2482
|
[mergedConfig.enableWebSocket, WebSocketService],
|
|
1434
2483
|
[mergedConfig.enableWebWorker, WebWorkerService],
|
|
2484
|
+
[mergedConfig.enableIntersectionObserver, IntersectionObserverService],
|
|
2485
|
+
[mergedConfig.enableResizeObserver, ResizeObserverService],
|
|
2486
|
+
[mergedConfig.enablePageVisibility, PageVisibilityService],
|
|
2487
|
+
[mergedConfig.enableBroadcastChannel, BroadcastChannelService],
|
|
2488
|
+
[mergedConfig.enableNetworkInformation, NetworkInformationService],
|
|
2489
|
+
[mergedConfig.enableScreenWakeLock, ScreenWakeLockService],
|
|
2490
|
+
[mergedConfig.enableScreenOrientation, ScreenOrientationService],
|
|
2491
|
+
[mergedConfig.enableFullscreen, FullscreenService],
|
|
2492
|
+
[mergedConfig.enableFileSystemAccess, FileSystemAccessService],
|
|
2493
|
+
[mergedConfig.enableMediaRecorder, MediaRecorderService],
|
|
2494
|
+
[mergedConfig.enableServerSentEvents, ServerSentEventsService],
|
|
2495
|
+
[mergedConfig.enableVibration, VibrationService],
|
|
2496
|
+
[mergedConfig.enableSpeechSynthesis, SpeechSynthesisService],
|
|
1435
2497
|
];
|
|
1436
2498
|
for (const [enabled, provider] of conditionalProviders) {
|
|
1437
2499
|
if (enabled) {
|
|
@@ -1492,6 +2554,45 @@ function provideCommunicationApis() {
|
|
|
1492
2554
|
WebSocketService,
|
|
1493
2555
|
]);
|
|
1494
2556
|
}
|
|
2557
|
+
function provideIntersectionObserver() {
|
|
2558
|
+
return makeEnvironmentProviders([IntersectionObserverService]);
|
|
2559
|
+
}
|
|
2560
|
+
function provideResizeObserver() {
|
|
2561
|
+
return makeEnvironmentProviders([ResizeObserverService]);
|
|
2562
|
+
}
|
|
2563
|
+
function providePageVisibility() {
|
|
2564
|
+
return makeEnvironmentProviders([PageVisibilityService]);
|
|
2565
|
+
}
|
|
2566
|
+
function provideBroadcastChannel() {
|
|
2567
|
+
return makeEnvironmentProviders([BroadcastChannelService]);
|
|
2568
|
+
}
|
|
2569
|
+
function provideNetworkInformation() {
|
|
2570
|
+
return makeEnvironmentProviders([NetworkInformationService]);
|
|
2571
|
+
}
|
|
2572
|
+
function provideScreenWakeLock() {
|
|
2573
|
+
return makeEnvironmentProviders([PermissionsService, ScreenWakeLockService]);
|
|
2574
|
+
}
|
|
2575
|
+
function provideScreenOrientation() {
|
|
2576
|
+
return makeEnvironmentProviders([ScreenOrientationService]);
|
|
2577
|
+
}
|
|
2578
|
+
function provideFullscreen() {
|
|
2579
|
+
return makeEnvironmentProviders([FullscreenService]);
|
|
2580
|
+
}
|
|
2581
|
+
function provideFileSystemAccess() {
|
|
2582
|
+
return makeEnvironmentProviders([PermissionsService, FileSystemAccessService]);
|
|
2583
|
+
}
|
|
2584
|
+
function provideMediaRecorder() {
|
|
2585
|
+
return makeEnvironmentProviders([PermissionsService, MediaRecorderService]);
|
|
2586
|
+
}
|
|
2587
|
+
function provideServerSentEvents() {
|
|
2588
|
+
return makeEnvironmentProviders([ServerSentEventsService]);
|
|
2589
|
+
}
|
|
2590
|
+
function provideVibration() {
|
|
2591
|
+
return makeEnvironmentProviders([VibrationService]);
|
|
2592
|
+
}
|
|
2593
|
+
function provideSpeechSynthesis() {
|
|
2594
|
+
return makeEnvironmentProviders([SpeechSynthesisService]);
|
|
2595
|
+
}
|
|
1495
2596
|
|
|
1496
2597
|
// Browser Web APIs Services
|
|
1497
2598
|
// Version
|
|
@@ -1501,4 +2602,4 @@ const version = '0.1.0';
|
|
|
1501
2602
|
* Generated bundle index. Do not edit.
|
|
1502
2603
|
*/
|
|
1503
2604
|
|
|
1504
|
-
export { BatteryService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, GeolocationService, MediaDevicesService, NotificationService, PermissionsService, WebShareService, WebSocketService, WebStorageService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, permissionGuard, provideBattery, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideGeolocation, provideLocationApis, provideMediaApis, provideMediaDevices, provideNotifications, providePermissions, provideStorageApis, provideWebShare, provideWebSocket, provideWebStorage, provideWebWorker, version };
|
|
2605
|
+
export { BatteryService, BroadcastChannelService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, FileSystemAccessService, FullscreenService, GeolocationService, IntersectionObserverService, MediaDevicesService, MediaRecorderService, NetworkInformationService, NotificationService, PageVisibilityService, PermissionsService, ResizeObserverService, ScreenOrientationService, ScreenWakeLockService, ServerSentEventsService, SpeechSynthesisService, VibrationService, WebShareService, WebSocketService, WebStorageService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, injectIntersectionObserver, injectNetworkInformation, injectPageVisibility, injectResizeObserver, injectScreenOrientation, permissionGuard, provideBattery, provideBroadcastChannel, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideFileSystemAccess, provideFullscreen, provideGeolocation, provideIntersectionObserver, provideLocationApis, provideMediaApis, provideMediaDevices, provideMediaRecorder, provideNetworkInformation, provideNotifications, providePageVisibility, providePermissions, provideResizeObserver, provideScreenOrientation, provideScreenWakeLock, provideServerSentEvents, provideSpeechSynthesis, provideStorageApis, provideVibration, provideWebShare, provideWebSocket, provideWebStorage, provideWebWorker, version };
|