@angular-helpers/browser-web-apis 21.0.2 → 21.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -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, makeEnvironmentProviders } from '@angular/core';
|
|
3
3
|
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
|
|
4
|
-
import { Observable } from 'rxjs';
|
|
4
|
+
import { Observable, fromEvent, Subject } 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,7 @@ 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 },
|
|
469
483
|
];
|
|
470
484
|
class BrowserCapabilityService {
|
|
471
485
|
getCapabilities() {
|
|
@@ -509,7 +523,7 @@ class BrowserCapabilityService {
|
|
|
509
523
|
label: capability.label,
|
|
510
524
|
supported: this.isSupported(capability.id),
|
|
511
525
|
secureContext,
|
|
512
|
-
requiresSecureContext: capability.requiresSecureContext
|
|
526
|
+
requiresSecureContext: capability.requiresSecureContext,
|
|
513
527
|
}));
|
|
514
528
|
}
|
|
515
529
|
async getPermissionState(permission) {
|
|
@@ -532,6 +546,752 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
532
546
|
args: [{ providedIn: 'root' }]
|
|
533
547
|
}] });
|
|
534
548
|
|
|
549
|
+
class BatteryService extends BrowserApiBaseService {
|
|
550
|
+
batteryManager = null;
|
|
551
|
+
getApiName() {
|
|
552
|
+
return 'battery';
|
|
553
|
+
}
|
|
554
|
+
ensureBatterySupport() {
|
|
555
|
+
const nav = navigator;
|
|
556
|
+
if (!('getBattery' in nav)) {
|
|
557
|
+
throw new Error('Battery API not supported in this browser');
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async initialize() {
|
|
561
|
+
this.ensureBatterySupport();
|
|
562
|
+
try {
|
|
563
|
+
const nav = navigator;
|
|
564
|
+
this.batteryManager = await nav.getBattery();
|
|
565
|
+
const batteryInfo = this.getBatteryInfo();
|
|
566
|
+
this.setupEventListeners();
|
|
567
|
+
return batteryInfo;
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
console.error('[BatteryService] Error initializing battery API:', error);
|
|
571
|
+
throw this.createError('Failed to initialize battery API', error);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
getBatteryInfo() {
|
|
575
|
+
if (!this.batteryManager) {
|
|
576
|
+
throw new Error('Battery service not initialized. Call initialize() first.');
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
charging: this.batteryManager.charging,
|
|
580
|
+
chargingTime: this.batteryManager.chargingTime,
|
|
581
|
+
dischargingTime: this.batteryManager.dischargingTime,
|
|
582
|
+
level: this.batteryManager.level,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
watchBatteryInfo() {
|
|
586
|
+
if (!this.batteryManager) {
|
|
587
|
+
throw new Error('Battery service not initialized. Call initialize() first.');
|
|
588
|
+
}
|
|
589
|
+
return new Observable((observer) => {
|
|
590
|
+
const updateBatteryInfo = () => {
|
|
591
|
+
observer.next(this.getBatteryInfo());
|
|
592
|
+
};
|
|
593
|
+
// Listen to all battery events
|
|
594
|
+
this.batteryManager.addEventListener('chargingchange', updateBatteryInfo);
|
|
595
|
+
this.batteryManager.addEventListener('levelchange', updateBatteryInfo);
|
|
596
|
+
this.batteryManager.addEventListener('chargingtimechange', updateBatteryInfo);
|
|
597
|
+
this.batteryManager.addEventListener('dischargingtimechange', updateBatteryInfo);
|
|
598
|
+
// Send initial value
|
|
599
|
+
updateBatteryInfo();
|
|
600
|
+
return () => {
|
|
601
|
+
// Cleanup event listeners
|
|
602
|
+
this.batteryManager.removeEventListener('chargingchange', updateBatteryInfo);
|
|
603
|
+
this.batteryManager.removeEventListener('levelchange', updateBatteryInfo);
|
|
604
|
+
this.batteryManager.removeEventListener('chargingtimechange', updateBatteryInfo);
|
|
605
|
+
this.batteryManager.removeEventListener('dischargingtimechange', updateBatteryInfo);
|
|
606
|
+
};
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
setupEventListeners() {
|
|
610
|
+
if (!this.batteryManager)
|
|
611
|
+
return;
|
|
612
|
+
this.batteryManager.addEventListener('chargingchange', () => {
|
|
613
|
+
console.log('[BatteryService] Charging status changed:', this.batteryManager.charging);
|
|
614
|
+
});
|
|
615
|
+
this.batteryManager.addEventListener('levelchange', () => {
|
|
616
|
+
console.log('[BatteryService] Battery level changed:', this.batteryManager.level);
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
// Direct access to native battery API
|
|
620
|
+
getNativeBatteryManager() {
|
|
621
|
+
if (!this.batteryManager) {
|
|
622
|
+
throw new Error('Battery service not initialized. Call initialize() first.');
|
|
623
|
+
}
|
|
624
|
+
return this.batteryManager;
|
|
625
|
+
}
|
|
626
|
+
isCharging() {
|
|
627
|
+
return this.getBatteryInfo().charging;
|
|
628
|
+
}
|
|
629
|
+
getLevel() {
|
|
630
|
+
return this.getBatteryInfo().level;
|
|
631
|
+
}
|
|
632
|
+
getChargingTime() {
|
|
633
|
+
return this.getBatteryInfo().chargingTime;
|
|
634
|
+
}
|
|
635
|
+
getDischargingTime() {
|
|
636
|
+
return this.getBatteryInfo().dischargingTime;
|
|
637
|
+
}
|
|
638
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BatteryService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
639
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BatteryService });
|
|
640
|
+
}
|
|
641
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BatteryService, decorators: [{
|
|
642
|
+
type: Injectable
|
|
643
|
+
}] });
|
|
644
|
+
|
|
645
|
+
class WebShareService extends BrowserApiBaseService {
|
|
646
|
+
getApiName() {
|
|
647
|
+
return 'web-share';
|
|
648
|
+
}
|
|
649
|
+
ensureWebShareSupport() {
|
|
650
|
+
if (!('share' in navigator)) {
|
|
651
|
+
throw new Error('Web Share API not supported in this browser');
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async share(data) {
|
|
655
|
+
this.ensureWebShareSupport();
|
|
656
|
+
try {
|
|
657
|
+
await navigator.share(data);
|
|
658
|
+
return { shared: true };
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
661
|
+
console.error('[WebShareService] Error sharing:', error);
|
|
662
|
+
const errorMessage = error instanceof Error ? error.message : 'Share failed';
|
|
663
|
+
return { shared: false, error: errorMessage };
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
canShare() {
|
|
667
|
+
return 'share' in navigator;
|
|
668
|
+
}
|
|
669
|
+
canShareFiles() {
|
|
670
|
+
if (!('share' in navigator))
|
|
671
|
+
return false;
|
|
672
|
+
// Check if the browser supports file sharing
|
|
673
|
+
const testFiles = [new File([''], 'test.txt', { type: 'text/plain' })];
|
|
674
|
+
return navigator.canShare?.({ files: testFiles }) ?? false;
|
|
675
|
+
}
|
|
676
|
+
// Direct access to native share API
|
|
677
|
+
getNativeShare() {
|
|
678
|
+
this.ensureWebShareSupport();
|
|
679
|
+
return navigator.share;
|
|
680
|
+
}
|
|
681
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebShareService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
682
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebShareService });
|
|
683
|
+
}
|
|
684
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebShareService, decorators: [{
|
|
685
|
+
type: Injectable
|
|
686
|
+
}] });
|
|
687
|
+
|
|
688
|
+
class WebStorageService extends BrowserApiBaseService {
|
|
689
|
+
storageEvents = signal(null, ...(ngDevMode ? [{ debugName: "storageEvents" }] : /* istanbul ignore next */ []));
|
|
690
|
+
destroyRef = inject(DestroyRef);
|
|
691
|
+
constructor() {
|
|
692
|
+
super();
|
|
693
|
+
this.setupEventListeners();
|
|
694
|
+
}
|
|
695
|
+
getApiName() {
|
|
696
|
+
return 'storage';
|
|
697
|
+
}
|
|
698
|
+
ensureStorageSupport() {
|
|
699
|
+
if (!this.isBrowserEnvironment() || typeof Storage === 'undefined') {
|
|
700
|
+
throw new Error('Storage API not supported in this browser');
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
setupEventListeners() {
|
|
704
|
+
if (this.isBrowserEnvironment()) {
|
|
705
|
+
fromEvent(window, 'storage')
|
|
706
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
707
|
+
.subscribe((event) => {
|
|
708
|
+
const storageEvent = event;
|
|
709
|
+
if (storageEvent.key && storageEvent.newValue !== null) {
|
|
710
|
+
this.storageEvents.set({
|
|
711
|
+
key: storageEvent.key,
|
|
712
|
+
newValue: this.deserializeValue(storageEvent.newValue),
|
|
713
|
+
oldValue: storageEvent.oldValue ? this.deserializeValue(storageEvent.oldValue) : null,
|
|
714
|
+
storageArea: storageEvent.storageArea === localStorage ? 'localStorage' : 'sessionStorage',
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
serializeValue(value, options) {
|
|
721
|
+
if (options?.serialize) {
|
|
722
|
+
return options.serialize(value);
|
|
723
|
+
}
|
|
724
|
+
return JSON.stringify(value);
|
|
725
|
+
}
|
|
726
|
+
deserializeValue(value, options) {
|
|
727
|
+
if (value === null)
|
|
728
|
+
return null;
|
|
729
|
+
if (options?.deserialize) {
|
|
730
|
+
return options.deserialize(value);
|
|
731
|
+
}
|
|
732
|
+
try {
|
|
733
|
+
return JSON.parse(value);
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
return value;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
getKey(key, options) {
|
|
740
|
+
const prefix = options?.prefix || '';
|
|
741
|
+
return prefix ? `${prefix}:${key}` : key;
|
|
742
|
+
}
|
|
743
|
+
// Local Storage Methods
|
|
744
|
+
setLocalStorage(key, value, options = {}) {
|
|
745
|
+
this.ensureStorageSupport();
|
|
746
|
+
try {
|
|
747
|
+
const serializedValue = this.serializeValue(value, options);
|
|
748
|
+
const fullKey = this.getKey(key, options);
|
|
749
|
+
localStorage.setItem(fullKey, serializedValue);
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
console.error('[WebStorageService] Error setting localStorage:', error);
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
getLocalStorage(key, defaultValue = null, options = {}) {
|
|
758
|
+
this.ensureStorageSupport();
|
|
759
|
+
try {
|
|
760
|
+
const fullKey = this.getKey(key, options);
|
|
761
|
+
const value = localStorage.getItem(fullKey);
|
|
762
|
+
return value !== null ? this.deserializeValue(value, options) : defaultValue;
|
|
763
|
+
}
|
|
764
|
+
catch (error) {
|
|
765
|
+
console.error('[WebStorageService] Error getting localStorage:', error);
|
|
766
|
+
return defaultValue;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
removeLocalStorage(key, options = {}) {
|
|
770
|
+
this.ensureStorageSupport();
|
|
771
|
+
try {
|
|
772
|
+
const fullKey = this.getKey(key, options);
|
|
773
|
+
localStorage.removeItem(fullKey);
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
console.error('[WebStorageService] Error removing localStorage:', error);
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
clearLocalStorage(options = {}) {
|
|
782
|
+
this.ensureStorageSupport();
|
|
783
|
+
try {
|
|
784
|
+
const prefix = options?.prefix;
|
|
785
|
+
if (prefix) {
|
|
786
|
+
// Only remove keys with the specified prefix
|
|
787
|
+
const keysToRemove = [];
|
|
788
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
789
|
+
const key = localStorage.key(i);
|
|
790
|
+
if (key && key.startsWith(`${prefix}:`)) {
|
|
791
|
+
keysToRemove.push(key);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
localStorage.clear();
|
|
798
|
+
}
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
console.error('[WebStorageService] Error clearing localStorage:', error);
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
// Session Storage Methods
|
|
807
|
+
setSessionStorage(key, value, options = {}) {
|
|
808
|
+
this.ensureStorageSupport();
|
|
809
|
+
try {
|
|
810
|
+
const serializedValue = this.serializeValue(value, options);
|
|
811
|
+
const fullKey = this.getKey(key, options);
|
|
812
|
+
sessionStorage.setItem(fullKey, serializedValue);
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
catch (error) {
|
|
816
|
+
console.error('[WebStorageService] Error setting sessionStorage:', error);
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
getSessionStorage(key, defaultValue = null, options = {}) {
|
|
821
|
+
this.ensureStorageSupport();
|
|
822
|
+
try {
|
|
823
|
+
const fullKey = this.getKey(key, options);
|
|
824
|
+
const value = sessionStorage.getItem(fullKey);
|
|
825
|
+
return value !== null ? this.deserializeValue(value, options) : defaultValue;
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
console.error('[WebStorageService] Error getting sessionStorage:', error);
|
|
829
|
+
return defaultValue;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
removeSessionStorage(key, options = {}) {
|
|
833
|
+
this.ensureStorageSupport();
|
|
834
|
+
try {
|
|
835
|
+
const fullKey = this.getKey(key, options);
|
|
836
|
+
sessionStorage.removeItem(fullKey);
|
|
837
|
+
return true;
|
|
838
|
+
}
|
|
839
|
+
catch (error) {
|
|
840
|
+
console.error('[WebStorageService] Error removing sessionStorage:', error);
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
clearSessionStorage(options = {}) {
|
|
845
|
+
this.ensureStorageSupport();
|
|
846
|
+
try {
|
|
847
|
+
const prefix = options?.prefix;
|
|
848
|
+
if (prefix) {
|
|
849
|
+
// Only remove keys with the specified prefix
|
|
850
|
+
const keysToRemove = [];
|
|
851
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
852
|
+
const key = sessionStorage.key(i);
|
|
853
|
+
if (key && key.startsWith(`${prefix}:`)) {
|
|
854
|
+
keysToRemove.push(key);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
keysToRemove.forEach((key) => sessionStorage.removeItem(key));
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
sessionStorage.clear();
|
|
861
|
+
}
|
|
862
|
+
return true;
|
|
863
|
+
}
|
|
864
|
+
catch (error) {
|
|
865
|
+
console.error('[WebStorageService] Error clearing sessionStorage:', error);
|
|
866
|
+
return false;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
// Utility Methods
|
|
870
|
+
getLocalStorageSize(options = {}) {
|
|
871
|
+
this.ensureStorageSupport();
|
|
872
|
+
let totalSize = 0;
|
|
873
|
+
const prefix = options?.prefix;
|
|
874
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
875
|
+
const key = localStorage.key(i);
|
|
876
|
+
if (key && (!prefix || key.startsWith(`${prefix}:`))) {
|
|
877
|
+
totalSize += (localStorage.getItem(key)?.length || 0) + key.length;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return totalSize;
|
|
881
|
+
}
|
|
882
|
+
getSessionStorageSize(options = {}) {
|
|
883
|
+
this.ensureStorageSupport();
|
|
884
|
+
let totalSize = 0;
|
|
885
|
+
const prefix = options?.prefix;
|
|
886
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
887
|
+
const key = sessionStorage.key(i);
|
|
888
|
+
if (key && (!prefix || key.startsWith(`${prefix}:`))) {
|
|
889
|
+
totalSize += (sessionStorage.getItem(key)?.length || 0) + key.length;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return totalSize;
|
|
893
|
+
}
|
|
894
|
+
getStorageEvents() {
|
|
895
|
+
return toObservable(this.storageEvents).pipe(filter((event) => event !== null), distinctUntilChanged((prev, curr) => prev.key === curr.key &&
|
|
896
|
+
prev.newValue === curr.newValue &&
|
|
897
|
+
prev.oldValue === curr.oldValue));
|
|
898
|
+
}
|
|
899
|
+
watchLocalStorage(key, options = {}) {
|
|
900
|
+
return this.getStorageEvents().pipe(map((event) => {
|
|
901
|
+
const fullKey = this.getKey(key, options);
|
|
902
|
+
if (event.key === fullKey && event.storageArea === 'localStorage') {
|
|
903
|
+
return event.newValue;
|
|
904
|
+
}
|
|
905
|
+
return this.getLocalStorage(key, null, options);
|
|
906
|
+
}));
|
|
907
|
+
}
|
|
908
|
+
watchSessionStorage(key, options = {}) {
|
|
909
|
+
return this.getStorageEvents().pipe(map((event) => {
|
|
910
|
+
const fullKey = this.getKey(key, options);
|
|
911
|
+
if (event.key === fullKey && event.storageArea === 'sessionStorage') {
|
|
912
|
+
return event.newValue;
|
|
913
|
+
}
|
|
914
|
+
return this.getSessionStorage(key, null, options);
|
|
915
|
+
}));
|
|
916
|
+
}
|
|
917
|
+
// Direct access to native storage APIs
|
|
918
|
+
getNativeLocalStorage() {
|
|
919
|
+
this.ensureStorageSupport();
|
|
920
|
+
return localStorage;
|
|
921
|
+
}
|
|
922
|
+
getNativeSessionStorage() {
|
|
923
|
+
this.ensureStorageSupport();
|
|
924
|
+
return sessionStorage;
|
|
925
|
+
}
|
|
926
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
927
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebStorageService });
|
|
928
|
+
}
|
|
929
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebStorageService, decorators: [{
|
|
930
|
+
type: Injectable
|
|
931
|
+
}], ctorParameters: () => [] });
|
|
932
|
+
|
|
933
|
+
class WebSocketService extends BrowserApiBaseService {
|
|
934
|
+
webSocket = null;
|
|
935
|
+
statusSubject = new Subject();
|
|
936
|
+
messageSubject = new Subject();
|
|
937
|
+
reconnectAttempts = 0;
|
|
938
|
+
reconnectTimer = null;
|
|
939
|
+
heartbeatTimer = null;
|
|
940
|
+
getApiName() {
|
|
941
|
+
return 'websocket';
|
|
942
|
+
}
|
|
943
|
+
ensureWebSocketSupport() {
|
|
944
|
+
if (typeof WebSocket === 'undefined') {
|
|
945
|
+
throw new Error('WebSocket API not supported in this browser');
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
connect(config) {
|
|
949
|
+
this.ensureWebSocketSupport();
|
|
950
|
+
return new Observable((observer) => {
|
|
951
|
+
this.disconnect(); // Disconnect existing connection if any
|
|
952
|
+
this.updateStatus({
|
|
953
|
+
connected: false,
|
|
954
|
+
connecting: true,
|
|
955
|
+
reconnecting: false,
|
|
956
|
+
reconnectAttempts: 0,
|
|
957
|
+
});
|
|
958
|
+
try {
|
|
959
|
+
this.webSocket = new WebSocket(config.url, config.protocols);
|
|
960
|
+
this.setupWebSocketHandlers(config);
|
|
961
|
+
observer.next(this.getCurrentStatus());
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
console.error('[WebSocketService] Error creating WebSocket:', error);
|
|
965
|
+
this.updateStatus({
|
|
966
|
+
connected: false,
|
|
967
|
+
connecting: false,
|
|
968
|
+
reconnecting: false,
|
|
969
|
+
error: error instanceof Error ? error.message : 'Connection failed',
|
|
970
|
+
reconnectAttempts: 0,
|
|
971
|
+
});
|
|
972
|
+
observer.next(this.getCurrentStatus());
|
|
973
|
+
}
|
|
974
|
+
return () => {
|
|
975
|
+
this.disconnect();
|
|
976
|
+
};
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
disconnect() {
|
|
980
|
+
if (this.reconnectTimer) {
|
|
981
|
+
clearTimeout(this.reconnectTimer);
|
|
982
|
+
this.reconnectTimer = null;
|
|
983
|
+
}
|
|
984
|
+
if (this.heartbeatTimer) {
|
|
985
|
+
clearInterval(this.heartbeatTimer);
|
|
986
|
+
this.heartbeatTimer = null;
|
|
987
|
+
}
|
|
988
|
+
if (this.webSocket) {
|
|
989
|
+
this.webSocket.close();
|
|
990
|
+
this.webSocket = null;
|
|
991
|
+
}
|
|
992
|
+
this.updateStatus({
|
|
993
|
+
connected: false,
|
|
994
|
+
connecting: false,
|
|
995
|
+
reconnecting: false,
|
|
996
|
+
reconnectAttempts: 0,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
send(message) {
|
|
1000
|
+
if (!this.webSocket || this.webSocket.readyState !== WebSocket.OPEN) {
|
|
1001
|
+
throw new Error('WebSocket is not connected');
|
|
1002
|
+
}
|
|
1003
|
+
const messageWithTimestamp = {
|
|
1004
|
+
...message,
|
|
1005
|
+
timestamp: Date.now(),
|
|
1006
|
+
};
|
|
1007
|
+
this.webSocket.send(JSON.stringify(messageWithTimestamp));
|
|
1008
|
+
}
|
|
1009
|
+
sendRaw(data) {
|
|
1010
|
+
if (!this.webSocket || this.webSocket.readyState !== WebSocket.OPEN) {
|
|
1011
|
+
throw new Error('WebSocket is not connected');
|
|
1012
|
+
}
|
|
1013
|
+
this.webSocket.send(data);
|
|
1014
|
+
}
|
|
1015
|
+
getStatus() {
|
|
1016
|
+
return this.statusSubject.asObservable();
|
|
1017
|
+
}
|
|
1018
|
+
getMessages() {
|
|
1019
|
+
return this.messageSubject
|
|
1020
|
+
.asObservable()
|
|
1021
|
+
.pipe(filter((msg) => true));
|
|
1022
|
+
}
|
|
1023
|
+
setupWebSocketHandlers(config) {
|
|
1024
|
+
if (!this.webSocket)
|
|
1025
|
+
return;
|
|
1026
|
+
this.webSocket.onopen = () => {
|
|
1027
|
+
console.log('[WebSocketService] Connected to:', config.url);
|
|
1028
|
+
this.reconnectAttempts = 0;
|
|
1029
|
+
this.updateStatus({
|
|
1030
|
+
connected: true,
|
|
1031
|
+
connecting: false,
|
|
1032
|
+
reconnecting: false,
|
|
1033
|
+
reconnectAttempts: 0,
|
|
1034
|
+
});
|
|
1035
|
+
// Start heartbeat if configured
|
|
1036
|
+
if (config.heartbeatInterval && config.heartbeatMessage) {
|
|
1037
|
+
this.startHeartbeat(config);
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
this.webSocket.onclose = (event) => {
|
|
1041
|
+
console.log('[WebSocketService] Connection closed:', event.code, event.reason);
|
|
1042
|
+
this.updateStatus({
|
|
1043
|
+
connected: false,
|
|
1044
|
+
connecting: false,
|
|
1045
|
+
reconnecting: false,
|
|
1046
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
1047
|
+
});
|
|
1048
|
+
// Attempt reconnection if not a clean close and reconnect is enabled
|
|
1049
|
+
if (!event.wasClean && config.reconnectInterval && config.maxReconnectAttempts) {
|
|
1050
|
+
this.attemptReconnect(config);
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
this.webSocket.onerror = (error) => {
|
|
1054
|
+
console.error('[WebSocketService] WebSocket error:', error);
|
|
1055
|
+
this.updateStatus({
|
|
1056
|
+
connected: false,
|
|
1057
|
+
connecting: false,
|
|
1058
|
+
reconnecting: false,
|
|
1059
|
+
error: 'WebSocket connection error',
|
|
1060
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
1061
|
+
});
|
|
1062
|
+
};
|
|
1063
|
+
this.webSocket.onmessage = (event) => {
|
|
1064
|
+
try {
|
|
1065
|
+
const message = JSON.parse(event.data);
|
|
1066
|
+
this.messageSubject.next(message);
|
|
1067
|
+
}
|
|
1068
|
+
catch (error) {
|
|
1069
|
+
console.error('[WebSocketService] Error parsing message:', error);
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
startHeartbeat(config) {
|
|
1074
|
+
this.heartbeatTimer = setInterval(() => {
|
|
1075
|
+
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
|
|
1076
|
+
this.send({
|
|
1077
|
+
type: 'heartbeat',
|
|
1078
|
+
data: config.heartbeatMessage,
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
}, config.heartbeatInterval);
|
|
1082
|
+
}
|
|
1083
|
+
attemptReconnect(config) {
|
|
1084
|
+
if (this.reconnectAttempts >= (config.maxReconnectAttempts || 5)) {
|
|
1085
|
+
console.log('[WebSocketService] Max reconnect attempts reached');
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
this.reconnectAttempts++;
|
|
1089
|
+
this.updateStatus({
|
|
1090
|
+
connected: false,
|
|
1091
|
+
connecting: false,
|
|
1092
|
+
reconnecting: true,
|
|
1093
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
1094
|
+
});
|
|
1095
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1096
|
+
console.log(`[WebSocketService] Reconnect attempt ${this.reconnectAttempts}`);
|
|
1097
|
+
this.connect(config);
|
|
1098
|
+
}, config.reconnectInterval || 3000);
|
|
1099
|
+
}
|
|
1100
|
+
updateStatus(status) {
|
|
1101
|
+
const currentStatus = this.getCurrentStatus();
|
|
1102
|
+
const newStatus = { ...currentStatus, ...status };
|
|
1103
|
+
this.statusSubject.next(newStatus);
|
|
1104
|
+
}
|
|
1105
|
+
getCurrentStatus() {
|
|
1106
|
+
return {
|
|
1107
|
+
connected: this.webSocket?.readyState === WebSocket.OPEN,
|
|
1108
|
+
connecting: false,
|
|
1109
|
+
reconnecting: false,
|
|
1110
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
// Direct access to native WebSocket
|
|
1114
|
+
getNativeWebSocket() {
|
|
1115
|
+
return this.webSocket;
|
|
1116
|
+
}
|
|
1117
|
+
isConnected() {
|
|
1118
|
+
return this.webSocket?.readyState === WebSocket.OPEN;
|
|
1119
|
+
}
|
|
1120
|
+
getReadyState() {
|
|
1121
|
+
return this.webSocket?.readyState ?? WebSocket.CLOSED;
|
|
1122
|
+
}
|
|
1123
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1124
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService });
|
|
1125
|
+
}
|
|
1126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService, decorators: [{
|
|
1127
|
+
type: Injectable
|
|
1128
|
+
}] });
|
|
1129
|
+
|
|
1130
|
+
class WebWorkerService extends BrowserApiBaseService {
|
|
1131
|
+
destroyRef = inject(DestroyRef);
|
|
1132
|
+
workers = new Map();
|
|
1133
|
+
workerStatuses = new Map();
|
|
1134
|
+
workerMessages = new Map();
|
|
1135
|
+
currentWorkerStatuses = new Map();
|
|
1136
|
+
getApiName() {
|
|
1137
|
+
return 'webworker';
|
|
1138
|
+
}
|
|
1139
|
+
ensureWorkerSupport() {
|
|
1140
|
+
if (typeof Worker === 'undefined') {
|
|
1141
|
+
throw new Error('Web Workers not supported in this browser');
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
createWorker(name, scriptUrl) {
|
|
1145
|
+
this.ensureWorkerSupport();
|
|
1146
|
+
return new Observable((observer) => {
|
|
1147
|
+
if (this.workers.has(name)) {
|
|
1148
|
+
observer.next(this.currentWorkerStatuses.get(name));
|
|
1149
|
+
return () => {
|
|
1150
|
+
// No-op: workers are managed explicitly via terminateWorker/terminateAllWorkers
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
try {
|
|
1154
|
+
const worker = new Worker(scriptUrl);
|
|
1155
|
+
this.workers.set(name, worker);
|
|
1156
|
+
this.setupWorker(name, worker);
|
|
1157
|
+
const status = {
|
|
1158
|
+
initialized: true,
|
|
1159
|
+
running: true,
|
|
1160
|
+
messageCount: 0,
|
|
1161
|
+
};
|
|
1162
|
+
this.currentWorkerStatuses.set(name, status);
|
|
1163
|
+
this.updateWorkerStatus(name, status);
|
|
1164
|
+
observer.next(status);
|
|
1165
|
+
}
|
|
1166
|
+
catch (error) {
|
|
1167
|
+
console.error(`[WebWorkerService] Failed to create worker ${name}:`, error);
|
|
1168
|
+
const status = {
|
|
1169
|
+
initialized: false,
|
|
1170
|
+
running: false,
|
|
1171
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
1172
|
+
messageCount: 0,
|
|
1173
|
+
};
|
|
1174
|
+
this.currentWorkerStatuses.set(name, status);
|
|
1175
|
+
this.updateWorkerStatus(name, status);
|
|
1176
|
+
observer.next(status);
|
|
1177
|
+
}
|
|
1178
|
+
return () => {
|
|
1179
|
+
// No-op: workers are managed explicitly via terminateWorker/terminateAllWorkers
|
|
1180
|
+
};
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
terminateWorker(name) {
|
|
1184
|
+
const worker = this.workers.get(name);
|
|
1185
|
+
if (worker) {
|
|
1186
|
+
worker.terminate();
|
|
1187
|
+
this.workers.delete(name);
|
|
1188
|
+
this.workerStatuses.delete(name);
|
|
1189
|
+
this.workerMessages.delete(name);
|
|
1190
|
+
this.currentWorkerStatuses.delete(name);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
terminateAllWorkers() {
|
|
1194
|
+
this.workers.forEach((_, name) => {
|
|
1195
|
+
this.terminateWorker(name);
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
postMessage(workerName, task) {
|
|
1199
|
+
const worker = this.workers.get(workerName);
|
|
1200
|
+
if (!worker) {
|
|
1201
|
+
console.error(`[WebWorkerService] Worker ${workerName} not found`);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
try {
|
|
1205
|
+
const message = { ...task, timestamp: Date.now() };
|
|
1206
|
+
if (task.transferable) {
|
|
1207
|
+
worker.postMessage(message, task.transferable);
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
worker.postMessage(message);
|
|
1211
|
+
}
|
|
1212
|
+
const currentStatus = this.currentWorkerStatuses.get(workerName);
|
|
1213
|
+
if (currentStatus) {
|
|
1214
|
+
currentStatus.messageCount++;
|
|
1215
|
+
this.updateWorkerStatus(workerName, currentStatus);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
catch (error) {
|
|
1219
|
+
console.error(`[WebWorkerService] Failed to post message to worker ${workerName}:`, error);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
getMessages(workerName) {
|
|
1223
|
+
if (!this.workerMessages.has(workerName)) {
|
|
1224
|
+
this.workerMessages.set(workerName, new Subject());
|
|
1225
|
+
}
|
|
1226
|
+
return this.workerMessages.get(workerName).asObservable();
|
|
1227
|
+
}
|
|
1228
|
+
getStatus(workerName) {
|
|
1229
|
+
if (!this.workerStatuses.has(workerName)) {
|
|
1230
|
+
this.workerStatuses.set(workerName, new Subject());
|
|
1231
|
+
}
|
|
1232
|
+
return this.workerStatuses.get(workerName).asObservable();
|
|
1233
|
+
}
|
|
1234
|
+
getCurrentStatus(workerName) {
|
|
1235
|
+
return this.currentWorkerStatuses.get(workerName);
|
|
1236
|
+
}
|
|
1237
|
+
getAllStatuses() {
|
|
1238
|
+
return new Map(this.currentWorkerStatuses);
|
|
1239
|
+
}
|
|
1240
|
+
isWorkerRunning(workerName) {
|
|
1241
|
+
const status = this.currentWorkerStatuses.get(workerName);
|
|
1242
|
+
return status?.running ?? false;
|
|
1243
|
+
}
|
|
1244
|
+
setupWorker(name, worker) {
|
|
1245
|
+
worker.onmessage = (event) => {
|
|
1246
|
+
const message = {
|
|
1247
|
+
id: event.data.id || `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
1248
|
+
type: event.data.type || 'message',
|
|
1249
|
+
data: event.data.data,
|
|
1250
|
+
timestamp: event.data.timestamp || Date.now(),
|
|
1251
|
+
};
|
|
1252
|
+
if (!this.workerMessages.has(name)) {
|
|
1253
|
+
this.workerMessages.set(name, new Subject());
|
|
1254
|
+
}
|
|
1255
|
+
this.workerMessages.get(name).next(message);
|
|
1256
|
+
};
|
|
1257
|
+
worker.onerror = (error) => {
|
|
1258
|
+
console.error(`[WebWorkerService] Worker ${name} error:`, error);
|
|
1259
|
+
const status = {
|
|
1260
|
+
initialized: true,
|
|
1261
|
+
running: false,
|
|
1262
|
+
error: error instanceof Error ? error.message : 'Worker error',
|
|
1263
|
+
messageCount: this.currentWorkerStatuses.get(name)?.messageCount ?? 0,
|
|
1264
|
+
};
|
|
1265
|
+
this.currentWorkerStatuses.set(name, status);
|
|
1266
|
+
this.updateWorkerStatus(name, status);
|
|
1267
|
+
};
|
|
1268
|
+
// Auto-cleanup when service is destroyed
|
|
1269
|
+
this.destroyRef.onDestroy(() => {
|
|
1270
|
+
this.terminateWorker(name);
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
updateWorkerStatus(name, status) {
|
|
1274
|
+
if (!this.workerStatuses.has(name)) {
|
|
1275
|
+
this.workerStatuses.set(name, new Subject());
|
|
1276
|
+
}
|
|
1277
|
+
this.workerStatuses.get(name).next(status);
|
|
1278
|
+
}
|
|
1279
|
+
// Direct access to native Worker API
|
|
1280
|
+
getNativeWorker(name) {
|
|
1281
|
+
return this.workers.get(name);
|
|
1282
|
+
}
|
|
1283
|
+
getAllWorkers() {
|
|
1284
|
+
return new Map(this.workers);
|
|
1285
|
+
}
|
|
1286
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebWorkerService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1287
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebWorkerService });
|
|
1288
|
+
}
|
|
1289
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebWorkerService, decorators: [{
|
|
1290
|
+
type: Injectable
|
|
1291
|
+
}] });
|
|
1292
|
+
|
|
1293
|
+
// Common types for browser APIs
|
|
1294
|
+
|
|
535
1295
|
class BrowserSupportUtil {
|
|
536
1296
|
static isSupported(feature) {
|
|
537
1297
|
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
@@ -570,9 +1330,9 @@ class BrowserSupportUtil {
|
|
|
570
1330
|
'clipboard',
|
|
571
1331
|
'clipboard-read',
|
|
572
1332
|
'clipboard-write',
|
|
573
|
-
'persistent-storage'
|
|
1333
|
+
'persistent-storage',
|
|
574
1334
|
];
|
|
575
|
-
return features.filter(feature => !this.isSupported(feature));
|
|
1335
|
+
return features.filter((feature) => !this.isSupported(feature));
|
|
576
1336
|
}
|
|
577
1337
|
static isSecureContext() {
|
|
578
1338
|
return typeof window !== 'undefined' ? window.isSecureContext : false;
|
|
@@ -595,7 +1355,7 @@ class BrowserSupportUtil {
|
|
|
595
1355
|
isChrome: /chrome/.test(userAgent) && !/edge/.test(userAgent),
|
|
596
1356
|
isFirefox: /firefox/.test(userAgent),
|
|
597
1357
|
isSafari: /safari/.test(userAgent) && !/chrome/.test(userAgent),
|
|
598
|
-
isEdge: /edge/.test(userAgent) || /edg/.test(userAgent)
|
|
1358
|
+
isEdge: /edge/.test(userAgent) || /edg/.test(userAgent),
|
|
599
1359
|
};
|
|
600
1360
|
}
|
|
601
1361
|
static getBrowserName(userAgent) {
|
|
@@ -615,26 +1375,22 @@ class BrowserSupportUtil {
|
|
|
615
1375
|
}
|
|
616
1376
|
}
|
|
617
1377
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const permission = route.data?.['permission'];
|
|
1378
|
+
/**
|
|
1379
|
+
* Functional guard that checks if the user has the required permission.
|
|
1380
|
+
* Usage in routes: { canActivate: [permissionGuard('camera')] }
|
|
1381
|
+
*/
|
|
1382
|
+
const permissionGuard = (permission) => {
|
|
1383
|
+
return async (_route) => {
|
|
1384
|
+
const permissionsService = inject(PermissionsService);
|
|
1385
|
+
const router = inject(Router);
|
|
627
1386
|
if (!permission) {
|
|
628
|
-
return
|
|
1387
|
+
return true;
|
|
629
1388
|
}
|
|
630
|
-
return this.checkPermission(permission);
|
|
631
|
-
}
|
|
632
|
-
async checkPermission(permission) {
|
|
633
1389
|
try {
|
|
634
|
-
const status = await
|
|
1390
|
+
const status = await permissionsService.query({ name: permission });
|
|
635
1391
|
if (status.state !== 'granted') {
|
|
636
|
-
|
|
637
|
-
queryParams: { permission }
|
|
1392
|
+
router.navigate(['/permission-denied'], {
|
|
1393
|
+
queryParams: { permission },
|
|
638
1394
|
});
|
|
639
1395
|
return false;
|
|
640
1396
|
}
|
|
@@ -642,18 +1398,99 @@ class PermissionGuard {
|
|
|
642
1398
|
}
|
|
643
1399
|
catch (error) {
|
|
644
1400
|
console.error('Permission guard error:', error);
|
|
645
|
-
|
|
646
|
-
queryParams: { permission }
|
|
1401
|
+
router.navigate(['/permission-denied'], {
|
|
1402
|
+
queryParams: { permission },
|
|
647
1403
|
});
|
|
648
1404
|
return false;
|
|
649
1405
|
}
|
|
1406
|
+
};
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
const defaultBrowserWebApisConfig = {
|
|
1410
|
+
enableCamera: true,
|
|
1411
|
+
enableGeolocation: true,
|
|
1412
|
+
enableNotifications: true,
|
|
1413
|
+
enableClipboard: true,
|
|
1414
|
+
enableBattery: false,
|
|
1415
|
+
enableMediaDevices: true,
|
|
1416
|
+
enableWebShare: false,
|
|
1417
|
+
enableWebStorage: false,
|
|
1418
|
+
enableWebSocket: false,
|
|
1419
|
+
enableWebWorker: false,
|
|
1420
|
+
};
|
|
1421
|
+
function provideBrowserWebApis(config = {}) {
|
|
1422
|
+
const mergedConfig = { ...defaultBrowserWebApisConfig, ...config };
|
|
1423
|
+
const providers = [PermissionsService];
|
|
1424
|
+
const conditionalProviders = [
|
|
1425
|
+
[mergedConfig.enableCamera, CameraService],
|
|
1426
|
+
[mergedConfig.enableGeolocation, GeolocationService],
|
|
1427
|
+
[mergedConfig.enableNotifications, NotificationService],
|
|
1428
|
+
[mergedConfig.enableClipboard, ClipboardService],
|
|
1429
|
+
[mergedConfig.enableMediaDevices, MediaDevicesService],
|
|
1430
|
+
[mergedConfig.enableBattery, BatteryService],
|
|
1431
|
+
[mergedConfig.enableWebShare, WebShareService],
|
|
1432
|
+
[mergedConfig.enableWebStorage, WebStorageService],
|
|
1433
|
+
[mergedConfig.enableWebSocket, WebSocketService],
|
|
1434
|
+
[mergedConfig.enableWebWorker, WebWorkerService],
|
|
1435
|
+
];
|
|
1436
|
+
for (const [enabled, provider] of conditionalProviders) {
|
|
1437
|
+
if (enabled) {
|
|
1438
|
+
providers.push(provider);
|
|
1439
|
+
}
|
|
650
1440
|
}
|
|
1441
|
+
return makeEnvironmentProviders(providers);
|
|
651
1442
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
1443
|
+
// Feature-specific providers for tree-shaking
|
|
1444
|
+
function provideCamera() {
|
|
1445
|
+
return makeEnvironmentProviders([PermissionsService, CameraService]);
|
|
1446
|
+
}
|
|
1447
|
+
function provideGeolocation() {
|
|
1448
|
+
return makeEnvironmentProviders([PermissionsService, GeolocationService]);
|
|
1449
|
+
}
|
|
1450
|
+
function provideNotifications() {
|
|
1451
|
+
return makeEnvironmentProviders([PermissionsService, NotificationService]);
|
|
1452
|
+
}
|
|
1453
|
+
function provideClipboard() {
|
|
1454
|
+
return makeEnvironmentProviders([PermissionsService, ClipboardService]);
|
|
1455
|
+
}
|
|
1456
|
+
function provideMediaDevices() {
|
|
1457
|
+
return makeEnvironmentProviders([PermissionsService, MediaDevicesService]);
|
|
1458
|
+
}
|
|
1459
|
+
function provideBattery() {
|
|
1460
|
+
return makeEnvironmentProviders([BatteryService]);
|
|
1461
|
+
}
|
|
1462
|
+
function provideWebShare() {
|
|
1463
|
+
return makeEnvironmentProviders([WebShareService]);
|
|
1464
|
+
}
|
|
1465
|
+
function provideWebStorage() {
|
|
1466
|
+
return makeEnvironmentProviders([WebStorageService]);
|
|
1467
|
+
}
|
|
1468
|
+
function provideWebSocket() {
|
|
1469
|
+
return makeEnvironmentProviders([WebSocketService]);
|
|
1470
|
+
}
|
|
1471
|
+
function provideWebWorker() {
|
|
1472
|
+
return makeEnvironmentProviders([WebWorkerService]);
|
|
1473
|
+
}
|
|
1474
|
+
function providePermissions() {
|
|
1475
|
+
return makeEnvironmentProviders([PermissionsService]);
|
|
1476
|
+
}
|
|
1477
|
+
// Combined providers for common use cases
|
|
1478
|
+
function provideMediaApis() {
|
|
1479
|
+
return makeEnvironmentProviders([PermissionsService, CameraService, MediaDevicesService]);
|
|
1480
|
+
}
|
|
1481
|
+
function provideLocationApis() {
|
|
1482
|
+
return makeEnvironmentProviders([PermissionsService, GeolocationService]);
|
|
1483
|
+
}
|
|
1484
|
+
function provideStorageApis() {
|
|
1485
|
+
return makeEnvironmentProviders([PermissionsService, ClipboardService, WebStorageService]);
|
|
1486
|
+
}
|
|
1487
|
+
function provideCommunicationApis() {
|
|
1488
|
+
return makeEnvironmentProviders([
|
|
1489
|
+
PermissionsService,
|
|
1490
|
+
NotificationService,
|
|
1491
|
+
WebShareService,
|
|
1492
|
+
WebSocketService,
|
|
1493
|
+
]);
|
|
657
1494
|
}
|
|
658
1495
|
|
|
659
1496
|
// Browser Web APIs Services
|
|
@@ -664,5 +1501,4 @@ const version = '0.1.0';
|
|
|
664
1501
|
* Generated bundle index. Do not edit.
|
|
665
1502
|
*/
|
|
666
1503
|
|
|
667
|
-
export { BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, GeolocationService, MediaDevicesService, NotificationService,
|
|
668
|
-
//# sourceMappingURL=angular-helpers-browser-web-apis.mjs.map
|
|
1504
|
+
export { BatteryService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, GeolocationService, MediaDevicesService, NotificationService, PermissionsService, WebShareService, WebSocketService, WebStorageService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, permissionGuard, provideBattery, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideGeolocation, provideLocationApis, provideMediaApis, provideMediaDevices, provideNotifications, providePermissions, provideStorageApis, provideWebShare, provideWebSocket, provideWebStorage, provideWebWorker, version };
|