@angular-helpers/browser-web-apis 21.5.0 → 21.7.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,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, DestroyRef, PLATFORM_ID, Injectable, signal, computed, isSignal, effect, ElementRef, makeEnvironmentProviders } from '@angular/core';
3
3
  import { isPlatformBrowser, isPlatformServer } from '@angular/common';
4
- import { Observable, fromEvent, BehaviorSubject, 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';
4
+ import { Observable, fromEvent, Subject, of, map as map$1 } from 'rxjs';
5
+ import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
+ import { filter, map, distinctUntilChanged } from 'rxjs/operators';
7
7
  import { Router } from '@angular/router';
8
8
 
9
9
  const BROWSER_API_LOGGER = new InjectionToken('BROWSER_API_LOGGER', {
@@ -779,255 +779,340 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
779
779
  type: Injectable
780
780
  }] });
781
781
 
782
- class WebStorageService extends BrowserApiBaseService {
783
- storageEvents = signal(null, ...(ngDevMode ? [{ debugName: "storageEvents" }] : /* istanbul ignore next */ []));
784
- constructor() {
785
- super();
786
- this.setupEventListeners();
787
- }
788
- getApiName() {
789
- return 'storage';
790
- }
791
- ensureSupported() {
792
- super.ensureSupported();
793
- if (typeof Storage === 'undefined') {
794
- throw new Error('Storage API not supported in this browser');
795
- }
796
- }
797
- setupEventListeners() {
798
- if (this.isBrowserEnvironment()) {
799
- fromEvent(window, 'storage')
800
- .pipe(takeUntilDestroyed(this.destroyRef))
801
- .subscribe((event) => {
802
- const storageEvent = event;
803
- const area = storageEvent.storageArea === localStorage ? 'localStorage' : 'sessionStorage';
804
- this.storageEvents.set({
805
- key: storageEvent.key,
806
- newValue: storageEvent.newValue ? this.deserializeValue(storageEvent.newValue) : null,
807
- oldValue: storageEvent.oldValue ? this.deserializeValue(storageEvent.oldValue) : null,
808
- storageArea: area,
809
- });
810
- });
811
- }
812
- }
813
- serializeValue(value, options) {
814
- if (options?.serialize) {
815
- return options.serialize(value);
816
- }
817
- return JSON.stringify(value);
782
+ const SECURITY_WARN_KEY = Symbol('storage-security-warned');
783
+ /**
784
+ * Implementation of `StorageNamespace` shared by `local` and `session` namespaces.
785
+ * Wraps every native access in try/catch so Safari private mode and sandboxed iframes
786
+ * (which throw `SecurityError`) degrade gracefully instead of crashing the app.
787
+ */
788
+ class StorageNamespaceImpl {
789
+ area;
790
+ events;
791
+ logger;
792
+ supportedCache = null;
793
+ constructor(area, events, logger) {
794
+ this.area = area;
795
+ this.events = events;
796
+ this.logger = logger;
818
797
  }
819
- deserializeValue(value, options) {
820
- if (value === null)
821
- return null;
822
- if (options?.deserialize) {
823
- return options.deserialize(value);
798
+ isSupported() {
799
+ if (this.supportedCache !== null)
800
+ return this.supportedCache;
801
+ if (typeof window === 'undefined' || typeof Storage === 'undefined') {
802
+ this.supportedCache = false;
803
+ return false;
824
804
  }
825
805
  try {
826
- return JSON.parse(value);
806
+ const store = this.getStore();
807
+ const probe = `__bwa_probe_${Date.now()}__`;
808
+ store.setItem(probe, '1');
809
+ store.removeItem(probe);
810
+ this.supportedCache = true;
827
811
  }
828
812
  catch {
829
- return value;
813
+ this.warnSecurityOnce();
814
+ this.supportedCache = false;
830
815
  }
816
+ return this.supportedCache;
831
817
  }
832
- getKey(key, options) {
833
- const prefix = options?.prefix || '';
834
- return prefix ? `${prefix}:${key}` : key;
835
- }
836
- emitStorageChange(fullKey, newValue, oldValue, area) {
837
- this.storageEvents.set({ key: fullKey, newValue, oldValue, storageArea: area });
838
- }
839
- // Local Storage Methods
840
- setLocalStorage(key, value, options = {}) {
841
- this.ensureSupported();
818
+ set(key, value, opts = {}) {
819
+ if (!this.isSupported())
820
+ return false;
842
821
  try {
843
- const serializedValue = this.serializeValue(value, options);
844
- const fullKey = this.getKey(key, options);
845
- const oldRaw = localStorage.getItem(fullKey);
846
- const oldValue = oldRaw !== null ? this.deserializeValue(oldRaw, options) : null;
847
- localStorage.setItem(fullKey, serializedValue);
848
- this.emitStorageChange(fullKey, value, oldValue, 'localStorage');
822
+ const fullKey = this.getKey(key, opts);
823
+ const oldRaw = this.getStore().getItem(fullKey);
824
+ const oldValue = oldRaw !== null ? this.deserializeValue(oldRaw, opts) : null;
825
+ const serialized = this.serializeValue(value, opts);
826
+ this.getStore().setItem(fullKey, serialized);
827
+ this.events.emit({ key: fullKey, newValue: value, oldValue, storageArea: this.area });
849
828
  return true;
850
829
  }
851
830
  catch (error) {
852
- this.logError('Error setting localStorage:', error);
831
+ this.logger.error(`[storage:${this.area}] set("${key}") failed`, error);
853
832
  return false;
854
833
  }
855
834
  }
856
- getLocalStorage(key, defaultValue = null, options = {}) {
857
- this.ensureSupported();
835
+ get(key, defaultValue = null, opts = {}) {
836
+ if (!this.isSupported())
837
+ return defaultValue;
858
838
  try {
859
- const fullKey = this.getKey(key, options);
860
- const value = localStorage.getItem(fullKey);
861
- return value !== null ? this.deserializeValue(value, options) : defaultValue;
839
+ const fullKey = this.getKey(key, opts);
840
+ const raw = this.getStore().getItem(fullKey);
841
+ return raw !== null ? this.deserializeValue(raw, opts) : defaultValue;
862
842
  }
863
843
  catch (error) {
864
- this.logError('Error getting localStorage:', error);
844
+ this.logger.error(`[storage:${this.area}] get("${key}") failed`, error);
865
845
  return defaultValue;
866
846
  }
867
847
  }
868
- removeLocalStorage(key, options = {}) {
869
- this.ensureSupported();
848
+ remove(key, opts = {}) {
849
+ if (!this.isSupported())
850
+ return false;
870
851
  try {
871
- const fullKey = this.getKey(key, options);
872
- const oldRaw = localStorage.getItem(fullKey);
873
- const oldValue = oldRaw !== null ? this.deserializeValue(oldRaw, options) : null;
874
- localStorage.removeItem(fullKey);
875
- this.emitStorageChange(fullKey, null, oldValue, 'localStorage');
852
+ const fullKey = this.getKey(key, opts);
853
+ const oldRaw = this.getStore().getItem(fullKey);
854
+ const oldValue = oldRaw !== null ? this.deserializeValue(oldRaw, opts) : null;
855
+ this.getStore().removeItem(fullKey);
856
+ this.events.emit({ key: fullKey, newValue: null, oldValue, storageArea: this.area });
876
857
  return true;
877
858
  }
878
859
  catch (error) {
879
- this.logError('Error removing localStorage:', error);
860
+ this.logger.error(`[storage:${this.area}] remove("${key}") failed`, error);
880
861
  return false;
881
862
  }
882
863
  }
883
- clearLocalStorage(options = {}) {
884
- this.ensureSupported();
864
+ clear(opts = {}) {
865
+ if (!this.isSupported())
866
+ return false;
885
867
  try {
886
- const prefix = options?.prefix;
868
+ const prefix = opts?.prefix;
869
+ const store = this.getStore();
887
870
  if (prefix) {
888
- const keysToRemove = [];
889
- for (let i = 0; i < localStorage.length; i++) {
890
- const key = localStorage.key(i);
891
- if (key && key.startsWith(`${prefix}:`)) {
892
- keysToRemove.push(key);
893
- }
871
+ const toRemove = [];
872
+ for (let i = 0; i < store.length; i++) {
873
+ const k = store.key(i);
874
+ if (k && k.startsWith(`${prefix}:`))
875
+ toRemove.push(k);
876
+ }
877
+ for (const k of toRemove) {
878
+ const oldRaw = store.getItem(k);
879
+ store.removeItem(k);
880
+ this.events.emit({
881
+ key: k,
882
+ newValue: null,
883
+ oldValue: oldRaw,
884
+ storageArea: this.area,
885
+ });
894
886
  }
895
- keysToRemove.forEach((key) => {
896
- const oldRaw = localStorage.getItem(key);
897
- localStorage.removeItem(key);
898
- this.emitStorageChange(key, null, oldRaw, 'localStorage');
899
- });
900
887
  }
901
888
  else {
902
- localStorage.clear();
903
- this.emitStorageChange(null, null, null, 'localStorage');
889
+ store.clear();
890
+ this.events.emit({ key: null, newValue: null, oldValue: null, storageArea: this.area });
904
891
  }
905
892
  return true;
906
893
  }
907
894
  catch (error) {
908
- this.logError('Error clearing localStorage:', error);
895
+ this.logger.error(`[storage:${this.area}] clear() failed`, error);
909
896
  return false;
910
897
  }
911
898
  }
912
- // Session Storage Methods
913
- setSessionStorage(key, value, options = {}) {
914
- this.ensureSupported();
899
+ size(opts = {}) {
900
+ if (!this.isSupported())
901
+ return 0;
915
902
  try {
916
- const serializedValue = this.serializeValue(value, options);
917
- const fullKey = this.getKey(key, options);
918
- const oldRaw = sessionStorage.getItem(fullKey);
919
- const oldValue = oldRaw !== null ? this.deserializeValue(oldRaw, options) : null;
920
- sessionStorage.setItem(fullKey, serializedValue);
921
- this.emitStorageChange(fullKey, value, oldValue, 'sessionStorage');
922
- return true;
903
+ const store = this.getStore();
904
+ const prefix = opts?.prefix;
905
+ let total = 0;
906
+ for (let i = 0; i < store.length; i++) {
907
+ const k = store.key(i);
908
+ if (k && (!prefix || k.startsWith(`${prefix}:`))) {
909
+ total += (store.getItem(k)?.length ?? 0) + k.length;
910
+ }
911
+ }
912
+ return total;
923
913
  }
924
914
  catch (error) {
925
- this.logError('Error setting sessionStorage:', error);
926
- return false;
915
+ this.logger.error(`[storage:${this.area}] size() failed`, error);
916
+ return 0;
927
917
  }
928
918
  }
929
- getSessionStorage(key, defaultValue = null, options = {}) {
930
- this.ensureSupported();
931
- try {
932
- const fullKey = this.getKey(key, options);
933
- const value = sessionStorage.getItem(fullKey);
934
- return value !== null ? this.deserializeValue(value, options) : defaultValue;
935
- }
936
- catch (error) {
937
- this.logError('Error getting sessionStorage:', error);
938
- return defaultValue;
919
+ watch(key, opts = {}) {
920
+ const fullKey = this.getKey(key, opts);
921
+ return this.events.events$.pipe(filter((event) => event.storageArea === this.area && (event.key === null || event.key === fullKey)), map((event) => event.newValue));
922
+ }
923
+ native() {
924
+ if (!this.isSupported()) {
925
+ throw new Error(`${this.area} not supported in this environment`);
939
926
  }
927
+ return this.getStore();
940
928
  }
941
- removeSessionStorage(key, options = {}) {
942
- this.ensureSupported();
929
+ getStore() {
930
+ return this.area === 'localStorage' ? window.localStorage : window.sessionStorage;
931
+ }
932
+ getKey(key, opts) {
933
+ const prefix = opts?.prefix ?? '';
934
+ return prefix ? `${prefix}:${key}` : key;
935
+ }
936
+ serializeValue(value, opts) {
937
+ if (opts?.serialize)
938
+ return opts.serialize(value);
939
+ return JSON.stringify(value);
940
+ }
941
+ deserializeValue(value, opts) {
942
+ if (opts?.deserialize)
943
+ return opts.deserialize(value);
943
944
  try {
944
- const fullKey = this.getKey(key, options);
945
- const oldRaw = sessionStorage.getItem(fullKey);
946
- const oldValue = oldRaw !== null ? this.deserializeValue(oldRaw, options) : null;
947
- sessionStorage.removeItem(fullKey);
948
- this.emitStorageChange(fullKey, null, oldValue, 'sessionStorage');
949
- return true;
945
+ return JSON.parse(value);
950
946
  }
951
- catch (error) {
952
- this.logError('Error removing sessionStorage:', error);
953
- return false;
947
+ catch {
948
+ return value;
954
949
  }
955
950
  }
956
- clearSessionStorage(options = {}) {
957
- this.ensureSupported();
951
+ warnSecurityOnce() {
952
+ const holder = globalThis;
953
+ if (!holder[SECURITY_WARN_KEY])
954
+ holder[SECURITY_WARN_KEY] = new Set();
955
+ if (holder[SECURITY_WARN_KEY].has(this.area))
956
+ return;
957
+ holder[SECURITY_WARN_KEY].add(this.area);
958
+ this.logger.warn(`[storage:${this.area}] access denied (SecurityError). Common causes: Safari private mode, ` +
959
+ 'sandboxed iframes, or browser storage disabled. Falling back to default values.');
960
+ }
961
+ }
962
+
963
+ let legacyDeprecationLogged$1 = false;
964
+ /**
965
+ * Web Storage service with two namespaces (`local`, `session`) sharing one method
966
+ * surface. SecurityError-safe (Safari private mode, sandboxed iframes return defaults
967
+ * instead of throwing).
968
+ *
969
+ * Preferred usage:
970
+ * ```ts
971
+ * const storage = inject(WebStorageService);
972
+ * storage.local.set('user', { id: 1 });
973
+ * const user = storage.local.get<{ id: number }>('user');
974
+ * storage.local.watch<{ id: number }>('user').subscribe(console.log);
975
+ * ```
976
+ *
977
+ * Legacy methods (`setLocalStorage`, `getLocalStorage`, etc.) remain as deprecated
978
+ * wrappers for one minor cycle; removal slated for v22.
979
+ */
980
+ class WebStorageService extends BrowserApiBaseService {
981
+ storageLogger = inject(BROWSER_API_LOGGER);
982
+ storageEvents = signal(null, ...(ngDevMode ? [{ debugName: "storageEvents" }] : /* istanbul ignore next */ []));
983
+ eventBus = {
984
+ emit: (event) => this.storageEvents.set(event),
985
+ events$: toObservable(this.storageEvents).pipe(filter((event) => event !== null), distinctUntilChanged((a, b) => a.key === b.key &&
986
+ a.newValue === b.newValue &&
987
+ a.oldValue === b.oldValue &&
988
+ a.storageArea === b.storageArea)),
989
+ };
990
+ /** Local storage namespace. */
991
+ local = new StorageNamespaceImpl('localStorage', this.eventBus, this.storageLogger);
992
+ /** Session storage namespace. */
993
+ session = new StorageNamespaceImpl('sessionStorage', this.eventBus, this.storageLogger);
994
+ constructor() {
995
+ super();
996
+ this.setupCrossTabListener();
997
+ }
998
+ getApiName() {
999
+ return 'storage';
1000
+ }
1001
+ ensureSupported() {
1002
+ super.ensureSupported();
1003
+ if (typeof Storage === 'undefined') {
1004
+ throw new Error('Storage API not supported in this browser');
1005
+ }
1006
+ }
1007
+ /** Returns true if either local or session storage is usable. */
1008
+ isSupported() {
1009
+ return this.local.isSupported() || this.session.isSupported();
1010
+ }
1011
+ /** Stream of every storage mutation observed in this tab or other tabs. */
1012
+ getStorageEvents() {
1013
+ return this.eventBus.events$;
1014
+ }
1015
+ setupCrossTabListener() {
1016
+ if (!this.isBrowserEnvironment())
1017
+ return;
1018
+ fromEvent(window, 'storage')
1019
+ .pipe(takeUntilDestroyed(this.destroyRef))
1020
+ .subscribe((event) => {
1021
+ const area = event.storageArea === window.localStorage ? 'localStorage' : 'sessionStorage';
1022
+ this.storageEvents.set({
1023
+ key: event.key,
1024
+ newValue: event.newValue ? this.safeParse(event.newValue) : null,
1025
+ oldValue: event.oldValue ? this.safeParse(event.oldValue) : null,
1026
+ storageArea: area,
1027
+ });
1028
+ });
1029
+ }
1030
+ safeParse(value) {
958
1031
  try {
959
- const prefix = options?.prefix;
960
- if (prefix) {
961
- const keysToRemove = [];
962
- for (let i = 0; i < sessionStorage.length; i++) {
963
- const key = sessionStorage.key(i);
964
- if (key && key.startsWith(`${prefix}:`)) {
965
- keysToRemove.push(key);
966
- }
967
- }
968
- keysToRemove.forEach((key) => {
969
- const oldRaw = sessionStorage.getItem(key);
970
- sessionStorage.removeItem(key);
971
- this.emitStorageChange(key, null, oldRaw, 'sessionStorage');
972
- });
973
- }
974
- else {
975
- sessionStorage.clear();
976
- this.emitStorageChange(null, null, null, 'sessionStorage');
977
- }
978
- return true;
1032
+ return JSON.parse(value);
979
1033
  }
980
- catch (error) {
981
- this.logError('Error clearing sessionStorage:', error);
982
- return false;
1034
+ catch {
1035
+ return value;
983
1036
  }
984
1037
  }
985
- // Utility Methods
1038
+ // ---------- legacy API (deprecated) ----------
1039
+ /** @deprecated Use `storage.local.set(key, value, opts)`. Removed in v22. */
1040
+ setLocalStorage(key, value, options = {}) {
1041
+ this.warnLegacyOnce();
1042
+ return this.local.set(key, value, options);
1043
+ }
1044
+ /** @deprecated Use `storage.local.get(key, defaultValue, opts)`. Removed in v22. */
1045
+ getLocalStorage(key, defaultValue = null, options = {}) {
1046
+ this.warnLegacyOnce();
1047
+ return this.local.get(key, defaultValue, options);
1048
+ }
1049
+ /** @deprecated Use `storage.local.remove(key, opts)`. Removed in v22. */
1050
+ removeLocalStorage(key, options = {}) {
1051
+ this.warnLegacyOnce();
1052
+ return this.local.remove(key, options);
1053
+ }
1054
+ /** @deprecated Use `storage.local.clear(opts)`. Removed in v22. */
1055
+ clearLocalStorage(options = {}) {
1056
+ this.warnLegacyOnce();
1057
+ return this.local.clear(options);
1058
+ }
1059
+ /** @deprecated Use `storage.session.set(key, value, opts)`. Removed in v22. */
1060
+ setSessionStorage(key, value, options = {}) {
1061
+ this.warnLegacyOnce();
1062
+ return this.session.set(key, value, options);
1063
+ }
1064
+ /** @deprecated Use `storage.session.get(key, defaultValue, opts)`. Removed in v22. */
1065
+ getSessionStorage(key, defaultValue = null, options = {}) {
1066
+ this.warnLegacyOnce();
1067
+ return this.session.get(key, defaultValue, options);
1068
+ }
1069
+ /** @deprecated Use `storage.session.remove(key, opts)`. Removed in v22. */
1070
+ removeSessionStorage(key, options = {}) {
1071
+ this.warnLegacyOnce();
1072
+ return this.session.remove(key, options);
1073
+ }
1074
+ /** @deprecated Use `storage.session.clear(opts)`. Removed in v22. */
1075
+ clearSessionStorage(options = {}) {
1076
+ this.warnLegacyOnce();
1077
+ return this.session.clear(options);
1078
+ }
1079
+ /** @deprecated Use `storage.local.size(opts)`. Removed in v22. */
986
1080
  getLocalStorageSize(options = {}) {
987
- this.ensureSupported();
988
- let totalSize = 0;
989
- const prefix = options?.prefix;
990
- for (let i = 0; i < localStorage.length; i++) {
991
- const key = localStorage.key(i);
992
- if (key && (!prefix || key.startsWith(`${prefix}:`))) {
993
- totalSize += (localStorage.getItem(key)?.length || 0) + key.length;
994
- }
995
- }
996
- return totalSize;
1081
+ this.warnLegacyOnce();
1082
+ return this.local.size(options);
997
1083
  }
1084
+ /** @deprecated Use `storage.session.size(opts)`. Removed in v22. */
998
1085
  getSessionStorageSize(options = {}) {
999
- this.ensureSupported();
1000
- let totalSize = 0;
1001
- const prefix = options?.prefix;
1002
- for (let i = 0; i < sessionStorage.length; i++) {
1003
- const key = sessionStorage.key(i);
1004
- if (key && (!prefix || key.startsWith(`${prefix}:`))) {
1005
- totalSize += (sessionStorage.getItem(key)?.length || 0) + key.length;
1006
- }
1007
- }
1008
- return totalSize;
1009
- }
1010
- getStorageEvents() {
1011
- return toObservable(this.storageEvents).pipe(filter((event) => event !== null), distinctUntilChanged((prev, curr) => prev.key === curr.key &&
1012
- prev.newValue === curr.newValue &&
1013
- prev.oldValue === curr.oldValue));
1086
+ this.warnLegacyOnce();
1087
+ return this.session.size(options);
1014
1088
  }
1089
+ /** @deprecated Use `storage.local.watch<T>(key, opts)`. Removed in v22. */
1015
1090
  watchLocalStorage(key, options = {}) {
1016
- const fullKey = this.getKey(key, options);
1017
- return this.getStorageEvents().pipe(filter((event) => event.storageArea === 'localStorage' && (event.key === null || event.key === fullKey)), map((event) => event.newValue));
1091
+ this.warnLegacyOnce();
1092
+ return this.local.watch(key, options);
1018
1093
  }
1094
+ /** @deprecated Use `storage.session.watch<T>(key, opts)`. Removed in v22. */
1019
1095
  watchSessionStorage(key, options = {}) {
1020
- const fullKey = this.getKey(key, options);
1021
- return this.getStorageEvents().pipe(filter((event) => event.storageArea === 'sessionStorage' && (event.key === null || event.key === fullKey)), map((event) => event.newValue));
1096
+ this.warnLegacyOnce();
1097
+ return this.session.watch(key, options);
1022
1098
  }
1023
- // Direct access to native storage APIs
1099
+ /** @deprecated Use `storage.local.native()`. Removed in v22. */
1024
1100
  getNativeLocalStorage() {
1025
- this.ensureSupported();
1026
- return localStorage;
1101
+ this.warnLegacyOnce();
1102
+ return this.local.native();
1027
1103
  }
1104
+ /** @deprecated Use `storage.session.native()`. Removed in v22. */
1028
1105
  getNativeSessionStorage() {
1029
- this.ensureSupported();
1030
- return sessionStorage;
1106
+ this.warnLegacyOnce();
1107
+ return this.session.native();
1108
+ }
1109
+ warnLegacyOnce() {
1110
+ if (legacyDeprecationLogged$1)
1111
+ return;
1112
+ legacyDeprecationLogged$1 = true;
1113
+ this.storageLogger.warn('[storage] WebStorageService.{set,get,remove,clear,watch}{Local,Session}Storage are ' +
1114
+ 'deprecated. Use storage.local and storage.session namespaces. Legacy methods will be ' +
1115
+ 'removed in v22.');
1031
1116
  }
1032
1117
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1033
1118
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebStorageService });
@@ -1036,19 +1121,288 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
1036
1121
  type: Injectable
1037
1122
  }], ctorParameters: () => [] });
1038
1123
 
1039
- class WebSocketService extends BrowserApiBaseService {
1040
- webSocket = null;
1041
- statusSubject = new BehaviorSubject({
1042
- connected: false,
1043
- connecting: false,
1044
- reconnecting: false,
1045
- reconnectAttempts: 0,
1046
- });
1047
- messageSubject = new Subject();
1048
- reconnectAttempts = 0;
1124
+ const DEFAULT_MAX_RECONNECT_DELAY = 30_000;
1125
+ const DEFAULT_REQUEST_TIMEOUT = 30_000;
1126
+ /**
1127
+ * Stateful WebSocket client wrapping a single connection. One instance per logical
1128
+ * connection (do NOT share between `connect()` calls).
1129
+ *
1130
+ * Surfaces:
1131
+ * - `status`: signal of the current connection state.
1132
+ * - `messages$`: stream of every received message (parsed JSON).
1133
+ * - `send` / `sendRaw`: outbound traffic.
1134
+ * - `request<T>(type, data)`: round-trip with id correlation and timeout.
1135
+ * - `close`: idempotent disposal.
1136
+ *
1137
+ * Reconnect uses exponential backoff with jitter, capped by `maxReconnectDelay`.
1138
+ */
1139
+ class WebSocketClient {
1140
+ config;
1141
+ logger;
1142
+ socket = null;
1049
1143
  reconnectTimer = null;
1050
1144
  heartbeatTimer = null;
1051
- _cleanup = this.destroyRef.onDestroy(() => this.disconnect());
1145
+ _status;
1146
+ _messages$ = new Subject();
1147
+ pendingRequests = new Map();
1148
+ disposed = false;
1149
+ reconnectAttempts = 0;
1150
+ constructor(config, logger, destroyRef) {
1151
+ this.config = config;
1152
+ this.logger = logger;
1153
+ this._status = signal({
1154
+ state: 'idle',
1155
+ reconnectAttempts: 0,
1156
+ error: null,
1157
+ }, ...(ngDevMode ? [{ debugName: "_status" }] : /* istanbul ignore next */ []));
1158
+ if (destroyRef) {
1159
+ destroyRef.onDestroy(() => this.close());
1160
+ }
1161
+ this.openSocket();
1162
+ }
1163
+ get status() {
1164
+ return this._status.asReadonly();
1165
+ }
1166
+ get messages$() {
1167
+ return this._messages$.asObservable();
1168
+ }
1169
+ messagesByType(type) {
1170
+ return this._messages$
1171
+ .asObservable()
1172
+ .pipe(filter((msg) => msg.type === type));
1173
+ }
1174
+ send(message) {
1175
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
1176
+ throw new Error('WebSocket is not connected');
1177
+ }
1178
+ const enriched = {
1179
+ ...message,
1180
+ id: message.id ?? this.generateId(),
1181
+ timestamp: message.timestamp ?? Date.now(),
1182
+ };
1183
+ this.socket.send(JSON.stringify(enriched));
1184
+ }
1185
+ sendRaw(data) {
1186
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
1187
+ throw new Error('WebSocket is not connected');
1188
+ }
1189
+ this.socket.send(data);
1190
+ }
1191
+ /**
1192
+ * Send a message and await a correlated response. The server MUST echo back the
1193
+ * `correlationId` from the request as `correlationId` on the response message.
1194
+ */
1195
+ request(type, data, opts) {
1196
+ const id = this.generateId();
1197
+ const timeoutMs = opts?.timeout ?? DEFAULT_REQUEST_TIMEOUT;
1198
+ return new Promise((resolve, reject) => {
1199
+ const timer = setTimeout(() => {
1200
+ this.pendingRequests.delete(id);
1201
+ reject(new Error(`WebSocket request timeout after ${timeoutMs}ms`));
1202
+ }, timeoutMs);
1203
+ this.pendingRequests.set(id, {
1204
+ resolve: resolve,
1205
+ reject,
1206
+ timer,
1207
+ });
1208
+ try {
1209
+ this.send({ id, type, data, correlationId: id });
1210
+ }
1211
+ catch (error) {
1212
+ clearTimeout(timer);
1213
+ this.pendingRequests.delete(id);
1214
+ reject(error instanceof Error ? error : new Error('WebSocket send failed'));
1215
+ }
1216
+ });
1217
+ }
1218
+ close() {
1219
+ if (this.disposed)
1220
+ return;
1221
+ this.disposed = true;
1222
+ this.clearTimers();
1223
+ if (this.socket) {
1224
+ try {
1225
+ this.socket.close();
1226
+ }
1227
+ catch {
1228
+ // Ignore — already closing.
1229
+ }
1230
+ this.socket = null;
1231
+ }
1232
+ this.rejectAllPending(new Error('WebSocket closed'));
1233
+ this.updateStatus({ state: 'closed', error: null });
1234
+ }
1235
+ /** Internal handle for tests and advanced usage. */
1236
+ getNativeSocket() {
1237
+ return this.socket;
1238
+ }
1239
+ // ---------- internals ----------
1240
+ openSocket() {
1241
+ if (this.disposed)
1242
+ return;
1243
+ this.updateStatus({ state: 'connecting', error: null });
1244
+ try {
1245
+ this.socket = new WebSocket(this.config.url, this.config.protocols);
1246
+ this.attachHandlers();
1247
+ }
1248
+ catch (error) {
1249
+ const message = error instanceof Error ? error.message : 'WebSocket open failed';
1250
+ this.logger.error('[websocket] Failed to construct socket', error);
1251
+ this.updateStatus({ state: 'closed', error: message });
1252
+ this.scheduleReconnect();
1253
+ }
1254
+ }
1255
+ attachHandlers() {
1256
+ if (!this.socket)
1257
+ return;
1258
+ this.socket.onopen = () => {
1259
+ this.reconnectAttempts = 0;
1260
+ this.updateStatus({ state: 'open', error: null, reconnectAttempts: 0 });
1261
+ this.startHeartbeat();
1262
+ };
1263
+ this.socket.onclose = (event) => {
1264
+ this.stopHeartbeat();
1265
+ if (this.disposed)
1266
+ return;
1267
+ this.updateStatus({
1268
+ state: 'closed',
1269
+ error: event.wasClean ? null : `closed: ${event.code} ${event.reason}`,
1270
+ });
1271
+ if (!event.wasClean) {
1272
+ this.scheduleReconnect();
1273
+ }
1274
+ };
1275
+ this.socket.onerror = () => {
1276
+ this.updateStatus({ error: 'WebSocket connection error' });
1277
+ };
1278
+ this.socket.onmessage = (event) => {
1279
+ this.handleIncoming(event.data);
1280
+ };
1281
+ }
1282
+ handleIncoming(raw) {
1283
+ let message;
1284
+ try {
1285
+ const text = typeof raw === 'string' ? raw : String(raw);
1286
+ message = JSON.parse(text);
1287
+ }
1288
+ catch (error) {
1289
+ this.logger.warn('[websocket] Failed to parse incoming message');
1290
+ this.logger.error('[websocket] parse error', error);
1291
+ return;
1292
+ }
1293
+ const correlationId = message.correlationId ?? message.id;
1294
+ if (correlationId && this.pendingRequests.has(correlationId)) {
1295
+ const pending = this.pendingRequests.get(correlationId);
1296
+ clearTimeout(pending.timer);
1297
+ this.pendingRequests.delete(correlationId);
1298
+ pending.resolve(message.data);
1299
+ return;
1300
+ }
1301
+ this._messages$.next(message);
1302
+ }
1303
+ scheduleReconnect() {
1304
+ if (this.disposed)
1305
+ return;
1306
+ const interval = this.config.reconnectInterval ?? 0;
1307
+ const maxAttempts = this.config.maxReconnectAttempts ?? 0;
1308
+ if (interval <= 0 || maxAttempts <= 0)
1309
+ return;
1310
+ if (this.reconnectAttempts >= maxAttempts) {
1311
+ this.updateStatus({
1312
+ state: 'closed',
1313
+ error: `Max reconnect attempts (${maxAttempts}) reached`,
1314
+ });
1315
+ return;
1316
+ }
1317
+ this.reconnectAttempts += 1;
1318
+ const delay = WebSocketClient.computeBackoffDelay(this.reconnectAttempts, interval, this.config.maxReconnectDelay ?? DEFAULT_MAX_RECONNECT_DELAY);
1319
+ this.updateStatus({
1320
+ state: 'reconnecting',
1321
+ reconnectAttempts: this.reconnectAttempts,
1322
+ });
1323
+ this.reconnectTimer = setTimeout(() => {
1324
+ this.reconnectTimer = null;
1325
+ this.openSocket();
1326
+ }, delay);
1327
+ }
1328
+ /**
1329
+ * Exponential backoff with full jitter:
1330
+ * baseDelay = min(maxDelay, interval * 2^(attempt - 1))
1331
+ * delay = random(0, baseDelay)
1332
+ */
1333
+ static computeBackoffDelay(attempt, interval, maxDelay) {
1334
+ const exp = Math.min(maxDelay, interval * Math.pow(2, attempt - 1));
1335
+ return Math.floor(Math.random() * exp);
1336
+ }
1337
+ startHeartbeat() {
1338
+ const { heartbeatInterval, heartbeatMessage } = this.config;
1339
+ if (!heartbeatInterval || heartbeatMessage === undefined)
1340
+ return;
1341
+ this.stopHeartbeat();
1342
+ this.heartbeatTimer = setInterval(() => {
1343
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
1344
+ try {
1345
+ this.send({ type: 'heartbeat', data: heartbeatMessage });
1346
+ }
1347
+ catch (error) {
1348
+ this.logger.warn('[websocket] heartbeat send failed');
1349
+ this.logger.error('[websocket] heartbeat error', error);
1350
+ }
1351
+ }
1352
+ }, heartbeatInterval);
1353
+ }
1354
+ stopHeartbeat() {
1355
+ if (this.heartbeatTimer) {
1356
+ clearInterval(this.heartbeatTimer);
1357
+ this.heartbeatTimer = null;
1358
+ }
1359
+ }
1360
+ clearTimers() {
1361
+ if (this.reconnectTimer) {
1362
+ clearTimeout(this.reconnectTimer);
1363
+ this.reconnectTimer = null;
1364
+ }
1365
+ this.stopHeartbeat();
1366
+ }
1367
+ rejectAllPending(reason) {
1368
+ this.pendingRequests.forEach((entry) => {
1369
+ clearTimeout(entry.timer);
1370
+ entry.reject(reason);
1371
+ });
1372
+ this.pendingRequests.clear();
1373
+ }
1374
+ updateStatus(partial) {
1375
+ this._status.update((current) => ({ ...current, ...partial }));
1376
+ }
1377
+ generateId() {
1378
+ if (typeof globalThis.crypto?.randomUUID === 'function') {
1379
+ return globalThis.crypto.randomUUID();
1380
+ }
1381
+ return `ws-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
1382
+ }
1383
+ }
1384
+
1385
+ let legacyDeprecationLogged = false;
1386
+ /**
1387
+ * Service that creates and tracks `WebSocketClient` instances.
1388
+ *
1389
+ * Preferred usage:
1390
+ * ```ts
1391
+ * const ws = inject(WebSocketService);
1392
+ * const client = ws.createClient({ url: 'wss://...' });
1393
+ * effect(() => console.log(client.status()));
1394
+ * await client.request('ping', {});
1395
+ * ```
1396
+ *
1397
+ * Legacy usage (`connect()` returning Observable) is preserved for one minor cycle
1398
+ * and will be removed in v22.
1399
+ */
1400
+ class WebSocketService extends BrowserApiBaseService {
1401
+ wsLogger = inject(BROWSER_API_LOGGER);
1402
+ clients = new Set();
1403
+ _cleanup = this.destroyRef.onDestroy(() => this.disposeAll());
1404
+ /** Legacy single-connection holder used by deprecated `connect()`/`send()` API. */
1405
+ legacyClient = null;
1052
1406
  getApiName() {
1053
1407
  return 'websocket';
1054
1408
  }
@@ -1058,174 +1412,133 @@ class WebSocketService extends BrowserApiBaseService {
1058
1412
  throw new Error('WebSocket API not supported in this browser');
1059
1413
  }
1060
1414
  }
1415
+ /**
1416
+ * Create a new WebSocket client. The client owns one connection and is the recommended
1417
+ * surface for all interactions (status signal, request/response, reconnect, etc.).
1418
+ *
1419
+ * The returned client is automatically disposed when the host injector is destroyed.
1420
+ */
1421
+ createClient(config) {
1422
+ this.ensureSupported();
1423
+ const client = new WebSocketClient(config, this.wsLogger, this.destroyRef);
1424
+ this.clients.add(client);
1425
+ return client;
1426
+ }
1427
+ /** Dispose every client created via `createClient()` (also called automatically on destroy). */
1428
+ disposeAll() {
1429
+ for (const client of this.clients) {
1430
+ client.close();
1431
+ }
1432
+ this.clients.clear();
1433
+ if (this.legacyClient) {
1434
+ this.legacyClient.close();
1435
+ this.legacyClient = null;
1436
+ }
1437
+ }
1438
+ // ---------- legacy API (deprecated) ----------
1439
+ /**
1440
+ * @deprecated Use {@link createClient} which returns a `WebSocketClient` exposing a
1441
+ * status signal, request/response, and proper reconnect. This wrapper will be removed
1442
+ * in v22.
1443
+ */
1061
1444
  connect(config) {
1062
1445
  this.ensureSupported();
1446
+ this.warnLegacyOnce();
1063
1447
  return new Observable((observer) => {
1064
- this.disconnect(); // Disconnect existing connection if any
1065
- this.updateStatus({
1066
- connected: false,
1067
- connecting: true,
1068
- reconnecting: false,
1069
- reconnectAttempts: 0,
1070
- });
1071
- try {
1072
- this.webSocket = new WebSocket(config.url, config.protocols);
1073
- this.setupWebSocketHandlers(config);
1074
- observer.next(this.statusSubject.getValue());
1075
- }
1076
- catch (error) {
1077
- this.logError('Error creating WebSocket:', error);
1078
- this.updateStatus({
1079
- connected: false,
1080
- connecting: false,
1081
- reconnecting: false,
1082
- error: error instanceof Error ? error.message : 'Connection failed',
1083
- reconnectAttempts: 0,
1084
- });
1085
- observer.next(this.statusSubject.getValue());
1448
+ if (this.legacyClient) {
1449
+ this.legacyClient.close();
1086
1450
  }
1451
+ const client = new WebSocketClient(config, this.wsLogger);
1452
+ this.legacyClient = client;
1453
+ const sub = toObservableLike(client).subscribe({
1454
+ next: (status) => observer.next(status),
1455
+ error: (err) => observer.error(err),
1456
+ });
1087
1457
  return () => {
1088
- this.disconnect();
1458
+ sub.unsubscribe();
1459
+ client.close();
1460
+ if (this.legacyClient === client) {
1461
+ this.legacyClient = null;
1462
+ }
1089
1463
  };
1090
1464
  });
1091
1465
  }
1466
+ /** @deprecated Use {@link createClient} and call `client.close()`. */
1092
1467
  disconnect() {
1093
- if (this.reconnectTimer) {
1094
- clearTimeout(this.reconnectTimer);
1095
- this.reconnectTimer = null;
1096
- }
1097
- if (this.heartbeatTimer) {
1098
- clearInterval(this.heartbeatTimer);
1099
- this.heartbeatTimer = null;
1100
- }
1101
- if (this.webSocket) {
1102
- this.webSocket.close();
1103
- this.webSocket = null;
1468
+ if (this.legacyClient) {
1469
+ this.legacyClient.close();
1470
+ this.legacyClient = null;
1104
1471
  }
1105
- this.updateStatus({
1106
- connected: false,
1107
- connecting: false,
1108
- reconnecting: false,
1109
- reconnectAttempts: 0,
1110
- });
1111
1472
  }
1473
+ /** @deprecated Use the client returned by {@link createClient}. */
1112
1474
  send(message) {
1113
- if (!this.webSocket || this.webSocket.readyState !== WebSocket.OPEN) {
1114
- throw new Error('WebSocket is not connected');
1475
+ if (!this.legacyClient) {
1476
+ throw new Error('No active legacy WebSocket. Call connect() first or use createClient().');
1115
1477
  }
1116
- const messageWithTimestamp = {
1117
- ...message,
1118
- timestamp: Date.now(),
1119
- };
1120
- this.webSocket.send(JSON.stringify(messageWithTimestamp));
1478
+ this.legacyClient.send(message);
1121
1479
  }
1480
+ /** @deprecated Use the client returned by {@link createClient}. */
1122
1481
  sendRaw(data) {
1123
- if (!this.webSocket || this.webSocket.readyState !== WebSocket.OPEN) {
1124
- throw new Error('WebSocket is not connected');
1482
+ if (!this.legacyClient) {
1483
+ throw new Error('No active legacy WebSocket. Call connect() first or use createClient().');
1125
1484
  }
1126
- this.webSocket.send(data);
1485
+ this.legacyClient.sendRaw(data);
1127
1486
  }
1487
+ /** @deprecated Use `client.status` from {@link createClient}. */
1128
1488
  getStatus() {
1129
- return this.statusSubject.asObservable();
1489
+ return new Observable((observer) => {
1490
+ if (!this.legacyClient) {
1491
+ observer.next({
1492
+ connected: false,
1493
+ connecting: false,
1494
+ reconnecting: false,
1495
+ reconnectAttempts: 0,
1496
+ });
1497
+ return () => {
1498
+ // No-op
1499
+ };
1500
+ }
1501
+ const sub = toObservableLike(this.legacyClient).subscribe((status) => observer.next(status));
1502
+ return () => sub.unsubscribe();
1503
+ });
1130
1504
  }
1505
+ /** @deprecated Use `client.messages$` from {@link createClient}. */
1131
1506
  getMessages() {
1132
- return this.messageSubject.asObservable();
1507
+ if (!this.legacyClient) {
1508
+ return new Observable(() => {
1509
+ // No-op stream until connected.
1510
+ });
1511
+ }
1512
+ return this.legacyClient.messages$;
1133
1513
  }
1514
+ /** @deprecated Use `client.messagesByType()` from {@link createClient}. */
1134
1515
  getMessagesByType(type) {
1135
- return this.messageSubject
1136
- .asObservable()
1137
- .pipe(filter((msg) => msg.type === type));
1138
- }
1139
- setupWebSocketHandlers(config) {
1140
- if (!this.webSocket)
1141
- return;
1142
- this.webSocket.onopen = () => {
1143
- this.logInfo(`Connected to: ${config.url}`);
1144
- this.reconnectAttempts = 0;
1145
- this.updateStatus({
1146
- connected: true,
1147
- connecting: false,
1148
- reconnecting: false,
1149
- reconnectAttempts: 0,
1150
- });
1151
- // Start heartbeat if configured
1152
- if (config.heartbeatInterval && config.heartbeatMessage) {
1153
- this.startHeartbeat(config);
1154
- }
1155
- };
1156
- this.webSocket.onclose = (event) => {
1157
- this.logInfo(`Connection closed: ${event.code} ${event.reason}`);
1158
- this.updateStatus({
1159
- connected: false,
1160
- connecting: false,
1161
- reconnecting: false,
1162
- reconnectAttempts: this.reconnectAttempts,
1163
- });
1164
- // Attempt reconnection if not a clean close and reconnect is enabled
1165
- if (!event.wasClean && config.reconnectInterval && config.maxReconnectAttempts) {
1166
- this.attemptReconnect(config);
1167
- }
1168
- };
1169
- this.webSocket.onerror = (error) => {
1170
- this.logError('WebSocket error:', error);
1171
- this.updateStatus({
1172
- connected: false,
1173
- connecting: false,
1174
- reconnecting: false,
1175
- error: 'WebSocket connection error',
1176
- reconnectAttempts: this.reconnectAttempts,
1516
+ if (!this.legacyClient) {
1517
+ return new Observable(() => {
1518
+ // No-op stream until connected.
1177
1519
  });
1178
- };
1179
- this.webSocket.onmessage = (event) => {
1180
- try {
1181
- const message = JSON.parse(event.data);
1182
- this.messageSubject.next(message);
1183
- }
1184
- catch (error) {
1185
- this.logError('Error parsing message:', error);
1186
- }
1187
- };
1188
- }
1189
- startHeartbeat(config) {
1190
- this.heartbeatTimer = setInterval(() => {
1191
- if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
1192
- this.send({
1193
- type: 'heartbeat',
1194
- data: config.heartbeatMessage,
1195
- });
1196
- }
1197
- }, config.heartbeatInterval);
1198
- }
1199
- attemptReconnect(config) {
1200
- if (this.reconnectAttempts >= (config.maxReconnectAttempts || 5)) {
1201
- this.logInfo('Max reconnect attempts reached');
1202
- return;
1203
1520
  }
1204
- this.reconnectAttempts++;
1205
- this.updateStatus({
1206
- connected: false,
1207
- connecting: false,
1208
- reconnecting: true,
1209
- reconnectAttempts: this.reconnectAttempts,
1210
- });
1211
- this.reconnectTimer = setTimeout(() => {
1212
- this.logInfo(`Reconnect attempt ${this.reconnectAttempts}`);
1213
- this.connect(config);
1214
- }, config.reconnectInterval || 3000);
1215
- }
1216
- updateStatus(status) {
1217
- const newStatus = { ...this.statusSubject.getValue(), ...status };
1218
- this.statusSubject.next(newStatus);
1521
+ return this.legacyClient.messagesByType(type);
1219
1522
  }
1220
- // Direct access to native WebSocket
1523
+ /** @deprecated Use the client returned by {@link createClient}. */
1221
1524
  getNativeWebSocket() {
1222
- return this.webSocket;
1525
+ return this.legacyClient?.getNativeSocket() ?? null;
1223
1526
  }
1527
+ /** @deprecated Use `client.status()` from {@link createClient}. */
1224
1528
  isConnected() {
1225
- return this.webSocket?.readyState === WebSocket.OPEN;
1529
+ return this.legacyClient?.status().state === 'open';
1226
1530
  }
1531
+ /** @deprecated Use the native socket via `client.getNativeSocket()`. */
1227
1532
  getReadyState() {
1228
- return this.webSocket?.readyState ?? WebSocket.CLOSED;
1533
+ return this.legacyClient?.getNativeSocket()?.readyState ?? WebSocket.CLOSED;
1534
+ }
1535
+ warnLegacyOnce() {
1536
+ if (legacyDeprecationLogged)
1537
+ return;
1538
+ legacyDeprecationLogged = true;
1539
+ this.wsLogger.warn('[websocket] WebSocketService.connect() is deprecated. Use WebSocketService.createClient() ' +
1540
+ 'which returns a WebSocketClient with a status signal, request/response, and proper reconnect. ' +
1541
+ 'The legacy API will be removed in v22.');
1229
1542
  }
1230
1543
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1231
1544
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService });
@@ -1233,6 +1546,27 @@ class WebSocketService extends BrowserApiBaseService {
1233
1546
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebSocketService, decorators: [{
1234
1547
  type: Injectable
1235
1548
  }] });
1549
+ /**
1550
+ * Build a stream of legacy `WebSocketStatus` snapshots from a v2 client. Used to keep the
1551
+ * deprecated `connect()` API behaving like before (Observable of legacy status).
1552
+ */
1553
+ function toObservableLike(client) {
1554
+ return new Observable((observer) => {
1555
+ const emit = () => {
1556
+ const v2 = client.status();
1557
+ observer.next({
1558
+ connected: v2.state === 'open',
1559
+ connecting: v2.state === 'connecting',
1560
+ reconnecting: v2.state === 'reconnecting',
1561
+ error: v2.error ?? undefined,
1562
+ reconnectAttempts: v2.reconnectAttempts,
1563
+ });
1564
+ };
1565
+ emit();
1566
+ const id = setInterval(emit, 100);
1567
+ return () => clearInterval(id);
1568
+ });
1569
+ }
1236
1570
 
1237
1571
  class WebWorkerService extends BrowserApiBaseService {
1238
1572
  workers = new Map();
@@ -2441,106 +2775,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
2441
2775
  type: Injectable
2442
2776
  }] });
2443
2777
 
2444
- function getIdleDetectorClass$1() {
2445
- return window.IdleDetector;
2446
- }
2447
- class IdleDetectorService extends BrowserApiBaseService {
2448
- getApiName() {
2449
- return 'idle-detector';
2450
- }
2451
- isSupported() {
2452
- return this.isBrowserEnvironment() && 'IdleDetector' in window;
2453
- }
2454
- async requestPermission() {
2455
- if (!this.isSupported()) {
2456
- throw new Error('IdleDetector API not supported');
2457
- }
2458
- return getIdleDetectorClass$1().requestPermission();
2459
- }
2460
- watch(options = {}) {
2461
- if (!this.isSupported()) {
2462
- return new Observable((o) => o.error(new Error('IdleDetector API not supported')));
2463
- }
2464
- return new Observable((subscriber) => {
2465
- const abortController = new AbortController();
2466
- const detector = new (getIdleDetectorClass$1())();
2467
- detector.addEventListener('change', () => {
2468
- subscriber.next({
2469
- user: detector.userState,
2470
- screen: detector.screenState,
2471
- });
2472
- });
2473
- detector
2474
- .start({
2475
- threshold: options.threshold ?? 60_000,
2476
- signal: abortController.signal,
2477
- })
2478
- .catch((err) => subscriber.error(err));
2479
- return () => abortController.abort();
2480
- });
2481
- }
2482
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IdleDetectorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2483
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IdleDetectorService });
2484
- }
2485
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: IdleDetectorService, decorators: [{
2486
- type: Injectable
2487
- }] });
2488
-
2489
- function getEyeDropperClass() {
2490
- return window.EyeDropper;
2491
- }
2492
- class EyeDropperService extends BrowserApiBaseService {
2493
- getApiName() {
2494
- return 'eye-dropper';
2495
- }
2496
- isSupported() {
2497
- return this.isBrowserEnvironment() && 'EyeDropper' in window;
2498
- }
2499
- async open(signal) {
2500
- if (!this.isSupported()) {
2501
- throw new Error('EyeDropper API not supported');
2502
- }
2503
- const dropper = new (getEyeDropperClass())();
2504
- return dropper.open(signal ? { signal } : undefined);
2505
- }
2506
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: EyeDropperService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2507
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: EyeDropperService });
2508
- }
2509
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: EyeDropperService, decorators: [{
2510
- type: Injectable
2511
- }] });
2512
-
2513
- function getBarcodeDetectorClass() {
2514
- return window.BarcodeDetector;
2515
- }
2516
- class BarcodeDetectorService extends BrowserApiBaseService {
2517
- getApiName() {
2518
- return 'barcode-detector';
2519
- }
2520
- isSupported() {
2521
- return this.isBrowserEnvironment() && 'BarcodeDetector' in window;
2522
- }
2523
- async getSupportedFormats() {
2524
- if (!this.isSupported())
2525
- return [];
2526
- return getBarcodeDetectorClass().getSupportedFormats();
2527
- }
2528
- async detect(image, formats) {
2529
- if (!this.isSupported()) {
2530
- throw new Error('BarcodeDetector API not supported');
2531
- }
2532
- const detector = formats
2533
- ? new (getBarcodeDetectorClass())({ formats })
2534
- : new (getBarcodeDetectorClass())();
2535
- return detector.detect(image);
2536
- }
2537
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BarcodeDetectorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2538
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BarcodeDetectorService });
2539
- }
2540
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BarcodeDetectorService, decorators: [{
2541
- type: Injectable
2542
- }] });
2543
-
2544
2778
  class WebAudioService extends BrowserApiBaseService {
2545
2779
  getApiName() {
2546
2780
  return 'web-audio';
@@ -2750,301 +2984,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
2750
2984
  type: Injectable
2751
2985
  }] });
2752
2986
 
2753
- function getBluetooth() {
2754
- return navigator.bluetooth;
2755
- }
2756
- class WebBluetoothService extends BrowserApiBaseService {
2757
- getApiName() {
2758
- return 'web-bluetooth';
2759
- }
2760
- isSupported() {
2761
- return this.isBrowserEnvironment() && !!getBluetooth();
2762
- }
2763
- async requestDevice(options = { acceptAllDevices: true }) {
2764
- if (!this.isSupported()) {
2765
- throw new Error('Web Bluetooth API not supported');
2766
- }
2767
- return getBluetooth().requestDevice(options);
2768
- }
2769
- async connect(device) {
2770
- if (!device.gatt) {
2771
- throw new Error('GATT server not available on this device');
2772
- }
2773
- return device.gatt.connect();
2774
- }
2775
- disconnect(device) {
2776
- device.gatt?.disconnect();
2777
- }
2778
- watchDisconnection(device) {
2779
- return new Observable((subscriber) => {
2780
- const handler = () => subscriber.next();
2781
- device.addEventListener('gattserverdisconnected', handler);
2782
- return () => device.removeEventListener('gattserverdisconnected', handler);
2783
- });
2784
- }
2785
- async readCharacteristic(server, serviceUuid, characteristicUuid) {
2786
- const service = await server.getPrimaryService(serviceUuid);
2787
- const characteristic = await service.getCharacteristic(characteristicUuid);
2788
- return characteristic.readValue();
2789
- }
2790
- async writeCharacteristic(server, serviceUuid, characteristicUuid, value) {
2791
- const service = await server.getPrimaryService(serviceUuid);
2792
- const characteristic = await service.getCharacteristic(characteristicUuid);
2793
- await characteristic.writeValue(value);
2794
- }
2795
- watchCharacteristic(server, serviceUuid, characteristicUuid) {
2796
- return new Observable((subscriber) => {
2797
- let characteristic;
2798
- server
2799
- .getPrimaryService(serviceUuid)
2800
- .then((service) => service.getCharacteristic(characteristicUuid))
2801
- .then((char) => {
2802
- characteristic = char;
2803
- const handler = (event) => {
2804
- const target = event.target;
2805
- if (target.value)
2806
- subscriber.next(target.value);
2807
- };
2808
- characteristic.addEventListener('characteristicvaluechanged', handler);
2809
- return characteristic.startNotifications();
2810
- })
2811
- .catch((err) => subscriber.error(err));
2812
- return () => {
2813
- characteristic?.stopNotifications().catch(() => { });
2814
- };
2815
- });
2816
- }
2817
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebBluetoothService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2818
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebBluetoothService });
2819
- }
2820
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebBluetoothService, decorators: [{
2821
- type: Injectable
2822
- }] });
2823
-
2824
- function getUsb() {
2825
- return navigator.usb;
2826
- }
2827
- class WebUsbService extends BrowserApiBaseService {
2828
- getApiName() {
2829
- return 'web-usb';
2830
- }
2831
- isSupported() {
2832
- return this.isBrowserEnvironment() && !!getUsb();
2833
- }
2834
- async requestDevice(filters = []) {
2835
- if (!this.isSupported()) {
2836
- throw new Error('WebUSB API not supported');
2837
- }
2838
- return getUsb().requestDevice({ filters });
2839
- }
2840
- async getDevices() {
2841
- if (!this.isSupported())
2842
- return [];
2843
- return getUsb().getDevices();
2844
- }
2845
- async open(device) {
2846
- await device.open();
2847
- }
2848
- async close(device) {
2849
- await device.close();
2850
- }
2851
- async selectConfiguration(device, configurationValue) {
2852
- await device.selectConfiguration(configurationValue);
2853
- }
2854
- async claimInterface(device, interfaceNumber) {
2855
- await device.claimInterface(interfaceNumber);
2856
- }
2857
- async releaseInterface(device, interfaceNumber) {
2858
- await device.releaseInterface(interfaceNumber);
2859
- }
2860
- async transferIn(device, endpointNumber, length) {
2861
- return device.transferIn(endpointNumber, length);
2862
- }
2863
- async transferOut(device, endpointNumber, data) {
2864
- return device.transferOut(endpointNumber, data);
2865
- }
2866
- watchConnection() {
2867
- if (!this.isSupported()) {
2868
- return new Observable((o) => o.error(new Error('WebUSB API not supported')));
2869
- }
2870
- return new Observable((subscriber) => {
2871
- const usb = getUsb();
2872
- const onConnect = (e) => subscriber.next({ device: e.device, type: 'connect' });
2873
- const onDisconnect = (e) => subscriber.next({ device: e.device, type: 'disconnect' });
2874
- usb.addEventListener('connect', onConnect);
2875
- usb.addEventListener('disconnect', onDisconnect);
2876
- return () => {
2877
- usb.removeEventListener('connect', onConnect);
2878
- usb.removeEventListener('disconnect', onDisconnect);
2879
- };
2880
- });
2881
- }
2882
- getDeviceInfo(device) {
2883
- return {
2884
- vendorId: device.vendorId,
2885
- productId: device.productId,
2886
- productName: device.productName,
2887
- manufacturerName: device.manufacturerName,
2888
- serialNumber: device.serialNumber,
2889
- opened: device.opened,
2890
- };
2891
- }
2892
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebUsbService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2893
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebUsbService });
2894
- }
2895
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebUsbService, decorators: [{
2896
- type: Injectable
2897
- }] });
2898
-
2899
- function getNdefReaderClass() {
2900
- return window.NDEFReader;
2901
- }
2902
- class WebNfcService extends BrowserApiBaseService {
2903
- getApiName() {
2904
- return 'web-nfc';
2905
- }
2906
- isSupported() {
2907
- return this.isBrowserEnvironment() && 'NDEFReader' in window;
2908
- }
2909
- scan() {
2910
- if (!this.isSupported()) {
2911
- return new Observable((o) => o.error(new Error('Web NFC API not supported')));
2912
- }
2913
- return new Observable((subscriber) => {
2914
- const abortController = new AbortController();
2915
- const reader = new (getNdefReaderClass())();
2916
- const onReading = (event) => {
2917
- const e = event;
2918
- subscriber.next({
2919
- serialNumber: e.serialNumber,
2920
- message: e.message,
2921
- });
2922
- };
2923
- const onError = (event) => {
2924
- subscriber.error(event.error ?? new Error('NFC read error'));
2925
- };
2926
- reader.addEventListener('reading', onReading);
2927
- reader.addEventListener('readingerror', onError);
2928
- reader
2929
- .scan({ signal: abortController.signal })
2930
- .catch((err) => subscriber.error(err));
2931
- return () => abortController.abort();
2932
- });
2933
- }
2934
- async write(message, options) {
2935
- if (!this.isSupported()) {
2936
- throw new Error('Web NFC API not supported');
2937
- }
2938
- const reader = new (getNdefReaderClass())();
2939
- await reader.write(message, options);
2940
- }
2941
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebNfcService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2942
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebNfcService });
2943
- }
2944
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebNfcService, decorators: [{
2945
- type: Injectable
2946
- }] });
2947
-
2948
- class PaymentRequestService extends BrowserApiBaseService {
2949
- getApiName() {
2950
- return 'payment-request';
2951
- }
2952
- isSupported() {
2953
- return this.isBrowserEnvironment() && 'PaymentRequest' in window;
2954
- }
2955
- async canMakePayment(methods, details) {
2956
- if (!this.isSupported())
2957
- return false;
2958
- const request = new PaymentRequest(methods, details);
2959
- return request.canMakePayment();
2960
- }
2961
- async show(methods, details, options) {
2962
- if (!this.isSupported()) {
2963
- throw new Error('Payment Request API not supported');
2964
- }
2965
- const request = new PaymentRequest(methods, details, options);
2966
- const response = await request.show();
2967
- const result = {
2968
- methodName: response.methodName,
2969
- details: response.details,
2970
- payerName: response.payerName ?? null,
2971
- payerEmail: response.payerEmail ?? null,
2972
- payerPhone: response.payerPhone ?? null,
2973
- };
2974
- await response.complete('success');
2975
- return result;
2976
- }
2977
- async abort(methods, details) {
2978
- if (!this.isSupported())
2979
- return;
2980
- const request = new PaymentRequest(methods, details);
2981
- await request.abort();
2982
- }
2983
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PaymentRequestService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2984
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PaymentRequestService });
2985
- }
2986
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PaymentRequestService, decorators: [{
2987
- type: Injectable
2988
- }] });
2989
-
2990
- class CredentialManagementService extends BrowserApiBaseService {
2991
- getApiName() {
2992
- return 'credential-management';
2993
- }
2994
- isSupported() {
2995
- return this.isBrowserEnvironment() && 'credentials' in navigator;
2996
- }
2997
- isPublicKeySupported() {
2998
- return this.isSupported() && 'PublicKeyCredential' in window;
2999
- }
3000
- async get(options) {
3001
- if (!this.isSupported()) {
3002
- throw new Error('Credential Management API not supported');
3003
- }
3004
- return navigator.credentials.get(options);
3005
- }
3006
- async store(credential) {
3007
- if (!this.isSupported()) {
3008
- throw new Error('Credential Management API not supported');
3009
- }
3010
- await navigator.credentials.store(credential);
3011
- }
3012
- async createPasswordCredential(data) {
3013
- if (!this.isSupported()) {
3014
- throw new Error('Credential Management API not supported');
3015
- }
3016
- return navigator.credentials.create({
3017
- password: data,
3018
- });
3019
- }
3020
- async createPublicKeyCredential(options) {
3021
- if (!this.isPublicKeySupported()) {
3022
- throw new Error('PublicKeyCredential API not supported');
3023
- }
3024
- return navigator.credentials.create({
3025
- publicKey: options,
3026
- });
3027
- }
3028
- async preventSilentAccess() {
3029
- if (!this.isSupported())
3030
- return;
3031
- await navigator.credentials.preventSilentAccess();
3032
- }
3033
- async isConditionalMediationAvailable() {
3034
- if (!this.isPublicKeySupported())
3035
- return false;
3036
- if ('isConditionalMediationAvailable' in PublicKeyCredential) {
3037
- return PublicKeyCredential.isConditionalMediationAvailable();
3038
- }
3039
- return false;
3040
- }
3041
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CredentialManagementService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
3042
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CredentialManagementService });
3043
- }
3044
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CredentialManagementService, decorators: [{
3045
- type: Injectable
3046
- }] });
3047
-
3048
2987
  // Common types for browser APIs
3049
2988
 
3050
2989
  function injectPageVisibility() {
@@ -3208,42 +3147,6 @@ function injectPerformanceObserver(config) {
3208
3147
  };
3209
3148
  }
3210
3149
 
3211
- function getIdleDetectorClass() {
3212
- return window.IdleDetector;
3213
- }
3214
- function injectIdleDetector(options = {}) {
3215
- const destroyRef = inject(DestroyRef);
3216
- const platformId = inject(PLATFORM_ID);
3217
- const defaultState = { user: 'active', screen: 'unlocked' };
3218
- const state = signal(defaultState, ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
3219
- if (isPlatformBrowser(platformId) && 'IdleDetector' in window) {
3220
- const abortController = new AbortController();
3221
- const detector = new (getIdleDetectorClass())();
3222
- detector.addEventListener('change', () => {
3223
- state.set({
3224
- user: detector.userState,
3225
- screen: detector.screenState,
3226
- });
3227
- });
3228
- detector
3229
- .start({
3230
- threshold: options.threshold ?? 60_000,
3231
- signal: abortController.signal,
3232
- })
3233
- .catch(() => {
3234
- /* permission denied or unsupported — keep defaults */
3235
- });
3236
- destroyRef.onDestroy(() => abortController.abort());
3237
- }
3238
- return {
3239
- state: state.asReadonly(),
3240
- userState: computed(() => state().user),
3241
- screenState: computed(() => state().screen),
3242
- isUserIdle: computed(() => state().user === 'idle'),
3243
- isScreenLocked: computed(() => state().screen === 'locked'),
3244
- };
3245
- }
3246
-
3247
3150
  function injectGamepad(index, intervalMs = 16) {
3248
3151
  const destroyRef = inject(DestroyRef);
3249
3152
  const platformId = inject(PLATFORM_ID);
@@ -3410,10 +3313,6 @@ function provideMediaRecorder() {
3410
3313
  return makeEnvironmentProviders([PermissionsService, MediaRecorderService]);
3411
3314
  }
3412
3315
 
3413
- function provideIdleDetector() {
3414
- return makeEnvironmentProviders([PermissionsService, IdleDetectorService]);
3415
- }
3416
-
3417
3316
  function provideBattery() {
3418
3317
  return makeEnvironmentProviders([BatteryService]);
3419
3318
  }
@@ -3482,14 +3381,6 @@ function providePerformanceObserver() {
3482
3381
  return makeEnvironmentProviders([PerformanceObserverService]);
3483
3382
  }
3484
3383
 
3485
- function provideEyeDropper() {
3486
- return makeEnvironmentProviders([EyeDropperService]);
3487
- }
3488
-
3489
- function provideBarcodeDetector() {
3490
- return makeEnvironmentProviders([BarcodeDetectorService]);
3491
- }
3492
-
3493
3384
  function provideWebAudio() {
3494
3385
  return makeEnvironmentProviders([WebAudioService]);
3495
3386
  }
@@ -3498,26 +3389,6 @@ function provideGamepad() {
3498
3389
  return makeEnvironmentProviders([GamepadService]);
3499
3390
  }
3500
3391
 
3501
- function provideWebBluetooth() {
3502
- return makeEnvironmentProviders([WebBluetoothService]);
3503
- }
3504
-
3505
- function provideWebUsb() {
3506
- return makeEnvironmentProviders([WebUsbService]);
3507
- }
3508
-
3509
- function provideWebNfc() {
3510
- return makeEnvironmentProviders([WebNfcService]);
3511
- }
3512
-
3513
- function providePaymentRequest() {
3514
- return makeEnvironmentProviders([PaymentRequestService]);
3515
- }
3516
-
3517
- function provideCredentialManagement() {
3518
- return makeEnvironmentProviders([CredentialManagementService]);
3519
- }
3520
-
3521
3392
  function provideMediaApis() {
3522
3393
  return makeEnvironmentProviders([PermissionsService, CameraService, MediaDevicesService]);
3523
3394
  }
@@ -3562,16 +3433,8 @@ const defaultBrowserWebApisConfig = {
3562
3433
  enableSpeechSynthesis: false,
3563
3434
  enableMutationObserver: false,
3564
3435
  enablePerformanceObserver: false,
3565
- enableIdleDetector: false,
3566
- enableEyeDropper: false,
3567
- enableBarcodeDetector: false,
3568
3436
  enableWebAudio: false,
3569
3437
  enableGamepad: false,
3570
- enableWebBluetooth: false,
3571
- enableWebUsb: false,
3572
- enableWebNfc: false,
3573
- enablePaymentRequest: false,
3574
- enableCredentialManagement: false,
3575
3438
  };
3576
3439
  function provideBrowserWebApis(config = {}) {
3577
3440
  const mergedConfig = { ...defaultBrowserWebApisConfig, ...config };
@@ -3602,16 +3465,8 @@ function provideBrowserWebApis(config = {}) {
3602
3465
  [mergedConfig.enableSpeechSynthesis, SpeechSynthesisService],
3603
3466
  [mergedConfig.enableMutationObserver, MutationObserverService],
3604
3467
  [mergedConfig.enablePerformanceObserver, PerformanceObserverService],
3605
- [mergedConfig.enableIdleDetector, IdleDetectorService],
3606
- [mergedConfig.enableEyeDropper, EyeDropperService],
3607
- [mergedConfig.enableBarcodeDetector, BarcodeDetectorService],
3608
3468
  [mergedConfig.enableWebAudio, WebAudioService],
3609
3469
  [mergedConfig.enableGamepad, GamepadService],
3610
- [mergedConfig.enableWebBluetooth, WebBluetoothService],
3611
- [mergedConfig.enableWebUsb, WebUsbService],
3612
- [mergedConfig.enableWebNfc, WebNfcService],
3613
- [mergedConfig.enablePaymentRequest, PaymentRequestService],
3614
- [mergedConfig.enableCredentialManagement, CredentialManagementService],
3615
3470
  ];
3616
3471
  for (const [enabled, provider] of conditionalProviders) {
3617
3472
  if (enabled) {
@@ -3629,4 +3484,4 @@ const version = '0.1.0';
3629
3484
  * Generated bundle index. Do not edit.
3630
3485
  */
3631
3486
 
3632
- export { BROWSER_API_LOGGER, BarcodeDetectorService, BatteryService, BroadcastChannelService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, ConnectionRegistryBaseService, CredentialManagementService, EyeDropperService, FileSystemAccessService, FullscreenService, GamepadService, GeolocationService, IdleDetectorService, IntersectionObserverService, MediaDevicesService, MediaRecorderService, MutationObserverService, NetworkInformationService, NotificationService, PageVisibilityService, PaymentRequestService, PerformanceObserverService, PermissionsService, ResizeObserverService, ScreenOrientationService, ScreenWakeLockService, ServerSentEventsService, SpeechSynthesisService, VibrationService, WebAudioService, WebBluetoothService, WebNfcService, WebShareService, WebSocketService, WebStorageService, WebUsbService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, injectGamepad, injectIdleDetector, injectIntersectionObserver, injectMutationObserver, injectNetworkInformation, injectPageVisibility, injectPerformanceObserver, injectResizeObserver, injectScreenOrientation, permissionGuard, provideBarcodeDetector, provideBattery, provideBroadcastChannel, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideCredentialManagement, provideEyeDropper, provideFileSystemAccess, provideFullscreen, provideGamepad, provideGeolocation, provideIdleDetector, provideIntersectionObserver, provideLocationApis, provideMediaApis, provideMediaDevices, provideMediaRecorder, provideMutationObserver, provideNetworkInformation, provideNotifications, providePageVisibility, providePaymentRequest, providePerformanceObserver, providePermissions, provideResizeObserver, provideScreenOrientation, provideScreenWakeLock, provideServerSentEvents, provideSpeechSynthesis, provideStorageApis, provideVibration, provideWebAudio, provideWebBluetooth, provideWebNfc, provideWebShare, provideWebSocket, provideWebStorage, provideWebUsb, provideWebWorker, version };
3487
+ export { BROWSER_API_LOGGER, BatteryService, BroadcastChannelService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, ConnectionRegistryBaseService, FileSystemAccessService, FullscreenService, GamepadService, GeolocationService, IntersectionObserverService, MediaDevicesService, MediaRecorderService, MutationObserverService, NetworkInformationService, NotificationService, PageVisibilityService, PerformanceObserverService, PermissionsService, ResizeObserverService, ScreenOrientationService, ScreenWakeLockService, ServerSentEventsService, SpeechSynthesisService, VibrationService, WebAudioService, WebShareService, WebSocketClient, WebSocketService, WebStorageService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, injectGamepad, injectIntersectionObserver, injectMutationObserver, injectNetworkInformation, injectPageVisibility, injectPerformanceObserver, injectResizeObserver, injectScreenOrientation, permissionGuard, provideBattery, provideBroadcastChannel, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideFileSystemAccess, provideFullscreen, provideGamepad, provideGeolocation, provideIntersectionObserver, provideLocationApis, provideMediaApis, provideMediaDevices, provideMediaRecorder, provideMutationObserver, provideNetworkInformation, provideNotifications, providePageVisibility, providePerformanceObserver, providePermissions, provideResizeObserver, provideScreenOrientation, provideScreenWakeLock, provideServerSentEvents, provideSpeechSynthesis, provideStorageApis, provideVibration, provideWebAudio, provideWebShare, provideWebSocket, provideWebStorage, provideWebWorker, version };