@angular-helpers/browser-web-apis 21.0.0 → 21.0.2
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.
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Injectable, inject, DestroyRef, PLATFORM_ID } from '@angular/core';
|
|
3
|
+
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
|
|
4
|
+
import { Observable } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
class PermissionsService {
|
|
7
|
+
async query(descriptor) {
|
|
8
|
+
if (!this.isSupported()) {
|
|
9
|
+
throw new Error('Permissions API not supported');
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
return await navigator.permissions.query(descriptor);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
console.error('Error querying permission:', error);
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
isSupported() {
|
|
20
|
+
return typeof navigator !== 'undefined' && 'permissions' in navigator;
|
|
21
|
+
}
|
|
22
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
23
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PermissionsService });
|
|
24
|
+
}
|
|
25
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PermissionsService, decorators: [{
|
|
26
|
+
type: Injectable
|
|
27
|
+
}] });
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Base class for all Browser Web API services
|
|
31
|
+
* Provides common functionality for:
|
|
32
|
+
* - Support checking
|
|
33
|
+
* - Permission management
|
|
34
|
+
* - Error handling
|
|
35
|
+
* - Lifecycle management with destroyRef
|
|
36
|
+
* - Logging
|
|
37
|
+
*/
|
|
38
|
+
class BrowserApiBaseService {
|
|
39
|
+
permissionsService = inject(PermissionsService);
|
|
40
|
+
destroyRef = inject(DestroyRef);
|
|
41
|
+
platformId = inject(PLATFORM_ID);
|
|
42
|
+
/**
|
|
43
|
+
* Check if running in browser environment using Angular's platform detection
|
|
44
|
+
*/
|
|
45
|
+
isBrowserEnvironment() {
|
|
46
|
+
return isPlatformBrowser(this.platformId);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if running in server environment using Angular's platform detection
|
|
50
|
+
*/
|
|
51
|
+
isServerEnvironment() {
|
|
52
|
+
return isPlatformServer(this.platformId);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Request a permission
|
|
56
|
+
*/
|
|
57
|
+
async requestPermission(permission) {
|
|
58
|
+
if (this.isServerEnvironment()) {
|
|
59
|
+
throw new Error(`${this.getApiName()} API not available in server environment`);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const status = await this.permissionsService.query({ name: permission });
|
|
63
|
+
return status.state === 'granted';
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error(`[${this.getApiName()}] Error requesting permission for ${permission}:`, error);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserApiBaseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
71
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserApiBaseService });
|
|
72
|
+
}
|
|
73
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserApiBaseService, decorators: [{
|
|
74
|
+
type: Injectable
|
|
75
|
+
}] });
|
|
76
|
+
|
|
77
|
+
class CameraService extends BrowserApiBaseService {
|
|
78
|
+
currentStream = null;
|
|
79
|
+
createError(message, cause) {
|
|
80
|
+
const error = new Error(message);
|
|
81
|
+
error.cause = cause;
|
|
82
|
+
return error;
|
|
83
|
+
}
|
|
84
|
+
getApiName() {
|
|
85
|
+
return 'camera';
|
|
86
|
+
}
|
|
87
|
+
ensureCameraSupport() {
|
|
88
|
+
if (!('mediaDevices' in navigator) || !('getUserMedia' in navigator.mediaDevices)) {
|
|
89
|
+
throw new Error('Camera API not supported in this browser');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async startCamera(constraints) {
|
|
93
|
+
this.ensureCameraSupport();
|
|
94
|
+
if (this.currentStream) {
|
|
95
|
+
this.stopCamera();
|
|
96
|
+
}
|
|
97
|
+
const permissionStatus = await this.permissionsService.query({ name: 'camera' });
|
|
98
|
+
if (permissionStatus.state !== 'granted') {
|
|
99
|
+
throw new Error('Camera permission required. Please grant camera access and try again.');
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const streamConstraints = constraints || {
|
|
103
|
+
video: {
|
|
104
|
+
width: { ideal: 1280 },
|
|
105
|
+
height: { ideal: 720 },
|
|
106
|
+
facingMode: 'user'
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
this.currentStream = await navigator.mediaDevices.getUserMedia(streamConstraints);
|
|
110
|
+
return this.currentStream;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.error('[CameraService] Error starting camera:', error);
|
|
114
|
+
if (error instanceof Error && error.name === 'NotAllowedError') {
|
|
115
|
+
throw this.createError('Camera permission denied by user. Please allow camera access in your browser settings and refresh the page.', error);
|
|
116
|
+
}
|
|
117
|
+
else if (error instanceof Error && error.name === 'NotFoundError') {
|
|
118
|
+
throw this.createError('No camera device found. Please connect a camera and try again.', error);
|
|
119
|
+
}
|
|
120
|
+
else if (error instanceof Error && error.name === 'NotReadableError') {
|
|
121
|
+
throw this.createError('Camera is already in use by another application. Please close other applications using the camera and try again.', error);
|
|
122
|
+
}
|
|
123
|
+
else if (error instanceof Error && error.name === 'OverconstrainedError') {
|
|
124
|
+
throw this.createError('Camera constraints cannot be satisfied. Try with different camera settings.', error);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
128
|
+
throw this.createError(`Camera error: ${errorMessage}`, error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
stopCamera() {
|
|
133
|
+
if (this.currentStream) {
|
|
134
|
+
this.currentStream.getTracks().forEach((track) => {
|
|
135
|
+
track.stop();
|
|
136
|
+
});
|
|
137
|
+
this.currentStream = null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async switchCamera(deviceId, constraints = {
|
|
141
|
+
video: {
|
|
142
|
+
width: { ideal: 1280 },
|
|
143
|
+
height: { ideal: 720 }
|
|
144
|
+
}
|
|
145
|
+
}) {
|
|
146
|
+
this.stopCamera();
|
|
147
|
+
const finalConstraints = {
|
|
148
|
+
video: constraints.video && typeof constraints.video === 'object'
|
|
149
|
+
? { ...constraints.video, deviceId: { exact: deviceId } }
|
|
150
|
+
: { deviceId: { exact: deviceId } }
|
|
151
|
+
};
|
|
152
|
+
return this.startCamera(finalConstraints);
|
|
153
|
+
}
|
|
154
|
+
async getCameraCapabilities(deviceId) {
|
|
155
|
+
this.ensureCameraSupport();
|
|
156
|
+
try {
|
|
157
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
158
|
+
video: { deviceId: { exact: deviceId } }
|
|
159
|
+
});
|
|
160
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
161
|
+
const capabilities = videoTrack.getCapabilities();
|
|
162
|
+
// Clean up the stream
|
|
163
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
164
|
+
return capabilities || null;
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.error('[CameraService] Error getting camera capabilities:', error);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
getCurrentStream() {
|
|
172
|
+
return this.currentStream;
|
|
173
|
+
}
|
|
174
|
+
isStreaming() {
|
|
175
|
+
return this.currentStream !== null;
|
|
176
|
+
}
|
|
177
|
+
async getVideoInputDevices() {
|
|
178
|
+
this.ensureCameraSupport();
|
|
179
|
+
try {
|
|
180
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
181
|
+
return devices.filter(device => device.kind === 'videoinput');
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
console.error('[CameraService] Error enumerating video devices:', error);
|
|
185
|
+
throw this.createError('Failed to enumerate video devices', error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Direct access to native camera API
|
|
189
|
+
getNativeMediaDevices() {
|
|
190
|
+
this.ensureCameraSupport();
|
|
191
|
+
return navigator.mediaDevices;
|
|
192
|
+
}
|
|
193
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CameraService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
194
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CameraService });
|
|
195
|
+
}
|
|
196
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CameraService, decorators: [{
|
|
197
|
+
type: Injectable
|
|
198
|
+
}] });
|
|
199
|
+
|
|
200
|
+
class GeolocationService extends BrowserApiBaseService {
|
|
201
|
+
getApiName() {
|
|
202
|
+
return 'geolocation';
|
|
203
|
+
}
|
|
204
|
+
ensureGeolocationSupport() {
|
|
205
|
+
if (!('geolocation' in navigator)) {
|
|
206
|
+
throw new Error('Geolocation API not supported in this browser');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
getCurrentPosition(options) {
|
|
210
|
+
this.ensureGeolocationSupport();
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
navigator.geolocation.getCurrentPosition((position) => resolve(position), (error) => {
|
|
213
|
+
console.error('[GeolocationService] Error getting position:', error);
|
|
214
|
+
reject(error);
|
|
215
|
+
}, options);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
watchPosition(options) {
|
|
219
|
+
this.ensureGeolocationSupport();
|
|
220
|
+
return new Observable((observer) => {
|
|
221
|
+
const watchId = navigator.geolocation.watchPosition((position) => observer.next(position), (error) => {
|
|
222
|
+
console.error('[GeolocationService] Error watching position:', error);
|
|
223
|
+
observer.error(error);
|
|
224
|
+
}, options);
|
|
225
|
+
return () => {
|
|
226
|
+
navigator.geolocation.clearWatch(watchId);
|
|
227
|
+
};
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
clearWatch(watchId) {
|
|
231
|
+
navigator.geolocation.clearWatch(watchId);
|
|
232
|
+
}
|
|
233
|
+
// Direct access to native geolocation API
|
|
234
|
+
getNativeGeolocation() {
|
|
235
|
+
this.ensureGeolocationSupport();
|
|
236
|
+
return navigator.geolocation;
|
|
237
|
+
}
|
|
238
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: GeolocationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
239
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: GeolocationService });
|
|
240
|
+
}
|
|
241
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: GeolocationService, decorators: [{
|
|
242
|
+
type: Injectable
|
|
243
|
+
}] });
|
|
244
|
+
|
|
245
|
+
class MediaDevicesService extends BrowserApiBaseService {
|
|
246
|
+
createError(message, cause) {
|
|
247
|
+
const error = new Error(message);
|
|
248
|
+
error.cause = cause;
|
|
249
|
+
return error;
|
|
250
|
+
}
|
|
251
|
+
getApiName() {
|
|
252
|
+
return 'media-devices';
|
|
253
|
+
}
|
|
254
|
+
ensureMediaDevicesSupport() {
|
|
255
|
+
if (!('mediaDevices' in navigator)) {
|
|
256
|
+
throw new Error('MediaDevices API not supported in this browser');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async getDevices() {
|
|
260
|
+
this.ensureMediaDevicesSupport();
|
|
261
|
+
try {
|
|
262
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
263
|
+
return devices;
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
console.error('[MediaDevicesService] Error enumerating devices:', error);
|
|
267
|
+
throw this.createError('Failed to enumerate media devices', error);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async getUserMedia(constraints) {
|
|
271
|
+
this.ensureMediaDevicesSupport();
|
|
272
|
+
try {
|
|
273
|
+
const defaultConstraints = {
|
|
274
|
+
video: true,
|
|
275
|
+
audio: true
|
|
276
|
+
};
|
|
277
|
+
const finalConstraints = constraints || defaultConstraints;
|
|
278
|
+
return await navigator.mediaDevices.getUserMedia(finalConstraints);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
console.error('[MediaDevicesService] Error getting user media:', error);
|
|
282
|
+
throw this.handleMediaError(error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async getDisplayMedia(constraints) {
|
|
286
|
+
this.ensureMediaDevicesSupport();
|
|
287
|
+
if (!('getDisplayMedia' in navigator.mediaDevices)) {
|
|
288
|
+
throw new Error('Display media API not supported in this browser');
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const defaultConstraints = {
|
|
292
|
+
video: true,
|
|
293
|
+
audio: false
|
|
294
|
+
};
|
|
295
|
+
const finalConstraints = constraints || defaultConstraints;
|
|
296
|
+
return await navigator.mediaDevices.getDisplayMedia(finalConstraints);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
console.error('[MediaDevicesService] Error getting display media:', error);
|
|
300
|
+
throw this.createError('Failed to get display media', error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
watchDeviceChanges() {
|
|
304
|
+
this.ensureMediaDevicesSupport();
|
|
305
|
+
return new Observable((observer) => {
|
|
306
|
+
const handleDeviceChange = async () => {
|
|
307
|
+
try {
|
|
308
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
309
|
+
observer.next(devices);
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
console.error('[MediaDevicesService] Error handling device change:', error);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
// Listen for device changes
|
|
316
|
+
navigator.mediaDevices.addEventListener('devicechange', handleDeviceChange);
|
|
317
|
+
// Get initial devices
|
|
318
|
+
handleDeviceChange();
|
|
319
|
+
return () => {
|
|
320
|
+
navigator.mediaDevices.removeEventListener('devicechange', handleDeviceChange);
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
getVideoInputDevices() {
|
|
325
|
+
return this.getDevicesByKind('videoinput');
|
|
326
|
+
}
|
|
327
|
+
getAudioInputDevices() {
|
|
328
|
+
return this.getDevicesByKind('audioinput');
|
|
329
|
+
}
|
|
330
|
+
getAudioOutputDevices() {
|
|
331
|
+
return this.getDevicesByKind('audiooutput');
|
|
332
|
+
}
|
|
333
|
+
async getDevicesByKind(kind) {
|
|
334
|
+
const devices = await this.getDevices();
|
|
335
|
+
return devices.filter(device => device.kind === kind);
|
|
336
|
+
}
|
|
337
|
+
handleMediaError(error) {
|
|
338
|
+
let message;
|
|
339
|
+
if (error instanceof Error) {
|
|
340
|
+
switch (error.name) {
|
|
341
|
+
case 'NotAllowedError':
|
|
342
|
+
message = 'Permission denied by user';
|
|
343
|
+
break;
|
|
344
|
+
case 'NotFoundError':
|
|
345
|
+
message = 'No media device found';
|
|
346
|
+
break;
|
|
347
|
+
case 'NotReadableError':
|
|
348
|
+
message = 'Media device is already in use';
|
|
349
|
+
break;
|
|
350
|
+
case 'OverconstrainedError':
|
|
351
|
+
message = 'Media constraints cannot be satisfied';
|
|
352
|
+
break;
|
|
353
|
+
case 'TypeError':
|
|
354
|
+
message = 'Invalid media constraints provided';
|
|
355
|
+
break;
|
|
356
|
+
default:
|
|
357
|
+
message = `Media error: ${error.message}`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
message = 'Unknown media error occurred';
|
|
362
|
+
}
|
|
363
|
+
return this.createError(message, error);
|
|
364
|
+
}
|
|
365
|
+
// Direct access to native media devices API
|
|
366
|
+
getNativeMediaDevices() {
|
|
367
|
+
this.ensureMediaDevicesSupport();
|
|
368
|
+
return navigator.mediaDevices;
|
|
369
|
+
}
|
|
370
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: MediaDevicesService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
371
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: MediaDevicesService });
|
|
372
|
+
}
|
|
373
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: MediaDevicesService, decorators: [{
|
|
374
|
+
type: Injectable
|
|
375
|
+
}] });
|
|
376
|
+
|
|
377
|
+
class NotificationService extends BrowserApiBaseService {
|
|
378
|
+
getApiName() {
|
|
379
|
+
return 'notifications';
|
|
380
|
+
}
|
|
381
|
+
async showNotification(title, options) {
|
|
382
|
+
if (!('Notification' in window)) {
|
|
383
|
+
throw new Error('Notification API not supported in this browser');
|
|
384
|
+
}
|
|
385
|
+
const permissionStatus = await this.permissionsService.query({ name: 'notifications' });
|
|
386
|
+
if (permissionStatus.state !== 'granted') {
|
|
387
|
+
throw new Error('Notification permission required. Please grant notification access and try again.');
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
return new Notification(title, options);
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
console.error('[NotificationService] Error showing notification:', error);
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: NotificationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
398
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: NotificationService });
|
|
399
|
+
}
|
|
400
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: NotificationService, decorators: [{
|
|
401
|
+
type: Injectable
|
|
402
|
+
}] });
|
|
403
|
+
|
|
404
|
+
class ClipboardService extends BrowserApiBaseService {
|
|
405
|
+
getApiName() {
|
|
406
|
+
return 'clipboard';
|
|
407
|
+
}
|
|
408
|
+
async ensureClipboardPermission(action) {
|
|
409
|
+
if (!('clipboard' in navigator)) {
|
|
410
|
+
throw new Error('Clipboard API not supported in this browser');
|
|
411
|
+
}
|
|
412
|
+
const permissionStatus = await this.permissionsService.query({
|
|
413
|
+
name: `clipboard-${action}`
|
|
414
|
+
});
|
|
415
|
+
if (permissionStatus.state !== 'granted') {
|
|
416
|
+
throw new Error(`Clipboard ${action} permission required. Please grant clipboard access and try again.`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async writeText(text) {
|
|
420
|
+
await this.ensureClipboardPermission('write');
|
|
421
|
+
try {
|
|
422
|
+
await navigator.clipboard.writeText(text);
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
console.error('[ClipboardService] Error writing to clipboard:', error);
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async readText() {
|
|
430
|
+
await this.ensureClipboardPermission('read');
|
|
431
|
+
try {
|
|
432
|
+
return await navigator.clipboard.readText();
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
console.error('[ClipboardService] Error reading from clipboard:', error);
|
|
436
|
+
throw error;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async writeTextSecure(text) {
|
|
440
|
+
await this.ensureClipboardPermission('write');
|
|
441
|
+
try {
|
|
442
|
+
await navigator.clipboard.writeText(text);
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
console.error('[ClipboardService] Error writing to clipboard:', error);
|
|
446
|
+
throw error;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ClipboardService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
450
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ClipboardService });
|
|
451
|
+
}
|
|
452
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ClipboardService, decorators: [{
|
|
453
|
+
type: Injectable
|
|
454
|
+
}] });
|
|
455
|
+
|
|
456
|
+
const BROWSER_CAPABILITIES = [
|
|
457
|
+
{ id: 'permissions', label: 'Permissions API', requiresSecureContext: false },
|
|
458
|
+
{ id: 'geolocation', label: 'Geolocation API', requiresSecureContext: true },
|
|
459
|
+
{ id: 'clipboard', label: 'Clipboard API', requiresSecureContext: true },
|
|
460
|
+
{ id: 'notification', label: 'Notification API', requiresSecureContext: true },
|
|
461
|
+
{ id: 'mediaDevices', label: 'MediaDevices API', requiresSecureContext: true },
|
|
462
|
+
{ id: 'camera', label: 'Camera API', requiresSecureContext: true },
|
|
463
|
+
{ id: 'webWorker', label: 'Web Worker API', requiresSecureContext: false },
|
|
464
|
+
{ id: 'regexSecurity', label: 'Regex Security', requiresSecureContext: false },
|
|
465
|
+
{ id: 'webStorage', label: 'Web Storage', requiresSecureContext: false },
|
|
466
|
+
{ id: 'webShare', label: 'Web Share', requiresSecureContext: true },
|
|
467
|
+
{ id: 'battery', label: 'Battery API', requiresSecureContext: false },
|
|
468
|
+
{ id: 'webSocket', label: 'WebSocket API', requiresSecureContext: false }
|
|
469
|
+
];
|
|
470
|
+
class BrowserCapabilityService {
|
|
471
|
+
getCapabilities() {
|
|
472
|
+
return BROWSER_CAPABILITIES;
|
|
473
|
+
}
|
|
474
|
+
isSecureContext() {
|
|
475
|
+
return typeof window !== 'undefined' && window.isSecureContext;
|
|
476
|
+
}
|
|
477
|
+
isSupported(capability) {
|
|
478
|
+
switch (capability) {
|
|
479
|
+
case 'permissions':
|
|
480
|
+
return typeof navigator !== 'undefined' && 'permissions' in navigator;
|
|
481
|
+
case 'geolocation':
|
|
482
|
+
return typeof navigator !== 'undefined' && 'geolocation' in navigator;
|
|
483
|
+
case 'clipboard':
|
|
484
|
+
return typeof navigator !== 'undefined' && 'clipboard' in navigator;
|
|
485
|
+
case 'notification':
|
|
486
|
+
return typeof window !== 'undefined' && 'Notification' in window;
|
|
487
|
+
case 'mediaDevices':
|
|
488
|
+
case 'camera':
|
|
489
|
+
return typeof navigator !== 'undefined' && 'mediaDevices' in navigator;
|
|
490
|
+
case 'webWorker':
|
|
491
|
+
case 'regexSecurity':
|
|
492
|
+
return typeof Worker !== 'undefined';
|
|
493
|
+
case 'webStorage':
|
|
494
|
+
return typeof Storage !== 'undefined';
|
|
495
|
+
case 'webShare':
|
|
496
|
+
return typeof navigator !== 'undefined' && 'share' in navigator;
|
|
497
|
+
case 'battery':
|
|
498
|
+
return typeof navigator !== 'undefined' && 'getBattery' in navigator;
|
|
499
|
+
case 'webSocket':
|
|
500
|
+
return typeof WebSocket !== 'undefined';
|
|
501
|
+
default:
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
getAllStatuses() {
|
|
506
|
+
const secureContext = this.isSecureContext();
|
|
507
|
+
return this.getCapabilities().map((capability) => ({
|
|
508
|
+
id: capability.id,
|
|
509
|
+
label: capability.label,
|
|
510
|
+
supported: this.isSupported(capability.id),
|
|
511
|
+
secureContext,
|
|
512
|
+
requiresSecureContext: capability.requiresSecureContext
|
|
513
|
+
}));
|
|
514
|
+
}
|
|
515
|
+
async getPermissionState(permission) {
|
|
516
|
+
if (typeof navigator === 'undefined' || !('permissions' in navigator)) {
|
|
517
|
+
return 'unknown';
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
const status = await navigator.permissions.query({ name: permission });
|
|
521
|
+
return status.state;
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
return 'unknown';
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserCapabilityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
528
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserCapabilityService, providedIn: 'root' });
|
|
529
|
+
}
|
|
530
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BrowserCapabilityService, decorators: [{
|
|
531
|
+
type: Injectable,
|
|
532
|
+
args: [{ providedIn: 'root' }]
|
|
533
|
+
}] });
|
|
534
|
+
|
|
535
|
+
class BrowserSupportUtil {
|
|
536
|
+
static isSupported(feature) {
|
|
537
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
switch (feature) {
|
|
541
|
+
case 'permissions':
|
|
542
|
+
return 'permissions' in navigator;
|
|
543
|
+
case 'camera':
|
|
544
|
+
return 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices;
|
|
545
|
+
case 'microphone':
|
|
546
|
+
return 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices;
|
|
547
|
+
case 'geolocation':
|
|
548
|
+
return 'geolocation' in navigator;
|
|
549
|
+
case 'notifications':
|
|
550
|
+
return 'Notification' in window;
|
|
551
|
+
case 'clipboard':
|
|
552
|
+
return 'clipboard' in navigator;
|
|
553
|
+
case 'clipboard-read':
|
|
554
|
+
return 'clipboard' in navigator && 'readText' in navigator.clipboard;
|
|
555
|
+
case 'clipboard-write':
|
|
556
|
+
return 'clipboard' in navigator && 'writeText' in navigator.clipboard;
|
|
557
|
+
case 'persistent-storage':
|
|
558
|
+
return 'storage' in navigator && 'persist' in navigator.storage;
|
|
559
|
+
default:
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
static getUnsupportedFeatures() {
|
|
564
|
+
const features = [
|
|
565
|
+
'permissions',
|
|
566
|
+
'camera',
|
|
567
|
+
'microphone',
|
|
568
|
+
'geolocation',
|
|
569
|
+
'notifications',
|
|
570
|
+
'clipboard',
|
|
571
|
+
'clipboard-read',
|
|
572
|
+
'clipboard-write',
|
|
573
|
+
'persistent-storage'
|
|
574
|
+
];
|
|
575
|
+
return features.filter(feature => !this.isSupported(feature));
|
|
576
|
+
}
|
|
577
|
+
static isSecureContext() {
|
|
578
|
+
return typeof window !== 'undefined' ? window.isSecureContext : false;
|
|
579
|
+
}
|
|
580
|
+
static getUserAgent() {
|
|
581
|
+
return typeof navigator !== 'undefined' ? navigator.userAgent : '';
|
|
582
|
+
}
|
|
583
|
+
static isMobile() {
|
|
584
|
+
const userAgent = this.getUserAgent().toLowerCase();
|
|
585
|
+
return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
|
|
586
|
+
}
|
|
587
|
+
static isDesktop() {
|
|
588
|
+
return !this.isMobile();
|
|
589
|
+
}
|
|
590
|
+
static getBrowserInfo() {
|
|
591
|
+
const userAgent = this.getUserAgent();
|
|
592
|
+
return {
|
|
593
|
+
name: this.getBrowserName(userAgent),
|
|
594
|
+
version: this.getBrowserVersion(userAgent),
|
|
595
|
+
isChrome: /chrome/.test(userAgent) && !/edge/.test(userAgent),
|
|
596
|
+
isFirefox: /firefox/.test(userAgent),
|
|
597
|
+
isSafari: /safari/.test(userAgent) && !/chrome/.test(userAgent),
|
|
598
|
+
isEdge: /edge/.test(userAgent) || /edg/.test(userAgent)
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
static getBrowserName(userAgent) {
|
|
602
|
+
if (/chrome/.test(userAgent) && !/edge/.test(userAgent))
|
|
603
|
+
return 'Chrome';
|
|
604
|
+
if (/firefox/.test(userAgent))
|
|
605
|
+
return 'Firefox';
|
|
606
|
+
if (/safari/.test(userAgent) && !/chrome/.test(userAgent))
|
|
607
|
+
return 'Safari';
|
|
608
|
+
if (/edge/.test(userAgent) || /edg/.test(userAgent))
|
|
609
|
+
return 'Edge';
|
|
610
|
+
return 'Unknown';
|
|
611
|
+
}
|
|
612
|
+
static getBrowserVersion(userAgent) {
|
|
613
|
+
const match = userAgent.match(/(chrome|firefox|safari|edge|edg)\/(\d+)/i);
|
|
614
|
+
return match ? match[2] : 'Unknown';
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
class PermissionGuard {
|
|
619
|
+
permissionsService;
|
|
620
|
+
router;
|
|
621
|
+
constructor(permissionsService, router) {
|
|
622
|
+
this.permissionsService = permissionsService;
|
|
623
|
+
this.router = router;
|
|
624
|
+
}
|
|
625
|
+
canActivate(route) {
|
|
626
|
+
const permission = route.data?.['permission'];
|
|
627
|
+
if (!permission) {
|
|
628
|
+
return Promise.resolve(true);
|
|
629
|
+
}
|
|
630
|
+
return this.checkPermission(permission);
|
|
631
|
+
}
|
|
632
|
+
async checkPermission(permission) {
|
|
633
|
+
try {
|
|
634
|
+
const status = await this.permissionsService.query({ name: permission });
|
|
635
|
+
if (status.state !== 'granted') {
|
|
636
|
+
this.router.navigate(['/permission-denied'], {
|
|
637
|
+
queryParams: { permission }
|
|
638
|
+
});
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
console.error('Permission guard error:', error);
|
|
645
|
+
this.router.navigate(['/permission-denied'], {
|
|
646
|
+
queryParams: { permission }
|
|
647
|
+
});
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function createPermissionGuard(permission) {
|
|
653
|
+
return {
|
|
654
|
+
canActivate: [PermissionGuard],
|
|
655
|
+
data: { permission }
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Browser Web APIs Services
|
|
660
|
+
// Version
|
|
661
|
+
const version = '0.1.0';
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Generated bundle index. Do not edit.
|
|
665
|
+
*/
|
|
666
|
+
|
|
667
|
+
export { BrowserCapabilityService, BrowserSupportUtil, CameraService, ClipboardService, GeolocationService, MediaDevicesService, NotificationService, PermissionGuard, PermissionsService, createPermissionGuard, version };
|
|
668
|
+
//# sourceMappingURL=angular-helpers-browser-web-apis.mjs.map
|