@angular-helpers/browser-web-apis 21.0.2 → 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.
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, inject, DestroyRef, PLATFORM_ID } 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 } from 'rxjs';
|
|
4
|
+
import { Observable, fromEvent, Subject, of, map as map$1 } from 'rxjs';
|
|
5
|
+
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
|
6
|
+
import { filter, distinctUntilChanged, map } from 'rxjs/operators';
|
|
7
|
+
import { Router } from '@angular/router';
|
|
5
8
|
|
|
6
9
|
class PermissionsService {
|
|
7
10
|
async query(descriptor) {
|
|
@@ -67,6 +70,16 @@ class BrowserApiBaseService {
|
|
|
67
70
|
return false;
|
|
68
71
|
}
|
|
69
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Create an error with proper cause chaining
|
|
75
|
+
*/
|
|
76
|
+
createError(message, cause) {
|
|
77
|
+
const error = new Error(message);
|
|
78
|
+
if (cause !== undefined) {
|
|
79
|
+
error.cause = cause;
|
|
80
|
+
}
|
|
81
|
+
return error;
|
|
82
|
+
}
|
|
70
83
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserApiBaseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
71
84
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserApiBaseService });
|
|
72
85
|
}
|
|
@@ -76,11 +89,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
76
89
|
|
|
77
90
|
class CameraService extends BrowserApiBaseService {
|
|
78
91
|
currentStream = null;
|
|
79
|
-
createError(message, cause) {
|
|
80
|
-
const error = new Error(message);
|
|
81
|
-
error.cause = cause;
|
|
82
|
-
return error;
|
|
83
|
-
}
|
|
84
92
|
getApiName() {
|
|
85
93
|
return 'camera';
|
|
86
94
|
}
|
|
@@ -103,8 +111,8 @@ class CameraService extends BrowserApiBaseService {
|
|
|
103
111
|
video: {
|
|
104
112
|
width: { ideal: 1280 },
|
|
105
113
|
height: { ideal: 720 },
|
|
106
|
-
facingMode: 'user'
|
|
107
|
-
}
|
|
114
|
+
facingMode: 'user',
|
|
115
|
+
},
|
|
108
116
|
};
|
|
109
117
|
this.currentStream = await navigator.mediaDevices.getUserMedia(streamConstraints);
|
|
110
118
|
return this.currentStream;
|
|
@@ -140,14 +148,14 @@ class CameraService extends BrowserApiBaseService {
|
|
|
140
148
|
async switchCamera(deviceId, constraints = {
|
|
141
149
|
video: {
|
|
142
150
|
width: { ideal: 1280 },
|
|
143
|
-
height: { ideal: 720 }
|
|
144
|
-
}
|
|
151
|
+
height: { ideal: 720 },
|
|
152
|
+
},
|
|
145
153
|
}) {
|
|
146
154
|
this.stopCamera();
|
|
147
155
|
const finalConstraints = {
|
|
148
156
|
video: constraints.video && typeof constraints.video === 'object'
|
|
149
157
|
? { ...constraints.video, deviceId: { exact: deviceId } }
|
|
150
|
-
: { deviceId: { exact: deviceId } }
|
|
158
|
+
: { deviceId: { exact: deviceId } },
|
|
151
159
|
};
|
|
152
160
|
return this.startCamera(finalConstraints);
|
|
153
161
|
}
|
|
@@ -155,7 +163,7 @@ class CameraService extends BrowserApiBaseService {
|
|
|
155
163
|
this.ensureCameraSupport();
|
|
156
164
|
try {
|
|
157
165
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
158
|
-
video: { deviceId: { exact: deviceId } }
|
|
166
|
+
video: { deviceId: { exact: deviceId } },
|
|
159
167
|
});
|
|
160
168
|
const videoTrack = stream.getVideoTracks()[0];
|
|
161
169
|
const capabilities = videoTrack.getCapabilities();
|
|
@@ -178,7 +186,7 @@ class CameraService extends BrowserApiBaseService {
|
|
|
178
186
|
this.ensureCameraSupport();
|
|
179
187
|
try {
|
|
180
188
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
181
|
-
return devices.filter(device => device.kind === 'videoinput');
|
|
189
|
+
return devices.filter((device) => device.kind === 'videoinput');
|
|
182
190
|
}
|
|
183
191
|
catch (error) {
|
|
184
192
|
console.error('[CameraService] Error enumerating video devices:', error);
|
|
@@ -243,11 +251,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
243
251
|
}] });
|
|
244
252
|
|
|
245
253
|
class MediaDevicesService extends BrowserApiBaseService {
|
|
246
|
-
createError(message, cause) {
|
|
247
|
-
const error = new Error(message);
|
|
248
|
-
error.cause = cause;
|
|
249
|
-
return error;
|
|
250
|
-
}
|
|
251
254
|
getApiName() {
|
|
252
255
|
return 'media-devices';
|
|
253
256
|
}
|
|
@@ -272,7 +275,7 @@ class MediaDevicesService extends BrowserApiBaseService {
|
|
|
272
275
|
try {
|
|
273
276
|
const defaultConstraints = {
|
|
274
277
|
video: true,
|
|
275
|
-
audio: true
|
|
278
|
+
audio: true,
|
|
276
279
|
};
|
|
277
280
|
const finalConstraints = constraints || defaultConstraints;
|
|
278
281
|
return await navigator.mediaDevices.getUserMedia(finalConstraints);
|
|
@@ -290,7 +293,7 @@ class MediaDevicesService extends BrowserApiBaseService {
|
|
|
290
293
|
try {
|
|
291
294
|
const defaultConstraints = {
|
|
292
295
|
video: true,
|
|
293
|
-
audio: false
|
|
296
|
+
audio: false,
|
|
294
297
|
};
|
|
295
298
|
const finalConstraints = constraints || defaultConstraints;
|
|
296
299
|
return await navigator.mediaDevices.getDisplayMedia(finalConstraints);
|
|
@@ -332,7 +335,7 @@ class MediaDevicesService extends BrowserApiBaseService {
|
|
|
332
335
|
}
|
|
333
336
|
async getDevicesByKind(kind) {
|
|
334
337
|
const devices = await this.getDevices();
|
|
335
|
-
return devices.filter(device => device.kind === kind);
|
|
338
|
+
return devices.filter((device) => device.kind === kind);
|
|
336
339
|
}
|
|
337
340
|
handleMediaError(error) {
|
|
338
341
|
let message;
|
|
@@ -378,12 +381,23 @@ class NotificationService extends BrowserApiBaseService {
|
|
|
378
381
|
getApiName() {
|
|
379
382
|
return 'notifications';
|
|
380
383
|
}
|
|
384
|
+
get permission() {
|
|
385
|
+
return Notification.permission;
|
|
386
|
+
}
|
|
387
|
+
isSupported() {
|
|
388
|
+
return 'Notification' in window;
|
|
389
|
+
}
|
|
390
|
+
async requestNotificationPermission() {
|
|
391
|
+
if (!this.isSupported()) {
|
|
392
|
+
throw new Error('Notification API not supported in this browser');
|
|
393
|
+
}
|
|
394
|
+
return Notification.requestPermission();
|
|
395
|
+
}
|
|
381
396
|
async showNotification(title, options) {
|
|
382
|
-
if (!(
|
|
397
|
+
if (!this.isSupported()) {
|
|
383
398
|
throw new Error('Notification API not supported in this browser');
|
|
384
399
|
}
|
|
385
|
-
|
|
386
|
-
if (permissionStatus.state !== 'granted') {
|
|
400
|
+
if (Notification.permission !== 'granted') {
|
|
387
401
|
throw new Error('Notification permission required. Please grant notification access and try again.');
|
|
388
402
|
}
|
|
389
403
|
try {
|
|
@@ -410,7 +424,7 @@ class ClipboardService extends BrowserApiBaseService {
|
|
|
410
424
|
throw new Error('Clipboard API not supported in this browser');
|
|
411
425
|
}
|
|
412
426
|
const permissionStatus = await this.permissionsService.query({
|
|
413
|
-
name: `clipboard-${action}
|
|
427
|
+
name: `clipboard-${action}`,
|
|
414
428
|
});
|
|
415
429
|
if (permissionStatus.state !== 'granted') {
|
|
416
430
|
throw new Error(`Clipboard ${action} permission required. Please grant clipboard access and try again.`);
|
|
@@ -465,7 +479,20 @@ const BROWSER_CAPABILITIES = [
|
|
|
465
479
|
{ id: 'webStorage', label: 'Web Storage', requiresSecureContext: false },
|
|
466
480
|
{ id: 'webShare', label: 'Web Share', requiresSecureContext: true },
|
|
467
481
|
{ id: 'battery', label: 'Battery API', requiresSecureContext: false },
|
|
468
|
-
{ id: 'webSocket', label: 'WebSocket API', requiresSecureContext: false }
|
|
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 },
|
|
469
496
|
];
|
|
470
497
|
class BrowserCapabilityService {
|
|
471
498
|
getCapabilities() {
|
|
@@ -498,6 +525,34 @@ class BrowserCapabilityService {
|
|
|
498
525
|
return typeof navigator !== 'undefined' && 'getBattery' in navigator;
|
|
499
526
|
case 'webSocket':
|
|
500
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;
|
|
501
556
|
default:
|
|
502
557
|
return false;
|
|
503
558
|
}
|
|
@@ -509,7 +564,7 @@ class BrowserCapabilityService {
|
|
|
509
564
|
label: capability.label,
|
|
510
565
|
supported: this.isSupported(capability.id),
|
|
511
566
|
secureContext,
|
|
512
|
-
requiresSecureContext: capability.requiresSecureContext
|
|
567
|
+
requiresSecureContext: capability.requiresSecureContext,
|
|
513
568
|
}));
|
|
514
569
|
}
|
|
515
570
|
async getPermissionState(permission) {
|
|
@@ -532,128 +587,2011 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
532
587
|
args: [{ providedIn: 'root' }]
|
|
533
588
|
}] });
|
|
534
589
|
|
|
535
|
-
class
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
590
|
+
class BatteryService extends BrowserApiBaseService {
|
|
591
|
+
batteryManager = null;
|
|
592
|
+
getApiName() {
|
|
593
|
+
return 'battery';
|
|
594
|
+
}
|
|
595
|
+
ensureBatterySupport() {
|
|
596
|
+
const nav = navigator;
|
|
597
|
+
if (!('getBattery' in nav)) {
|
|
598
|
+
throw new Error('Battery API not supported in this browser');
|
|
539
599
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
case 'clipboard-read':
|
|
554
|
-
return 'clipboard' in navigator && 'readText' in navigator.clipboard;
|
|
555
|
-
case 'clipboard-write':
|
|
556
|
-
return 'clipboard' in navigator && 'writeText' in navigator.clipboard;
|
|
557
|
-
case 'persistent-storage':
|
|
558
|
-
return 'storage' in navigator && 'persist' in navigator.storage;
|
|
559
|
-
default:
|
|
560
|
-
return false;
|
|
600
|
+
}
|
|
601
|
+
async initialize() {
|
|
602
|
+
this.ensureBatterySupport();
|
|
603
|
+
try {
|
|
604
|
+
const nav = navigator;
|
|
605
|
+
this.batteryManager = await nav.getBattery();
|
|
606
|
+
const batteryInfo = this.getBatteryInfo();
|
|
607
|
+
this.setupEventListeners();
|
|
608
|
+
return batteryInfo;
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
console.error('[BatteryService] Error initializing battery API:', error);
|
|
612
|
+
throw this.createError('Failed to initialize battery API', error);
|
|
561
613
|
}
|
|
562
614
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
'
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
'persistent-storage'
|
|
574
|
-
];
|
|
575
|
-
return features.filter(feature => !this.isSupported(feature));
|
|
615
|
+
getBatteryInfo() {
|
|
616
|
+
if (!this.batteryManager) {
|
|
617
|
+
throw new Error('Battery service not initialized. Call initialize() first.');
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
charging: this.batteryManager.charging,
|
|
621
|
+
chargingTime: this.batteryManager.chargingTime,
|
|
622
|
+
dischargingTime: this.batteryManager.dischargingTime,
|
|
623
|
+
level: this.batteryManager.level,
|
|
624
|
+
};
|
|
576
625
|
}
|
|
577
|
-
|
|
578
|
-
|
|
626
|
+
watchBatteryInfo() {
|
|
627
|
+
if (!this.batteryManager) {
|
|
628
|
+
throw new Error('Battery service not initialized. Call initialize() first.');
|
|
629
|
+
}
|
|
630
|
+
return new Observable((observer) => {
|
|
631
|
+
const updateBatteryInfo = () => {
|
|
632
|
+
observer.next(this.getBatteryInfo());
|
|
633
|
+
};
|
|
634
|
+
// Listen to all battery events
|
|
635
|
+
this.batteryManager.addEventListener('chargingchange', updateBatteryInfo);
|
|
636
|
+
this.batteryManager.addEventListener('levelchange', updateBatteryInfo);
|
|
637
|
+
this.batteryManager.addEventListener('chargingtimechange', updateBatteryInfo);
|
|
638
|
+
this.batteryManager.addEventListener('dischargingtimechange', updateBatteryInfo);
|
|
639
|
+
// Send initial value
|
|
640
|
+
updateBatteryInfo();
|
|
641
|
+
return () => {
|
|
642
|
+
// Cleanup event listeners
|
|
643
|
+
this.batteryManager.removeEventListener('chargingchange', updateBatteryInfo);
|
|
644
|
+
this.batteryManager.removeEventListener('levelchange', updateBatteryInfo);
|
|
645
|
+
this.batteryManager.removeEventListener('chargingtimechange', updateBatteryInfo);
|
|
646
|
+
this.batteryManager.removeEventListener('dischargingtimechange', updateBatteryInfo);
|
|
647
|
+
};
|
|
648
|
+
});
|
|
579
649
|
}
|
|
580
|
-
|
|
581
|
-
|
|
650
|
+
setupEventListeners() {
|
|
651
|
+
if (!this.batteryManager)
|
|
652
|
+
return;
|
|
653
|
+
this.batteryManager.addEventListener('chargingchange', () => {
|
|
654
|
+
console.log('[BatteryService] Charging status changed:', this.batteryManager.charging);
|
|
655
|
+
});
|
|
656
|
+
this.batteryManager.addEventListener('levelchange', () => {
|
|
657
|
+
console.log('[BatteryService] Battery level changed:', this.batteryManager.level);
|
|
658
|
+
});
|
|
582
659
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
660
|
+
// Direct access to native battery API
|
|
661
|
+
getNativeBatteryManager() {
|
|
662
|
+
if (!this.batteryManager) {
|
|
663
|
+
throw new Error('Battery service not initialized. Call initialize() first.');
|
|
664
|
+
}
|
|
665
|
+
return this.batteryManager;
|
|
586
666
|
}
|
|
587
|
-
|
|
588
|
-
return
|
|
667
|
+
isCharging() {
|
|
668
|
+
return this.getBatteryInfo().charging;
|
|
589
669
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
return {
|
|
593
|
-
name: this.getBrowserName(userAgent),
|
|
594
|
-
version: this.getBrowserVersion(userAgent),
|
|
595
|
-
isChrome: /chrome/.test(userAgent) && !/edge/.test(userAgent),
|
|
596
|
-
isFirefox: /firefox/.test(userAgent),
|
|
597
|
-
isSafari: /safari/.test(userAgent) && !/chrome/.test(userAgent),
|
|
598
|
-
isEdge: /edge/.test(userAgent) || /edg/.test(userAgent)
|
|
599
|
-
};
|
|
670
|
+
getLevel() {
|
|
671
|
+
return this.getBatteryInfo().level;
|
|
600
672
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
return 'Chrome';
|
|
604
|
-
if (/firefox/.test(userAgent))
|
|
605
|
-
return 'Firefox';
|
|
606
|
-
if (/safari/.test(userAgent) && !/chrome/.test(userAgent))
|
|
607
|
-
return 'Safari';
|
|
608
|
-
if (/edge/.test(userAgent) || /edg/.test(userAgent))
|
|
609
|
-
return 'Edge';
|
|
610
|
-
return 'Unknown';
|
|
673
|
+
getChargingTime() {
|
|
674
|
+
return this.getBatteryInfo().chargingTime;
|
|
611
675
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
return match ? match[2] : 'Unknown';
|
|
676
|
+
getDischargingTime() {
|
|
677
|
+
return this.getBatteryInfo().dischargingTime;
|
|
615
678
|
}
|
|
679
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BatteryService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
680
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BatteryService });
|
|
616
681
|
}
|
|
682
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BatteryService, decorators: [{
|
|
683
|
+
type: Injectable
|
|
684
|
+
}] });
|
|
617
685
|
|
|
618
|
-
class
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
constructor(permissionsService, router) {
|
|
622
|
-
this.permissionsService = permissionsService;
|
|
623
|
-
this.router = router;
|
|
686
|
+
class WebShareService extends BrowserApiBaseService {
|
|
687
|
+
getApiName() {
|
|
688
|
+
return 'web-share';
|
|
624
689
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
return Promise.resolve(true);
|
|
690
|
+
ensureWebShareSupport() {
|
|
691
|
+
if (!('share' in navigator)) {
|
|
692
|
+
throw new Error('Web Share API not supported in this browser');
|
|
629
693
|
}
|
|
630
|
-
return this.checkPermission(permission);
|
|
631
694
|
}
|
|
632
|
-
async
|
|
695
|
+
async share(data) {
|
|
696
|
+
this.ensureWebShareSupport();
|
|
633
697
|
try {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
698
|
+
await navigator.share(data);
|
|
699
|
+
return { shared: true };
|
|
700
|
+
}
|
|
701
|
+
catch (error) {
|
|
702
|
+
console.error('[WebShareService] Error sharing:', error);
|
|
703
|
+
const errorMessage = error instanceof Error ? error.message : 'Share failed';
|
|
704
|
+
return { shared: false, error: errorMessage };
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
canShare() {
|
|
708
|
+
return 'share' in navigator;
|
|
709
|
+
}
|
|
710
|
+
canShareFiles() {
|
|
711
|
+
if (!('share' in navigator))
|
|
712
|
+
return false;
|
|
713
|
+
// Check if the browser supports file sharing
|
|
714
|
+
const testFiles = [new File([''], 'test.txt', { type: 'text/plain' })];
|
|
715
|
+
return navigator.canShare?.({ files: testFiles }) ?? false;
|
|
716
|
+
}
|
|
717
|
+
// Direct access to native share API
|
|
718
|
+
getNativeShare() {
|
|
719
|
+
this.ensureWebShareSupport();
|
|
720
|
+
return navigator.share;
|
|
721
|
+
}
|
|
722
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebShareService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
723
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebShareService });
|
|
724
|
+
}
|
|
725
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebShareService, decorators: [{
|
|
726
|
+
type: Injectable
|
|
727
|
+
}] });
|
|
728
|
+
|
|
729
|
+
class WebStorageService extends BrowserApiBaseService {
|
|
730
|
+
storageEvents = signal(null, ...(ngDevMode ? [{ debugName: "storageEvents" }] : /* istanbul ignore next */ []));
|
|
731
|
+
destroyRef = inject(DestroyRef);
|
|
732
|
+
constructor() {
|
|
733
|
+
super();
|
|
734
|
+
this.setupEventListeners();
|
|
735
|
+
}
|
|
736
|
+
getApiName() {
|
|
737
|
+
return 'storage';
|
|
738
|
+
}
|
|
739
|
+
ensureStorageSupport() {
|
|
740
|
+
if (!this.isBrowserEnvironment() || typeof Storage === 'undefined') {
|
|
741
|
+
throw new Error('Storage API not supported in this browser');
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
setupEventListeners() {
|
|
745
|
+
if (this.isBrowserEnvironment()) {
|
|
746
|
+
fromEvent(window, 'storage')
|
|
747
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
748
|
+
.subscribe((event) => {
|
|
749
|
+
const storageEvent = event;
|
|
750
|
+
if (storageEvent.key && storageEvent.newValue !== null) {
|
|
751
|
+
this.storageEvents.set({
|
|
752
|
+
key: storageEvent.key,
|
|
753
|
+
newValue: this.deserializeValue(storageEvent.newValue),
|
|
754
|
+
oldValue: storageEvent.oldValue ? this.deserializeValue(storageEvent.oldValue) : null,
|
|
755
|
+
storageArea: storageEvent.storageArea === localStorage ? 'localStorage' : 'sessionStorage',
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
serializeValue(value, options) {
|
|
762
|
+
if (options?.serialize) {
|
|
763
|
+
return options.serialize(value);
|
|
764
|
+
}
|
|
765
|
+
return JSON.stringify(value);
|
|
766
|
+
}
|
|
767
|
+
deserializeValue(value, options) {
|
|
768
|
+
if (value === null)
|
|
769
|
+
return null;
|
|
770
|
+
if (options?.deserialize) {
|
|
771
|
+
return options.deserialize(value);
|
|
772
|
+
}
|
|
773
|
+
try {
|
|
774
|
+
return JSON.parse(value);
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
return value;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
getKey(key, options) {
|
|
781
|
+
const prefix = options?.prefix || '';
|
|
782
|
+
return prefix ? `${prefix}:${key}` : key;
|
|
783
|
+
}
|
|
784
|
+
// Local Storage Methods
|
|
785
|
+
setLocalStorage(key, value, options = {}) {
|
|
786
|
+
this.ensureStorageSupport();
|
|
787
|
+
try {
|
|
788
|
+
const serializedValue = this.serializeValue(value, options);
|
|
789
|
+
const fullKey = this.getKey(key, options);
|
|
790
|
+
localStorage.setItem(fullKey, serializedValue);
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
console.error('[WebStorageService] Error setting localStorage:', error);
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
getLocalStorage(key, defaultValue = null, options = {}) {
|
|
799
|
+
this.ensureStorageSupport();
|
|
800
|
+
try {
|
|
801
|
+
const fullKey = this.getKey(key, options);
|
|
802
|
+
const value = localStorage.getItem(fullKey);
|
|
803
|
+
return value !== null ? this.deserializeValue(value, options) : defaultValue;
|
|
804
|
+
}
|
|
805
|
+
catch (error) {
|
|
806
|
+
console.error('[WebStorageService] Error getting localStorage:', error);
|
|
807
|
+
return defaultValue;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
removeLocalStorage(key, options = {}) {
|
|
811
|
+
this.ensureStorageSupport();
|
|
812
|
+
try {
|
|
813
|
+
const fullKey = this.getKey(key, options);
|
|
814
|
+
localStorage.removeItem(fullKey);
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
catch (error) {
|
|
818
|
+
console.error('[WebStorageService] Error removing localStorage:', error);
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
clearLocalStorage(options = {}) {
|
|
823
|
+
this.ensureStorageSupport();
|
|
824
|
+
try {
|
|
825
|
+
const prefix = options?.prefix;
|
|
826
|
+
if (prefix) {
|
|
827
|
+
// Only remove keys with the specified prefix
|
|
828
|
+
const keysToRemove = [];
|
|
829
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
830
|
+
const key = localStorage.key(i);
|
|
831
|
+
if (key && key.startsWith(`${prefix}:`)) {
|
|
832
|
+
keysToRemove.push(key);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
localStorage.clear();
|
|
640
839
|
}
|
|
641
840
|
return true;
|
|
642
841
|
}
|
|
643
842
|
catch (error) {
|
|
644
|
-
console.error('
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
843
|
+
console.error('[WebStorageService] Error clearing localStorage:', error);
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
// Session Storage Methods
|
|
848
|
+
setSessionStorage(key, value, options = {}) {
|
|
849
|
+
this.ensureStorageSupport();
|
|
850
|
+
try {
|
|
851
|
+
const serializedValue = this.serializeValue(value, options);
|
|
852
|
+
const fullKey = this.getKey(key, options);
|
|
853
|
+
sessionStorage.setItem(fullKey, serializedValue);
|
|
854
|
+
return true;
|
|
855
|
+
}
|
|
856
|
+
catch (error) {
|
|
857
|
+
console.error('[WebStorageService] Error setting sessionStorage:', error);
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
getSessionStorage(key, defaultValue = null, options = {}) {
|
|
862
|
+
this.ensureStorageSupport();
|
|
863
|
+
try {
|
|
864
|
+
const fullKey = this.getKey(key, options);
|
|
865
|
+
const value = sessionStorage.getItem(fullKey);
|
|
866
|
+
return value !== null ? this.deserializeValue(value, options) : defaultValue;
|
|
867
|
+
}
|
|
868
|
+
catch (error) {
|
|
869
|
+
console.error('[WebStorageService] Error getting sessionStorage:', error);
|
|
870
|
+
return defaultValue;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
removeSessionStorage(key, options = {}) {
|
|
874
|
+
this.ensureStorageSupport();
|
|
875
|
+
try {
|
|
876
|
+
const fullKey = this.getKey(key, options);
|
|
877
|
+
sessionStorage.removeItem(fullKey);
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
catch (error) {
|
|
881
|
+
console.error('[WebStorageService] Error removing sessionStorage:', error);
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
clearSessionStorage(options = {}) {
|
|
886
|
+
this.ensureStorageSupport();
|
|
887
|
+
try {
|
|
888
|
+
const prefix = options?.prefix;
|
|
889
|
+
if (prefix) {
|
|
890
|
+
// Only remove keys with the specified prefix
|
|
891
|
+
const keysToRemove = [];
|
|
892
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
893
|
+
const key = sessionStorage.key(i);
|
|
894
|
+
if (key && key.startsWith(`${prefix}:`)) {
|
|
895
|
+
keysToRemove.push(key);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
keysToRemove.forEach((key) => sessionStorage.removeItem(key));
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
sessionStorage.clear();
|
|
902
|
+
}
|
|
903
|
+
return true;
|
|
904
|
+
}
|
|
905
|
+
catch (error) {
|
|
906
|
+
console.error('[WebStorageService] Error clearing sessionStorage:', error);
|
|
648
907
|
return false;
|
|
649
908
|
}
|
|
650
909
|
}
|
|
910
|
+
// Utility Methods
|
|
911
|
+
getLocalStorageSize(options = {}) {
|
|
912
|
+
this.ensureStorageSupport();
|
|
913
|
+
let totalSize = 0;
|
|
914
|
+
const prefix = options?.prefix;
|
|
915
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
916
|
+
const key = localStorage.key(i);
|
|
917
|
+
if (key && (!prefix || key.startsWith(`${prefix}:`))) {
|
|
918
|
+
totalSize += (localStorage.getItem(key)?.length || 0) + key.length;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return totalSize;
|
|
922
|
+
}
|
|
923
|
+
getSessionStorageSize(options = {}) {
|
|
924
|
+
this.ensureStorageSupport();
|
|
925
|
+
let totalSize = 0;
|
|
926
|
+
const prefix = options?.prefix;
|
|
927
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
928
|
+
const key = sessionStorage.key(i);
|
|
929
|
+
if (key && (!prefix || key.startsWith(`${prefix}:`))) {
|
|
930
|
+
totalSize += (sessionStorage.getItem(key)?.length || 0) + key.length;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return totalSize;
|
|
934
|
+
}
|
|
935
|
+
getStorageEvents() {
|
|
936
|
+
return toObservable(this.storageEvents).pipe(filter((event) => event !== null), distinctUntilChanged((prev, curr) => prev.key === curr.key &&
|
|
937
|
+
prev.newValue === curr.newValue &&
|
|
938
|
+
prev.oldValue === curr.oldValue));
|
|
939
|
+
}
|
|
940
|
+
watchLocalStorage(key, options = {}) {
|
|
941
|
+
return this.getStorageEvents().pipe(map((event) => {
|
|
942
|
+
const fullKey = this.getKey(key, options);
|
|
943
|
+
if (event.key === fullKey && event.storageArea === 'localStorage') {
|
|
944
|
+
return event.newValue;
|
|
945
|
+
}
|
|
946
|
+
return this.getLocalStorage(key, null, options);
|
|
947
|
+
}));
|
|
948
|
+
}
|
|
949
|
+
watchSessionStorage(key, options = {}) {
|
|
950
|
+
return this.getStorageEvents().pipe(map((event) => {
|
|
951
|
+
const fullKey = this.getKey(key, options);
|
|
952
|
+
if (event.key === fullKey && event.storageArea === 'sessionStorage') {
|
|
953
|
+
return event.newValue;
|
|
954
|
+
}
|
|
955
|
+
return this.getSessionStorage(key, null, options);
|
|
956
|
+
}));
|
|
957
|
+
}
|
|
958
|
+
// Direct access to native storage APIs
|
|
959
|
+
getNativeLocalStorage() {
|
|
960
|
+
this.ensureStorageSupport();
|
|
961
|
+
return localStorage;
|
|
962
|
+
}
|
|
963
|
+
getNativeSessionStorage() {
|
|
964
|
+
this.ensureStorageSupport();
|
|
965
|
+
return sessionStorage;
|
|
966
|
+
}
|
|
967
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
968
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebStorageService });
|
|
651
969
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
970
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebStorageService, decorators: [{
|
|
971
|
+
type: Injectable
|
|
972
|
+
}], ctorParameters: () => [] });
|
|
973
|
+
|
|
974
|
+
class WebSocketService extends BrowserApiBaseService {
|
|
975
|
+
webSocket = null;
|
|
976
|
+
statusSubject = new Subject();
|
|
977
|
+
messageSubject = new Subject();
|
|
978
|
+
reconnectAttempts = 0;
|
|
979
|
+
reconnectTimer = null;
|
|
980
|
+
heartbeatTimer = null;
|
|
981
|
+
getApiName() {
|
|
982
|
+
return 'websocket';
|
|
983
|
+
}
|
|
984
|
+
ensureWebSocketSupport() {
|
|
985
|
+
if (typeof WebSocket === 'undefined') {
|
|
986
|
+
throw new Error('WebSocket API not supported in this browser');
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
connect(config) {
|
|
990
|
+
this.ensureWebSocketSupport();
|
|
991
|
+
return new Observable((observer) => {
|
|
992
|
+
this.disconnect(); // Disconnect existing connection if any
|
|
993
|
+
this.updateStatus({
|
|
994
|
+
connected: false,
|
|
995
|
+
connecting: true,
|
|
996
|
+
reconnecting: false,
|
|
997
|
+
reconnectAttempts: 0,
|
|
998
|
+
});
|
|
999
|
+
try {
|
|
1000
|
+
this.webSocket = new WebSocket(config.url, config.protocols);
|
|
1001
|
+
this.setupWebSocketHandlers(config);
|
|
1002
|
+
observer.next(this.getCurrentStatus());
|
|
1003
|
+
}
|
|
1004
|
+
catch (error) {
|
|
1005
|
+
console.error('[WebSocketService] Error creating WebSocket:', error);
|
|
1006
|
+
this.updateStatus({
|
|
1007
|
+
connected: false,
|
|
1008
|
+
connecting: false,
|
|
1009
|
+
reconnecting: false,
|
|
1010
|
+
error: error instanceof Error ? error.message : 'Connection failed',
|
|
1011
|
+
reconnectAttempts: 0,
|
|
1012
|
+
});
|
|
1013
|
+
observer.next(this.getCurrentStatus());
|
|
1014
|
+
}
|
|
1015
|
+
return () => {
|
|
1016
|
+
this.disconnect();
|
|
1017
|
+
};
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
disconnect() {
|
|
1021
|
+
if (this.reconnectTimer) {
|
|
1022
|
+
clearTimeout(this.reconnectTimer);
|
|
1023
|
+
this.reconnectTimer = null;
|
|
1024
|
+
}
|
|
1025
|
+
if (this.heartbeatTimer) {
|
|
1026
|
+
clearInterval(this.heartbeatTimer);
|
|
1027
|
+
this.heartbeatTimer = null;
|
|
1028
|
+
}
|
|
1029
|
+
if (this.webSocket) {
|
|
1030
|
+
this.webSocket.close();
|
|
1031
|
+
this.webSocket = null;
|
|
1032
|
+
}
|
|
1033
|
+
this.updateStatus({
|
|
1034
|
+
connected: false,
|
|
1035
|
+
connecting: false,
|
|
1036
|
+
reconnecting: false,
|
|
1037
|
+
reconnectAttempts: 0,
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
send(message) {
|
|
1041
|
+
if (!this.webSocket || this.webSocket.readyState !== WebSocket.OPEN) {
|
|
1042
|
+
throw new Error('WebSocket is not connected');
|
|
1043
|
+
}
|
|
1044
|
+
const messageWithTimestamp = {
|
|
1045
|
+
...message,
|
|
1046
|
+
timestamp: Date.now(),
|
|
1047
|
+
};
|
|
1048
|
+
this.webSocket.send(JSON.stringify(messageWithTimestamp));
|
|
1049
|
+
}
|
|
1050
|
+
sendRaw(data) {
|
|
1051
|
+
if (!this.webSocket || this.webSocket.readyState !== WebSocket.OPEN) {
|
|
1052
|
+
throw new Error('WebSocket is not connected');
|
|
1053
|
+
}
|
|
1054
|
+
this.webSocket.send(data);
|
|
1055
|
+
}
|
|
1056
|
+
getStatus() {
|
|
1057
|
+
return this.statusSubject.asObservable();
|
|
1058
|
+
}
|
|
1059
|
+
getMessages() {
|
|
1060
|
+
return this.messageSubject
|
|
1061
|
+
.asObservable()
|
|
1062
|
+
.pipe(filter((msg) => true));
|
|
1063
|
+
}
|
|
1064
|
+
setupWebSocketHandlers(config) {
|
|
1065
|
+
if (!this.webSocket)
|
|
1066
|
+
return;
|
|
1067
|
+
this.webSocket.onopen = () => {
|
|
1068
|
+
console.log('[WebSocketService] Connected to:', config.url);
|
|
1069
|
+
this.reconnectAttempts = 0;
|
|
1070
|
+
this.updateStatus({
|
|
1071
|
+
connected: true,
|
|
1072
|
+
connecting: false,
|
|
1073
|
+
reconnecting: false,
|
|
1074
|
+
reconnectAttempts: 0,
|
|
1075
|
+
});
|
|
1076
|
+
// Start heartbeat if configured
|
|
1077
|
+
if (config.heartbeatInterval && config.heartbeatMessage) {
|
|
1078
|
+
this.startHeartbeat(config);
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
this.webSocket.onclose = (event) => {
|
|
1082
|
+
console.log('[WebSocketService] Connection closed:', event.code, event.reason);
|
|
1083
|
+
this.updateStatus({
|
|
1084
|
+
connected: false,
|
|
1085
|
+
connecting: false,
|
|
1086
|
+
reconnecting: false,
|
|
1087
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
1088
|
+
});
|
|
1089
|
+
// Attempt reconnection if not a clean close and reconnect is enabled
|
|
1090
|
+
if (!event.wasClean && config.reconnectInterval && config.maxReconnectAttempts) {
|
|
1091
|
+
this.attemptReconnect(config);
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
this.webSocket.onerror = (error) => {
|
|
1095
|
+
console.error('[WebSocketService] WebSocket error:', error);
|
|
1096
|
+
this.updateStatus({
|
|
1097
|
+
connected: false,
|
|
1098
|
+
connecting: false,
|
|
1099
|
+
reconnecting: false,
|
|
1100
|
+
error: 'WebSocket connection error',
|
|
1101
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
1102
|
+
});
|
|
1103
|
+
};
|
|
1104
|
+
this.webSocket.onmessage = (event) => {
|
|
1105
|
+
try {
|
|
1106
|
+
const message = JSON.parse(event.data);
|
|
1107
|
+
this.messageSubject.next(message);
|
|
1108
|
+
}
|
|
1109
|
+
catch (error) {
|
|
1110
|
+
console.error('[WebSocketService] Error parsing message:', error);
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
startHeartbeat(config) {
|
|
1115
|
+
this.heartbeatTimer = setInterval(() => {
|
|
1116
|
+
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
|
|
1117
|
+
this.send({
|
|
1118
|
+
type: 'heartbeat',
|
|
1119
|
+
data: config.heartbeatMessage,
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
}, config.heartbeatInterval);
|
|
1123
|
+
}
|
|
1124
|
+
attemptReconnect(config) {
|
|
1125
|
+
if (this.reconnectAttempts >= (config.maxReconnectAttempts || 5)) {
|
|
1126
|
+
console.log('[WebSocketService] Max reconnect attempts reached');
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
this.reconnectAttempts++;
|
|
1130
|
+
this.updateStatus({
|
|
1131
|
+
connected: false,
|
|
1132
|
+
connecting: false,
|
|
1133
|
+
reconnecting: true,
|
|
1134
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
1135
|
+
});
|
|
1136
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1137
|
+
console.log(`[WebSocketService] Reconnect attempt ${this.reconnectAttempts}`);
|
|
1138
|
+
this.connect(config);
|
|
1139
|
+
}, config.reconnectInterval || 3000);
|
|
1140
|
+
}
|
|
1141
|
+
updateStatus(status) {
|
|
1142
|
+
const currentStatus = this.getCurrentStatus();
|
|
1143
|
+
const newStatus = { ...currentStatus, ...status };
|
|
1144
|
+
this.statusSubject.next(newStatus);
|
|
1145
|
+
}
|
|
1146
|
+
getCurrentStatus() {
|
|
1147
|
+
return {
|
|
1148
|
+
connected: this.webSocket?.readyState === WebSocket.OPEN,
|
|
1149
|
+
connecting: false,
|
|
1150
|
+
reconnecting: false,
|
|
1151
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
// Direct access to native WebSocket
|
|
1155
|
+
getNativeWebSocket() {
|
|
1156
|
+
return this.webSocket;
|
|
1157
|
+
}
|
|
1158
|
+
isConnected() {
|
|
1159
|
+
return this.webSocket?.readyState === WebSocket.OPEN;
|
|
1160
|
+
}
|
|
1161
|
+
getReadyState() {
|
|
1162
|
+
return this.webSocket?.readyState ?? WebSocket.CLOSED;
|
|
1163
|
+
}
|
|
1164
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1165
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService });
|
|
1166
|
+
}
|
|
1167
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService, decorators: [{
|
|
1168
|
+
type: Injectable
|
|
1169
|
+
}] });
|
|
1170
|
+
|
|
1171
|
+
class WebWorkerService extends BrowserApiBaseService {
|
|
1172
|
+
destroyRef = inject(DestroyRef);
|
|
1173
|
+
workers = new Map();
|
|
1174
|
+
workerStatuses = new Map();
|
|
1175
|
+
workerMessages = new Map();
|
|
1176
|
+
currentWorkerStatuses = new Map();
|
|
1177
|
+
getApiName() {
|
|
1178
|
+
return 'webworker';
|
|
1179
|
+
}
|
|
1180
|
+
ensureWorkerSupport() {
|
|
1181
|
+
if (typeof Worker === 'undefined') {
|
|
1182
|
+
throw new Error('Web Workers not supported in this browser');
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
createWorker(name, scriptUrl) {
|
|
1186
|
+
this.ensureWorkerSupport();
|
|
1187
|
+
return new Observable((observer) => {
|
|
1188
|
+
if (this.workers.has(name)) {
|
|
1189
|
+
observer.next(this.currentWorkerStatuses.get(name));
|
|
1190
|
+
return () => {
|
|
1191
|
+
// No-op: workers are managed explicitly via terminateWorker/terminateAllWorkers
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
try {
|
|
1195
|
+
const worker = new Worker(scriptUrl);
|
|
1196
|
+
this.workers.set(name, worker);
|
|
1197
|
+
this.setupWorker(name, worker);
|
|
1198
|
+
const status = {
|
|
1199
|
+
initialized: true,
|
|
1200
|
+
running: true,
|
|
1201
|
+
messageCount: 0,
|
|
1202
|
+
};
|
|
1203
|
+
this.currentWorkerStatuses.set(name, status);
|
|
1204
|
+
this.updateWorkerStatus(name, status);
|
|
1205
|
+
observer.next(status);
|
|
1206
|
+
}
|
|
1207
|
+
catch (error) {
|
|
1208
|
+
console.error(`[WebWorkerService] Failed to create worker ${name}:`, error);
|
|
1209
|
+
const status = {
|
|
1210
|
+
initialized: false,
|
|
1211
|
+
running: false,
|
|
1212
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
1213
|
+
messageCount: 0,
|
|
1214
|
+
};
|
|
1215
|
+
this.currentWorkerStatuses.set(name, status);
|
|
1216
|
+
this.updateWorkerStatus(name, status);
|
|
1217
|
+
observer.next(status);
|
|
1218
|
+
}
|
|
1219
|
+
return () => {
|
|
1220
|
+
// No-op: workers are managed explicitly via terminateWorker/terminateAllWorkers
|
|
1221
|
+
};
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
terminateWorker(name) {
|
|
1225
|
+
const worker = this.workers.get(name);
|
|
1226
|
+
if (worker) {
|
|
1227
|
+
worker.terminate();
|
|
1228
|
+
this.workers.delete(name);
|
|
1229
|
+
this.workerStatuses.delete(name);
|
|
1230
|
+
this.workerMessages.delete(name);
|
|
1231
|
+
this.currentWorkerStatuses.delete(name);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
terminateAllWorkers() {
|
|
1235
|
+
this.workers.forEach((_, name) => {
|
|
1236
|
+
this.terminateWorker(name);
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
postMessage(workerName, task) {
|
|
1240
|
+
const worker = this.workers.get(workerName);
|
|
1241
|
+
if (!worker) {
|
|
1242
|
+
console.error(`[WebWorkerService] Worker ${workerName} not found`);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
try {
|
|
1246
|
+
const message = { ...task, timestamp: Date.now() };
|
|
1247
|
+
if (task.transferable) {
|
|
1248
|
+
worker.postMessage(message, task.transferable);
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1251
|
+
worker.postMessage(message);
|
|
1252
|
+
}
|
|
1253
|
+
const currentStatus = this.currentWorkerStatuses.get(workerName);
|
|
1254
|
+
if (currentStatus) {
|
|
1255
|
+
currentStatus.messageCount++;
|
|
1256
|
+
this.updateWorkerStatus(workerName, currentStatus);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
catch (error) {
|
|
1260
|
+
console.error(`[WebWorkerService] Failed to post message to worker ${workerName}:`, error);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
getMessages(workerName) {
|
|
1264
|
+
if (!this.workerMessages.has(workerName)) {
|
|
1265
|
+
this.workerMessages.set(workerName, new Subject());
|
|
1266
|
+
}
|
|
1267
|
+
return this.workerMessages.get(workerName).asObservable();
|
|
1268
|
+
}
|
|
1269
|
+
getStatus(workerName) {
|
|
1270
|
+
if (!this.workerStatuses.has(workerName)) {
|
|
1271
|
+
this.workerStatuses.set(workerName, new Subject());
|
|
1272
|
+
}
|
|
1273
|
+
return this.workerStatuses.get(workerName).asObservable();
|
|
1274
|
+
}
|
|
1275
|
+
getCurrentStatus(workerName) {
|
|
1276
|
+
return this.currentWorkerStatuses.get(workerName);
|
|
1277
|
+
}
|
|
1278
|
+
getAllStatuses() {
|
|
1279
|
+
return new Map(this.currentWorkerStatuses);
|
|
1280
|
+
}
|
|
1281
|
+
isWorkerRunning(workerName) {
|
|
1282
|
+
const status = this.currentWorkerStatuses.get(workerName);
|
|
1283
|
+
return status?.running ?? false;
|
|
1284
|
+
}
|
|
1285
|
+
setupWorker(name, worker) {
|
|
1286
|
+
worker.onmessage = (event) => {
|
|
1287
|
+
const message = {
|
|
1288
|
+
id: event.data.id || `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
1289
|
+
type: event.data.type || 'message',
|
|
1290
|
+
data: event.data.data,
|
|
1291
|
+
timestamp: event.data.timestamp || Date.now(),
|
|
1292
|
+
};
|
|
1293
|
+
if (!this.workerMessages.has(name)) {
|
|
1294
|
+
this.workerMessages.set(name, new Subject());
|
|
1295
|
+
}
|
|
1296
|
+
this.workerMessages.get(name).next(message);
|
|
1297
|
+
};
|
|
1298
|
+
worker.onerror = (error) => {
|
|
1299
|
+
console.error(`[WebWorkerService] Worker ${name} error:`, error);
|
|
1300
|
+
const status = {
|
|
1301
|
+
initialized: true,
|
|
1302
|
+
running: false,
|
|
1303
|
+
error: error instanceof Error ? error.message : 'Worker error',
|
|
1304
|
+
messageCount: this.currentWorkerStatuses.get(name)?.messageCount ?? 0,
|
|
1305
|
+
};
|
|
1306
|
+
this.currentWorkerStatuses.set(name, status);
|
|
1307
|
+
this.updateWorkerStatus(name, status);
|
|
1308
|
+
};
|
|
1309
|
+
// Auto-cleanup when service is destroyed
|
|
1310
|
+
this.destroyRef.onDestroy(() => {
|
|
1311
|
+
this.terminateWorker(name);
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
updateWorkerStatus(name, status) {
|
|
1315
|
+
if (!this.workerStatuses.has(name)) {
|
|
1316
|
+
this.workerStatuses.set(name, new Subject());
|
|
1317
|
+
}
|
|
1318
|
+
this.workerStatuses.get(name).next(status);
|
|
1319
|
+
}
|
|
1320
|
+
// Direct access to native Worker API
|
|
1321
|
+
getNativeWorker(name) {
|
|
1322
|
+
return this.workers.get(name);
|
|
1323
|
+
}
|
|
1324
|
+
getAllWorkers() {
|
|
1325
|
+
return new Map(this.workers);
|
|
1326
|
+
}
|
|
1327
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebWorkerService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1328
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebWorkerService });
|
|
1329
|
+
}
|
|
1330
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebWorkerService, decorators: [{
|
|
1331
|
+
type: Injectable
|
|
1332
|
+
}] });
|
|
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
|
+
|
|
2240
|
+
// Common types for browser APIs
|
|
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
|
+
|
|
2331
|
+
class BrowserSupportUtil {
|
|
2332
|
+
static isSupported(feature) {
|
|
2333
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
2334
|
+
return false;
|
|
2335
|
+
}
|
|
2336
|
+
switch (feature) {
|
|
2337
|
+
case 'permissions':
|
|
2338
|
+
return 'permissions' in navigator;
|
|
2339
|
+
case 'camera':
|
|
2340
|
+
return 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices;
|
|
2341
|
+
case 'microphone':
|
|
2342
|
+
return 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices;
|
|
2343
|
+
case 'geolocation':
|
|
2344
|
+
return 'geolocation' in navigator;
|
|
2345
|
+
case 'notifications':
|
|
2346
|
+
return 'Notification' in window;
|
|
2347
|
+
case 'clipboard':
|
|
2348
|
+
return 'clipboard' in navigator;
|
|
2349
|
+
case 'clipboard-read':
|
|
2350
|
+
return 'clipboard' in navigator && 'readText' in navigator.clipboard;
|
|
2351
|
+
case 'clipboard-write':
|
|
2352
|
+
return 'clipboard' in navigator && 'writeText' in navigator.clipboard;
|
|
2353
|
+
case 'persistent-storage':
|
|
2354
|
+
return 'storage' in navigator && 'persist' in navigator.storage;
|
|
2355
|
+
default:
|
|
2356
|
+
return false;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
static getUnsupportedFeatures() {
|
|
2360
|
+
const features = [
|
|
2361
|
+
'permissions',
|
|
2362
|
+
'camera',
|
|
2363
|
+
'microphone',
|
|
2364
|
+
'geolocation',
|
|
2365
|
+
'notifications',
|
|
2366
|
+
'clipboard',
|
|
2367
|
+
'clipboard-read',
|
|
2368
|
+
'clipboard-write',
|
|
2369
|
+
'persistent-storage',
|
|
2370
|
+
];
|
|
2371
|
+
return features.filter((feature) => !this.isSupported(feature));
|
|
2372
|
+
}
|
|
2373
|
+
static isSecureContext() {
|
|
2374
|
+
return typeof window !== 'undefined' ? window.isSecureContext : false;
|
|
2375
|
+
}
|
|
2376
|
+
static getUserAgent() {
|
|
2377
|
+
return typeof navigator !== 'undefined' ? navigator.userAgent : '';
|
|
2378
|
+
}
|
|
2379
|
+
static isMobile() {
|
|
2380
|
+
const userAgent = this.getUserAgent().toLowerCase();
|
|
2381
|
+
return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
|
|
2382
|
+
}
|
|
2383
|
+
static isDesktop() {
|
|
2384
|
+
return !this.isMobile();
|
|
2385
|
+
}
|
|
2386
|
+
static getBrowserInfo() {
|
|
2387
|
+
const userAgent = this.getUserAgent();
|
|
2388
|
+
return {
|
|
2389
|
+
name: this.getBrowserName(userAgent),
|
|
2390
|
+
version: this.getBrowserVersion(userAgent),
|
|
2391
|
+
isChrome: /chrome/.test(userAgent) && !/edge/.test(userAgent),
|
|
2392
|
+
isFirefox: /firefox/.test(userAgent),
|
|
2393
|
+
isSafari: /safari/.test(userAgent) && !/chrome/.test(userAgent),
|
|
2394
|
+
isEdge: /edge/.test(userAgent) || /edg/.test(userAgent),
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
static getBrowserName(userAgent) {
|
|
2398
|
+
if (/chrome/.test(userAgent) && !/edge/.test(userAgent))
|
|
2399
|
+
return 'Chrome';
|
|
2400
|
+
if (/firefox/.test(userAgent))
|
|
2401
|
+
return 'Firefox';
|
|
2402
|
+
if (/safari/.test(userAgent) && !/chrome/.test(userAgent))
|
|
2403
|
+
return 'Safari';
|
|
2404
|
+
if (/edge/.test(userAgent) || /edg/.test(userAgent))
|
|
2405
|
+
return 'Edge';
|
|
2406
|
+
return 'Unknown';
|
|
2407
|
+
}
|
|
2408
|
+
static getBrowserVersion(userAgent) {
|
|
2409
|
+
const match = userAgent.match(/(chrome|firefox|safari|edge|edg)\/(\d+)/i);
|
|
2410
|
+
return match ? match[2] : 'Unknown';
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
/**
|
|
2415
|
+
* Functional guard that checks if the user has the required permission.
|
|
2416
|
+
* Usage in routes: { canActivate: [permissionGuard('camera')] }
|
|
2417
|
+
*/
|
|
2418
|
+
const permissionGuard = (permission) => {
|
|
2419
|
+
return async (_route) => {
|
|
2420
|
+
const permissionsService = inject(PermissionsService);
|
|
2421
|
+
const router = inject(Router);
|
|
2422
|
+
if (!permission) {
|
|
2423
|
+
return true;
|
|
2424
|
+
}
|
|
2425
|
+
try {
|
|
2426
|
+
const status = await permissionsService.query({ name: permission });
|
|
2427
|
+
if (status.state !== 'granted') {
|
|
2428
|
+
router.navigate(['/permission-denied'], {
|
|
2429
|
+
queryParams: { permission },
|
|
2430
|
+
});
|
|
2431
|
+
return false;
|
|
2432
|
+
}
|
|
2433
|
+
return true;
|
|
2434
|
+
}
|
|
2435
|
+
catch (error) {
|
|
2436
|
+
console.error('Permission guard error:', error);
|
|
2437
|
+
router.navigate(['/permission-denied'], {
|
|
2438
|
+
queryParams: { permission },
|
|
2439
|
+
});
|
|
2440
|
+
return false;
|
|
2441
|
+
}
|
|
2442
|
+
};
|
|
2443
|
+
};
|
|
2444
|
+
|
|
2445
|
+
const defaultBrowserWebApisConfig = {
|
|
2446
|
+
enableCamera: true,
|
|
2447
|
+
enableGeolocation: true,
|
|
2448
|
+
enableNotifications: true,
|
|
2449
|
+
enableClipboard: true,
|
|
2450
|
+
enableBattery: false,
|
|
2451
|
+
enableMediaDevices: true,
|
|
2452
|
+
enableWebShare: false,
|
|
2453
|
+
enableWebStorage: false,
|
|
2454
|
+
enableWebSocket: false,
|
|
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,
|
|
2469
|
+
};
|
|
2470
|
+
function provideBrowserWebApis(config = {}) {
|
|
2471
|
+
const mergedConfig = { ...defaultBrowserWebApisConfig, ...config };
|
|
2472
|
+
const providers = [PermissionsService];
|
|
2473
|
+
const conditionalProviders = [
|
|
2474
|
+
[mergedConfig.enableCamera, CameraService],
|
|
2475
|
+
[mergedConfig.enableGeolocation, GeolocationService],
|
|
2476
|
+
[mergedConfig.enableNotifications, NotificationService],
|
|
2477
|
+
[mergedConfig.enableClipboard, ClipboardService],
|
|
2478
|
+
[mergedConfig.enableMediaDevices, MediaDevicesService],
|
|
2479
|
+
[mergedConfig.enableBattery, BatteryService],
|
|
2480
|
+
[mergedConfig.enableWebShare, WebShareService],
|
|
2481
|
+
[mergedConfig.enableWebStorage, WebStorageService],
|
|
2482
|
+
[mergedConfig.enableWebSocket, WebSocketService],
|
|
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],
|
|
2497
|
+
];
|
|
2498
|
+
for (const [enabled, provider] of conditionalProviders) {
|
|
2499
|
+
if (enabled) {
|
|
2500
|
+
providers.push(provider);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
return makeEnvironmentProviders(providers);
|
|
2504
|
+
}
|
|
2505
|
+
// Feature-specific providers for tree-shaking
|
|
2506
|
+
function provideCamera() {
|
|
2507
|
+
return makeEnvironmentProviders([PermissionsService, CameraService]);
|
|
2508
|
+
}
|
|
2509
|
+
function provideGeolocation() {
|
|
2510
|
+
return makeEnvironmentProviders([PermissionsService, GeolocationService]);
|
|
2511
|
+
}
|
|
2512
|
+
function provideNotifications() {
|
|
2513
|
+
return makeEnvironmentProviders([PermissionsService, NotificationService]);
|
|
2514
|
+
}
|
|
2515
|
+
function provideClipboard() {
|
|
2516
|
+
return makeEnvironmentProviders([PermissionsService, ClipboardService]);
|
|
2517
|
+
}
|
|
2518
|
+
function provideMediaDevices() {
|
|
2519
|
+
return makeEnvironmentProviders([PermissionsService, MediaDevicesService]);
|
|
2520
|
+
}
|
|
2521
|
+
function provideBattery() {
|
|
2522
|
+
return makeEnvironmentProviders([BatteryService]);
|
|
2523
|
+
}
|
|
2524
|
+
function provideWebShare() {
|
|
2525
|
+
return makeEnvironmentProviders([WebShareService]);
|
|
2526
|
+
}
|
|
2527
|
+
function provideWebStorage() {
|
|
2528
|
+
return makeEnvironmentProviders([WebStorageService]);
|
|
2529
|
+
}
|
|
2530
|
+
function provideWebSocket() {
|
|
2531
|
+
return makeEnvironmentProviders([WebSocketService]);
|
|
2532
|
+
}
|
|
2533
|
+
function provideWebWorker() {
|
|
2534
|
+
return makeEnvironmentProviders([WebWorkerService]);
|
|
2535
|
+
}
|
|
2536
|
+
function providePermissions() {
|
|
2537
|
+
return makeEnvironmentProviders([PermissionsService]);
|
|
2538
|
+
}
|
|
2539
|
+
// Combined providers for common use cases
|
|
2540
|
+
function provideMediaApis() {
|
|
2541
|
+
return makeEnvironmentProviders([PermissionsService, CameraService, MediaDevicesService]);
|
|
2542
|
+
}
|
|
2543
|
+
function provideLocationApis() {
|
|
2544
|
+
return makeEnvironmentProviders([PermissionsService, GeolocationService]);
|
|
2545
|
+
}
|
|
2546
|
+
function provideStorageApis() {
|
|
2547
|
+
return makeEnvironmentProviders([PermissionsService, ClipboardService, WebStorageService]);
|
|
2548
|
+
}
|
|
2549
|
+
function provideCommunicationApis() {
|
|
2550
|
+
return makeEnvironmentProviders([
|
|
2551
|
+
PermissionsService,
|
|
2552
|
+
NotificationService,
|
|
2553
|
+
WebShareService,
|
|
2554
|
+
WebSocketService,
|
|
2555
|
+
]);
|
|
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]);
|
|
657
2595
|
}
|
|
658
2596
|
|
|
659
2597
|
// Browser Web APIs Services
|
|
@@ -664,5 +2602,4 @@ const version = '0.1.0';
|
|
|
664
2602
|
* Generated bundle index. Do not edit.
|
|
665
2603
|
*/
|
|
666
2604
|
|
|
667
|
-
export { BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, GeolocationService, MediaDevicesService, NotificationService,
|
|
668
|
-
//# sourceMappingURL=angular-helpers-browser-web-apis.mjs.map
|
|
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 };
|