@angular-helpers/browser-web-apis 21.7.0 → 21.10.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,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, DestroyRef, PLATFORM_ID,
|
|
2
|
+
import { InjectionToken, Injectable, inject, DestroyRef, PLATFORM_ID, signal, computed, isSignal, effect, ElementRef, makeEnvironmentProviders } from '@angular/core';
|
|
3
3
|
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
|
|
4
4
|
import { Observable, fromEvent, Subject, of, map as map$1 } from 'rxjs';
|
|
5
5
|
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
@@ -18,14 +18,191 @@ const BROWSER_API_LOGGER = new InjectionToken('BROWSER_API_LOGGER', {
|
|
|
18
18
|
}),
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
const BROWSER_CAPABILITIES = [
|
|
22
|
+
{ id: 'permissions', label: 'Permissions API', requiresSecureContext: false },
|
|
23
|
+
{ id: 'geolocation', label: 'Geolocation API', requiresSecureContext: true },
|
|
24
|
+
{ id: 'clipboard', label: 'Clipboard API', requiresSecureContext: true },
|
|
25
|
+
{ id: 'notification', label: 'Notification API', requiresSecureContext: true },
|
|
26
|
+
{ id: 'mediaDevices', label: 'MediaDevices API', requiresSecureContext: true },
|
|
27
|
+
{ id: 'camera', label: 'Camera API', requiresSecureContext: true },
|
|
28
|
+
{ id: 'webWorker', label: 'Web Worker API', requiresSecureContext: false },
|
|
29
|
+
{ id: 'regexSecurity', label: 'Regex Security', requiresSecureContext: false },
|
|
30
|
+
{ id: 'webStorage', label: 'Web Storage', requiresSecureContext: false },
|
|
31
|
+
{ id: 'webShare', label: 'Web Share', requiresSecureContext: true },
|
|
32
|
+
{ id: 'battery', label: 'Battery API', requiresSecureContext: false },
|
|
33
|
+
{ id: 'webSocket', label: 'WebSocket API', requiresSecureContext: false },
|
|
34
|
+
{ id: 'intersectionObserver', label: 'Intersection Observer', requiresSecureContext: false },
|
|
35
|
+
{ id: 'resizeObserver', label: 'Resize Observer', requiresSecureContext: false },
|
|
36
|
+
{ id: 'pageVisibility', label: 'Page Visibility API', requiresSecureContext: false },
|
|
37
|
+
{ id: 'broadcastChannel', label: 'Broadcast Channel API', requiresSecureContext: false },
|
|
38
|
+
{ id: 'networkInformation', label: 'Network Information API', requiresSecureContext: false },
|
|
39
|
+
{ id: 'screenWakeLock', label: 'Screen Wake Lock API', requiresSecureContext: true },
|
|
40
|
+
{ id: 'screenOrientation', label: 'Screen Orientation API', requiresSecureContext: false },
|
|
41
|
+
{ id: 'fullscreen', label: 'Fullscreen API', requiresSecureContext: false },
|
|
42
|
+
{ id: 'fileSystemAccess', label: 'File System Access API', requiresSecureContext: true },
|
|
43
|
+
{ id: 'mediaRecorder', label: 'MediaRecorder API', requiresSecureContext: true },
|
|
44
|
+
{ id: 'serverSentEvents', label: 'Server-Sent Events', requiresSecureContext: false },
|
|
45
|
+
{ id: 'vibration', label: 'Vibration API', requiresSecureContext: false },
|
|
46
|
+
{ id: 'speechSynthesis', label: 'Speech Synthesis API', requiresSecureContext: false },
|
|
47
|
+
{ id: 'mutationObserver', label: 'Mutation Observer', requiresSecureContext: false },
|
|
48
|
+
{ id: 'performanceObserver', label: 'Performance Observer', requiresSecureContext: false },
|
|
49
|
+
{ id: 'idleDetector', label: 'Idle Detection API', requiresSecureContext: true },
|
|
50
|
+
{ id: 'eyeDropper', label: 'EyeDropper API', requiresSecureContext: true },
|
|
51
|
+
{ id: 'barcodeDetector', label: 'Barcode Detection API', requiresSecureContext: true },
|
|
52
|
+
{ id: 'webAudio', label: 'Web Audio API', requiresSecureContext: false },
|
|
53
|
+
{ id: 'gamepad', label: 'Gamepad API', requiresSecureContext: true },
|
|
54
|
+
{ id: 'webBluetooth', label: 'Web Bluetooth API', requiresSecureContext: true },
|
|
55
|
+
{ id: 'webUsb', label: 'WebUSB API', requiresSecureContext: true },
|
|
56
|
+
{ id: 'webNfc', label: 'Web NFC API', requiresSecureContext: true },
|
|
57
|
+
{ id: 'paymentRequest', label: 'Payment Request API', requiresSecureContext: true },
|
|
58
|
+
{ id: 'credentialManagement', label: 'Credential Management API', requiresSecureContext: true },
|
|
59
|
+
{ id: 'webLocks', label: 'Web Locks API', requiresSecureContext: true },
|
|
60
|
+
{ id: 'storageManager', label: 'Storage Manager API', requiresSecureContext: true },
|
|
61
|
+
{ id: 'compressionStreams', label: 'Compression Streams API', requiresSecureContext: false },
|
|
62
|
+
];
|
|
63
|
+
class BrowserCapabilityService {
|
|
64
|
+
getCapabilities() {
|
|
65
|
+
return BROWSER_CAPABILITIES;
|
|
66
|
+
}
|
|
67
|
+
isSecureContext() {
|
|
68
|
+
return typeof window !== 'undefined' && window.isSecureContext;
|
|
69
|
+
}
|
|
70
|
+
isSupported(capability) {
|
|
71
|
+
switch (capability) {
|
|
72
|
+
case 'permissions':
|
|
73
|
+
return typeof navigator !== 'undefined' && 'permissions' in navigator;
|
|
74
|
+
case 'geolocation':
|
|
75
|
+
return typeof navigator !== 'undefined' && 'geolocation' in navigator;
|
|
76
|
+
case 'clipboard':
|
|
77
|
+
return typeof navigator !== 'undefined' && 'clipboard' in navigator;
|
|
78
|
+
case 'notification':
|
|
79
|
+
return typeof window !== 'undefined' && 'Notification' in window;
|
|
80
|
+
case 'mediaDevices':
|
|
81
|
+
case 'camera':
|
|
82
|
+
return typeof navigator !== 'undefined' && 'mediaDevices' in navigator;
|
|
83
|
+
case 'webWorker':
|
|
84
|
+
case 'regexSecurity':
|
|
85
|
+
return typeof Worker !== 'undefined';
|
|
86
|
+
case 'webStorage':
|
|
87
|
+
return typeof Storage !== 'undefined';
|
|
88
|
+
case 'webShare':
|
|
89
|
+
return typeof navigator !== 'undefined' && 'share' in navigator;
|
|
90
|
+
case 'battery':
|
|
91
|
+
return typeof navigator !== 'undefined' && 'getBattery' in navigator;
|
|
92
|
+
case 'webSocket':
|
|
93
|
+
return typeof WebSocket !== 'undefined';
|
|
94
|
+
case 'intersectionObserver':
|
|
95
|
+
return typeof IntersectionObserver !== 'undefined';
|
|
96
|
+
case 'resizeObserver':
|
|
97
|
+
return typeof ResizeObserver !== 'undefined';
|
|
98
|
+
case 'pageVisibility':
|
|
99
|
+
return typeof document !== 'undefined' && 'hidden' in document;
|
|
100
|
+
case 'broadcastChannel':
|
|
101
|
+
return typeof BroadcastChannel !== 'undefined';
|
|
102
|
+
case 'networkInformation':
|
|
103
|
+
return (typeof navigator !== 'undefined' &&
|
|
104
|
+
('connection' in navigator || 'mozConnection' in navigator));
|
|
105
|
+
case 'screenWakeLock':
|
|
106
|
+
return typeof navigator !== 'undefined' && 'wakeLock' in navigator;
|
|
107
|
+
case 'screenOrientation':
|
|
108
|
+
return typeof screen !== 'undefined' && 'orientation' in screen;
|
|
109
|
+
case 'fullscreen':
|
|
110
|
+
return (typeof document !== 'undefined' &&
|
|
111
|
+
('fullscreenEnabled' in document || 'webkitFullscreenEnabled' in document));
|
|
112
|
+
case 'fileSystemAccess':
|
|
113
|
+
return typeof window !== 'undefined' && 'showOpenFilePicker' in window;
|
|
114
|
+
case 'mediaRecorder':
|
|
115
|
+
return typeof MediaRecorder !== 'undefined';
|
|
116
|
+
case 'serverSentEvents':
|
|
117
|
+
return typeof EventSource !== 'undefined';
|
|
118
|
+
case 'vibration':
|
|
119
|
+
return typeof navigator !== 'undefined' && 'vibrate' in navigator;
|
|
120
|
+
case 'speechSynthesis':
|
|
121
|
+
return typeof window !== 'undefined' && 'speechSynthesis' in window;
|
|
122
|
+
case 'mutationObserver':
|
|
123
|
+
return typeof MutationObserver !== 'undefined';
|
|
124
|
+
case 'performanceObserver':
|
|
125
|
+
return typeof PerformanceObserver !== 'undefined';
|
|
126
|
+
case 'idleDetector':
|
|
127
|
+
return typeof window !== 'undefined' && 'IdleDetector' in window;
|
|
128
|
+
case 'eyeDropper':
|
|
129
|
+
return typeof window !== 'undefined' && 'EyeDropper' in window;
|
|
130
|
+
case 'barcodeDetector':
|
|
131
|
+
return typeof window !== 'undefined' && 'BarcodeDetector' in window;
|
|
132
|
+
case 'webAudio':
|
|
133
|
+
return typeof window !== 'undefined' && 'AudioContext' in window;
|
|
134
|
+
case 'gamepad':
|
|
135
|
+
return typeof navigator !== 'undefined' && 'getGamepads' in navigator;
|
|
136
|
+
case 'webBluetooth':
|
|
137
|
+
return typeof navigator !== 'undefined' && 'bluetooth' in navigator;
|
|
138
|
+
case 'webUsb':
|
|
139
|
+
return typeof navigator !== 'undefined' && 'usb' in navigator;
|
|
140
|
+
case 'webNfc':
|
|
141
|
+
return typeof window !== 'undefined' && 'NDEFReader' in window;
|
|
142
|
+
case 'paymentRequest':
|
|
143
|
+
return typeof window !== 'undefined' && 'PaymentRequest' in window;
|
|
144
|
+
case 'credentialManagement':
|
|
145
|
+
return typeof navigator !== 'undefined' && 'credentials' in navigator;
|
|
146
|
+
case 'webLocks':
|
|
147
|
+
return typeof navigator !== 'undefined' && 'locks' in navigator;
|
|
148
|
+
case 'storageManager':
|
|
149
|
+
return (typeof navigator !== 'undefined' &&
|
|
150
|
+
'storage' in navigator &&
|
|
151
|
+
typeof navigator.storage.estimate === 'function');
|
|
152
|
+
case 'compressionStreams':
|
|
153
|
+
return (typeof CompressionStream !== 'undefined' && typeof DecompressionStream !== 'undefined');
|
|
154
|
+
default:
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
getAllStatuses() {
|
|
159
|
+
const secureContext = this.isSecureContext();
|
|
160
|
+
return this.getCapabilities().map((capability) => ({
|
|
161
|
+
id: capability.id,
|
|
162
|
+
label: capability.label,
|
|
163
|
+
supported: this.isSupported(capability.id),
|
|
164
|
+
secureContext,
|
|
165
|
+
requiresSecureContext: capability.requiresSecureContext,
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
async getPermissionState(permission) {
|
|
169
|
+
if (typeof navigator === 'undefined' || !('permissions' in navigator)) {
|
|
170
|
+
return 'unknown';
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const status = await navigator.permissions.query({ name: permission });
|
|
174
|
+
return status.state;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return 'unknown';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserCapabilityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
181
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserCapabilityService, providedIn: 'root' });
|
|
182
|
+
}
|
|
183
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserCapabilityService, decorators: [{
|
|
184
|
+
type: Injectable,
|
|
185
|
+
args: [{ providedIn: 'root' }]
|
|
186
|
+
}] });
|
|
187
|
+
|
|
21
188
|
/**
|
|
22
189
|
* Base class for all Browser Web API services.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* -
|
|
190
|
+
*
|
|
191
|
+
* ## Support detection contract
|
|
192
|
+
*
|
|
193
|
+
* Services follow ONE pattern (do not invent variants):
|
|
194
|
+
*
|
|
195
|
+
* - `isSupported(): boolean` — public, side-effect free, SSR-safe. Default
|
|
196
|
+
* implementation delegates to {@link BrowserCapabilityService} when the subclass
|
|
197
|
+
* overrides {@link getCapabilityId} (recommended).
|
|
198
|
+
* - `ensureSupported(): void` — internal Template Method. Throws when called outside
|
|
199
|
+
* browser or when the underlying API is missing.
|
|
200
|
+
*
|
|
201
|
+
* ## Error surfacing contract
|
|
202
|
+
*
|
|
203
|
+
* - **Imperative methods** MUST call `ensureSupported()` and throw synchronously.
|
|
204
|
+
* - **Stream-returning methods** MUST guard with `isSupported()` and surface
|
|
205
|
+
* unsupported state as `Observable.error(...)` (NOT throw inline).
|
|
29
206
|
*
|
|
30
207
|
* Services that also need permission querying should extend
|
|
31
208
|
* `PermissionAwareBrowserApiBaseService` instead.
|
|
@@ -34,30 +211,46 @@ class BrowserApiBaseService {
|
|
|
34
211
|
destroyRef = inject(DestroyRef);
|
|
35
212
|
platformId = inject(PLATFORM_ID);
|
|
36
213
|
logger = inject(BROWSER_API_LOGGER);
|
|
214
|
+
capabilities = inject(BrowserCapabilityService);
|
|
37
215
|
/**
|
|
38
|
-
*
|
|
216
|
+
* Optional hook for subclasses to delegate feature detection to
|
|
217
|
+
* {@link BrowserCapabilityService}. Returning a capability id removes the need to
|
|
218
|
+
* implement `isSupported()` manually and avoids drift between per-service checks
|
|
219
|
+
* and the centralized capability registry.
|
|
39
220
|
*/
|
|
221
|
+
getCapabilityId() {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
/** Public, SSR-safe support check. Override only if you need extra constraints. */
|
|
225
|
+
isSupported() {
|
|
226
|
+
if (!this.isBrowserEnvironment())
|
|
227
|
+
return false;
|
|
228
|
+
const capabilityId = this.getCapabilityId();
|
|
229
|
+
if (capabilityId !== null) {
|
|
230
|
+
return this.capabilities.isSupported(capabilityId);
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
40
234
|
isBrowserEnvironment() {
|
|
41
235
|
return isPlatformBrowser(this.platformId);
|
|
42
236
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Check if running in server environment using Angular's platform detection.
|
|
45
|
-
*/
|
|
46
237
|
isServerEnvironment() {
|
|
47
238
|
return isPlatformServer(this.platformId);
|
|
48
239
|
}
|
|
49
240
|
/**
|
|
50
|
-
* Template Method: asserts the service can run in the current environment.
|
|
51
|
-
*
|
|
241
|
+
* Template Method: asserts the service can run in the current environment. Subclasses
|
|
242
|
+
* that need extra checks beyond capability detection MUST call `super.ensureSupported()`
|
|
243
|
+
* first, then add their own check.
|
|
52
244
|
*/
|
|
53
245
|
ensureSupported() {
|
|
54
246
|
if (!this.isBrowserEnvironment()) {
|
|
55
247
|
throw new Error(`${this.getApiName()} API not available in server environment`);
|
|
56
248
|
}
|
|
249
|
+
const capabilityId = this.getCapabilityId();
|
|
250
|
+
if (capabilityId !== null && !this.capabilities.isSupported(capabilityId)) {
|
|
251
|
+
throw new Error(`${this.getApiName()} API not supported in this browser`);
|
|
252
|
+
}
|
|
57
253
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Create an error with proper cause chaining.
|
|
60
|
-
*/
|
|
61
254
|
createError(message, cause) {
|
|
62
255
|
const error = new Error(message);
|
|
63
256
|
if (cause !== undefined) {
|
|
@@ -65,21 +258,12 @@ class BrowserApiBaseService {
|
|
|
65
258
|
}
|
|
66
259
|
return error;
|
|
67
260
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Log an error through the injected BROWSER_API_LOGGER (default: console).
|
|
70
|
-
*/
|
|
71
261
|
logError(message, error) {
|
|
72
262
|
this.logger.error(`[${this.getApiName()}] ${message}`, error);
|
|
73
263
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Log a warning through the injected BROWSER_API_LOGGER (default: console).
|
|
76
|
-
*/
|
|
77
264
|
logWarn(message) {
|
|
78
265
|
this.logger.warn(`[${this.getApiName()}] ${message}`);
|
|
79
266
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Log an informational message through the injected BROWSER_API_LOGGER (default: console).
|
|
82
|
-
*/
|
|
83
267
|
logInfo(message) {
|
|
84
268
|
this.logger.info(`[${this.getApiName()}] ${message}`);
|
|
85
269
|
}
|
|
@@ -112,8 +296,8 @@ class PermissionsService extends BrowserApiBaseService {
|
|
|
112
296
|
throw error;
|
|
113
297
|
}
|
|
114
298
|
}
|
|
115
|
-
|
|
116
|
-
return
|
|
299
|
+
getCapabilityId() {
|
|
300
|
+
return 'permissions';
|
|
117
301
|
}
|
|
118
302
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PermissionsService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
119
303
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PermissionsService });
|
|
@@ -127,10 +311,13 @@ class CameraService extends BrowserApiBaseService {
|
|
|
127
311
|
getApiName() {
|
|
128
312
|
return 'camera';
|
|
129
313
|
}
|
|
314
|
+
getCapabilityId() {
|
|
315
|
+
return 'camera';
|
|
316
|
+
}
|
|
130
317
|
ensureSupported() {
|
|
131
318
|
super.ensureSupported();
|
|
132
319
|
if (!navigator.mediaDevices?.getUserMedia) {
|
|
133
|
-
throw new Error('Camera API not supported —
|
|
320
|
+
throw new Error('Camera API not supported — getUserMedia missing or HTTPS required');
|
|
134
321
|
}
|
|
135
322
|
}
|
|
136
323
|
async startCamera(constraints) {
|
|
@@ -246,11 +433,8 @@ class GeolocationService extends BrowserApiBaseService {
|
|
|
246
433
|
getApiName() {
|
|
247
434
|
return 'geolocation';
|
|
248
435
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (!('geolocation' in navigator)) {
|
|
252
|
-
throw new Error('Geolocation API not supported — a secure context (HTTPS) is required');
|
|
253
|
-
}
|
|
436
|
+
getCapabilityId() {
|
|
437
|
+
return 'geolocation';
|
|
254
438
|
}
|
|
255
439
|
getCurrentPosition(options) {
|
|
256
440
|
this.ensureSupported();
|
|
@@ -292,6 +476,9 @@ class MediaDevicesService extends BrowserApiBaseService {
|
|
|
292
476
|
getApiName() {
|
|
293
477
|
return 'media-devices';
|
|
294
478
|
}
|
|
479
|
+
getCapabilityId() {
|
|
480
|
+
return 'mediaDevices';
|
|
481
|
+
}
|
|
295
482
|
ensureSupported() {
|
|
296
483
|
super.ensureSupported();
|
|
297
484
|
if (!navigator.mediaDevices) {
|
|
@@ -420,11 +607,8 @@ class NotificationService extends BrowserApiBaseService {
|
|
|
420
607
|
getApiName() {
|
|
421
608
|
return 'notifications';
|
|
422
609
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (!('Notification' in window)) {
|
|
426
|
-
throw new Error('Notifications API not supported in this browser');
|
|
427
|
-
}
|
|
610
|
+
getCapabilityId() {
|
|
611
|
+
return 'notification';
|
|
428
612
|
}
|
|
429
613
|
get permission() {
|
|
430
614
|
if (!this.isBrowserEnvironment() || !('Notification' in window)) {
|
|
@@ -460,6 +644,9 @@ class ClipboardService extends BrowserApiBaseService {
|
|
|
460
644
|
getApiName() {
|
|
461
645
|
return 'clipboard';
|
|
462
646
|
}
|
|
647
|
+
getCapabilityId() {
|
|
648
|
+
return 'clipboard';
|
|
649
|
+
}
|
|
463
650
|
ensureSupported() {
|
|
464
651
|
super.ensureSupported();
|
|
465
652
|
if (!navigator.clipboard) {
|
|
@@ -493,173 +680,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
493
680
|
type: Injectable
|
|
494
681
|
}] });
|
|
495
682
|
|
|
496
|
-
const BROWSER_CAPABILITIES = [
|
|
497
|
-
{ id: 'permissions', label: 'Permissions API', requiresSecureContext: false },
|
|
498
|
-
{ id: 'geolocation', label: 'Geolocation API', requiresSecureContext: true },
|
|
499
|
-
{ id: 'clipboard', label: 'Clipboard API', requiresSecureContext: true },
|
|
500
|
-
{ id: 'notification', label: 'Notification API', requiresSecureContext: true },
|
|
501
|
-
{ id: 'mediaDevices', label: 'MediaDevices API', requiresSecureContext: true },
|
|
502
|
-
{ id: 'camera', label: 'Camera API', requiresSecureContext: true },
|
|
503
|
-
{ id: 'webWorker', label: 'Web Worker API', requiresSecureContext: false },
|
|
504
|
-
{ id: 'regexSecurity', label: 'Regex Security', requiresSecureContext: false },
|
|
505
|
-
{ id: 'webStorage', label: 'Web Storage', requiresSecureContext: false },
|
|
506
|
-
{ id: 'webShare', label: 'Web Share', requiresSecureContext: true },
|
|
507
|
-
{ id: 'battery', label: 'Battery API', requiresSecureContext: false },
|
|
508
|
-
{ id: 'webSocket', label: 'WebSocket API', requiresSecureContext: false },
|
|
509
|
-
{ id: 'intersectionObserver', label: 'Intersection Observer', requiresSecureContext: false },
|
|
510
|
-
{ id: 'resizeObserver', label: 'Resize Observer', requiresSecureContext: false },
|
|
511
|
-
{ id: 'pageVisibility', label: 'Page Visibility API', requiresSecureContext: false },
|
|
512
|
-
{ id: 'broadcastChannel', label: 'Broadcast Channel API', requiresSecureContext: false },
|
|
513
|
-
{ id: 'networkInformation', label: 'Network Information API', requiresSecureContext: false },
|
|
514
|
-
{ id: 'screenWakeLock', label: 'Screen Wake Lock API', requiresSecureContext: true },
|
|
515
|
-
{ id: 'screenOrientation', label: 'Screen Orientation API', requiresSecureContext: false },
|
|
516
|
-
{ id: 'fullscreen', label: 'Fullscreen API', requiresSecureContext: false },
|
|
517
|
-
{ id: 'fileSystemAccess', label: 'File System Access API', requiresSecureContext: true },
|
|
518
|
-
{ id: 'mediaRecorder', label: 'MediaRecorder API', requiresSecureContext: true },
|
|
519
|
-
{ id: 'serverSentEvents', label: 'Server-Sent Events', requiresSecureContext: false },
|
|
520
|
-
{ id: 'vibration', label: 'Vibration API', requiresSecureContext: false },
|
|
521
|
-
{ id: 'speechSynthesis', label: 'Speech Synthesis API', requiresSecureContext: false },
|
|
522
|
-
{ id: 'mutationObserver', label: 'Mutation Observer', requiresSecureContext: false },
|
|
523
|
-
{ id: 'performanceObserver', label: 'Performance Observer', requiresSecureContext: false },
|
|
524
|
-
{ id: 'idleDetector', label: 'Idle Detection API', requiresSecureContext: true },
|
|
525
|
-
{ id: 'eyeDropper', label: 'EyeDropper API', requiresSecureContext: true },
|
|
526
|
-
{ id: 'barcodeDetector', label: 'Barcode Detection API', requiresSecureContext: true },
|
|
527
|
-
{ id: 'webAudio', label: 'Web Audio API', requiresSecureContext: false },
|
|
528
|
-
{ id: 'gamepad', label: 'Gamepad API', requiresSecureContext: true },
|
|
529
|
-
{ id: 'webBluetooth', label: 'Web Bluetooth API', requiresSecureContext: true },
|
|
530
|
-
{ id: 'webUsb', label: 'WebUSB API', requiresSecureContext: true },
|
|
531
|
-
{ id: 'webNfc', label: 'Web NFC API', requiresSecureContext: true },
|
|
532
|
-
{ id: 'paymentRequest', label: 'Payment Request API', requiresSecureContext: true },
|
|
533
|
-
{ id: 'credentialManagement', label: 'Credential Management API', requiresSecureContext: true },
|
|
534
|
-
];
|
|
535
|
-
class BrowserCapabilityService {
|
|
536
|
-
getCapabilities() {
|
|
537
|
-
return BROWSER_CAPABILITIES;
|
|
538
|
-
}
|
|
539
|
-
isSecureContext() {
|
|
540
|
-
return typeof window !== 'undefined' && window.isSecureContext;
|
|
541
|
-
}
|
|
542
|
-
isSupported(capability) {
|
|
543
|
-
switch (capability) {
|
|
544
|
-
case 'permissions':
|
|
545
|
-
return typeof navigator !== 'undefined' && 'permissions' in navigator;
|
|
546
|
-
case 'geolocation':
|
|
547
|
-
return typeof navigator !== 'undefined' && 'geolocation' in navigator;
|
|
548
|
-
case 'clipboard':
|
|
549
|
-
return typeof navigator !== 'undefined' && 'clipboard' in navigator;
|
|
550
|
-
case 'notification':
|
|
551
|
-
return typeof window !== 'undefined' && 'Notification' in window;
|
|
552
|
-
case 'mediaDevices':
|
|
553
|
-
case 'camera':
|
|
554
|
-
return typeof navigator !== 'undefined' && 'mediaDevices' in navigator;
|
|
555
|
-
case 'webWorker':
|
|
556
|
-
case 'regexSecurity':
|
|
557
|
-
return typeof Worker !== 'undefined';
|
|
558
|
-
case 'webStorage':
|
|
559
|
-
return typeof Storage !== 'undefined';
|
|
560
|
-
case 'webShare':
|
|
561
|
-
return typeof navigator !== 'undefined' && 'share' in navigator;
|
|
562
|
-
case 'battery':
|
|
563
|
-
return typeof navigator !== 'undefined' && 'getBattery' in navigator;
|
|
564
|
-
case 'webSocket':
|
|
565
|
-
return typeof WebSocket !== 'undefined';
|
|
566
|
-
case 'intersectionObserver':
|
|
567
|
-
return typeof IntersectionObserver !== 'undefined';
|
|
568
|
-
case 'resizeObserver':
|
|
569
|
-
return typeof ResizeObserver !== 'undefined';
|
|
570
|
-
case 'pageVisibility':
|
|
571
|
-
return typeof document !== 'undefined' && 'hidden' in document;
|
|
572
|
-
case 'broadcastChannel':
|
|
573
|
-
return typeof BroadcastChannel !== 'undefined';
|
|
574
|
-
case 'networkInformation':
|
|
575
|
-
return (typeof navigator !== 'undefined' &&
|
|
576
|
-
('connection' in navigator || 'mozConnection' in navigator));
|
|
577
|
-
case 'screenWakeLock':
|
|
578
|
-
return typeof navigator !== 'undefined' && 'wakeLock' in navigator;
|
|
579
|
-
case 'screenOrientation':
|
|
580
|
-
return typeof screen !== 'undefined' && 'orientation' in screen;
|
|
581
|
-
case 'fullscreen':
|
|
582
|
-
return (typeof document !== 'undefined' &&
|
|
583
|
-
('fullscreenEnabled' in document || 'webkitFullscreenEnabled' in document));
|
|
584
|
-
case 'fileSystemAccess':
|
|
585
|
-
return typeof window !== 'undefined' && 'showOpenFilePicker' in window;
|
|
586
|
-
case 'mediaRecorder':
|
|
587
|
-
return typeof MediaRecorder !== 'undefined';
|
|
588
|
-
case 'serverSentEvents':
|
|
589
|
-
return typeof EventSource !== 'undefined';
|
|
590
|
-
case 'vibration':
|
|
591
|
-
return typeof navigator !== 'undefined' && 'vibrate' in navigator;
|
|
592
|
-
case 'speechSynthesis':
|
|
593
|
-
return typeof window !== 'undefined' && 'speechSynthesis' in window;
|
|
594
|
-
case 'mutationObserver':
|
|
595
|
-
return typeof MutationObserver !== 'undefined';
|
|
596
|
-
case 'performanceObserver':
|
|
597
|
-
return typeof PerformanceObserver !== 'undefined';
|
|
598
|
-
case 'idleDetector':
|
|
599
|
-
return typeof window !== 'undefined' && 'IdleDetector' in window;
|
|
600
|
-
case 'eyeDropper':
|
|
601
|
-
return typeof window !== 'undefined' && 'EyeDropper' in window;
|
|
602
|
-
case 'barcodeDetector':
|
|
603
|
-
return typeof window !== 'undefined' && 'BarcodeDetector' in window;
|
|
604
|
-
case 'webAudio':
|
|
605
|
-
return typeof window !== 'undefined' && 'AudioContext' in window;
|
|
606
|
-
case 'gamepad':
|
|
607
|
-
return typeof navigator !== 'undefined' && 'getGamepads' in navigator;
|
|
608
|
-
case 'webBluetooth':
|
|
609
|
-
return typeof navigator !== 'undefined' && 'bluetooth' in navigator;
|
|
610
|
-
case 'webUsb':
|
|
611
|
-
return typeof navigator !== 'undefined' && 'usb' in navigator;
|
|
612
|
-
case 'webNfc':
|
|
613
|
-
return typeof window !== 'undefined' && 'NDEFReader' in window;
|
|
614
|
-
case 'paymentRequest':
|
|
615
|
-
return typeof window !== 'undefined' && 'PaymentRequest' in window;
|
|
616
|
-
case 'credentialManagement':
|
|
617
|
-
return typeof navigator !== 'undefined' && 'credentials' in navigator;
|
|
618
|
-
default:
|
|
619
|
-
return false;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
getAllStatuses() {
|
|
623
|
-
const secureContext = this.isSecureContext();
|
|
624
|
-
return this.getCapabilities().map((capability) => ({
|
|
625
|
-
id: capability.id,
|
|
626
|
-
label: capability.label,
|
|
627
|
-
supported: this.isSupported(capability.id),
|
|
628
|
-
secureContext,
|
|
629
|
-
requiresSecureContext: capability.requiresSecureContext,
|
|
630
|
-
}));
|
|
631
|
-
}
|
|
632
|
-
async getPermissionState(permission) {
|
|
633
|
-
if (typeof navigator === 'undefined' || !('permissions' in navigator)) {
|
|
634
|
-
return 'unknown';
|
|
635
|
-
}
|
|
636
|
-
try {
|
|
637
|
-
const status = await navigator.permissions.query({ name: permission });
|
|
638
|
-
return status.state;
|
|
639
|
-
}
|
|
640
|
-
catch {
|
|
641
|
-
return 'unknown';
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserCapabilityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
645
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserCapabilityService, providedIn: 'root' });
|
|
646
|
-
}
|
|
647
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserCapabilityService, decorators: [{
|
|
648
|
-
type: Injectable,
|
|
649
|
-
args: [{ providedIn: 'root' }]
|
|
650
|
-
}] });
|
|
651
|
-
|
|
652
683
|
class BatteryService extends BrowserApiBaseService {
|
|
653
684
|
batteryManager = null;
|
|
654
685
|
getApiName() {
|
|
655
686
|
return 'battery';
|
|
656
687
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const nav = navigator;
|
|
660
|
-
if (!('getBattery' in nav)) {
|
|
661
|
-
throw new Error('Battery Status API not supported in this browser');
|
|
662
|
-
}
|
|
688
|
+
getCapabilityId() {
|
|
689
|
+
return 'battery';
|
|
663
690
|
}
|
|
664
691
|
async initialize() {
|
|
665
692
|
this.ensureSupported();
|
|
@@ -739,11 +766,8 @@ class WebShareService extends BrowserApiBaseService {
|
|
|
739
766
|
getApiName() {
|
|
740
767
|
return 'web-share';
|
|
741
768
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if (!('share' in navigator)) {
|
|
745
|
-
throw new Error('Web Share API not supported in this browser');
|
|
746
|
-
}
|
|
769
|
+
getCapabilityId() {
|
|
770
|
+
return 'webShare';
|
|
747
771
|
}
|
|
748
772
|
async share(data) {
|
|
749
773
|
this.ensureSupported();
|
|
@@ -998,11 +1022,8 @@ class WebStorageService extends BrowserApiBaseService {
|
|
|
998
1022
|
getApiName() {
|
|
999
1023
|
return 'storage';
|
|
1000
1024
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
if (typeof Storage === 'undefined') {
|
|
1004
|
-
throw new Error('Storage API not supported in this browser');
|
|
1005
|
-
}
|
|
1025
|
+
getCapabilityId() {
|
|
1026
|
+
return 'webStorage';
|
|
1006
1027
|
}
|
|
1007
1028
|
/** Returns true if either local or session storage is usable. */
|
|
1008
1029
|
isSupported() {
|
|
@@ -1122,7 +1143,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
1122
1143
|
}], ctorParameters: () => [] });
|
|
1123
1144
|
|
|
1124
1145
|
const DEFAULT_MAX_RECONNECT_DELAY = 30_000;
|
|
1125
|
-
const DEFAULT_REQUEST_TIMEOUT = 30_000;
|
|
1146
|
+
const DEFAULT_REQUEST_TIMEOUT$1 = 30_000;
|
|
1126
1147
|
/**
|
|
1127
1148
|
* Stateful WebSocket client wrapping a single connection. One instance per logical
|
|
1128
1149
|
* connection (do NOT share between `connect()` calls).
|
|
@@ -1194,7 +1215,7 @@ class WebSocketClient {
|
|
|
1194
1215
|
*/
|
|
1195
1216
|
request(type, data, opts) {
|
|
1196
1217
|
const id = this.generateId();
|
|
1197
|
-
const timeoutMs = opts?.timeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
1218
|
+
const timeoutMs = opts?.timeout ?? DEFAULT_REQUEST_TIMEOUT$1;
|
|
1198
1219
|
return new Promise((resolve, reject) => {
|
|
1199
1220
|
const timer = setTimeout(() => {
|
|
1200
1221
|
this.pendingRequests.delete(id);
|
|
@@ -1406,11 +1427,8 @@ class WebSocketService extends BrowserApiBaseService {
|
|
|
1406
1427
|
getApiName() {
|
|
1407
1428
|
return 'websocket';
|
|
1408
1429
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
if (typeof WebSocket === 'undefined') {
|
|
1412
|
-
throw new Error('WebSocket API not supported in this browser');
|
|
1413
|
-
}
|
|
1430
|
+
getCapabilityId() {
|
|
1431
|
+
return 'webSocket';
|
|
1414
1432
|
}
|
|
1415
1433
|
/**
|
|
1416
1434
|
* Create a new WebSocket client. The client owns one connection and is the recommended
|
|
@@ -1568,162 +1586,259 @@ function toObservableLike(client) {
|
|
|
1568
1586
|
});
|
|
1569
1587
|
}
|
|
1570
1588
|
|
|
1589
|
+
const DEFAULT_REQUEST_TIMEOUT = 30_000;
|
|
1590
|
+
/**
|
|
1591
|
+
* Service for creating and managing Web Workers with first-class support for
|
|
1592
|
+
* request/response over `postMessage` (id correlation, timeout, transferables).
|
|
1593
|
+
*
|
|
1594
|
+
* Status is exposed both as a `Signal<WorkerStatus>` (preferred via
|
|
1595
|
+
* {@link getStatusSignal}) and as an `Observable<WorkerStatus>` for backward
|
|
1596
|
+
* compatibility ({@link createWorker}, {@link getStatus}).
|
|
1597
|
+
*
|
|
1598
|
+
* The service registers a single `DestroyRef.onDestroy` in its constructor that
|
|
1599
|
+
* terminates every registered worker; per-worker handlers do not register their
|
|
1600
|
+
* own cleanup callbacks (avoids accumulating callbacks per `setupWorker` call,
|
|
1601
|
+
* a previous leak source).
|
|
1602
|
+
*/
|
|
1571
1603
|
class WebWorkerService extends BrowserApiBaseService {
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
workerMessages = new Map();
|
|
1575
|
-
currentWorkerStatuses = new Map();
|
|
1604
|
+
workerLogger = inject(BROWSER_API_LOGGER);
|
|
1605
|
+
entries = new Map();
|
|
1576
1606
|
_cleanup = this.destroyRef.onDestroy(() => this.terminateAllWorkers());
|
|
1577
1607
|
getApiName() {
|
|
1578
1608
|
return 'webworker';
|
|
1579
1609
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
if (typeof Worker === 'undefined') {
|
|
1583
|
-
throw new Error('Web Workers not supported in this browser');
|
|
1584
|
-
}
|
|
1610
|
+
getCapabilityId() {
|
|
1611
|
+
return 'webWorker';
|
|
1585
1612
|
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Create a worker. Idempotent: calling twice with the same name returns the
|
|
1615
|
+
* existing entry without recreating the worker.
|
|
1616
|
+
*
|
|
1617
|
+
* Returns an `Observable<WorkerStatus>` for backward compatibility. Prefer
|
|
1618
|
+
* {@link createWorkerSignal} for new code.
|
|
1619
|
+
*/
|
|
1586
1620
|
createWorker(name, scriptUrl) {
|
|
1621
|
+
const status = this.createWorkerSignal(name, scriptUrl);
|
|
1622
|
+
return toObservable(status);
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Create a worker (signal-first). Returns the status signal; status is also
|
|
1626
|
+
* accessible later via {@link getStatusSignal}.
|
|
1627
|
+
*/
|
|
1628
|
+
createWorkerSignal(name, scriptUrl) {
|
|
1587
1629
|
this.ensureSupported();
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
this.logError(`Failed to create worker ${name}:`, error);
|
|
1610
|
-
const status = {
|
|
1611
|
-
initialized: false,
|
|
1612
|
-
running: false,
|
|
1613
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
1614
|
-
messageCount: 0,
|
|
1615
|
-
};
|
|
1616
|
-
this.currentWorkerStatuses.set(name, status);
|
|
1617
|
-
this.updateWorkerStatus(name, status);
|
|
1618
|
-
observer.next(status);
|
|
1619
|
-
}
|
|
1620
|
-
return () => {
|
|
1621
|
-
// No-op: workers are managed explicitly via terminateWorker/terminateAllWorkers
|
|
1622
|
-
};
|
|
1623
|
-
});
|
|
1630
|
+
const existing = this.entries.get(name);
|
|
1631
|
+
if (existing && existing.worker)
|
|
1632
|
+
return existing.status.asReadonly();
|
|
1633
|
+
const entry = this.ensureEntry(name);
|
|
1634
|
+
try {
|
|
1635
|
+
const worker = new Worker(scriptUrl);
|
|
1636
|
+
entry.worker = worker;
|
|
1637
|
+
this.attachHandlers(name, entry);
|
|
1638
|
+
entry.status.set({ initialized: true, running: true, messageCount: 0 });
|
|
1639
|
+
}
|
|
1640
|
+
catch (error) {
|
|
1641
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1642
|
+
entry.status.set({
|
|
1643
|
+
initialized: false,
|
|
1644
|
+
running: false,
|
|
1645
|
+
error: message,
|
|
1646
|
+
messageCount: 0,
|
|
1647
|
+
});
|
|
1648
|
+
this.workerLogger.error(`[webworker] Failed to create worker "${name}"`, error);
|
|
1649
|
+
}
|
|
1650
|
+
return entry.status.asReadonly();
|
|
1624
1651
|
}
|
|
1625
1652
|
terminateWorker(name) {
|
|
1626
|
-
const
|
|
1627
|
-
if (
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1653
|
+
const entry = this.entries.get(name);
|
|
1654
|
+
if (!entry)
|
|
1655
|
+
return;
|
|
1656
|
+
this.rejectPending(entry, new Error(`Worker "${name}" terminated`));
|
|
1657
|
+
if (entry.worker)
|
|
1658
|
+
entry.worker.terminate();
|
|
1659
|
+
entry.messages$.complete();
|
|
1660
|
+
this.entries.delete(name);
|
|
1634
1661
|
}
|
|
1635
1662
|
terminateAllWorkers() {
|
|
1636
|
-
this.
|
|
1663
|
+
for (const name of [...this.entries.keys()]) {
|
|
1637
1664
|
this.terminateWorker(name);
|
|
1638
|
-
}
|
|
1665
|
+
}
|
|
1639
1666
|
}
|
|
1667
|
+
/** Send a fire-and-forget message. Use {@link request} when you need a reply. */
|
|
1640
1668
|
postMessage(workerName, task) {
|
|
1641
|
-
const
|
|
1642
|
-
if (!worker) {
|
|
1643
|
-
this.
|
|
1669
|
+
const entry = this.entries.get(workerName);
|
|
1670
|
+
if (!entry || !entry.worker) {
|
|
1671
|
+
this.workerLogger.error(`[webworker] postMessage: worker "${workerName}" not found`);
|
|
1644
1672
|
return;
|
|
1645
1673
|
}
|
|
1674
|
+
const message = {
|
|
1675
|
+
id: task.id ?? this.generateId(),
|
|
1676
|
+
type: task.type,
|
|
1677
|
+
data: task.data,
|
|
1678
|
+
timestamp: Date.now(),
|
|
1679
|
+
};
|
|
1646
1680
|
try {
|
|
1647
|
-
const message = { ...task, timestamp: Date.now() };
|
|
1648
1681
|
if (task.transferable) {
|
|
1649
|
-
worker.postMessage(message, task.transferable);
|
|
1682
|
+
entry.worker.postMessage(message, task.transferable);
|
|
1650
1683
|
}
|
|
1651
1684
|
else {
|
|
1652
|
-
worker.postMessage(message);
|
|
1653
|
-
}
|
|
1654
|
-
const currentStatus = this.currentWorkerStatuses.get(workerName);
|
|
1655
|
-
if (currentStatus) {
|
|
1656
|
-
currentStatus.messageCount++;
|
|
1657
|
-
this.updateWorkerStatus(workerName, currentStatus);
|
|
1685
|
+
entry.worker.postMessage(message);
|
|
1658
1686
|
}
|
|
1687
|
+
this.bumpMessageCount(entry);
|
|
1659
1688
|
}
|
|
1660
1689
|
catch (error) {
|
|
1661
|
-
this.
|
|
1690
|
+
this.workerLogger.error(`[webworker] postMessage failed for "${workerName}"`, error);
|
|
1662
1691
|
}
|
|
1663
1692
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1693
|
+
/**
|
|
1694
|
+
* Send a message and await a correlated response. The worker MUST send back a
|
|
1695
|
+
* message containing `correlationId` matching the request id.
|
|
1696
|
+
*
|
|
1697
|
+
* ```ts
|
|
1698
|
+
* const result = await ws.request<{ ok: boolean }>('worker', 'compute', { n: 1 });
|
|
1699
|
+
* ```
|
|
1700
|
+
*/
|
|
1701
|
+
request(workerName, type, data, opts) {
|
|
1702
|
+
const entry = this.entries.get(workerName);
|
|
1703
|
+
if (!entry || !entry.worker) {
|
|
1704
|
+
return Promise.reject(new Error(`Worker "${workerName}" not found`));
|
|
1667
1705
|
}
|
|
1668
|
-
|
|
1706
|
+
const id = this.generateId();
|
|
1707
|
+
const timeoutMs = opts?.timeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
1708
|
+
return new Promise((resolve, reject) => {
|
|
1709
|
+
const timer = setTimeout(() => {
|
|
1710
|
+
entry.pending.delete(id);
|
|
1711
|
+
reject(new Error(`WebWorker "${workerName}" request timeout after ${timeoutMs}ms`));
|
|
1712
|
+
}, timeoutMs);
|
|
1713
|
+
entry.pending.set(id, {
|
|
1714
|
+
resolve: resolve,
|
|
1715
|
+
reject,
|
|
1716
|
+
timer,
|
|
1717
|
+
});
|
|
1718
|
+
const message = {
|
|
1719
|
+
id,
|
|
1720
|
+
type,
|
|
1721
|
+
data,
|
|
1722
|
+
timestamp: Date.now(),
|
|
1723
|
+
correlationId: id,
|
|
1724
|
+
};
|
|
1725
|
+
try {
|
|
1726
|
+
if (opts?.transferable) {
|
|
1727
|
+
entry.worker.postMessage(message, opts.transferable);
|
|
1728
|
+
}
|
|
1729
|
+
else {
|
|
1730
|
+
entry.worker.postMessage(message);
|
|
1731
|
+
}
|
|
1732
|
+
this.bumpMessageCount(entry);
|
|
1733
|
+
}
|
|
1734
|
+
catch (error) {
|
|
1735
|
+
clearTimeout(timer);
|
|
1736
|
+
entry.pending.delete(id);
|
|
1737
|
+
reject(error instanceof Error ? error : new Error('postMessage failed'));
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
getMessages(workerName) {
|
|
1742
|
+
const entry = this.ensureEntry(workerName);
|
|
1743
|
+
return entry.messages$.asObservable();
|
|
1744
|
+
}
|
|
1745
|
+
getMessagesByType(workerName, type) {
|
|
1746
|
+
return this.getMessages(workerName).pipe(filter((m) => m.type === type));
|
|
1669
1747
|
}
|
|
1748
|
+
/** @deprecated Use {@link getStatusSignal}. Kept as Observable for backward compat. */
|
|
1670
1749
|
getStatus(workerName) {
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
return this.
|
|
1750
|
+
return toObservable(this.getStatusSignal(workerName));
|
|
1751
|
+
}
|
|
1752
|
+
getStatusSignal(workerName) {
|
|
1753
|
+
return this.ensureEntry(workerName).status.asReadonly();
|
|
1675
1754
|
}
|
|
1676
1755
|
getCurrentStatus(workerName) {
|
|
1677
|
-
return this.
|
|
1756
|
+
return this.entries.get(workerName)?.status();
|
|
1678
1757
|
}
|
|
1679
1758
|
getAllStatuses() {
|
|
1680
|
-
|
|
1759
|
+
const result = new Map();
|
|
1760
|
+
for (const [name, entry] of this.entries)
|
|
1761
|
+
result.set(name, entry.status());
|
|
1762
|
+
return result;
|
|
1681
1763
|
}
|
|
1682
1764
|
isWorkerRunning(workerName) {
|
|
1683
|
-
|
|
1684
|
-
|
|
1765
|
+
return this.entries.get(workerName)?.status().running ?? false;
|
|
1766
|
+
}
|
|
1767
|
+
getNativeWorker(name) {
|
|
1768
|
+
return this.entries.get(name)?.worker ?? undefined;
|
|
1685
1769
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1770
|
+
getAllWorkers() {
|
|
1771
|
+
const result = new Map();
|
|
1772
|
+
for (const [name, entry] of this.entries) {
|
|
1773
|
+
if (entry.worker)
|
|
1774
|
+
result.set(name, entry.worker);
|
|
1775
|
+
}
|
|
1776
|
+
return result;
|
|
1777
|
+
}
|
|
1778
|
+
// ---------- internals ----------
|
|
1779
|
+
attachHandlers(name, entry) {
|
|
1780
|
+
if (!entry.worker)
|
|
1781
|
+
return;
|
|
1782
|
+
entry.worker.onmessage = (event) => {
|
|
1783
|
+
const data = event.data ?? {};
|
|
1784
|
+
const correlationId = data.correlationId ?? data.id;
|
|
1785
|
+
if (correlationId && entry.pending.has(correlationId)) {
|
|
1786
|
+
const p = entry.pending.get(correlationId);
|
|
1787
|
+
clearTimeout(p.timer);
|
|
1788
|
+
entry.pending.delete(correlationId);
|
|
1789
|
+
p.resolve(data.data);
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1688
1792
|
const message = {
|
|
1689
|
-
id:
|
|
1690
|
-
type:
|
|
1691
|
-
data:
|
|
1692
|
-
timestamp:
|
|
1793
|
+
id: data.id ?? this.generateId(),
|
|
1794
|
+
type: data.type ?? 'message',
|
|
1795
|
+
data: data.data,
|
|
1796
|
+
timestamp: data.timestamp ?? Date.now(),
|
|
1797
|
+
correlationId: data.correlationId,
|
|
1693
1798
|
};
|
|
1694
|
-
|
|
1695
|
-
this.workerMessages.set(name, new Subject());
|
|
1696
|
-
}
|
|
1697
|
-
this.workerMessages.get(name).next(message);
|
|
1799
|
+
entry.messages$.next(message);
|
|
1698
1800
|
};
|
|
1699
|
-
worker.onerror = (error) => {
|
|
1700
|
-
this.
|
|
1701
|
-
|
|
1702
|
-
|
|
1801
|
+
entry.worker.onerror = (error) => {
|
|
1802
|
+
this.workerLogger.error(`[webworker] Worker "${name}" error`, error);
|
|
1803
|
+
entry.status.update((s) => ({
|
|
1804
|
+
...s,
|
|
1703
1805
|
running: false,
|
|
1704
1806
|
error: error instanceof Error ? error.message : 'Worker error',
|
|
1705
|
-
|
|
1706
|
-
};
|
|
1707
|
-
this.currentWorkerStatuses.set(name, status);
|
|
1708
|
-
this.updateWorkerStatus(name, status);
|
|
1807
|
+
}));
|
|
1709
1808
|
};
|
|
1710
|
-
// Auto-cleanup when service is destroyed
|
|
1711
|
-
this.destroyRef.onDestroy(() => {
|
|
1712
|
-
this.terminateWorker(name);
|
|
1713
|
-
});
|
|
1714
1809
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1810
|
+
rejectPending(entry, reason) {
|
|
1811
|
+
for (const p of entry.pending.values()) {
|
|
1812
|
+
clearTimeout(p.timer);
|
|
1813
|
+
p.reject(reason);
|
|
1718
1814
|
}
|
|
1719
|
-
|
|
1815
|
+
entry.pending.clear();
|
|
1720
1816
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
return this.workers.get(name);
|
|
1817
|
+
bumpMessageCount(entry) {
|
|
1818
|
+
entry.status.update((s) => ({ ...s, messageCount: s.messageCount + 1 }));
|
|
1724
1819
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1820
|
+
ensureEntry(workerName) {
|
|
1821
|
+
let entry = this.entries.get(workerName);
|
|
1822
|
+
if (!entry) {
|
|
1823
|
+
entry = {
|
|
1824
|
+
worker: null,
|
|
1825
|
+
status: signal({
|
|
1826
|
+
initialized: false,
|
|
1827
|
+
running: false,
|
|
1828
|
+
messageCount: 0,
|
|
1829
|
+
}),
|
|
1830
|
+
messages$: new Subject(),
|
|
1831
|
+
pending: new Map(),
|
|
1832
|
+
};
|
|
1833
|
+
this.entries.set(workerName, entry);
|
|
1834
|
+
}
|
|
1835
|
+
return entry;
|
|
1836
|
+
}
|
|
1837
|
+
generateId() {
|
|
1838
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
1839
|
+
return globalThis.crypto.randomUUID();
|
|
1840
|
+
}
|
|
1841
|
+
return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
1727
1842
|
}
|
|
1728
1843
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebWorkerService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1729
1844
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebWorkerService });
|
|
@@ -1763,8 +1878,8 @@ class IntersectionObserverService extends BrowserApiBaseService {
|
|
|
1763
1878
|
getApiName() {
|
|
1764
1879
|
return 'intersection-observer';
|
|
1765
1880
|
}
|
|
1766
|
-
|
|
1767
|
-
return
|
|
1881
|
+
getCapabilityId() {
|
|
1882
|
+
return 'intersectionObserver';
|
|
1768
1883
|
}
|
|
1769
1884
|
observe(element, options = {}) {
|
|
1770
1885
|
if (!this.isSupported()) {
|
|
@@ -1823,8 +1938,8 @@ class ResizeObserverService extends BrowserApiBaseService {
|
|
|
1823
1938
|
getApiName() {
|
|
1824
1939
|
return 'resize-observer';
|
|
1825
1940
|
}
|
|
1826
|
-
|
|
1827
|
-
return
|
|
1941
|
+
getCapabilityId() {
|
|
1942
|
+
return 'resizeObserver';
|
|
1828
1943
|
}
|
|
1829
1944
|
observe(element, options = {}) {
|
|
1830
1945
|
if (!this.isSupported()) {
|
|
@@ -1864,8 +1979,8 @@ class PageVisibilityService extends BrowserApiBaseService {
|
|
|
1864
1979
|
getApiName() {
|
|
1865
1980
|
return 'page-visibility';
|
|
1866
1981
|
}
|
|
1867
|
-
|
|
1868
|
-
return
|
|
1982
|
+
getCapabilityId() {
|
|
1983
|
+
return 'pageVisibility';
|
|
1869
1984
|
}
|
|
1870
1985
|
get isHidden() {
|
|
1871
1986
|
if (!this.isSupported())
|
|
@@ -1941,13 +2056,11 @@ class BroadcastChannelService extends ConnectionRegistryBaseService {
|
|
|
1941
2056
|
closeNativeConnection(channel) {
|
|
1942
2057
|
channel.close();
|
|
1943
2058
|
}
|
|
1944
|
-
|
|
1945
|
-
return
|
|
2059
|
+
getCapabilityId() {
|
|
2060
|
+
return 'broadcastChannel';
|
|
1946
2061
|
}
|
|
1947
2062
|
ensureBroadcastChannelSupported() {
|
|
1948
|
-
|
|
1949
|
-
throw new Error('BroadcastChannel API not supported in this environment');
|
|
1950
|
-
}
|
|
2063
|
+
this.ensureSupported();
|
|
1951
2064
|
}
|
|
1952
2065
|
open(name) {
|
|
1953
2066
|
this.ensureBroadcastChannelSupported();
|
|
@@ -2044,8 +2157,8 @@ class NetworkInformationService extends BrowserApiBaseService {
|
|
|
2044
2157
|
getApiName() {
|
|
2045
2158
|
return 'network-information';
|
|
2046
2159
|
}
|
|
2047
|
-
|
|
2048
|
-
return
|
|
2160
|
+
getCapabilityId() {
|
|
2161
|
+
return 'networkInformation';
|
|
2049
2162
|
}
|
|
2050
2163
|
getSnapshot() {
|
|
2051
2164
|
return this.isBrowserEnvironment() ? getNetworkSnapshot() : { online: true };
|
|
@@ -2068,8 +2181,8 @@ class ScreenWakeLockService extends BrowserApiBaseService {
|
|
|
2068
2181
|
getApiName() {
|
|
2069
2182
|
return 'screen-wake-lock';
|
|
2070
2183
|
}
|
|
2071
|
-
|
|
2072
|
-
return
|
|
2184
|
+
getCapabilityId() {
|
|
2185
|
+
return 'screenWakeLock';
|
|
2073
2186
|
}
|
|
2074
2187
|
get isActive() {
|
|
2075
2188
|
return this.sentinel !== null && !this.sentinel.released;
|
|
@@ -2163,8 +2276,8 @@ class ScreenOrientationService extends BrowserApiBaseService {
|
|
|
2163
2276
|
getApiName() {
|
|
2164
2277
|
return 'screen-orientation';
|
|
2165
2278
|
}
|
|
2166
|
-
|
|
2167
|
-
return
|
|
2279
|
+
getCapabilityId() {
|
|
2280
|
+
return 'screenOrientation';
|
|
2168
2281
|
}
|
|
2169
2282
|
getSnapshot() {
|
|
2170
2283
|
return this.isBrowserEnvironment()
|
|
@@ -2208,8 +2321,12 @@ class FullscreenService extends BrowserApiBaseService {
|
|
|
2208
2321
|
getApiName() {
|
|
2209
2322
|
return 'fullscreen';
|
|
2210
2323
|
}
|
|
2324
|
+
getCapabilityId() {
|
|
2325
|
+
return 'fullscreen';
|
|
2326
|
+
}
|
|
2327
|
+
/** Override to also check the *enabled* flag (browser may have disabled fullscreen). */
|
|
2211
2328
|
isSupported() {
|
|
2212
|
-
if (!
|
|
2329
|
+
if (!super.isSupported())
|
|
2213
2330
|
return false;
|
|
2214
2331
|
return !!(document.fullscreenEnabled ??
|
|
2215
2332
|
document.webkitFullscreenEnabled);
|
|
@@ -2300,17 +2417,18 @@ class FileSystemAccessService extends BrowserApiBaseService {
|
|
|
2300
2417
|
getApiName() {
|
|
2301
2418
|
return 'file-system-access';
|
|
2302
2419
|
}
|
|
2420
|
+
getCapabilityId() {
|
|
2421
|
+
return 'fileSystemAccess';
|
|
2422
|
+
}
|
|
2423
|
+
/** Override to also assert secure context (required by the spec). */
|
|
2303
2424
|
isSupported() {
|
|
2304
|
-
return
|
|
2425
|
+
return super.isSupported() && typeof window !== 'undefined' && window.isSecureContext;
|
|
2305
2426
|
}
|
|
2306
2427
|
get win() {
|
|
2307
2428
|
return window;
|
|
2308
2429
|
}
|
|
2309
2430
|
ensureSupported() {
|
|
2310
2431
|
super.ensureSupported();
|
|
2311
|
-
if (!('showOpenFilePicker' in window)) {
|
|
2312
|
-
throw new Error('File System Access API not supported in this browser');
|
|
2313
|
-
}
|
|
2314
2432
|
if (!window.isSecureContext) {
|
|
2315
2433
|
throw new Error('File System Access API requires a secure context (HTTPS)');
|
|
2316
2434
|
}
|
|
@@ -2386,8 +2504,8 @@ class MediaRecorderService extends BrowserApiBaseService {
|
|
|
2386
2504
|
getApiName() {
|
|
2387
2505
|
return 'media-recorder';
|
|
2388
2506
|
}
|
|
2389
|
-
|
|
2390
|
-
return
|
|
2507
|
+
getCapabilityId() {
|
|
2508
|
+
return 'mediaRecorder';
|
|
2391
2509
|
}
|
|
2392
2510
|
get state() {
|
|
2393
2511
|
return this.recorder?.state ?? 'inactive';
|
|
@@ -2477,13 +2595,11 @@ class ServerSentEventsService extends ConnectionRegistryBaseService {
|
|
|
2477
2595
|
closeNativeConnection(source) {
|
|
2478
2596
|
source.close();
|
|
2479
2597
|
}
|
|
2480
|
-
|
|
2481
|
-
return
|
|
2598
|
+
getCapabilityId() {
|
|
2599
|
+
return 'serverSentEvents';
|
|
2482
2600
|
}
|
|
2483
2601
|
ensureSSESupported() {
|
|
2484
|
-
|
|
2485
|
-
throw new Error('Server-Sent Events (EventSource) not supported in this environment');
|
|
2486
|
-
}
|
|
2602
|
+
this.ensureSupported();
|
|
2487
2603
|
}
|
|
2488
2604
|
connect(url, config = {}) {
|
|
2489
2605
|
this.ensureSSESupported();
|
|
@@ -2563,8 +2679,8 @@ class VibrationService extends BrowserApiBaseService {
|
|
|
2563
2679
|
notification: [200],
|
|
2564
2680
|
doubleTap: [50, 100, 50],
|
|
2565
2681
|
};
|
|
2566
|
-
|
|
2567
|
-
return
|
|
2682
|
+
getCapabilityId() {
|
|
2683
|
+
return 'vibration';
|
|
2568
2684
|
}
|
|
2569
2685
|
vibrate(pattern = 200) {
|
|
2570
2686
|
if (!this.isSupported())
|
|
@@ -2597,13 +2713,11 @@ class SpeechSynthesisService extends BrowserApiBaseService {
|
|
|
2597
2713
|
getApiName() {
|
|
2598
2714
|
return 'speech-synthesis';
|
|
2599
2715
|
}
|
|
2600
|
-
|
|
2601
|
-
return
|
|
2716
|
+
getCapabilityId() {
|
|
2717
|
+
return 'speechSynthesis';
|
|
2602
2718
|
}
|
|
2603
2719
|
ensureSpeechSynthesisSupported() {
|
|
2604
|
-
|
|
2605
|
-
throw new Error('Speech Synthesis API not supported in this browser');
|
|
2606
|
-
}
|
|
2720
|
+
this.ensureSupported();
|
|
2607
2721
|
}
|
|
2608
2722
|
get state() {
|
|
2609
2723
|
if (!this.isSupported())
|
|
@@ -2713,8 +2827,8 @@ class MutationObserverService extends BrowserApiBaseService {
|
|
|
2713
2827
|
getApiName() {
|
|
2714
2828
|
return 'mutation-observer';
|
|
2715
2829
|
}
|
|
2716
|
-
|
|
2717
|
-
return
|
|
2830
|
+
getCapabilityId() {
|
|
2831
|
+
return 'mutationObserver';
|
|
2718
2832
|
}
|
|
2719
2833
|
observe(target, options) {
|
|
2720
2834
|
if (!this.isSupported()) {
|
|
@@ -2751,8 +2865,8 @@ class PerformanceObserverService extends BrowserApiBaseService {
|
|
|
2751
2865
|
getApiName() {
|
|
2752
2866
|
return 'performance-observer';
|
|
2753
2867
|
}
|
|
2754
|
-
|
|
2755
|
-
return
|
|
2868
|
+
getCapabilityId() {
|
|
2869
|
+
return 'performanceObserver';
|
|
2756
2870
|
}
|
|
2757
2871
|
observe(config) {
|
|
2758
2872
|
if (!this.isSupported()) {
|
|
@@ -2780,8 +2894,8 @@ class WebAudioService extends BrowserApiBaseService {
|
|
|
2780
2894
|
return 'web-audio';
|
|
2781
2895
|
}
|
|
2782
2896
|
context = null;
|
|
2783
|
-
|
|
2784
|
-
return
|
|
2897
|
+
getCapabilityId() {
|
|
2898
|
+
return 'webAudio';
|
|
2785
2899
|
}
|
|
2786
2900
|
getContext() {
|
|
2787
2901
|
if (!this.isSupported()) {
|
|
@@ -2942,8 +3056,8 @@ class GamepadService extends BrowserApiBaseService {
|
|
|
2942
3056
|
getApiName() {
|
|
2943
3057
|
return 'gamepad';
|
|
2944
3058
|
}
|
|
2945
|
-
|
|
2946
|
-
return
|
|
3059
|
+
getCapabilityId() {
|
|
3060
|
+
return 'gamepad';
|
|
2947
3061
|
}
|
|
2948
3062
|
getSnapshot(index) {
|
|
2949
3063
|
if (!this.isSupported())
|
|
@@ -2984,6 +3098,193 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
|
|
|
2984
3098
|
type: Injectable
|
|
2985
3099
|
}] });
|
|
2986
3100
|
|
|
3101
|
+
/**
|
|
3102
|
+
* Service wrapping `navigator.locks` (Web Locks API). Coordinates exclusive or
|
|
3103
|
+
* shared access to a named resource across tabs and workers.
|
|
3104
|
+
*
|
|
3105
|
+
* ```ts
|
|
3106
|
+
* const locks = inject(WebLocksService);
|
|
3107
|
+
* await locks.acquire('user-cache', async () => {
|
|
3108
|
+
* // critical section — no other tab is in this block at the same time
|
|
3109
|
+
* });
|
|
3110
|
+
* ```
|
|
3111
|
+
*/
|
|
3112
|
+
class WebLocksService extends BrowserApiBaseService {
|
|
3113
|
+
getApiName() {
|
|
3114
|
+
return 'web-locks';
|
|
3115
|
+
}
|
|
3116
|
+
isSupported() {
|
|
3117
|
+
if (!this.isBrowserEnvironment())
|
|
3118
|
+
return false;
|
|
3119
|
+
return !!navigator.locks;
|
|
3120
|
+
}
|
|
3121
|
+
/**
|
|
3122
|
+
* Acquire a lock and run the callback while holding it. The lock is released
|
|
3123
|
+
* automatically when the callback resolves or rejects.
|
|
3124
|
+
*/
|
|
3125
|
+
acquire(name, callback, options = {}) {
|
|
3126
|
+
this.ensureSupported();
|
|
3127
|
+
const nav = navigator;
|
|
3128
|
+
if (Object.keys(options).length === 0) {
|
|
3129
|
+
return nav.locks.request(name, () => callback());
|
|
3130
|
+
}
|
|
3131
|
+
return nav.locks.request(name, options, () => callback());
|
|
3132
|
+
}
|
|
3133
|
+
/**
|
|
3134
|
+
* Query the current lock state. Useful for diagnostics and tests; do not gate
|
|
3135
|
+
* critical-section logic on this — it's a snapshot, not a reservation.
|
|
3136
|
+
*/
|
|
3137
|
+
query() {
|
|
3138
|
+
this.ensureSupported();
|
|
3139
|
+
return navigator.locks.query();
|
|
3140
|
+
}
|
|
3141
|
+
ensureSupported() {
|
|
3142
|
+
super.ensureSupported();
|
|
3143
|
+
if (!this.isSupported()) {
|
|
3144
|
+
throw new Error('Web Locks API not supported in this browser');
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebLocksService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
3148
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebLocksService });
|
|
3149
|
+
}
|
|
3150
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: WebLocksService, decorators: [{
|
|
3151
|
+
type: Injectable
|
|
3152
|
+
}] });
|
|
3153
|
+
|
|
3154
|
+
/**
|
|
3155
|
+
* Service wrapping `navigator.storage` (StorageManager API). Exposes:
|
|
3156
|
+
* - `estimate()`: how much origin storage is in use vs available
|
|
3157
|
+
* - `persist()`: ask the browser to make storage persistent (eviction-protected)
|
|
3158
|
+
* - `persisted()`: check whether storage is currently persistent
|
|
3159
|
+
*/
|
|
3160
|
+
class StorageManagerService extends BrowserApiBaseService {
|
|
3161
|
+
getApiName() {
|
|
3162
|
+
return 'storage-manager';
|
|
3163
|
+
}
|
|
3164
|
+
isSupported() {
|
|
3165
|
+
if (!this.isBrowserEnvironment())
|
|
3166
|
+
return false;
|
|
3167
|
+
const sm = navigator.storage;
|
|
3168
|
+
return !!sm && typeof sm.estimate === 'function';
|
|
3169
|
+
}
|
|
3170
|
+
async estimate() {
|
|
3171
|
+
this.ensureSupported();
|
|
3172
|
+
const sm = navigator.storage;
|
|
3173
|
+
const result = await sm.estimate();
|
|
3174
|
+
return {
|
|
3175
|
+
usage: result.usage ?? 0,
|
|
3176
|
+
quota: result.quota ?? 0,
|
|
3177
|
+
usageDetails: result
|
|
3178
|
+
.usageDetails,
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3181
|
+
async persist() {
|
|
3182
|
+
this.ensureSupported();
|
|
3183
|
+
const sm = navigator.storage;
|
|
3184
|
+
if (typeof sm.persist !== 'function')
|
|
3185
|
+
return false;
|
|
3186
|
+
return sm.persist();
|
|
3187
|
+
}
|
|
3188
|
+
async persisted() {
|
|
3189
|
+
this.ensureSupported();
|
|
3190
|
+
const sm = navigator.storage;
|
|
3191
|
+
if (typeof sm.persisted !== 'function')
|
|
3192
|
+
return false;
|
|
3193
|
+
return sm.persisted();
|
|
3194
|
+
}
|
|
3195
|
+
ensureSupported() {
|
|
3196
|
+
super.ensureSupported();
|
|
3197
|
+
if (!this.isSupported()) {
|
|
3198
|
+
throw new Error('StorageManager API not supported in this browser');
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: StorageManagerService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
3202
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: StorageManagerService });
|
|
3203
|
+
}
|
|
3204
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: StorageManagerService, decorators: [{
|
|
3205
|
+
type: Injectable
|
|
3206
|
+
}] });
|
|
3207
|
+
|
|
3208
|
+
/**
|
|
3209
|
+
* Service wrapping `CompressionStream` and `DecompressionStream`.
|
|
3210
|
+
*
|
|
3211
|
+
* ```ts
|
|
3212
|
+
* const cmp = inject(CompressionService);
|
|
3213
|
+
* const compressed = await cmp.compress(jsonBytes, 'gzip');
|
|
3214
|
+
* const original = await cmp.decompress(compressed, 'gzip');
|
|
3215
|
+
* ```
|
|
3216
|
+
*/
|
|
3217
|
+
class CompressionService extends BrowserApiBaseService {
|
|
3218
|
+
getApiName() {
|
|
3219
|
+
return 'compression-streams';
|
|
3220
|
+
}
|
|
3221
|
+
isSupported() {
|
|
3222
|
+
if (!this.isBrowserEnvironment())
|
|
3223
|
+
return false;
|
|
3224
|
+
const g = globalThis;
|
|
3225
|
+
return typeof g.CompressionStream === 'function' && typeof g.DecompressionStream === 'function';
|
|
3226
|
+
}
|
|
3227
|
+
/** Compress a `Uint8Array`/`ArrayBuffer` using the given format. */
|
|
3228
|
+
async compress(data, format = 'gzip') {
|
|
3229
|
+
this.ensureSupported();
|
|
3230
|
+
const g = globalThis;
|
|
3231
|
+
const stream = new g.CompressionStream(format);
|
|
3232
|
+
return this.runStream(data, stream);
|
|
3233
|
+
}
|
|
3234
|
+
/** Decompress a `Uint8Array`/`ArrayBuffer` using the given format. */
|
|
3235
|
+
async decompress(data, format = 'gzip') {
|
|
3236
|
+
this.ensureSupported();
|
|
3237
|
+
const g = globalThis;
|
|
3238
|
+
const stream = new g.DecompressionStream(format);
|
|
3239
|
+
return this.runStream(data, stream);
|
|
3240
|
+
}
|
|
3241
|
+
/** Convenience: compress a UTF-8 string and return the compressed bytes. */
|
|
3242
|
+
async compressString(value, format = 'gzip') {
|
|
3243
|
+
return this.compress(new TextEncoder().encode(value), format);
|
|
3244
|
+
}
|
|
3245
|
+
/** Convenience: decompress bytes into a UTF-8 string. */
|
|
3246
|
+
async decompressString(data, format = 'gzip') {
|
|
3247
|
+
const bytes = await this.decompress(data, format);
|
|
3248
|
+
return new TextDecoder().decode(bytes);
|
|
3249
|
+
}
|
|
3250
|
+
ensureSupported() {
|
|
3251
|
+
super.ensureSupported();
|
|
3252
|
+
if (!this.isSupported()) {
|
|
3253
|
+
throw new Error('Compression Streams API not supported in this browser');
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
async runStream(data, stream) {
|
|
3257
|
+
const writer = stream.writable.getWriter();
|
|
3258
|
+
const writePromise = writer.write(data instanceof Uint8Array ? data : new Uint8Array(data));
|
|
3259
|
+
const closePromise = writer.close();
|
|
3260
|
+
const reader = stream.readable.getReader();
|
|
3261
|
+
const chunks = [];
|
|
3262
|
+
let total = 0;
|
|
3263
|
+
while (true) {
|
|
3264
|
+
const { done, value } = await reader.read();
|
|
3265
|
+
if (done)
|
|
3266
|
+
break;
|
|
3267
|
+
const chunk = value;
|
|
3268
|
+
chunks.push(chunk);
|
|
3269
|
+
total += chunk.byteLength;
|
|
3270
|
+
}
|
|
3271
|
+
await writePromise;
|
|
3272
|
+
await closePromise;
|
|
3273
|
+
const result = new Uint8Array(total);
|
|
3274
|
+
let offset = 0;
|
|
3275
|
+
for (const chunk of chunks) {
|
|
3276
|
+
result.set(chunk, offset);
|
|
3277
|
+
offset += chunk.byteLength;
|
|
3278
|
+
}
|
|
3279
|
+
return result;
|
|
3280
|
+
}
|
|
3281
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CompressionService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
3282
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CompressionService });
|
|
3283
|
+
}
|
|
3284
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CompressionService, decorators: [{
|
|
3285
|
+
type: Injectable
|
|
3286
|
+
}] });
|
|
3287
|
+
|
|
2987
3288
|
// Common types for browser APIs
|
|
2988
3289
|
|
|
2989
3290
|
function injectPageVisibility() {
|
|
@@ -3163,6 +3464,257 @@ function injectGamepad(index, intervalMs = 16) {
|
|
|
3163
3464
|
};
|
|
3164
3465
|
}
|
|
3165
3466
|
|
|
3467
|
+
function injectClipboard() {
|
|
3468
|
+
const destroyRef = inject(DestroyRef);
|
|
3469
|
+
const platformId = inject(PLATFORM_ID);
|
|
3470
|
+
const isBrowser = isPlatformBrowser(platformId);
|
|
3471
|
+
const supported = signal(isBrowser && typeof navigator !== 'undefined' && !!navigator.clipboard, ...(ngDevMode ? [{ debugName: "supported" }] : /* istanbul ignore next */ []));
|
|
3472
|
+
const text = signal(null, ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
3473
|
+
const error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
3474
|
+
const busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
3475
|
+
let disposed = false;
|
|
3476
|
+
destroyRef.onDestroy(() => {
|
|
3477
|
+
disposed = true;
|
|
3478
|
+
});
|
|
3479
|
+
const writeText = async (value) => {
|
|
3480
|
+
if (!supported() || disposed) {
|
|
3481
|
+
error.set('Clipboard API not supported');
|
|
3482
|
+
return false;
|
|
3483
|
+
}
|
|
3484
|
+
busy.set(true);
|
|
3485
|
+
try {
|
|
3486
|
+
await navigator.clipboard.writeText(value);
|
|
3487
|
+
if (!disposed) {
|
|
3488
|
+
text.set(value);
|
|
3489
|
+
error.set(null);
|
|
3490
|
+
}
|
|
3491
|
+
return true;
|
|
3492
|
+
}
|
|
3493
|
+
catch (err) {
|
|
3494
|
+
if (!disposed)
|
|
3495
|
+
error.set(err instanceof Error ? err.message : 'writeText failed');
|
|
3496
|
+
return false;
|
|
3497
|
+
}
|
|
3498
|
+
finally {
|
|
3499
|
+
if (!disposed)
|
|
3500
|
+
busy.set(false);
|
|
3501
|
+
}
|
|
3502
|
+
};
|
|
3503
|
+
const readText = async () => {
|
|
3504
|
+
if (!supported() || disposed) {
|
|
3505
|
+
error.set('Clipboard API not supported');
|
|
3506
|
+
return null;
|
|
3507
|
+
}
|
|
3508
|
+
busy.set(true);
|
|
3509
|
+
try {
|
|
3510
|
+
const value = await navigator.clipboard.readText();
|
|
3511
|
+
if (!disposed) {
|
|
3512
|
+
text.set(value);
|
|
3513
|
+
error.set(null);
|
|
3514
|
+
}
|
|
3515
|
+
return value;
|
|
3516
|
+
}
|
|
3517
|
+
catch (err) {
|
|
3518
|
+
if (!disposed)
|
|
3519
|
+
error.set(err instanceof Error ? err.message : 'readText failed');
|
|
3520
|
+
return null;
|
|
3521
|
+
}
|
|
3522
|
+
finally {
|
|
3523
|
+
if (!disposed)
|
|
3524
|
+
busy.set(false);
|
|
3525
|
+
}
|
|
3526
|
+
};
|
|
3527
|
+
return {
|
|
3528
|
+
text: text.asReadonly(),
|
|
3529
|
+
error: error.asReadonly(),
|
|
3530
|
+
busy: busy.asReadonly(),
|
|
3531
|
+
isSupported: supported.asReadonly(),
|
|
3532
|
+
writeText,
|
|
3533
|
+
readText,
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
function injectGeolocation(opts = {}) {
|
|
3538
|
+
const destroyRef = inject(DestroyRef);
|
|
3539
|
+
const platformId = inject(PLATFORM_ID);
|
|
3540
|
+
const isBrowser = isPlatformBrowser(platformId);
|
|
3541
|
+
const supported = signal(isBrowser && typeof navigator !== 'undefined' && 'geolocation' in navigator, ...(ngDevMode ? [{ debugName: "supported" }] : /* istanbul ignore next */ []));
|
|
3542
|
+
const position = signal(null, ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
|
|
3543
|
+
const error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
3544
|
+
const watching = signal(false, ...(ngDevMode ? [{ debugName: "watching" }] : /* istanbul ignore next */ []));
|
|
3545
|
+
let watchId = null;
|
|
3546
|
+
const watch = (positionOpts) => {
|
|
3547
|
+
if (!supported() || watchId !== null)
|
|
3548
|
+
return;
|
|
3549
|
+
watching.set(true);
|
|
3550
|
+
watchId = navigator.geolocation.watchPosition((pos) => {
|
|
3551
|
+
position.set(pos);
|
|
3552
|
+
error.set(null);
|
|
3553
|
+
}, (err) => error.set(err), positionOpts);
|
|
3554
|
+
};
|
|
3555
|
+
const stop = () => {
|
|
3556
|
+
if (watchId !== null) {
|
|
3557
|
+
navigator.geolocation.clearWatch(watchId);
|
|
3558
|
+
watchId = null;
|
|
3559
|
+
}
|
|
3560
|
+
watching.set(false);
|
|
3561
|
+
};
|
|
3562
|
+
const getCurrent = (positionOpts) => {
|
|
3563
|
+
if (!supported()) {
|
|
3564
|
+
return Promise.reject(new Error('Geolocation API not supported'));
|
|
3565
|
+
}
|
|
3566
|
+
return new Promise((resolve, reject) => {
|
|
3567
|
+
navigator.geolocation.getCurrentPosition((pos) => {
|
|
3568
|
+
position.set(pos);
|
|
3569
|
+
error.set(null);
|
|
3570
|
+
resolve(pos);
|
|
3571
|
+
}, (err) => {
|
|
3572
|
+
error.set(err);
|
|
3573
|
+
reject(err);
|
|
3574
|
+
}, positionOpts);
|
|
3575
|
+
});
|
|
3576
|
+
};
|
|
3577
|
+
destroyRef.onDestroy(() => stop());
|
|
3578
|
+
if (opts.watch)
|
|
3579
|
+
watch(opts);
|
|
3580
|
+
return {
|
|
3581
|
+
position: position.asReadonly(),
|
|
3582
|
+
error: error.asReadonly(),
|
|
3583
|
+
watching: watching.asReadonly(),
|
|
3584
|
+
isSupported: supported.asReadonly(),
|
|
3585
|
+
watch,
|
|
3586
|
+
stop,
|
|
3587
|
+
getCurrent,
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
function injectBattery() {
|
|
3592
|
+
const destroyRef = inject(DestroyRef);
|
|
3593
|
+
const platformId = inject(PLATFORM_ID);
|
|
3594
|
+
const isBrowser = isPlatformBrowser(platformId);
|
|
3595
|
+
const supported = signal(isBrowser && typeof navigator !== 'undefined' && 'getBattery' in navigator, ...(ngDevMode ? [{ debugName: "supported" }] : /* istanbul ignore next */ []));
|
|
3596
|
+
const info = signal(null, ...(ngDevMode ? [{ debugName: "info" }] : /* istanbul ignore next */ []));
|
|
3597
|
+
const error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
3598
|
+
let manager = null;
|
|
3599
|
+
let disposed = false;
|
|
3600
|
+
const snapshot = () => manager
|
|
3601
|
+
? {
|
|
3602
|
+
charging: manager.charging,
|
|
3603
|
+
level: manager.level,
|
|
3604
|
+
chargingTime: manager.chargingTime,
|
|
3605
|
+
dischargingTime: manager.dischargingTime,
|
|
3606
|
+
}
|
|
3607
|
+
: null;
|
|
3608
|
+
const update = () => {
|
|
3609
|
+
if (!disposed)
|
|
3610
|
+
info.set(snapshot());
|
|
3611
|
+
};
|
|
3612
|
+
const events = [
|
|
3613
|
+
'chargingchange',
|
|
3614
|
+
'levelchange',
|
|
3615
|
+
'chargingtimechange',
|
|
3616
|
+
'dischargingtimechange',
|
|
3617
|
+
];
|
|
3618
|
+
const refresh = async () => {
|
|
3619
|
+
if (!supported() || disposed)
|
|
3620
|
+
return;
|
|
3621
|
+
try {
|
|
3622
|
+
if (!manager) {
|
|
3623
|
+
const nav = navigator;
|
|
3624
|
+
manager = await nav.getBattery();
|
|
3625
|
+
for (const ev of events) {
|
|
3626
|
+
manager.addEventListener(ev, update);
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
update();
|
|
3630
|
+
}
|
|
3631
|
+
catch (err) {
|
|
3632
|
+
if (!disposed)
|
|
3633
|
+
error.set(err instanceof Error ? err.message : 'getBattery failed');
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
destroyRef.onDestroy(() => {
|
|
3637
|
+
disposed = true;
|
|
3638
|
+
if (manager) {
|
|
3639
|
+
for (const ev of events) {
|
|
3640
|
+
manager.removeEventListener(ev, update);
|
|
3641
|
+
}
|
|
3642
|
+
manager = null;
|
|
3643
|
+
}
|
|
3644
|
+
});
|
|
3645
|
+
if (supported())
|
|
3646
|
+
void refresh();
|
|
3647
|
+
return {
|
|
3648
|
+
info: info.asReadonly(),
|
|
3649
|
+
error: error.asReadonly(),
|
|
3650
|
+
isSupported: supported.asReadonly(),
|
|
3651
|
+
refresh,
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
function injectWakeLock() {
|
|
3656
|
+
const destroyRef = inject(DestroyRef);
|
|
3657
|
+
const platformId = inject(PLATFORM_ID);
|
|
3658
|
+
const isBrowser = isPlatformBrowser(platformId);
|
|
3659
|
+
const supported = signal(isBrowser && typeof navigator !== 'undefined' && 'wakeLock' in navigator, ...(ngDevMode ? [{ debugName: "supported" }] : /* istanbul ignore next */ []));
|
|
3660
|
+
const active = signal(false, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
3661
|
+
const error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
3662
|
+
let sentinel = null;
|
|
3663
|
+
let disposed = false;
|
|
3664
|
+
const onRelease = () => {
|
|
3665
|
+
if (!disposed)
|
|
3666
|
+
active.set(false);
|
|
3667
|
+
sentinel = null;
|
|
3668
|
+
};
|
|
3669
|
+
const request = async () => {
|
|
3670
|
+
if (!supported() || disposed) {
|
|
3671
|
+
error.set('Screen Wake Lock API not supported');
|
|
3672
|
+
return false;
|
|
3673
|
+
}
|
|
3674
|
+
if (sentinel && !sentinel.released)
|
|
3675
|
+
return true;
|
|
3676
|
+
try {
|
|
3677
|
+
const nav = navigator;
|
|
3678
|
+
sentinel = await nav.wakeLock.request('screen');
|
|
3679
|
+
sentinel.addEventListener('release', onRelease);
|
|
3680
|
+
if (!disposed) {
|
|
3681
|
+
active.set(true);
|
|
3682
|
+
error.set(null);
|
|
3683
|
+
}
|
|
3684
|
+
return true;
|
|
3685
|
+
}
|
|
3686
|
+
catch (err) {
|
|
3687
|
+
if (!disposed)
|
|
3688
|
+
error.set(err instanceof Error ? err.message : 'wakeLock.request failed');
|
|
3689
|
+
return false;
|
|
3690
|
+
}
|
|
3691
|
+
};
|
|
3692
|
+
const release = async () => {
|
|
3693
|
+
if (!sentinel || sentinel.released)
|
|
3694
|
+
return;
|
|
3695
|
+
try {
|
|
3696
|
+
await sentinel.release();
|
|
3697
|
+
}
|
|
3698
|
+
finally {
|
|
3699
|
+
onRelease();
|
|
3700
|
+
}
|
|
3701
|
+
};
|
|
3702
|
+
destroyRef.onDestroy(() => {
|
|
3703
|
+
disposed = true;
|
|
3704
|
+
if (sentinel && !sentinel.released) {
|
|
3705
|
+
void sentinel.release();
|
|
3706
|
+
}
|
|
3707
|
+
sentinel = null;
|
|
3708
|
+
});
|
|
3709
|
+
return {
|
|
3710
|
+
active: active.asReadonly(),
|
|
3711
|
+
error: error.asReadonly(),
|
|
3712
|
+
isSupported: supported.asReadonly(),
|
|
3713
|
+
request,
|
|
3714
|
+
release,
|
|
3715
|
+
};
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3166
3718
|
class BrowserSupportUtil {
|
|
3167
3719
|
static isSupported(feature) {
|
|
3168
3720
|
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
@@ -3389,6 +3941,18 @@ function provideGamepad() {
|
|
|
3389
3941
|
return makeEnvironmentProviders([GamepadService]);
|
|
3390
3942
|
}
|
|
3391
3943
|
|
|
3944
|
+
function provideWebLocks() {
|
|
3945
|
+
return makeEnvironmentProviders([WebLocksService]);
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
function provideStorageManager() {
|
|
3949
|
+
return makeEnvironmentProviders([StorageManagerService]);
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
function provideCompression() {
|
|
3953
|
+
return makeEnvironmentProviders([CompressionService]);
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3392
3956
|
function provideMediaApis() {
|
|
3393
3957
|
return makeEnvironmentProviders([PermissionsService, CameraService, MediaDevicesService]);
|
|
3394
3958
|
}
|
|
@@ -3484,4 +4048,4 @@ const version = '0.1.0';
|
|
|
3484
4048
|
* Generated bundle index. Do not edit.
|
|
3485
4049
|
*/
|
|
3486
4050
|
|
|
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 };
|
|
4051
|
+
export { BROWSER_API_LOGGER, BatteryService, BroadcastChannelService, BrowserApiBaseService, BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, CompressionService, ConnectionRegistryBaseService, FileSystemAccessService, FullscreenService, GamepadService, GeolocationService, IntersectionObserverService, MediaDevicesService, MediaRecorderService, MutationObserverService, NetworkInformationService, NotificationService, PageVisibilityService, PerformanceObserverService, PermissionsService, ResizeObserverService, ScreenOrientationService, ScreenWakeLockService, ServerSentEventsService, SpeechSynthesisService, StorageManagerService, VibrationService, WebAudioService, WebLocksService, WebShareService, WebSocketClient, WebSocketService, WebStorageService, WebWorkerService, permissionGuard as createPermissionGuard, defaultBrowserWebApisConfig, injectBattery, injectClipboard, injectGamepad, injectGeolocation, injectIntersectionObserver, injectMutationObserver, injectNetworkInformation, injectPageVisibility, injectPerformanceObserver, injectResizeObserver, injectScreenOrientation, injectWakeLock, permissionGuard, provideBattery, provideBroadcastChannel, provideBrowserWebApis, provideCamera, provideClipboard, provideCommunicationApis, provideCompression, provideFileSystemAccess, provideFullscreen, provideGamepad, provideGeolocation, provideIntersectionObserver, provideLocationApis, provideMediaApis, provideMediaDevices, provideMediaRecorder, provideMutationObserver, provideNetworkInformation, provideNotifications, providePageVisibility, providePerformanceObserver, providePermissions, provideResizeObserver, provideScreenOrientation, provideScreenWakeLock, provideServerSentEvents, provideSpeechSynthesis, provideStorageApis, provideStorageManager, provideVibration, provideWebAudio, provideWebLocks, provideWebShare, provideWebSocket, provideWebStorage, provideWebWorker, version };
|