@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 (!('Notification' in window)) {
397
+ if (!this.isSupported()) {
383
398
  throw new Error('Notification API not supported in this browser');
384
399
  }
385
- const permissionStatus = await this.permissionsService.query({ name: 'notifications' });
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
- class PermissionGuard {
619
- permissionsService;
620
- router;
621
- constructor(permissionsService, router) {
622
- this.permissionsService = permissionsService;
623
- this.router = router;
624
- }
625
- canActivate(route) {
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 Promise.resolve(true);
1387
+ return true;
629
1388
  }
630
- return this.checkPermission(permission);
631
- }
632
- async checkPermission(permission) {
633
1389
  try {
634
- const status = await this.permissionsService.query({ name: permission });
1390
+ const status = await permissionsService.query({ name: permission });
635
1391
  if (status.state !== 'granted') {
636
- this.router.navigate(['/permission-denied'], {
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
- this.router.navigate(['/permission-denied'], {
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
- function createPermissionGuard(permission) {
653
- return {
654
- canActivate: [PermissionGuard],
655
- data: { permission }
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, PermissionGuard, PermissionsService, createPermissionGuard, version };
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 };