@angular-helpers/browser-web-apis 21.0.2 → 21.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Angular services package for a structured and secure access layer over browser Web APIs.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
🌐 **Documentation & Demo**: https://gaspar1992.github.io/angular-helpers/
|
|
6
|
+
|
|
7
|
+
[Leer en español](./README.es.md)
|
|
6
8
|
|
|
7
9
|
## Features
|
|
8
10
|
|
|
@@ -16,26 +18,54 @@ Angular services package for a structured and secure access layer over browser W
|
|
|
16
18
|
## Available Services
|
|
17
19
|
|
|
18
20
|
### Media and Device APIs
|
|
21
|
+
|
|
19
22
|
- `CameraService` - Camera access with permission handling
|
|
20
23
|
- `MediaDevicesService` - Media device listing and management
|
|
21
24
|
- `GeolocationService` - Geolocation API access
|
|
22
25
|
- `NotificationService` - Browser notifications API
|
|
26
|
+
- `MediaRecorderService` - Record audio/video from MediaStream
|
|
27
|
+
|
|
28
|
+
### Observer APIs
|
|
29
|
+
|
|
30
|
+
- `IntersectionObserverService` - Detect when elements enter/exit viewport
|
|
31
|
+
- `ResizeObserverService` - Watch for element size changes
|
|
32
|
+
|
|
33
|
+
### System APIs
|
|
34
|
+
|
|
35
|
+
- `BatteryService` - Monitor battery status and charging state
|
|
36
|
+
- `PageVisibilityService` - Track document visibility state changes
|
|
37
|
+
- `ScreenWakeLockService` - Prevent screen from dimming or locking
|
|
38
|
+
- `ScreenOrientationService` - Read and lock screen orientation
|
|
39
|
+
- `FullscreenService` - Toggle fullscreen mode for elements
|
|
40
|
+
- `VibrationService` - Trigger haptic feedback patterns
|
|
41
|
+
- `SpeechSynthesisService` - Text-to-speech with voice selection
|
|
42
|
+
|
|
43
|
+
### Network APIs
|
|
23
44
|
|
|
24
|
-
### Web APIs
|
|
25
|
-
- `WebWorkerService` - Web Worker management
|
|
26
45
|
- `WebSocketService` - WebSocket connection handling
|
|
46
|
+
- `ServerSentEventsService` - Server-Sent Events client
|
|
47
|
+
- `BroadcastChannelService` - Inter-tab communication
|
|
48
|
+
- `NetworkInformationService` - Connection info and online status
|
|
49
|
+
|
|
50
|
+
### Storage & I/O APIs
|
|
51
|
+
|
|
27
52
|
- `WebStorageService` - LocalStorage and SessionStorage helpers
|
|
28
53
|
- `WebShareService` - Native Web Share API support
|
|
29
54
|
- `ClipboardService` - System clipboard access
|
|
55
|
+
- `FileSystemAccessService` - Open/save files via native picker
|
|
56
|
+
|
|
57
|
+
### Web APIs
|
|
58
|
+
|
|
59
|
+
- `WebWorkerService` - Web Worker management
|
|
60
|
+
|
|
61
|
+
### Security & Capabilities
|
|
30
62
|
|
|
31
|
-
### Security
|
|
32
|
-
- `RegexSecurityService` - ReDoS prevention with worker-based validation
|
|
33
63
|
- `PermissionsService` - Centralized browser permissions handling
|
|
64
|
+
- `BrowserCapabilityService` - Feature-detection for browser API availability
|
|
34
65
|
|
|
35
66
|
### Utilities
|
|
36
|
-
|
|
67
|
+
|
|
37
68
|
- `BrowserApiBaseService` - Shared base class for browser API services
|
|
38
|
-
- `MediaDeviceBaseService` - Shared base class for media device services
|
|
39
69
|
|
|
40
70
|
## Installation
|
|
41
71
|
|
|
@@ -53,7 +83,7 @@ bootstrapApplication(AppComponent, {
|
|
|
53
83
|
provideBrowserWebApis({
|
|
54
84
|
enableCamera: true,
|
|
55
85
|
enableGeolocation: true,
|
|
56
|
-
|
|
86
|
+
enableNotifications: true,
|
|
57
87
|
}),
|
|
58
88
|
],
|
|
59
89
|
});
|
|
@@ -67,7 +97,7 @@ bootstrapApplication(AppComponent, {
|
|
|
67
97
|
import { CameraService } from '@angular-helpers/browser-web-apis';
|
|
68
98
|
|
|
69
99
|
export class PhotoComponent {
|
|
70
|
-
|
|
100
|
+
private cameraService = inject(CameraService);
|
|
71
101
|
|
|
72
102
|
async takePhoto() {
|
|
73
103
|
try {
|
|
@@ -88,28 +118,17 @@ export class PhotoComponent {
|
|
|
88
118
|
}
|
|
89
119
|
```
|
|
90
120
|
|
|
91
|
-
###
|
|
121
|
+
### BrowserCapabilityService
|
|
92
122
|
|
|
93
123
|
```typescript
|
|
94
|
-
import {
|
|
95
|
-
|
|
96
|
-
export class SecurityComponent {
|
|
97
|
-
constructor(private regexSecurity: RegexSecurityService) {}
|
|
98
|
-
|
|
99
|
-
async testPattern() {
|
|
100
|
-
const pattern = '(.+)+'; // Potentially unsafe regex pattern
|
|
101
|
-
const text = 'some text to test';
|
|
124
|
+
import { BrowserCapabilityService } from '@angular-helpers/browser-web-apis';
|
|
102
125
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
timeout: 5000,
|
|
106
|
-
safeMode: true,
|
|
107
|
-
});
|
|
126
|
+
export class MyComponent {
|
|
127
|
+
private capability = inject(BrowserCapabilityService);
|
|
108
128
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
console.error('Regex test failed:', error);
|
|
129
|
+
ngOnInit() {
|
|
130
|
+
if (this.capability.isSupported('geolocation')) {
|
|
131
|
+
console.log('Geolocation is available');
|
|
113
132
|
}
|
|
114
133
|
}
|
|
115
134
|
}
|
|
@@ -121,7 +140,7 @@ export class SecurityComponent {
|
|
|
121
140
|
import { GeolocationService } from '@angular-helpers/browser-web-apis';
|
|
122
141
|
|
|
123
142
|
export class LocationComponent {
|
|
124
|
-
|
|
143
|
+
private geolocation = inject(GeolocationService);
|
|
125
144
|
|
|
126
145
|
async getCurrentLocation() {
|
|
127
146
|
try {
|
|
@@ -139,6 +158,367 @@ export class LocationComponent {
|
|
|
139
158
|
}
|
|
140
159
|
```
|
|
141
160
|
|
|
161
|
+
### IntersectionObserverService
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { IntersectionObserverService } from '@angular-helpers/browser-web-apis';
|
|
165
|
+
|
|
166
|
+
export class LazyImageComponent {
|
|
167
|
+
private intersectionService = inject(IntersectionObserverService);
|
|
168
|
+
private elementRef = inject(ElementRef);
|
|
169
|
+
|
|
170
|
+
isVisible = signal(false);
|
|
171
|
+
|
|
172
|
+
ngAfterViewInit() {
|
|
173
|
+
this.intersectionService
|
|
174
|
+
.observeVisibility(this.elementRef.nativeElement, { threshold: 0.5 })
|
|
175
|
+
.subscribe((visible) => this.isVisible.set(visible));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### ResizeObserverService
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { ResizeObserverService } from '@angular-helpers/browser-web-apis';
|
|
184
|
+
|
|
185
|
+
export class ResponsiveComponent {
|
|
186
|
+
private resizeService = inject(ResizeObserverService);
|
|
187
|
+
private elementRef = inject(ElementRef);
|
|
188
|
+
|
|
189
|
+
elementSize = signal<ElementSize | null>(null);
|
|
190
|
+
|
|
191
|
+
ngAfterViewInit() {
|
|
192
|
+
this.resizeService
|
|
193
|
+
.observeSize(this.elementRef.nativeElement)
|
|
194
|
+
.subscribe((size) => this.elementSize.set(size));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### PageVisibilityService
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { PageVisibilityService } from '@angular-helpers/browser-web-apis';
|
|
203
|
+
|
|
204
|
+
export class AnalyticsComponent {
|
|
205
|
+
private visibilityService = inject(PageVisibilityService);
|
|
206
|
+
|
|
207
|
+
ngOnInit() {
|
|
208
|
+
this.visibilityService.watch().subscribe((state) => {
|
|
209
|
+
console.log('Page is now:', state);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### FullscreenService
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { FullscreenService } from '@angular-helpers/browser-web-apis';
|
|
219
|
+
|
|
220
|
+
export class VideoPlayerComponent {
|
|
221
|
+
private fullscreenService = inject(FullscreenService);
|
|
222
|
+
|
|
223
|
+
async toggleFullscreen() {
|
|
224
|
+
await this.fullscreenService.toggle();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### ScreenWakeLockService
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { ScreenWakeLockService } from '@angular-helpers/browser-web-apis';
|
|
233
|
+
|
|
234
|
+
export class PresentationComponent {
|
|
235
|
+
private wakeLockService = inject(ScreenWakeLockService);
|
|
236
|
+
|
|
237
|
+
async keepScreenOn() {
|
|
238
|
+
await this.wakeLockService.request();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async releaseScreen() {
|
|
242
|
+
await this.wakeLockService.release();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### BroadcastChannelService
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { BroadcastChannelService } from '@angular-helpers/browser-web-apis';
|
|
251
|
+
|
|
252
|
+
export class SyncComponent {
|
|
253
|
+
private broadcastService = inject(BroadcastChannelService);
|
|
254
|
+
|
|
255
|
+
ngOnInit() {
|
|
256
|
+
// Listen for messages from other tabs
|
|
257
|
+
this.broadcastService.open<string>('app-sync').subscribe((msg) => {
|
|
258
|
+
console.log('Received:', msg);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
sendMessage(data: string) {
|
|
263
|
+
this.broadcastService.post('app-sync', data);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### ServerSentEventsService
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { ServerSentEventsService } from '@angular-helpers/browser-web-apis';
|
|
272
|
+
|
|
273
|
+
export class LiveFeedComponent {
|
|
274
|
+
private sseService = inject(ServerSentEventsService);
|
|
275
|
+
|
|
276
|
+
connectToEvents() {
|
|
277
|
+
this.sseService.connect('https://api.example.com/events').subscribe({
|
|
278
|
+
next: (message) => console.log('Event:', message),
|
|
279
|
+
error: (err) => console.error('SSE error:', err),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### VibrationService
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { VibrationService } from '@angular-helpers/browser-web-apis';
|
|
289
|
+
|
|
290
|
+
export class FeedbackComponent {
|
|
291
|
+
private vibrationService = inject(VibrationService);
|
|
292
|
+
|
|
293
|
+
onSuccess() {
|
|
294
|
+
this.vibrationService.success();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
onError() {
|
|
298
|
+
this.vibrationService.error();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### SpeechSynthesisService
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { SpeechSynthesisService } from '@angular-helpers/browser-web-apis';
|
|
307
|
+
|
|
308
|
+
export class VoiceComponent {
|
|
309
|
+
private speechService = inject(SpeechSynthesisService);
|
|
310
|
+
|
|
311
|
+
speakText(text: string) {
|
|
312
|
+
this.speechService.speak(text).subscribe((state) => {
|
|
313
|
+
console.log('Speech state:', state);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### BatteryService
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { BatteryService } from '@angular-helpers/browser-web-apis';
|
|
323
|
+
|
|
324
|
+
export class PowerComponent {
|
|
325
|
+
private batteryService = inject(BatteryService);
|
|
326
|
+
|
|
327
|
+
async ngOnInit() {
|
|
328
|
+
try {
|
|
329
|
+
// Initialize and get initial battery info
|
|
330
|
+
const batteryInfo = await this.batteryService.initialize();
|
|
331
|
+
console.log('Battery level:', batteryInfo.level);
|
|
332
|
+
console.log('Is charging:', batteryInfo.charging);
|
|
333
|
+
|
|
334
|
+
// Watch for battery changes
|
|
335
|
+
this.batteryService.watchBatteryInfo().subscribe((info) => {
|
|
336
|
+
console.log('Battery updated:', info);
|
|
337
|
+
});
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error('Battery API not supported:', error);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### ClipboardService
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { ClipboardService } from '@angular-helpers/browser-web-apis';
|
|
349
|
+
|
|
350
|
+
export class CopyComponent {
|
|
351
|
+
private clipboardService = inject(ClipboardService);
|
|
352
|
+
|
|
353
|
+
async copyToClipboard(text: string) {
|
|
354
|
+
try {
|
|
355
|
+
await this.clipboardService.writeText(text);
|
|
356
|
+
console.log('Copied successfully');
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error('Failed to copy:', error);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async pasteFromClipboard(): Promise<string> {
|
|
363
|
+
try {
|
|
364
|
+
const text = await this.clipboardService.readText();
|
|
365
|
+
return text;
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error('Failed to read clipboard:', error);
|
|
368
|
+
return '';
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### FileSystemAccessService
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import { FileSystemAccessService } from '@angular-helpers/browser-web-apis';
|
|
378
|
+
|
|
379
|
+
export class FileManagerComponent {
|
|
380
|
+
private fileService = inject(FileSystemAccessService);
|
|
381
|
+
|
|
382
|
+
async openFiles() {
|
|
383
|
+
try {
|
|
384
|
+
const files = await this.fileService.openFile({
|
|
385
|
+
multiple: true,
|
|
386
|
+
types: [
|
|
387
|
+
{
|
|
388
|
+
description: 'Text files',
|
|
389
|
+
accept: { 'text/plain': ['.txt'] },
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
});
|
|
393
|
+
console.log('Selected files:', files);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
console.error('Failed to open files:', error);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async saveContent(content: string) {
|
|
400
|
+
try {
|
|
401
|
+
await this.fileService.saveFile(content, {
|
|
402
|
+
suggestedName: 'document.txt',
|
|
403
|
+
});
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error('Failed to save file:', error);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### MediaRecorderService
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
import { MediaRecorderService } from '@angular-helpers/browser-web-apis';
|
|
415
|
+
|
|
416
|
+
export class RecorderComponent {
|
|
417
|
+
private recorderService = inject(MediaRecorderService);
|
|
418
|
+
private stream: MediaStream | null = null;
|
|
419
|
+
|
|
420
|
+
async startRecording() {
|
|
421
|
+
try {
|
|
422
|
+
this.stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
423
|
+
await this.recorderService.start(this.stream, { mimeType: 'video/webm' });
|
|
424
|
+
} catch (error) {
|
|
425
|
+
console.error('Failed to start recording:', error);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
stopRecording() {
|
|
430
|
+
const result = this.recorderService.stop();
|
|
431
|
+
if (result) {
|
|
432
|
+
console.log('Recording saved, blob URL:', result.url);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### WebSocketService
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import { WebSocketService } from '@angular-helpers/browser-web-apis';
|
|
442
|
+
|
|
443
|
+
export class LiveComponent {
|
|
444
|
+
private wsService = inject(WebSocketService);
|
|
445
|
+
|
|
446
|
+
connect() {
|
|
447
|
+
this.wsService
|
|
448
|
+
.connect({
|
|
449
|
+
url: 'wss://example.com/socket',
|
|
450
|
+
reconnectInterval: 3000,
|
|
451
|
+
maxReconnectAttempts: 5,
|
|
452
|
+
})
|
|
453
|
+
.subscribe((status) => {
|
|
454
|
+
console.log('Connection status:', status);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
this.wsService.getMessages().subscribe((message) => {
|
|
458
|
+
console.log('Received:', message);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
sendMessage(data: unknown) {
|
|
463
|
+
this.wsService.send({ type: 'message', data });
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### WebStorageService
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { WebStorageService } from '@angular-helpers/browser-web-apis';
|
|
472
|
+
|
|
473
|
+
export class SettingsComponent {
|
|
474
|
+
private storageService = inject(WebStorageService);
|
|
475
|
+
|
|
476
|
+
saveSetting(key: string, value: unknown) {
|
|
477
|
+
this.storageService.setLocalStorage(key, value);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
getSetting<T>(key: string): T | null {
|
|
481
|
+
return this.storageService.getLocalStorage<T>(key);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
watchSetting<T>(key: string) {
|
|
485
|
+
return this.storageService.watchLocalStorage<T>(key).subscribe((value) => {
|
|
486
|
+
console.log('Setting changed:', value);
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### WebWorkerService
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
import { WebWorkerService } from '@angular-helpers/browser-web-apis';
|
|
496
|
+
|
|
497
|
+
export class WorkerComponent {
|
|
498
|
+
private workerService = inject(WebWorkerService);
|
|
499
|
+
|
|
500
|
+
async createWorker() {
|
|
501
|
+
this.workerService
|
|
502
|
+
.createWorker('calc-worker', '/assets/workers/calc.worker.js')
|
|
503
|
+
.subscribe((status) => {
|
|
504
|
+
console.log('Worker status:', status);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
this.workerService.getMessages('calc-worker').subscribe((message) => {
|
|
508
|
+
console.log('Worker response:', message);
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
sendTask(data: unknown) {
|
|
513
|
+
this.workerService.postMessage('calc-worker', {
|
|
514
|
+
id: 'task-1',
|
|
515
|
+
type: 'CALCULATE',
|
|
516
|
+
data,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
142
522
|
## Browser Support
|
|
143
523
|
|
|
144
524
|
The services automatically validate browser support and unsupported-path handling:
|